• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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