Home | History | Annotate | Download | only in mac
      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