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 52using namespace WebCore; 53using 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 76namespace WebKit { 77 78static 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 93void 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 104static 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 117static 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 123void 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 201static 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 258static 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