2007-04-09 Andrew Wellington <proton@wiretapped.net>
[WebKit-https.git] / WebCore / platform / mac / PasteboardMac.mm
1 /*
2  * Copyright (C) 2006 Apple Computer, Inc.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
24  */
25
26 #import "config.h"
27 #import "Pasteboard.h"
28
29 #import "CachedResource.h"
30 #import "CharacterNames.h"
31 #import "DOMRangeInternal.h"
32 #import "Document.h"
33 #import "DocumentFragment.h"
34 #import "Editor.h"
35 #import "EditorClient.h"
36 #import "HitTestResult.h"
37 #import "Image.h"
38 #import "KURL.h"
39 #import "LoaderNSURLExtras.h"
40 #import "MimeTypeRegistry.h"
41 #import "RenderImage.h"
42 #import "WebCoreNSStringExtras.h"
43 #import "WebCoreSystemInterface.h"
44 #import "markup.h"
45
46 #import <wtf/RetainPtr.h>
47
48 @interface NSAttributedString (AppKitSecretsIKnowAbout)
49 - (id)_initWithDOMRange:(DOMRange *)domRange;
50 @end
51
52 namespace WebCore {
53
54 NSString *WebArchivePboardType;
55 NSString *WebSmartPastePboardType;
56 NSString *WebURLNamePboardType;
57 NSString *WebURLPboardType;
58 NSString *WebURLsWithTitlesPboardType;
59
60 #ifndef BUILDING_ON_TIGER
61 static NSArray* selectionPasteboardTypes(bool canSmartCopyOrDelete, bool selectionContainsAttachments)
62 {
63     if (selectionContainsAttachments) {
64         if (canSmartCopyOrDelete)
65             return [NSArray arrayWithObjects:WebSmartPastePboardType, WebArchivePboardType, NSRTFDPboardType, NSRTFPboardType, NSStringPboardType, nil];
66         else
67             return [NSArray arrayWithObjects:WebArchivePboardType, NSRTFDPboardType, NSRTFPboardType, NSStringPboardType, nil];
68     } else { // Don't write RTFD to the pasteboard when the copied attributed string has no attachments.
69         if (canSmartCopyOrDelete)
70             return [NSArray arrayWithObjects:WebSmartPastePboardType, WebArchivePboardType, NSRTFPboardType, NSStringPboardType, nil];
71         else
72             return [NSArray arrayWithObjects:WebArchivePboardType, NSRTFPboardType, NSStringPboardType, nil];
73     }
74 }
75 #endif
76
77 static NSArray* writableTypesForURL()
78 {
79     static NSArray *types = nil;
80     if (!types) {
81         types = [[NSArray alloc] initWithObjects:
82             WebURLsWithTitlesPboardType,
83             NSURLPboardType,
84             WebURLPboardType,
85             WebURLNamePboardType,
86             NSStringPboardType,
87             nil];
88     }
89     return types;
90 }
91
92 static NSArray* writableTypesForImage()
93 {
94     static NSMutableArray *types = nil;
95     if (!types) {
96         types = [[NSMutableArray alloc] initWithObjects:NSTIFFPboardType, nil];
97         [types addObjectsFromArray:writableTypesForURL()];
98         [types addObject:NSRTFDPboardType];
99     }
100     return types;
101 }
102
103 Pasteboard* Pasteboard::generalPasteboard() 
104 {
105     static Pasteboard* pasteboard = new Pasteboard([NSPasteboard generalPasteboard]);
106     return pasteboard;
107 }
108
109 Pasteboard::Pasteboard(NSPasteboard* pboard)
110     : m_pasteboard(pboard)
111 {
112     WebArchivePboardType          = @"Apple Web Archive pasteboard type";
113     WebSmartPastePboardType       = @"NeXT smart paste pasteboard type";
114     WebURLNamePboardType          = wkCreateURLNPasteboardFlavorTypeName();
115     WebURLPboardType              = wkCreateURLPasteboardFlavorTypeName();
116     WebURLsWithTitlesPboardType   = @"WebURLsWithTitlesPboardType";
117 }
118
119 void Pasteboard::clear()
120 {
121     [m_pasteboard declareTypes:[NSArray array] owner:nil];
122 }
123
124 static NSAttributedString *stripAttachmentCharacters(NSAttributedString *string)
125 {
126     const unichar attachmentCharacter = NSAttachmentCharacter;
127     static RetainPtr<NSString> attachmentCharacterString = [NSString stringWithCharacters:&attachmentCharacter length:1];
128     NSMutableAttributedString *result = [[string mutableCopy] autorelease];
129     NSRange attachmentRange = [[result string] rangeOfString:attachmentCharacterString.get()];
130     while (attachmentRange.location != NSNotFound) {
131         [result replaceCharactersInRange:attachmentRange withString:@""];
132         attachmentRange = [[result string] rangeOfString:attachmentCharacterString.get()];
133     }
134     return result;
135 }
136
137 void Pasteboard::writeSelection(NSPasteboard* pasteboard, Range* selectedRange, bool canSmartCopyOrDelete, Frame* frame)
138 {
139     if (WebArchivePboardType == nil)
140         Pasteboard::generalPasteboard(); //Initialises pasteboard types
141     ASSERT(selectedRange);
142     
143     NSAttributedString *attributedString = [[[NSAttributedString alloc] _initWithDOMRange:[DOMRange _wrapRange:selectedRange]] autorelease];
144 #ifdef BUILDING_ON_TIGER
145     // 4930197: Mail overrides [WebHTMLView pasteboardTypesForSelection] in order to add another type to the pasteboard
146     // after WebKit does.  On Tiger we must call this function so that Mail code will be executed, meaning that 
147     // we can't call WebCore::Pasteboard's method for setting types. 
148     
149     NSArray *types = frame->editor()->client()->pasteboardTypesForSelection(frame);
150     // Don't write RTFD to the pasteboard when the copied attributed string has no attachments.
151     NSMutableArray *mutableTypes = nil;
152     if (![attributedString containsAttachments]) {
153         mutableTypes = [[types mutableCopy] autorelease];
154         [mutableTypes removeObject:NSRTFDPboardType];
155         types = mutableTypes;
156     }
157     [pasteboard declareTypes:types owner:nil];    
158 #else
159     NSArray *types = selectionPasteboardTypes(canSmartCopyOrDelete, [attributedString containsAttachments]);
160     [pasteboard declareTypes:types owner:nil];
161     frame->editor()->client()->didSetSelectionTypesForPasteboard();
162 #endif
163     
164     // Put HTML on the pasteboard.
165     if ([types containsObject:WebArchivePboardType]) {
166         [pasteboard setData:frame->editor()->client()->dataForArchivedSelection(frame) forType:WebArchivePboardType];
167     }
168     
169     // Put the attributed string on the pasteboard (RTF/RTFD format).
170     if ([types containsObject:NSRTFDPboardType]) {
171         NSData *RTFDData = [attributedString RTFDFromRange:NSMakeRange(0, [attributedString length]) documentAttributes:nil];
172         [pasteboard setData:RTFDData forType:NSRTFDPboardType];
173     }
174     if ([types containsObject:NSRTFPboardType]) {
175         if ([attributedString containsAttachments])
176             attributedString = stripAttachmentCharacters(attributedString);
177         NSData *RTFData = [attributedString RTFFromRange:NSMakeRange(0, [attributedString length]) documentAttributes:nil];
178         [pasteboard setData:RTFData forType:NSRTFPboardType];
179     }
180     
181     // Put plain string on the pasteboard.
182     if ([types containsObject:NSStringPboardType]) {
183         // Map &nbsp; to a plain old space because this is better for source code, other browsers do it,
184         // and because HTML forces you to do this any time you want two spaces in a row.
185         String text = selectedRange->text();
186         text.replace('\\', frame->backslashAsCurrencySymbol());
187         NSMutableString *s = [[[(NSString*)text copy] autorelease] mutableCopy];
188         
189         NSString *NonBreakingSpaceString = [NSString stringWithCharacters:&noBreakSpace length:1];
190         [s replaceOccurrencesOfString:NonBreakingSpaceString withString:@" " options:0 range:NSMakeRange(0, [s length])];
191         [pasteboard setString:s forType:NSStringPboardType];
192         [s release];
193     }
194     
195     if ([types containsObject:WebSmartPastePboardType]) {
196         [pasteboard setData:nil forType:WebSmartPastePboardType];
197     }
198 }
199     
200 void Pasteboard::writeSelection(Range* selectedRange, bool canSmartCopyOrDelete, Frame* frame)
201 {
202     Pasteboard::writeSelection(m_pasteboard, selectedRange, canSmartCopyOrDelete, frame);
203 }
204
205 void Pasteboard::writeURL(NSPasteboard* pasteboard, NSArray* types, const KURL& url, const String& titleStr, Frame* frame)
206 {
207     if (WebArchivePboardType == nil)
208         Pasteboard::generalPasteboard(); //Initialises pasteboard types
209    
210     if (types == nil) {
211         types = writableTypesForURL();
212         [pasteboard declareTypes:types owner:nil];
213     }
214     
215     ASSERT(!url.isEmpty());
216     
217     NSURL *URL = url.getNSURL();
218     NSString *userVisibleString = frame->editor()->client()->userVisibleString(URL);
219     
220     NSString *title = (NSString*)titleStr;
221     if ([title length] == 0) {
222         title = [[URL path] lastPathComponent];
223         if ([title length] == 0)
224             title = userVisibleString;
225     }
226         
227     if ([types containsObject:WebURLsWithTitlesPboardType])
228         [pasteboard setPropertyList:[NSArray arrayWithObjects:[NSArray arrayWithObject:userVisibleString], 
229                                      [NSArray arrayWithObject:(NSString*)titleStr.stripWhiteSpace()], 
230                                      nil]
231                             forType:WebURLsWithTitlesPboardType];
232     if ([types containsObject:NSURLPboardType])
233         [URL writeToPasteboard:pasteboard];
234     if ([types containsObject:WebURLPboardType])
235         [pasteboard setString:userVisibleString forType:WebURLPboardType];
236     if ([types containsObject:WebURLNamePboardType])
237         [pasteboard setString:title forType:WebURLNamePboardType];
238     if ([types containsObject:NSStringPboardType])
239         [pasteboard setString:userVisibleString forType:NSStringPboardType];
240 }
241     
242 void Pasteboard::writeURL(const KURL& url, const String& titleStr, Frame* frame)
243 {
244     Pasteboard::writeURL(m_pasteboard, nil, url, titleStr, frame);
245 }
246
247 static NSFileWrapper* fileWrapperForImage(CachedResource* resource, NSURL *URL)
248 {
249     SharedBuffer* coreData = resource->data();
250     NSData *data = [[[NSData alloc] initWithBytes:coreData->platformData() 
251         length:coreData->platformDataSize()] autorelease];
252     NSFileWrapper *wrapper = [[[NSFileWrapper alloc] initRegularFileWithContents:data] autorelease];
253     String coreMIMEType = resource->response().mimeType();
254     NSString *MIMEType = nil;
255     if (!coreMIMEType.isNull())
256         MIMEType = coreMIMEType;
257     [wrapper setPreferredFilename:suggestedFilenameWithMIMEType(URL, MIMEType)];
258     return wrapper;
259 }
260
261 void Pasteboard::writeFileWrapperAsRTFDAttachment(NSFileWrapper* wrapper)
262 {
263     NSTextAttachment *attachment = [[NSTextAttachment alloc] initWithFileWrapper:wrapper];
264     
265     NSAttributedString *string = [NSAttributedString attributedStringWithAttachment:attachment];
266     [attachment release];
267     
268     NSData *RTFDData = [string RTFDFromRange:NSMakeRange(0, [string length]) documentAttributes:nil];
269     [m_pasteboard setData:RTFDData forType:NSRTFDPboardType];
270 }
271
272 void Pasteboard::writeImage(const HitTestResult& result)
273 {    
274     KURL coreURL = result.absoluteLinkURL();
275     if (coreURL.isEmpty())
276         coreURL = result.absoluteImageURL();
277     NSURL *URL = coreURL.getNSURL();
278     ASSERT(URL);
279
280     Node* node = result.innerNonSharedNode();
281     if (!node)
282         return;
283
284     NSString *title = result.altDisplayString().isNull() ? nil : (NSString*)(result.altDisplayString());
285     Frame* frame = node->document()->frame();
286
287     NSArray* types = writableTypesForImage();
288     [m_pasteboard declareTypes:types owner:nil];
289     writeURL(m_pasteboard, types, URL, title, frame);
290
291     Image* coreImage = result.image();
292     ASSERT(coreImage);
293     if (!coreImage)
294         return;
295     [m_pasteboard setData:[coreImage->getNSImage() TIFFRepresentation] forType:NSTIFFPboardType];
296
297     RenderImage* renderer = static_cast<RenderImage*>(node->renderer());
298     CachedResource* imageResource = static_cast<CachedResource*>(renderer->cachedImage());
299     ASSERT(imageResource);
300     String MIMEType = imageResource->response().mimeType();
301     ASSERT(MimeTypeRegistry::isSupportedImageResourceMIMEType(MIMEType));
302
303     if (imageResource)
304         writeFileWrapperAsRTFDAttachment(fileWrapperForImage(imageResource, URL));
305 }
306
307 void Pasteboard::writeImage(Node* imageNode, const KURL& url)
308 {
309     NSURL *URL = url.getNSURL();
310     ASSERT(URL);
311
312     Frame* frame = imageNode->document()->frame();
313     NSString *title = imageNode->document()->title();
314     
315     NSArray *types = writableTypesForImage();
316     [m_pasteboard declareTypes:types owner:nil];
317     writeURL(m_pasteboard, types, URL, title, frame);
318
319     if (!imageNode->renderer() || !imageNode->renderer()->isImage())
320         return;
321     RenderImage* renderer = static_cast<RenderImage*>(imageNode->renderer());
322     CachedImage* imageResource = renderer->cachedImage();
323     if (!imageResource)
324         return;
325     String MIMEType = imageResource->response().mimeType();
326     ASSERT(MimeTypeRegistry::isSupportedImageResourceMIMEType(MIMEType));
327
328     [m_pasteboard setData:[imageResource->image()->getNSImage() TIFFRepresentation] forType:NSTIFFPboardType];
329     
330     writeFileWrapperAsRTFDAttachment(fileWrapperForImage(imageResource, URL));
331 }
332
333 bool Pasteboard::canSmartReplace()
334 {
335     return [[m_pasteboard types] containsObject:WebSmartPastePboardType];
336 }
337
338 String Pasteboard::plainText(Frame* frame)
339 {
340     NSArray *types = [m_pasteboard types];
341     
342     if ([types containsObject:NSStringPboardType])
343         return [m_pasteboard stringForType:NSStringPboardType];
344     
345     NSAttributedString *attributedString = nil;
346     NSString *string;
347
348     if ([types containsObject:NSRTFDPboardType])
349         attributedString = [[NSAttributedString alloc] initWithRTFD:[m_pasteboard dataForType:NSRTFDPboardType] documentAttributes:NULL];
350     if (attributedString == nil && [types containsObject:NSRTFPboardType])
351         attributedString = [[NSAttributedString alloc] initWithRTF:[m_pasteboard dataForType:NSRTFPboardType] documentAttributes:NULL];
352     if (attributedString != nil) {
353         string = [[attributedString string] copy];
354         [attributedString release];
355         return [string autorelease];
356     }
357     
358     if ([types containsObject:NSFilenamesPboardType]) {
359         string = [[m_pasteboard propertyListForType:NSFilenamesPboardType] componentsJoinedByString:@"\n"];
360         if (string != nil)
361             return string;
362     }
363     
364     
365     NSURL *URL;
366     
367     if ((URL = [NSURL URLFromPasteboard:m_pasteboard])) {
368         // FIXME: using the editorClient to call into webkit, for now, since 
369         // calling [URL _web_userVisibleString] from WebCore involves migrating a sizable web of 
370         // helper code that should either be done in a separate patch or figured out in another way.
371         string = frame->editor()->client()->userVisibleString(URL);
372         if ([string length] > 0)
373             return string;
374     }
375
376     
377     return String(); 
378 }
379
380 PassRefPtr<DocumentFragment> Pasteboard::documentFragment(Frame* frame, PassRefPtr<Range> context, bool allowPlainText, bool& chosePlainText)
381 {
382     NSArray *types = [m_pasteboard types];
383     chosePlainText = false;
384
385     if ([types containsObject:NSHTMLPboardType]) {
386         NSString *HTMLString = [m_pasteboard stringForType:NSHTMLPboardType];
387         // This is a hack to make Microsoft's HTML pasteboard data work. See 3778785.
388         if ([HTMLString hasPrefix:@"Version:"]) {
389             NSRange range = [HTMLString rangeOfString:@"<html" options:NSCaseInsensitiveSearch];
390             if (range.location != NSNotFound) {
391                 HTMLString = [HTMLString substringFromIndex:range.location];
392             }
393         }
394         if ([HTMLString length] != 0) {
395             RefPtr<DocumentFragment> fragment = createFragmentFromMarkup(frame->document(), HTMLString, "");
396             if (fragment)
397                 return fragment.release();
398         }
399     }
400     
401     if (allowPlainText && [types containsObject:NSStringPboardType]) {
402         chosePlainText = true;
403         RefPtr<DocumentFragment> fragment = createFragmentFromText(context.get(), [m_pasteboard stringForType:NSStringPboardType]);
404         if (fragment)
405             return fragment.release();
406     }
407     
408     return 0;
409 }
410
411 }