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#if PLATFORM(MAC)
30
31#import "APIUIClient.h"
32#import "AttributedString.h"
33#import "ColorSpaceData.h"
34#import "DataReference.h"
35#import "DictionaryPopupInfo.h"
36#import "EditingRange.h"
37#import "EditorState.h"
38#import "MenuUtilities.h"
39#import "NativeWebKeyboardEvent.h"
40#import "PageClient.h"
41#import "PageClientImpl.h"
42#import "PluginComplexTextInputState.h"
43#import "StringUtilities.h"
44#import "TextChecker.h"
45#import "WKBrowsingContextControllerInternal.h"
46#import "WebContext.h"
47#import "WebPageMessages.h"
48#import "WebProcessProxy.h"
49#import <WebCore/DictationAlternative.h>
50#import <WebCore/GraphicsLayer.h>
51#import <WebCore/RuntimeApplicationChecks.h>
52#import <WebCore/SharedBuffer.h>
53#import <WebCore/TextAlternativeWithRange.h>
54#import <WebCore/UserAgent.h>
55#import <mach-o/dyld.h>
56#import <wtf/NeverDestroyed.h>
57#import <wtf/text/StringConcatenate.h>
58
59@interface NSApplication (Details)
60- (void)speakString:(NSString *)string;
61@end
62
63#define MESSAGE_CHECK(assertion) MESSAGE_CHECK_BASE(assertion, process().connection())
64
65using namespace WebCore;
66
67namespace WebKit {
68
69static inline bool expectsLegacyImplicitRubberBandControl()
70{
71    if (applicationIsSafari()) {
72        const int32_t firstVersionOfSafariNotExpectingImplicitRubberBandControl = 0x021A0F00; // 538.15.0
73        bool linkedAgainstSafariExpectingImplicitRubberBandControl = NSVersionOfLinkTimeLibrary("Safari") < firstVersionOfSafariNotExpectingImplicitRubberBandControl;
74        return linkedAgainstSafariExpectingImplicitRubberBandControl;
75    }
76
77    const int32_t firstVersionOfWebKit2WithNoImplicitRubberBandControl = 0x021A0200; // 538.2.0
78    int32_t linkedWebKit2Version = NSVersionOfLinkTimeLibrary("WebKit2");
79    return linkedWebKit2Version != -1 && linkedWebKit2Version < firstVersionOfWebKit2WithNoImplicitRubberBandControl;
80}
81
82void WebPageProxy::platformInitialize()
83{
84    static bool clientExpectsLegacyImplicitRubberBandControl = expectsLegacyImplicitRubberBandControl();
85    setShouldUseImplicitRubberBandControl(clientExpectsLegacyImplicitRubberBandControl);
86}
87
88static String webKitBundleVersionString()
89{
90    return [[NSBundle bundleForClass:NSClassFromString(@"WKView")] objectForInfoDictionaryKey:(NSString *)kCFBundleVersionKey];
91}
92
93String WebPageProxy::standardUserAgent(const String& applicationNameForUserAgent)
94{
95    return standardUserAgentWithApplicationName(applicationNameForUserAgent, webKitBundleVersionString());
96}
97
98void WebPageProxy::getIsSpeaking(bool& isSpeaking)
99{
100    isSpeaking = [NSApp isSpeaking];
101}
102
103void WebPageProxy::speak(const String& string)
104{
105    [NSApp speakString:nsStringFromWebCoreString(string)];
106}
107
108void WebPageProxy::stopSpeaking()
109{
110    [NSApp stopSpeaking:nil];
111}
112
113void WebPageProxy::searchWithSpotlight(const String& string)
114{
115    [[NSWorkspace sharedWorkspace] showSearchResultsForQueryString:nsStringFromWebCoreString(string)];
116}
117
118void WebPageProxy::searchTheWeb(const String& string)
119{
120    NSPasteboard *pasteboard = [NSPasteboard pasteboardWithUniqueName];
121    [pasteboard declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil];
122    [pasteboard setString:string forType:NSStringPboardType];
123
124    NSPerformService(@"Search With %WebSearchProvider@", pasteboard);
125}
126
127void WebPageProxy::windowAndViewFramesChanged(const FloatRect& viewFrameInWindowCoordinates, const FloatPoint& accessibilityViewCoordinates)
128{
129    if (!isValid())
130        return;
131
132    // In case the UI client overrides getWindowFrame(), we call it here to make sure we send the appropriate window frame.
133    FloatRect windowFrameInScreenCoordinates = m_uiClient->windowFrame(this);
134    FloatRect windowFrameInUnflippedScreenCoordinates = m_pageClient.convertToUserSpace(windowFrameInScreenCoordinates);
135
136    process().send(Messages::WebPage::WindowAndViewFramesChanged(windowFrameInScreenCoordinates, windowFrameInUnflippedScreenCoordinates, viewFrameInWindowCoordinates, accessibilityViewCoordinates), m_pageID);
137}
138
139void WebPageProxy::setMainFrameIsScrollable(bool isScrollable)
140{
141    if (!isValid())
142        return;
143
144    process().send(Messages::WebPage::SetMainFrameIsScrollable(isScrollable), m_pageID);
145}
146
147#if !USE(ASYNC_NSTEXTINPUTCLIENT)
148
149void WebPageProxy::setComposition(const String& text, Vector<CompositionUnderline> underlines, const EditingRange& selectionRange, const EditingRange& replacementRange)
150{
151    if (!isValid()) {
152        // If this fails, we should call -discardMarkedText on input context to notify the input method.
153        // This will happen naturally later, as part of reloading the page.
154        return;
155    }
156
157    process().sendSync(Messages::WebPage::SetComposition(text, underlines, selectionRange, replacementRange), Messages::WebPage::SetComposition::Reply(m_editorState), m_pageID);
158}
159
160void WebPageProxy::confirmComposition()
161{
162    if (!isValid())
163        return;
164
165    process().sendSync(Messages::WebPage::ConfirmComposition(), Messages::WebPage::ConfirmComposition::Reply(m_editorState), m_pageID);
166}
167
168bool WebPageProxy::insertText(const String& text, const EditingRange& replacementRange)
169{
170    if (!isValid())
171        return true;
172
173    bool handled = true;
174    process().sendSync(Messages::WebPage::InsertText(text, replacementRange), Messages::WebPage::InsertText::Reply(handled, m_editorState), m_pageID);
175#if PLATFORM(MAC) && !USE(ASYNC_NSTEXTINPUTCLIENT)
176    m_temporarilyClosedComposition = false;
177#endif
178
179    return handled;
180}
181
182bool WebPageProxy::insertDictatedText(const String& text, const EditingRange& replacementRange, const Vector<TextAlternativeWithRange>& dictationAlternativesWithRange)
183{
184#if USE(DICTATION_ALTERNATIVES)
185    if (dictationAlternativesWithRange.isEmpty())
186        return insertText(text, replacementRange);
187
188    if (!isValid())
189        return true;
190
191    Vector<DictationAlternative> dictationAlternatives;
192
193    for (size_t i = 0; i < dictationAlternativesWithRange.size(); ++i) {
194        const TextAlternativeWithRange& alternativeWithRange = dictationAlternativesWithRange[i];
195        uint64_t dictationContext = m_pageClient.addDictationAlternatives(alternativeWithRange.alternatives);
196        if (dictationContext)
197            dictationAlternatives.append(DictationAlternative(alternativeWithRange.range.location, alternativeWithRange.range.length, dictationContext));
198    }
199
200    if (dictationAlternatives.isEmpty())
201        return insertText(text, replacementRange);
202
203    bool handled = true;
204    process().sendSync(Messages::WebPage::InsertDictatedText(text, replacementRange, dictationAlternatives), Messages::WebPage::InsertDictatedText::Reply(handled, m_editorState), m_pageID);
205    return handled;
206#else
207    return insertText(text, replacementRange);
208#endif
209}
210
211void WebPageProxy::getMarkedRange(EditingRange& result)
212{
213    result = EditingRange();
214
215    if (!isValid())
216        return;
217
218    process().sendSync(Messages::WebPage::GetMarkedRange(), Messages::WebPage::GetMarkedRange::Reply(result), m_pageID);
219    MESSAGE_CHECK(result.isValid());
220}
221
222void WebPageProxy::getSelectedRange(EditingRange& result)
223{
224    result = EditingRange();
225
226    if (!isValid())
227        return;
228
229    process().sendSync(Messages::WebPage::GetSelectedRange(), Messages::WebPage::GetSelectedRange::Reply(result), m_pageID);
230    MESSAGE_CHECK(result.isValid());
231}
232
233void WebPageProxy::getAttributedSubstringFromRange(const EditingRange& range, AttributedString& result)
234{
235    if (!isValid())
236        return;
237    process().sendSync(Messages::WebPage::GetAttributedSubstringFromRange(range), Messages::WebPage::GetAttributedSubstringFromRange::Reply(result), m_pageID);
238}
239
240uint64_t WebPageProxy::characterIndexForPoint(const IntPoint point)
241{
242    if (!isValid())
243        return 0;
244
245    uint64_t result = 0;
246    process().sendSync(Messages::WebPage::CharacterIndexForPoint(point), Messages::WebPage::CharacterIndexForPoint::Reply(result), m_pageID);
247    return result;
248}
249
250IntRect WebPageProxy::firstRectForCharacterRange(const EditingRange& range)
251{
252    if (!isValid())
253        return IntRect();
254
255    IntRect resultRect;
256    process().sendSync(Messages::WebPage::FirstRectForCharacterRange(range), Messages::WebPage::FirstRectForCharacterRange::Reply(resultRect), m_pageID);
257    return resultRect;
258}
259
260bool WebPageProxy::executeKeypressCommands(const Vector<WebCore::KeypressCommand>& commands)
261{
262    if (!isValid())
263        return false;
264
265    bool result = false;
266    process().sendSync(Messages::WebPage::ExecuteKeypressCommands(commands), Messages::WebPage::ExecuteKeypressCommands::Reply(result, m_editorState), m_pageID);
267#if PLATFORM(MAC) && !USE(ASYNC_NSTEXTINPUTCLIENT)
268    m_temporarilyClosedComposition = false;
269#endif
270    return result;
271}
272
273void WebPageProxy::cancelComposition()
274{
275    if (!isValid())
276        return;
277
278    process().sendSync(Messages::WebPage::CancelComposition(), Messages::WebPage::CancelComposition::Reply(m_editorState), m_pageID);
279}
280
281#endif // !USE(ASYNC_NSTEXTINPUTCLIENT)
282
283void WebPageProxy::insertDictatedTextAsync(const String& text, const EditingRange& replacementRange, const Vector<TextAlternativeWithRange>& dictationAlternativesWithRange, bool registerUndoGroup)
284{
285#if USE(DICTATION_ALTERNATIVES)
286    if (!isValid())
287        return;
288
289    Vector<DictationAlternative> dictationAlternatives;
290
291    for (const TextAlternativeWithRange& alternativeWithRange : dictationAlternativesWithRange) {
292        uint64_t dictationContext = m_pageClient.addDictationAlternatives(alternativeWithRange.alternatives);
293        if (dictationContext)
294            dictationAlternatives.append(DictationAlternative(alternativeWithRange.range.location, alternativeWithRange.range.length, dictationContext));
295    }
296
297    if (dictationAlternatives.isEmpty()) {
298        insertTextAsync(text, replacementRange, registerUndoGroup);
299        return;
300    }
301
302    process().send(Messages::WebPage::InsertDictatedTextAsync(text, replacementRange, dictationAlternatives, registerUndoGroup), m_pageID);
303#else
304    insertTextAsync(text, replacementRange, registerUndoGroup);
305#endif
306}
307
308void WebPageProxy::attributedSubstringForCharacterRangeAsync(const EditingRange& range, std::function<void (const AttributedString&, const EditingRange&, CallbackBase::Error)> callbackFunction)
309{
310    if (!isValid()) {
311        callbackFunction(AttributedString(), EditingRange(), CallbackBase::Error::Unknown);
312        return;
313    }
314
315    uint64_t callbackID = m_callbacks.put(WTF::move(callbackFunction), std::make_unique<ProcessThrottler::BackgroundActivityToken>(m_process->throttler()));
316
317    process().send(Messages::WebPage::AttributedSubstringForCharacterRangeAsync(range, callbackID), m_pageID);
318}
319
320void WebPageProxy::attributedStringForCharacterRangeCallback(const AttributedString& string, const EditingRange& actualRange, uint64_t callbackID)
321{
322    MESSAGE_CHECK(actualRange.isValid());
323
324    auto callback = m_callbacks.take<AttributedStringForCharacterRangeCallback>(callbackID);
325    if (!callback) {
326        // FIXME: Log error or assert.
327        // this can validly happen if a load invalidated the callback, though
328        return;
329    }
330
331    callback->performCallbackWithReturnValue(string, actualRange);
332}
333
334String WebPageProxy::stringSelectionForPasteboard()
335{
336    String value;
337    if (!isValid())
338        return value;
339
340    const auto messageTimeout = std::chrono::seconds(20);
341    process().sendSync(Messages::WebPage::GetStringSelectionForPasteboard(), Messages::WebPage::GetStringSelectionForPasteboard::Reply(value), m_pageID, messageTimeout);
342    return value;
343}
344
345PassRefPtr<WebCore::SharedBuffer> WebPageProxy::dataSelectionForPasteboard(const String& pasteboardType)
346{
347    if (!isValid())
348        return 0;
349    SharedMemory::Handle handle;
350    uint64_t size = 0;
351    const auto messageTimeout = std::chrono::seconds(20);
352    process().sendSync(Messages::WebPage::GetDataSelectionForPasteboard(pasteboardType),
353                                                Messages::WebPage::GetDataSelectionForPasteboard::Reply(handle, size), m_pageID, messageTimeout);
354    if (handle.isNull())
355        return 0;
356    RefPtr<SharedMemory> sharedMemoryBuffer = SharedMemory::create(handle, SharedMemory::ReadOnly);
357    return SharedBuffer::create(static_cast<unsigned char *>(sharedMemoryBuffer->data()), size);
358}
359
360bool WebPageProxy::readSelectionFromPasteboard(const String& pasteboardName)
361{
362    if (!isValid())
363        return false;
364
365    bool result = false;
366    const auto messageTimeout = std::chrono::seconds(20);
367    process().sendSync(Messages::WebPage::ReadSelectionFromPasteboard(pasteboardName), Messages::WebPage::ReadSelectionFromPasteboard::Reply(result), m_pageID, messageTimeout);
368    return result;
369}
370
371#if ENABLE(SERVICE_CONTROLS)
372void WebPageProxy::replaceSelectionWithPasteboardData(const Vector<String>& types, const IPC::DataReference& data)
373{
374    process().send(Messages::WebPage::ReplaceSelectionWithPasteboardData(types, data), m_pageID);
375}
376#endif
377
378#if ENABLE(DRAG_SUPPORT)
379void WebPageProxy::setDragImage(const WebCore::IntPoint& clientPosition, const ShareableBitmap::Handle& dragImageHandle, bool isLinkDrag)
380{
381    RefPtr<ShareableBitmap> dragImage = ShareableBitmap::create(dragImageHandle);
382    if (!dragImage)
383        return;
384
385    m_pageClient.setDragImage(clientPosition, dragImage.release(), isLinkDrag);
386}
387
388void WebPageProxy::setPromisedData(const String& pasteboardName, const SharedMemory::Handle& imageHandle, uint64_t imageSize, const String& filename, const String& extension,
389                                   const String& title, const String& url, const String& visibleURL, const SharedMemory::Handle& archiveHandle, uint64_t archiveSize)
390{
391    RefPtr<SharedMemory> sharedMemoryImage = SharedMemory::create(imageHandle, SharedMemory::ReadOnly);
392    RefPtr<SharedBuffer> imageBuffer = SharedBuffer::create(static_cast<unsigned char*>(sharedMemoryImage->data()), imageSize);
393    RefPtr<SharedBuffer> archiveBuffer;
394
395    if (!archiveHandle.isNull()) {
396        RefPtr<SharedMemory> sharedMemoryArchive = SharedMemory::create(archiveHandle, SharedMemory::ReadOnly);;
397        archiveBuffer = SharedBuffer::create(static_cast<unsigned char*>(sharedMemoryArchive->data()), archiveSize);
398    }
399    m_pageClient.setPromisedData(pasteboardName, imageBuffer, filename, extension, title, url, visibleURL, archiveBuffer);
400}
401#endif
402
403void WebPageProxy::performDictionaryLookupAtLocation(const WebCore::FloatPoint& point)
404{
405    if (!isValid())
406        return;
407
408    process().send(Messages::WebPage::PerformDictionaryLookupAtLocation(point), m_pageID);
409}
410
411// Complex text input support for plug-ins.
412void WebPageProxy::sendComplexTextInputToPlugin(uint64_t pluginComplexTextInputIdentifier, const String& textInput)
413{
414    if (!isValid())
415        return;
416
417    process().send(Messages::WebPage::SendComplexTextInputToPlugin(pluginComplexTextInputIdentifier, textInput), m_pageID);
418}
419
420void WebPageProxy::uppercaseWord()
421{
422    process().send(Messages::WebPage::UppercaseWord(), m_pageID);
423}
424
425void WebPageProxy::lowercaseWord()
426{
427    process().send(Messages::WebPage::LowercaseWord(), m_pageID);
428}
429
430void WebPageProxy::capitalizeWord()
431{
432    process().send(Messages::WebPage::CapitalizeWord(), m_pageID);
433}
434
435void WebPageProxy::setSmartInsertDeleteEnabled(bool isSmartInsertDeleteEnabled)
436{
437    if (m_isSmartInsertDeleteEnabled == isSmartInsertDeleteEnabled)
438        return;
439
440    TextChecker::setSmartInsertDeleteEnabled(isSmartInsertDeleteEnabled);
441    m_isSmartInsertDeleteEnabled = isSmartInsertDeleteEnabled;
442    process().send(Messages::WebPage::SetSmartInsertDeleteEnabled(isSmartInsertDeleteEnabled), m_pageID);
443}
444
445void WebPageProxy::didPerformDictionaryLookup(const AttributedString& text, const DictionaryPopupInfo& dictionaryPopupInfo)
446{
447    m_pageClient.didPerformDictionaryLookup(text, dictionaryPopupInfo);
448}
449
450void WebPageProxy::registerWebProcessAccessibilityToken(const IPC::DataReference& data)
451{
452    if (!isValid())
453        return;
454
455    m_pageClient.accessibilityWebProcessTokenReceived(data);
456}
457
458void WebPageProxy::makeFirstResponder()
459{
460    m_pageClient.makeFirstResponder();
461}
462
463ColorSpaceData WebPageProxy::colorSpace()
464{
465    return m_pageClient.colorSpace();
466}
467
468void WebPageProxy::registerUIProcessAccessibilityTokens(const IPC::DataReference& elementToken, const IPC::DataReference& windowToken)
469{
470    if (!isValid())
471        return;
472
473    process().send(Messages::WebPage::RegisterUIProcessAccessibilityTokens(elementToken, windowToken), m_pageID);
474}
475
476void WebPageProxy::pluginFocusOrWindowFocusChanged(uint64_t pluginComplexTextInputIdentifier, bool pluginHasFocusAndWindowHasFocus)
477{
478    m_pageClient.pluginFocusOrWindowFocusChanged(pluginComplexTextInputIdentifier, pluginHasFocusAndWindowHasFocus);
479}
480
481void WebPageProxy::setPluginComplexTextInputState(uint64_t pluginComplexTextInputIdentifier, uint64_t pluginComplexTextInputState)
482{
483    MESSAGE_CHECK(isValidPluginComplexTextInputState(pluginComplexTextInputState));
484
485    m_pageClient.setPluginComplexTextInputState(pluginComplexTextInputIdentifier, static_cast<PluginComplexTextInputState>(pluginComplexTextInputState));
486}
487
488void WebPageProxy::executeSavedCommandBySelector(const String& selector, bool& handled)
489{
490    MESSAGE_CHECK(isValidKeypressCommandName(selector));
491
492    handled = m_pageClient.executeSavedCommandBySelector(selector);
493}
494
495bool WebPageProxy::shouldDelayWindowOrderingForEvent(const WebKit::WebMouseEvent& event)
496{
497    if (process().state() != WebProcessProxy::State::Running)
498        return false;
499
500    bool result = false;
501    const auto messageTimeout = std::chrono::seconds(3);
502    process().sendSync(Messages::WebPage::ShouldDelayWindowOrderingEvent(event), Messages::WebPage::ShouldDelayWindowOrderingEvent::Reply(result), m_pageID, messageTimeout);
503    return result;
504}
505
506bool WebPageProxy::acceptsFirstMouse(int eventNumber, const WebKit::WebMouseEvent& event)
507{
508    if (!isValid())
509        return false;
510
511    bool result = false;
512    const auto messageTimeout = std::chrono::seconds(3);
513    process().sendSync(Messages::WebPage::AcceptsFirstMouse(eventNumber, event), Messages::WebPage::AcceptsFirstMouse::Reply(result), m_pageID, messageTimeout);
514    return result;
515}
516
517WKView* WebPageProxy::wkView() const
518{
519    return m_pageClient.wkView();
520}
521
522void WebPageProxy::intrinsicContentSizeDidChange(const IntSize& intrinsicContentSize)
523{
524    m_pageClient.intrinsicContentSizeDidChange(intrinsicContentSize);
525}
526
527void WebPageProxy::setAcceleratedCompositingRootLayer(LayerOrView* rootLayer)
528{
529    m_pageClient.setAcceleratedCompositingRootLayer(rootLayer);
530}
531
532LayerOrView* WebPageProxy::acceleratedCompositingRootLayer() const
533{
534    return m_pageClient.acceleratedCompositingRootLayer();
535}
536
537static NSString *temporaryPDFDirectoryPath()
538{
539    static NSString *temporaryPDFDirectoryPath;
540
541    if (!temporaryPDFDirectoryPath) {
542        NSString *temporaryDirectoryTemplate = [NSTemporaryDirectory() stringByAppendingPathComponent:@"WebKitPDFs-XXXXXX"];
543        CString templateRepresentation = [temporaryDirectoryTemplate fileSystemRepresentation];
544
545        if (mkdtemp(templateRepresentation.mutableData()))
546            temporaryPDFDirectoryPath = [[[NSFileManager defaultManager] stringWithFileSystemRepresentation:templateRepresentation.data() length:templateRepresentation.length()] copy];
547    }
548
549    return temporaryPDFDirectoryPath;
550}
551
552static NSString *pathToPDFOnDisk(const String& suggestedFilename)
553{
554    NSString *pdfDirectoryPath = temporaryPDFDirectoryPath();
555    if (!pdfDirectoryPath) {
556        WTFLogAlways("Cannot create temporary PDF download directory.");
557        return nil;
558    }
559
560    NSString *path = [pdfDirectoryPath stringByAppendingPathComponent:suggestedFilename];
561
562    NSFileManager *fileManager = [NSFileManager defaultManager];
563    if ([fileManager fileExistsAtPath:path]) {
564        NSString *pathTemplatePrefix = [pdfDirectoryPath stringByAppendingPathComponent:@"XXXXXX-"];
565        NSString *pathTemplate = [pathTemplatePrefix stringByAppendingString:suggestedFilename];
566        CString pathTemplateRepresentation = [pathTemplate fileSystemRepresentation];
567
568        int fd = mkstemps(pathTemplateRepresentation.mutableData(), pathTemplateRepresentation.length() - strlen([pathTemplatePrefix fileSystemRepresentation]) + 1);
569        if (fd < 0) {
570            WTFLogAlways("Cannot create PDF file in the temporary directory (%s).", suggestedFilename.utf8().data());
571            return nil;
572        }
573
574        close(fd);
575        path = [fileManager stringWithFileSystemRepresentation:pathTemplateRepresentation.data() length:pathTemplateRepresentation.length()];
576    }
577
578    return path;
579}
580
581void WebPageProxy::savePDFToTemporaryFolderAndOpenWithNativeApplicationRaw(const String& suggestedFilename, const String& originatingURLString, const uint8_t* data, unsigned long size, const String& pdfUUID)
582{
583    // FIXME: Write originatingURLString to the file's originating URL metadata (perhaps WKSetMetadataURL?).
584    UNUSED_PARAM(originatingURLString);
585
586    if (!suggestedFilename.endsWith(".pdf", false)) {
587        WTFLogAlways("Cannot save file without .pdf extension to the temporary directory.");
588        return;
589    }
590
591    if (!size) {
592        WTFLogAlways("Cannot save empty PDF file to the temporary directory.");
593        return;
594    }
595
596    NSString *nsPath = pathToPDFOnDisk(suggestedFilename);
597
598    if (!nsPath)
599        return;
600
601    RetainPtr<NSNumber> permissions = adoptNS([[NSNumber alloc] initWithInt:S_IRUSR]);
602    RetainPtr<NSDictionary> fileAttributes = adoptNS([[NSDictionary alloc] initWithObjectsAndKeys:permissions.get(), NSFilePosixPermissions, nil]);
603    RetainPtr<NSData> nsData = adoptNS([[NSData alloc] initWithBytesNoCopy:(void*)data length:size freeWhenDone:NO]);
604
605    if (![[NSFileManager defaultManager] createFileAtPath:nsPath contents:nsData.get() attributes:fileAttributes.get()]) {
606        WTFLogAlways("Cannot create PDF file in the temporary directory (%s).", suggestedFilename.utf8().data());
607        return;
608    }
609
610    m_temporaryPDFFiles.add(pdfUUID, nsPath);
611
612    [[NSWorkspace sharedWorkspace] openFile:nsPath];
613}
614
615void WebPageProxy::savePDFToTemporaryFolderAndOpenWithNativeApplication(const String& suggestedFilename, const String& originatingURLString, const IPC::DataReference& data, const String& pdfUUID)
616{
617    if (data.isEmpty()) {
618        WTFLogAlways("Cannot save empty PDF file to the temporary directory.");
619        return;
620    }
621
622    savePDFToTemporaryFolderAndOpenWithNativeApplicationRaw(suggestedFilename, originatingURLString, data.data(), data.size(), pdfUUID);
623}
624
625void WebPageProxy::openPDFFromTemporaryFolderWithNativeApplication(const String& pdfUUID)
626{
627    String pdfFilename = m_temporaryPDFFiles.get(pdfUUID);
628
629    if (!pdfFilename.endsWith(".pdf", false))
630        return;
631
632    [[NSWorkspace sharedWorkspace] openFile:pdfFilename];
633}
634
635#if ENABLE(TELEPHONE_NUMBER_DETECTION)
636void WebPageProxy::showTelephoneNumberMenu(const String& telephoneNumber, const WebCore::IntPoint& point)
637{
638    NSArray *menuItems = menuItemsForTelephoneNumber(telephoneNumber);
639
640    Vector<WebContextMenuItemData> items;
641    for (NSMenuItem *item in menuItems) {
642        RetainPtr<NSMenuItem> retainedItem = item;
643        std::function<void()> handler = [retainedItem]() {
644            NSMenuItem *item = retainedItem.get();
645            [[item target] performSelector:[item action] withObject:item];
646        };
647
648        items.append(WebContextMenuItemData(ContextMenuItem(item), handler));
649    }
650
651    ContextMenuContextData contextData(TelephoneNumberContext);
652    internalShowContextMenu(point, contextData, items, ContextMenuClientEligibility::NotEligibleForClient, nullptr);
653}
654#endif
655
656#if ENABLE(SERVICE_CONTROLS)
657void WebPageProxy::showSelectionServiceMenu(const IPC::DataReference& selectionAsRTFD, const Vector<String>& telephoneNumbers, bool isEditable, const IntPoint& point)
658{
659    Vector<WebContextMenuItemData> items;
660    ContextMenuContextData contextData(selectionAsRTFD.vector(), telephoneNumbers, isEditable);
661
662    internalShowContextMenu(point, contextData, items, ContextMenuClientEligibility::NotEligibleForClient, nullptr);
663}
664#endif
665
666CGRect WebPageProxy::boundsOfLayerInLayerBackedWindowCoordinates(CALayer *layer) const
667{
668    return m_pageClient.boundsOfLayerInLayerBackedWindowCoordinates(layer);
669}
670
671} // namespace WebKit
672
673#endif // PLATFORM(MAC)
674