cf41f64624e80886b523b8319492b982ac61a05d
[WebKit-https.git] / Source / WebKit2 / UIProcess / mac / WebPageProxyMac.mm
1 /*
2  * Copyright (C) 2010, 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 "WebPageProxy.h"
28
29 #import "AttributedString.h"
30 #import "ColorSpaceData.h"
31 #import "DataReference.h"
32 #import "DictionaryPopupInfo.h"
33 #import "EditorState.h"
34 #import "NativeWebKeyboardEvent.h"
35 #import "PluginComplexTextInputState.h"
36 #import "PageClient.h"
37 #import "PageClientImpl.h"
38 #import "StringUtilities.h"
39 #import "TextChecker.h"
40 #import "WebPageMessages.h"
41 #import "WebProcessProxy.h"
42 #import <WebCore/DictationAlternative.h>
43 #import <WebCore/GraphicsLayer.h>
44 #import <WebCore/SharedBuffer.h>
45 #import <WebCore/SystemVersionMac.h>
46 #import <WebCore/TextAlternativeWithRange.h>
47 #import <WebKitSystemInterface.h>
48 #import <wtf/text/StringConcatenate.h>
49
50 @interface NSApplication (Details)
51 - (void)speakString:(NSString *)string;
52 @end
53
54 #define MESSAGE_CHECK(assertion) MESSAGE_CHECK_BASE(assertion, process()->connection())
55
56 using namespace WebCore;
57
58 namespace WebKit {
59
60 #if defined(__ppc__) || defined(__ppc64__)
61 #define PROCESSOR "PPC"
62 #elif defined(__i386__) || defined(__x86_64__)
63 #define PROCESSOR "Intel"
64 #else
65 #error Unknown architecture
66 #endif
67
68 static NSString *systemMarketingVersionForUserAgentString()
69 {
70     // Use underscores instead of dots because when we first added the Mac OS X version to the user agent string
71     // we were concerned about old DHTML libraries interpreting "4." as Netscape 4. That's no longer a concern for us
72     // but we're sticking with the underscores for compatibility with the format used by older versions of Safari.
73     return [systemMarketingVersion() stringByReplacingOccurrencesOfString:@"." withString:@"_"];
74 }
75
76 static String userVisibleWebKitVersionString()
77 {
78     // If the version is 4 digits long or longer, then the first digit represents
79     // the version of the OS. Our user agent string should not include this first digit,
80     // so strip it off and report the rest as the version. <rdar://problem/4997547>
81     NSString *fullVersion = [[NSBundle bundleForClass:NSClassFromString(@"WKView")] objectForInfoDictionaryKey:(NSString *)kCFBundleVersionKey];
82     NSRange nonDigitRange = [fullVersion rangeOfCharacterFromSet:[[NSCharacterSet decimalDigitCharacterSet] invertedSet]];
83     if (nonDigitRange.location == NSNotFound && [fullVersion length] >= 4)
84         return [fullVersion substringFromIndex:1];
85     if (nonDigitRange.location != NSNotFound && nonDigitRange.location >= 4)
86         return [fullVersion substringFromIndex:1];
87     return fullVersion;
88 }
89
90 String WebPageProxy::standardUserAgent(const String& applicationNameForUserAgent)
91 {
92     DEFINE_STATIC_LOCAL(String, osVersion, (systemMarketingVersionForUserAgentString()));
93     DEFINE_STATIC_LOCAL(String, webKitVersion, (userVisibleWebKitVersionString()));
94
95     if (applicationNameForUserAgent.isEmpty())
96         return makeString("Mozilla/5.0 (Macintosh; " PROCESSOR " Mac OS X ", osVersion, ") AppleWebKit/", webKitVersion, " (KHTML, like Gecko)");
97     return makeString("Mozilla/5.0 (Macintosh; " PROCESSOR " Mac OS X ", osVersion, ") AppleWebKit/", webKitVersion, " (KHTML, like Gecko) ", applicationNameForUserAgent);
98 }
99
100 void WebPageProxy::getIsSpeaking(bool& isSpeaking)
101 {
102     isSpeaking = [NSApp isSpeaking];
103 }
104
105 void WebPageProxy::speak(const String& string)
106 {
107     [NSApp speakString:nsStringFromWebCoreString(string)];
108 }
109
110 void WebPageProxy::stopSpeaking()
111 {
112     [NSApp stopSpeaking:nil];
113 }
114
115 void WebPageProxy::searchWithSpotlight(const String& string)
116 {
117     [[NSWorkspace sharedWorkspace] showSearchResultsForQueryString:nsStringFromWebCoreString(string)];
118 }
119     
120 CGContextRef WebPageProxy::containingWindowGraphicsContext()
121 {
122     return m_pageClient->containingWindowGraphicsContext();
123 }
124
125 void WebPageProxy::updateWindowIsVisible(bool windowIsVisible)
126 {
127     if (!isValid())
128         return;
129     process()->send(Messages::WebPage::SetWindowIsVisible(windowIsVisible), m_pageID);
130 }
131
132 void WebPageProxy::windowAndViewFramesChanged(const FloatRect& windowFrameInScreenCoordinates, const FloatRect& viewFrameInWindowCoordinates, const FloatPoint& accessibilityViewCoordinates)
133 {
134     if (!isValid())
135         return;
136
137     process()->send(Messages::WebPage::WindowAndViewFramesChanged(windowFrameInScreenCoordinates, viewFrameInWindowCoordinates, accessibilityViewCoordinates), m_pageID);
138 }
139
140 void WebPageProxy::viewExposedRectChanged(const FloatRect& exposedRect)
141 {
142     if (!isValid())
143         return;
144
145     process()->send(Messages::WebPage::ViewExposedRectChanged(exposedRect), m_pageID);
146 }
147
148 void WebPageProxy::setMainFrameIsScrollable(bool isScrollable)
149 {
150     if (!isValid())
151         return;
152
153     process()->send(Messages::WebPage::SetMainFrameIsScrollable(isScrollable), m_pageID);
154 }
155
156 void WebPageProxy::setComposition(const String& text, Vector<CompositionUnderline> underlines, uint64_t selectionStart, uint64_t selectionEnd, uint64_t replacementRangeStart, uint64_t replacementRangeEnd)
157 {
158     if (!isValid()) {
159         // If this fails, we should call -discardMarkedText on input context to notify the input method.
160         // This will happen naturally later, as part of reloading the page.
161         return;
162     }
163
164     process()->sendSync(Messages::WebPage::SetComposition(text, underlines, selectionStart, selectionEnd, replacementRangeStart, replacementRangeEnd), Messages::WebPage::SetComposition::Reply(m_editorState), m_pageID);
165 }
166
167 void WebPageProxy::confirmComposition()
168 {
169     if (!isValid())
170         return;
171
172     process()->sendSync(Messages::WebPage::ConfirmComposition(), Messages::WebPage::ConfirmComposition::Reply(m_editorState), m_pageID);
173 }
174
175 void WebPageProxy::cancelComposition()
176 {
177     if (!isValid())
178         return;
179
180     process()->sendSync(Messages::WebPage::CancelComposition(), Messages::WebPage::ConfirmComposition::Reply(m_editorState), m_pageID);
181 }
182
183 bool WebPageProxy::insertText(const String& text, uint64_t replacementRangeStart, uint64_t replacementRangeEnd)
184 {
185     if (!isValid())
186         return true;
187
188     bool handled = true;
189     process()->sendSync(Messages::WebPage::InsertText(text, replacementRangeStart, replacementRangeEnd), Messages::WebPage::InsertText::Reply(handled, m_editorState), m_pageID);
190     return handled;
191 }
192
193 bool WebPageProxy::insertDictatedText(const String& text, uint64_t replacementRangeStart, uint64_t replacementRangeEnd, const Vector<TextAlternativeWithRange>& dictationAlternativesWithRange)
194 {
195 #if USE(DICTATION_ALTERNATIVES)
196     if (dictationAlternativesWithRange.isEmpty())
197         return insertText(text, replacementRangeStart, replacementRangeEnd);
198
199     if (!isValid())
200         return true;
201
202     Vector<DictationAlternative> dictationAlternatives;
203
204     for (size_t i = 0; i < dictationAlternativesWithRange.size(); ++i) {
205         const TextAlternativeWithRange& alternativeWithRange = dictationAlternativesWithRange[i];
206         uint64_t dictationContext = m_pageClient->addDictationAlternatives(alternativeWithRange.alternatives);
207         if (dictationContext)
208             dictationAlternatives.append(DictationAlternative(alternativeWithRange.range.location, alternativeWithRange.range.length, dictationContext));
209     }
210
211     if (dictationAlternatives.isEmpty())
212         return insertText(text, replacementRangeStart, replacementRangeEnd);
213
214     bool handled = true;
215     process()->sendSync(Messages::WebPage::InsertDictatedText(text, replacementRangeStart, replacementRangeEnd, dictationAlternatives), Messages::WebPage::InsertDictatedText::Reply(handled, m_editorState), m_pageID);
216     return handled;
217 #else
218     return insertText(text, replacementRangeStart, replacementRangeEnd);
219 #endif
220 }
221
222 void WebPageProxy::getMarkedRange(uint64_t& location, uint64_t& length)
223 {
224     location = NSNotFound;
225     length = 0;
226
227     if (!isValid())
228         return;
229
230     process()->sendSync(Messages::WebPage::GetMarkedRange(), Messages::WebPage::GetMarkedRange::Reply(location, length), m_pageID);
231 }
232
233 void WebPageProxy::getSelectedRange(uint64_t& location, uint64_t& length)
234 {
235     location = NSNotFound;
236     length = 0;
237
238     if (!isValid())
239         return;
240
241     process()->sendSync(Messages::WebPage::GetSelectedRange(), Messages::WebPage::GetSelectedRange::Reply(location, length), m_pageID);
242 }
243
244 void WebPageProxy::getAttributedSubstringFromRange(uint64_t location, uint64_t length, AttributedString& result)
245 {
246     if (!isValid())
247         return;
248     process()->sendSync(Messages::WebPage::GetAttributedSubstringFromRange(location, length), Messages::WebPage::GetAttributedSubstringFromRange::Reply(result), m_pageID);
249 }
250
251 uint64_t WebPageProxy::characterIndexForPoint(const IntPoint point)
252 {
253     if (!isValid())
254         return 0;
255
256     uint64_t result = 0;
257     process()->sendSync(Messages::WebPage::CharacterIndexForPoint(point), Messages::WebPage::CharacterIndexForPoint::Reply(result), m_pageID);
258     return result;
259 }
260
261 IntRect WebPageProxy::firstRectForCharacterRange(uint64_t location, uint64_t length)
262 {
263     if (!isValid())
264         return IntRect();
265
266     IntRect resultRect;
267     process()->sendSync(Messages::WebPage::FirstRectForCharacterRange(location, length), Messages::WebPage::FirstRectForCharacterRange::Reply(resultRect), m_pageID);
268     return resultRect;
269 }
270
271 bool WebPageProxy::executeKeypressCommands(const Vector<WebCore::KeypressCommand>& commands)
272 {
273     if (!isValid())
274         return false;
275
276     bool result = false;
277     process()->sendSync(Messages::WebPage::ExecuteKeypressCommands(commands), Messages::WebPage::ExecuteKeypressCommands::Reply(result, m_editorState), m_pageID);
278     return result;
279 }
280
281 String WebPageProxy::stringSelectionForPasteboard()
282 {
283     String value;
284     if (!isValid())
285         return value;
286     
287     const double messageTimeout = 20;
288     process()->sendSync(Messages::WebPage::GetStringSelectionForPasteboard(), Messages::WebPage::GetStringSelectionForPasteboard::Reply(value), m_pageID, messageTimeout);
289     return value;
290 }
291
292 PassRefPtr<WebCore::SharedBuffer> WebPageProxy::dataSelectionForPasteboard(const String& pasteboardType)
293 {
294     if (!isValid())
295         return 0;
296     SharedMemory::Handle handle;
297     uint64_t size = 0;
298     const double messageTimeout = 20;
299     process()->sendSync(Messages::WebPage::GetDataSelectionForPasteboard(pasteboardType),
300                                                 Messages::WebPage::GetDataSelectionForPasteboard::Reply(handle, size), m_pageID, messageTimeout);
301     if (handle.isNull())
302         return 0;
303     RefPtr<SharedMemory> sharedMemoryBuffer = SharedMemory::create(handle, SharedMemory::ReadOnly);
304     return SharedBuffer::create(static_cast<unsigned char *>(sharedMemoryBuffer->data()), size);
305 }
306
307 bool WebPageProxy::readSelectionFromPasteboard(const String& pasteboardName)
308 {
309     if (!isValid())
310         return false;
311
312     bool result = false;
313     const double messageTimeout = 20;
314     process()->sendSync(Messages::WebPage::ReadSelectionFromPasteboard(pasteboardName), Messages::WebPage::ReadSelectionFromPasteboard::Reply(result), m_pageID, messageTimeout);
315     return result;
316 }
317
318 #if ENABLE(DRAG_SUPPORT)
319 void WebPageProxy::setDragImage(const WebCore::IntPoint& clientPosition, const ShareableBitmap::Handle& dragImageHandle, bool isLinkDrag)
320 {
321     RefPtr<ShareableBitmap> dragImage = ShareableBitmap::create(dragImageHandle);
322     if (!dragImage)
323         return;
324     
325     m_pageClient->setDragImage(clientPosition, dragImage.release(), isLinkDrag);
326 }
327
328 void WebPageProxy::setPromisedData(const String& pasteboardName, const SharedMemory::Handle& imageHandle, uint64_t imageSize, const String& filename, const String& extension,
329                                    const String& title, const String& url, const String& visibleURL, const SharedMemory::Handle& archiveHandle, uint64_t archiveSize)
330 {
331     RefPtr<SharedMemory> sharedMemoryImage = SharedMemory::create(imageHandle, SharedMemory::ReadOnly);
332     RefPtr<SharedBuffer> imageBuffer = SharedBuffer::create(static_cast<unsigned char*>(sharedMemoryImage->data()), imageSize);
333     RefPtr<SharedBuffer> archiveBuffer;
334     
335     if (!archiveHandle.isNull()) {
336         RefPtr<SharedMemory> sharedMemoryArchive = SharedMemory::create(archiveHandle, SharedMemory::ReadOnly);;
337         archiveBuffer = SharedBuffer::create(static_cast<unsigned char*>(sharedMemoryArchive->data()), archiveSize);
338     }
339     m_pageClient->setPromisedData(pasteboardName, imageBuffer, filename, extension, title, url, visibleURL, archiveBuffer);
340 }
341 #endif
342
343 void WebPageProxy::performDictionaryLookupAtLocation(const WebCore::FloatPoint& point)
344 {
345     if (!isValid())
346         return;
347
348     process()->send(Messages::WebPage::PerformDictionaryLookupAtLocation(point), m_pageID); 
349 }
350
351 void WebPageProxy::interpretQueuedKeyEvent(const EditorState& state, bool& handled, Vector<WebCore::KeypressCommand>& commands)
352 {
353     m_editorState = state;
354     handled = m_pageClient->interpretKeyEvent(m_keyEventQueue.first(), commands);
355 }
356
357 // Complex text input support for plug-ins.
358 void WebPageProxy::sendComplexTextInputToPlugin(uint64_t pluginComplexTextInputIdentifier, const String& textInput)
359 {
360     if (!isValid())
361         return;
362     
363     process()->send(Messages::WebPage::SendComplexTextInputToPlugin(pluginComplexTextInputIdentifier, textInput), m_pageID);
364 }
365
366 void WebPageProxy::uppercaseWord()
367 {
368     process()->send(Messages::WebPage::UppercaseWord(), m_pageID);
369 }
370
371 void WebPageProxy::lowercaseWord()
372 {
373     process()->send(Messages::WebPage::LowercaseWord(), m_pageID);
374 }
375
376 void WebPageProxy::capitalizeWord()
377 {
378     process()->send(Messages::WebPage::CapitalizeWord(), m_pageID);
379 }
380
381 void WebPageProxy::setSmartInsertDeleteEnabled(bool isSmartInsertDeleteEnabled)
382 {
383     if (m_isSmartInsertDeleteEnabled == isSmartInsertDeleteEnabled)
384         return;
385
386     TextChecker::setSmartInsertDeleteEnabled(isSmartInsertDeleteEnabled);
387     m_isSmartInsertDeleteEnabled = isSmartInsertDeleteEnabled;
388     process()->send(Messages::WebPage::SetSmartInsertDeleteEnabled(isSmartInsertDeleteEnabled), m_pageID);
389 }
390
391 void WebPageProxy::didPerformDictionaryLookup(const AttributedString& text, const DictionaryPopupInfo& dictionaryPopupInfo)
392 {
393     m_pageClient->didPerformDictionaryLookup(text, dictionaryPopupInfo);
394 }
395     
396 void WebPageProxy::registerWebProcessAccessibilityToken(const CoreIPC::DataReference& data)
397 {
398     m_pageClient->accessibilityWebProcessTokenReceived(data);
399 }    
400     
401 void WebPageProxy::makeFirstResponder()
402 {
403     m_pageClient->makeFirstResponder();
404 }
405
406 ColorSpaceData WebPageProxy::colorSpace()
407 {
408     return m_pageClient->colorSpace();
409 }
410
411 void WebPageProxy::registerUIProcessAccessibilityTokens(const CoreIPC::DataReference& elementToken, const CoreIPC::DataReference& windowToken)
412 {
413     if (!isValid())
414         return;
415
416     process()->send(Messages::WebPage::RegisterUIProcessAccessibilityTokens(elementToken, windowToken), m_pageID);
417 }
418
419 void WebPageProxy::pluginFocusOrWindowFocusChanged(uint64_t pluginComplexTextInputIdentifier, bool pluginHasFocusAndWindowHasFocus)
420 {
421     m_pageClient->pluginFocusOrWindowFocusChanged(pluginComplexTextInputIdentifier, pluginHasFocusAndWindowHasFocus);
422 }
423
424 void WebPageProxy::setPluginComplexTextInputState(uint64_t pluginComplexTextInputIdentifier, uint64_t pluginComplexTextInputState)
425 {
426     MESSAGE_CHECK(isValidPluginComplexTextInputState(pluginComplexTextInputState));
427
428     m_pageClient->setPluginComplexTextInputState(pluginComplexTextInputIdentifier, static_cast<PluginComplexTextInputState>(pluginComplexTextInputState));
429 }
430
431 void WebPageProxy::executeSavedCommandBySelector(const String& selector, bool& handled)
432 {
433     MESSAGE_CHECK(isValidKeypressCommandName(selector));
434
435     handled = m_pageClient->executeSavedCommandBySelector(selector);
436 }
437
438 bool WebPageProxy::shouldDelayWindowOrderingForEvent(const WebKit::WebMouseEvent& event)
439 {
440     if (!process()->isValid())
441         return false;
442
443     bool result = false;
444     const double messageTimeout = 3;
445     process()->sendSync(Messages::WebPage::ShouldDelayWindowOrderingEvent(event), Messages::WebPage::ShouldDelayWindowOrderingEvent::Reply(result), m_pageID, messageTimeout);
446     return result;
447 }
448
449 bool WebPageProxy::acceptsFirstMouse(int eventNumber, const WebKit::WebMouseEvent& event)
450 {
451     if (!isValid())
452         return false;
453
454     bool result = false;
455     const double messageTimeout = 3;
456     process()->sendSync(Messages::WebPage::AcceptsFirstMouse(eventNumber, event), Messages::WebPage::AcceptsFirstMouse::Reply(result), m_pageID, messageTimeout);
457     return result;
458 }
459
460 WKView* WebPageProxy::wkView() const
461 {
462     return m_pageClient->wkView();
463 }
464
465 void WebPageProxy::intrinsicContentSizeDidChange(const IntSize& intrinsicContentSize)
466 {
467     m_pageClient->intrinsicContentSizeDidChange(intrinsicContentSize);
468 }
469
470 void WebPageProxy::setAcceleratedCompositingRootLayer(const GraphicsLayer* rootLayer)
471 {
472     m_pageClient->setAcceleratedCompositingRootLayer(rootLayer->platformLayer());
473 }
474
475 static NSString *temporaryPDFDirectoryPath()
476 {
477     static NSString *temporaryPDFDirectoryPath;
478
479     if (!temporaryPDFDirectoryPath) {
480         NSString *temporaryDirectoryTemplate = [NSTemporaryDirectory() stringByAppendingPathComponent:@"WebKitPDFs-XXXXXX"];
481         CString templateRepresentation = [temporaryDirectoryTemplate fileSystemRepresentation];
482
483         if (mkdtemp(templateRepresentation.mutableData()))
484             temporaryPDFDirectoryPath = [[[NSFileManager defaultManager] stringWithFileSystemRepresentation:templateRepresentation.data() length:templateRepresentation.length()] copy];
485     }
486
487     return temporaryPDFDirectoryPath;
488 }
489
490 static NSString *pathToPDFOnDisk(const String& suggestedFilename)
491 {
492     NSString *pdfDirectoryPath = temporaryPDFDirectoryPath();
493     if (!pdfDirectoryPath) {
494         WTFLogAlways("Cannot create temporary PDF download directory.");
495         return nil;
496     }
497
498     NSString *path = [pdfDirectoryPath stringByAppendingPathComponent:suggestedFilename];
499
500     NSFileManager *fileManager = [NSFileManager defaultManager];
501     if ([fileManager fileExistsAtPath:path]) {
502         NSString *pathTemplatePrefix = [pdfDirectoryPath stringByAppendingPathComponent:@"XXXXXX-"];
503         NSString *pathTemplate = [pathTemplatePrefix stringByAppendingString:suggestedFilename];
504         CString pathTemplateRepresentation = [pathTemplate fileSystemRepresentation];
505
506         int fd = mkstemps(pathTemplateRepresentation.mutableData(), pathTemplateRepresentation.length() - strlen([pathTemplatePrefix fileSystemRepresentation]) + 1);
507         if (fd < 0) {
508             WTFLogAlways("Cannot create PDF file in the temporary directory (%s).", suggestedFilename.utf8().data());
509             return nil;
510         }
511
512         close(fd);
513         path = [fileManager stringWithFileSystemRepresentation:pathTemplateRepresentation.data() length:pathTemplateRepresentation.length()];
514     }
515
516     return path;
517 }
518
519 void WebPageProxy::savePDFToTemporaryFolderAndOpenWithNativeApplicationRaw(const String& suggestedFilename, const String& originatingURLString, const uint8_t* data, unsigned long size, const String& pdfUUID)
520 {
521     // FIXME: Write originatingURLString to the file's originating URL metadata (perhaps WKSetMetadataURL?).
522     UNUSED_PARAM(originatingURLString);
523
524     if (!suggestedFilename.endsWith(".pdf", false)) {
525         WTFLogAlways("Cannot save file without .pdf extension to the temporary directory.");
526         return;
527     }
528
529     if (!size) {
530         WTFLogAlways("Cannot save empty PDF file to the temporary directory.");
531         return;
532     }
533
534     NSString *nsPath = pathToPDFOnDisk(suggestedFilename);
535
536     if (!nsPath)
537         return;
538
539     RetainPtr<NSNumber> permissions = adoptNS([[NSNumber alloc] initWithInt:S_IRUSR]);
540     RetainPtr<NSDictionary> fileAttributes = adoptNS([[NSDictionary alloc] initWithObjectsAndKeys:permissions.get(), NSFilePosixPermissions, nil]);
541     RetainPtr<NSData> nsData = adoptNS([[NSData alloc] initWithBytesNoCopy:(void*)data length:size freeWhenDone:NO]);
542
543     if (![[NSFileManager defaultManager] createFileAtPath:nsPath contents:nsData.get() attributes:fileAttributes.get()]) {
544         WTFLogAlways("Cannot create PDF file in the temporary directory (%s).", suggestedFilename.utf8().data());
545         return;
546     }
547
548     m_temporaryPDFFiles.add(pdfUUID, nsPath);
549
550     [[NSWorkspace sharedWorkspace] openFile:nsPath];
551 }
552
553 void WebPageProxy::savePDFToTemporaryFolderAndOpenWithNativeApplication(const String& suggestedFilename, const String& originatingURLString, const CoreIPC::DataReference& data, const String& pdfUUID)
554 {
555     if (data.isEmpty()) {
556         WTFLogAlways("Cannot save empty PDF file to the temporary directory.");
557         return;
558     }
559
560     savePDFToTemporaryFolderAndOpenWithNativeApplicationRaw(suggestedFilename, originatingURLString, data.data(), data.size(), pdfUUID);
561 }
562
563 void WebPageProxy::openPDFFromTemporaryFolderWithNativeApplication(const String& pdfUUID)
564 {
565     String pdfFilename = m_temporaryPDFFiles.get(pdfUUID);
566
567     if (!pdfFilename.endsWith(".pdf", false))
568         return;
569
570     [[NSWorkspace sharedWorkspace] openFile:pdfFilename];
571 }
572
573 } // namespace WebKit