1 /* 2 * Copyright (C) 2011 Apple 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 INC. AND ITS CONTRIBUTORS ``AS IS'' 14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS 17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 23 * THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26 #import "config.h" 27 #import "WebDragClient.h" 28 29 #import "PasteboardTypes.h" 30 #import "ShareableBitmap.h" 31 #import "WebCoreArgumentCoders.h" 32 #import "WebPage.h" 33 #import "WebPageProxyMessages.h" 34 #import <WebCore/CachedImage.h> 35 #import <WebCore/DOMElementInternal.h> 36 #import <WebCore/DOMPrivate.h> 37 #import <WebCore/DragController.h> 38 #import <WebCore/FrameView.h> 39 #import <WebCore/GraphicsContext.h> 40 #import <WebCore/LegacyWebArchive.h> 41 #import <WebCore/RenderImage.h> 42 #import <WebCore/ResourceHandle.h> 43 #import <WebCore/StringTruncator.h> 44 #import <WebKit/WebArchive.h> 45 #import <WebKit/WebKitNSStringExtras.h> 46 #import <WebKit/WebNSFileManagerExtras.h> 47 #import <WebKit/WebNSPasteboardExtras.h> 48 #import <WebKit/WebNSURLExtras.h> 49 #import <WebKitSystemInterface.h> 50 #import <wtf/StdLibExtras.h> 51 52 using namespace WebCore; 53 using namespace WebKit; 54 55 // Internal AppKit class. If the pasteboard handling was in the same process 56 // that called the dragImage method, this would be created automatically. 57 // Create it explicitly because dragImage is called in the UI process. 58 @interface NSFilePromiseDragSource : NSObject 59 { 60 char _unknownFields[256]; 61 } 62 - (id)initWithSource:(id)dragSource; 63 - (void)setTypes:(NSArray *)types onPasteboard:(NSPasteboard *)pasteboard; 64 @end 65 66 @interface WKPasteboardFilePromiseOwner : NSFilePromiseDragSource 67 @end 68 69 @interface WKPasteboardOwner : NSObject 70 { 71 CachedResourceHandle<CachedImage> _image; 72 } 73 - (id)initWithImage:(CachedImage*)image; 74 @end 75 76 namespace WebKit { 77 78 static PassRefPtr<ShareableBitmap> convertImageToBitmap(NSImage *image) 79 { 80 RefPtr<ShareableBitmap> bitmap = ShareableBitmap::createShareable(IntSize([image size]), ShareableBitmap::SupportsAlpha); 81 OwnPtr<GraphicsContext> graphicsContext = bitmap->createGraphicsContext(); 82 83 RetainPtr<NSGraphicsContext> savedContext = [NSGraphicsContext currentContext]; 84 85 [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:graphicsContext->platformContext() flipped:YES]]; 86 [image drawInRect:NSMakeRect(0, 0, bitmap->size().width(), bitmap->size().height()) fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1 respectFlipped:YES hints:nil]; 87 88 [NSGraphicsContext setCurrentContext:savedContext.get()]; 89 90 return bitmap.release(); 91 } 92 93 void WebDragClient::startDrag(RetainPtr<NSImage> image, const IntPoint& point, const IntPoint&, Clipboard*, Frame* frame, bool linkDrag) 94 { 95 RefPtr<ShareableBitmap> bitmap = convertImageToBitmap(image.get()); 96 ShareableBitmap::Handle handle; 97 if (!bitmap->createHandle(handle)) 98 return; 99 100 // FIXME: Seems this message should be named StartDrag, not SetDragImage. 101 m_page->send(Messages::WebPageProxy::SetDragImage(frame->view()->contentsToWindow(point), handle, linkDrag)); 102 } 103 104 static CachedImage* cachedImage(Element* element) 105 { 106 RenderObject* renderer = element->renderer(); 107 if (!renderer) 108 return 0; 109 if (!renderer->isRenderImage()) 110 return 0; 111 CachedImage* image = toRenderImage(renderer)->cachedImage(); 112 if (!image || image->errorOccurred()) 113 return 0; 114 return image; 115 } 116 117 static NSArray *arrayForURLsWithTitles(NSURL *URL, NSString *title) 118 { 119 return [NSArray arrayWithObjects:[NSArray arrayWithObject:[URL _web_originalDataAsString]], 120 [NSArray arrayWithObject:[title _webkit_stringByTrimmingWhitespace]], nil]; 121 } 122 123 void WebDragClient::declareAndWriteDragImage(NSPasteboard *pasteboard, DOMElement *element, NSURL *URL, NSString *title, WebCore::Frame*) 124 { 125 ASSERT(element); 126 ASSERT(pasteboard && pasteboard == [NSPasteboard pasteboardWithName:NSDragPboard]); 127 128 Element* coreElement = core(element); 129 130 CachedImage* image = cachedImage(coreElement); 131 132 NSString *extension = @""; 133 if (image) { 134 extension = image->image()->filenameExtension(); 135 if (![extension length]) 136 return; 137 } 138 139 if (![title length]) { 140 title = [[URL path] lastPathComponent]; 141 if (![title length]) 142 title = [URL _web_userVisibleString]; 143 } 144 145 RefPtr<LegacyWebArchive> archive = LegacyWebArchive::create(coreElement); 146 147 RetainPtr<NSMutableArray> types(AdoptNS, [[NSMutableArray alloc] initWithObjects:NSFilesPromisePboardType, nil]); 148 [types.get() addObjectsFromArray:archive ? PasteboardTypes::forImagesWithArchive() : PasteboardTypes::forImages()]; 149 150 RetainPtr<WKPasteboardOwner> pasteboardOwner(AdoptNS, [[WKPasteboardOwner alloc] initWithImage:image]); 151 152 RetainPtr<WKPasteboardFilePromiseOwner> filePromiseOwner(AdoptNS, [(WKPasteboardFilePromiseOwner *)[WKPasteboardFilePromiseOwner alloc] initWithSource:pasteboardOwner.get()]); 153 m_page->setDragSource(filePromiseOwner.get()); 154 155 [pasteboard declareTypes:types.get() owner:pasteboardOwner.leakRef()]; 156 157 [pasteboard setPropertyList:[NSArray arrayWithObject:extension] forType:NSFilesPromisePboardType]; 158 159 [filePromiseOwner.get() setTypes:[pasteboard propertyListForType:NSFilesPromisePboardType] onPasteboard:pasteboard]; 160 161 [URL writeToPasteboard:pasteboard]; 162 163 [pasteboard setString:[URL _web_originalDataAsString] forType:PasteboardTypes::WebURLPboardType]; 164 165 [pasteboard setString:title forType:PasteboardTypes::WebURLNamePboardType]; 166 167 [pasteboard setString:[URL _web_userVisibleString] forType:NSStringPboardType]; 168 169 [pasteboard setPropertyList:arrayForURLsWithTitles(URL, title) forType:PasteboardTypes::WebURLsWithTitlesPboardType]; 170 171 if (archive) 172 [pasteboard setData:(NSData *)archive->rawDataRepresentation().get() forType:PasteboardTypes::WebArchivePboardType]; 173 } 174 175 } // namespace WebKit 176 177 @implementation WKPasteboardFilePromiseOwner 178 179 // The AppKit implementation of copyDropDirectory gets the current pasteboard in 180 // a way that only works in the process where the drag is initiated. We supply 181 // an implementation that gets the pasteboard by name instead. 182 - (CFURLRef)copyDropDirectory 183 { 184 PasteboardRef pasteboard; 185 OSStatus status = PasteboardCreate((CFStringRef)NSDragPboard, &pasteboard); 186 if (status != noErr || !pasteboard) 187 return 0; 188 CFURLRef location = 0; 189 status = PasteboardCopyPasteLocation(pasteboard, &location); 190 CFRelease(pasteboard); 191 if (status != noErr || !location) 192 return 0; 193 CFMakeCollectable(location); 194 return location; 195 } 196 197 @end 198 199 @implementation WKPasteboardOwner 200 201 static CachedResourceClient* promisedDataClient() 202 { 203 static CachedResourceClient* client = new CachedResourceClient; 204 return client; 205 } 206 207 - (void)clearImage 208 { 209 if (!_image) 210 return; 211 _image->removeClient(promisedDataClient()); 212 _image = 0; 213 } 214 215 - (id)initWithImage:(CachedImage*)image 216 { 217 self = [super init]; 218 if (!self) 219 return nil; 220 221 _image = image; 222 if (image) 223 image->addClient(promisedDataClient()); 224 return self; 225 } 226 227 - (void)dealloc 228 { 229 [self clearImage]; 230 [super dealloc]; 231 } 232 233 - (void)finalize 234 { 235 [self clearImage]; 236 [super finalize]; 237 } 238 239 - (void)pasteboard:(NSPasteboard *)pasteboard provideDataForType:(NSString *)type 240 { 241 if ([type isEqual:NSTIFFPboardType]) { 242 if (_image) { 243 if (Image* image = _image->image()) 244 [pasteboard setData:(NSData *)image->getTIFFRepresentation() forType:NSTIFFPboardType]; 245 [self clearImage]; 246 } 247 return; 248 } 249 // FIXME: Handle RTFD here. 250 } 251 252 - (void)pasteboardChangedOwner:(NSPasteboard *)pasteboard 253 { 254 [self clearImage]; 255 CFRelease(self); // Balanced by the leakRef that WebDragClient::declareAndWriteDragImage does when making this pasteboard owner. 256 } 257 258 static bool matchesExtensionOrEquivalent(NSString *filename, NSString *extension) 259 { 260 NSString *extensionAsSuffix = [@"." stringByAppendingString:extension]; 261 return [filename _webkit_hasCaseInsensitiveSuffix:extensionAsSuffix] 262 || ([extension _webkit_isCaseInsensitiveEqualToString:@"jpeg"] 263 && [filename _webkit_hasCaseInsensitiveSuffix:@".jpg"]); 264 } 265 266 - (NSArray *)namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination 267 { 268 NSFileWrapper *wrapper = nil; 269 NSURL *draggingImageURL = nil; 270 271 if (_image) { 272 if (SharedBuffer* buffer = _image->CachedResource::data()) { 273 NSData *data = buffer->createNSData(); 274 NSURLResponse *response = _image->response().nsURLResponse(); 275 draggingImageURL = [response URL]; 276 wrapper = [[[NSFileWrapper alloc] initRegularFileWithContents:data] autorelease]; 277 NSString* filename = [response suggestedFilename]; 278 NSString* trueExtension(_image->image()->filenameExtension()); 279 if (!matchesExtensionOrEquivalent(filename, trueExtension)) 280 filename = [[filename stringByAppendingString:@"."] stringByAppendingString:trueExtension]; 281 [wrapper setPreferredFilename:filename]; 282 } 283 } 284 285 // FIXME: Do we need to handle the case where we do not have a CachedImage? 286 // WebKit1 had code for this case. 287 288 if (!wrapper) { 289 LOG_ERROR("Failed to create image file."); 290 return nil; 291 } 292 293 // FIXME: Report an error if we fail to create a file. 294 NSString *path = [[dropDestination path] stringByAppendingPathComponent:[wrapper preferredFilename]]; 295 path = [[NSFileManager defaultManager] _webkit_pathWithUniqueFilenameForPath:path]; 296 if (![wrapper writeToFile:path atomically:NO updateFilenames:YES]) 297 LOG_ERROR("Failed to create image file via -[NSFileWrapper writeToFile:atomically:updateFilenames:] at path %@", path); 298 299 if (draggingImageURL) 300 [[NSFileManager defaultManager] _webkit_setMetadataURL:[draggingImageURL absoluteString] referrer:nil atPath:path]; 301 302 return [NSArray arrayWithObject:[path lastPathComponent]]; 303 } 304 305 @end 306