fffc5e08a1670a0caf153ec70fa72021720e5b7a
[WebKit-https.git] / Source / WebKit / UIProcess / mac / WebPageProxyMac.mm
1 /*
2  * Copyright (C) 2010-2018 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 "EditingRange.h"
36 #import "EditorState.h"
37 #import "MenuUtilities.h"
38 #import "NativeWebKeyboardEvent.h"
39 #import "PDFContextMenu.h"
40 #import "PageClient.h"
41 #import "PageClientImplMac.h"
42 #import "PluginComplexTextInputState.h"
43 #import "RemoteLayerTreeHost.h"
44 #import "StringUtilities.h"
45 #import "TextChecker.h"
46 #import "WKBrowsingContextControllerInternal.h"
47 #import "WKSharingServicePickerDelegate.h"
48 #import "WebContextMenuProxyMac.h"
49 #import "WebPageMessages.h"
50 #import "WebProcessProxy.h"
51 #import <WebCore/DictationAlternative.h>
52 #import <WebCore/DictionaryLookup.h>
53 #import <WebCore/DragItem.h>
54 #import <WebCore/GraphicsLayer.h>
55 #import <WebCore/LegacyNSPasteboardTypes.h>
56 #import <WebCore/RuntimeApplicationChecks.h>
57 #import <WebCore/SharedBuffer.h>
58 #import <WebCore/TextAlternativeWithRange.h>
59 #import <WebCore/UserAgent.h>
60 #import <WebCore/ValidationBubble.h>
61 #import <mach-o/dyld.h>
62 #import <pal/spi/mac/NSApplicationSPI.h>
63 #import <pal/spi/mac/NSMenuSPI.h>
64 #import <wtf/ProcessPrivilege.h>
65 #import <wtf/text/StringConcatenate.h>
66
67 #define MESSAGE_CHECK(assertion) MESSAGE_CHECK_BASE(assertion, process().connection())
68 #define MESSAGE_CHECK_URL(url) MESSAGE_CHECK_BASE(m_process->checkURLReceivedFromWebProcess(url), m_process->connection())
69
70 using namespace WebCore;
71
72 @interface NSApplication ()
73 - (BOOL)isSpeaking;
74 - (void)speakString:(NSString *)string;
75 - (void)stopSpeaking:(id)sender;
76 @end
77
78 #if ENABLE(PDFKIT_PLUGIN)
79 @interface WKPDFMenuTarget : NSObject {
80     NSMenuItem *_selectedMenuItem;
81 }
82 - (NSMenuItem *)selectedMenuItem;
83 - (void)contextMenuAction:(NSMenuItem *)sender;
84 @end
85
86 @implementation WKPDFMenuTarget
87 - (instancetype)init
88 {
89     self = [super init];
90     if (!self)
91         return nil;
92     
93     _selectedMenuItem = nil;
94     return self;
95 }
96
97 - (NSMenuItem *)selectedMenuItem
98 {
99     return _selectedMenuItem;
100 }
101
102 - (void)contextMenuAction:(NSMenuItem *)sender
103 {
104     _selectedMenuItem = sender;
105 }
106 @end // implementation WKPDFMenuTarget
107 #endif
108
109 namespace WebKit {
110
111 static inline bool expectsLegacyImplicitRubberBandControl()
112 {
113     if (MacApplication::isSafari()) {
114         const int32_t firstVersionOfSafariNotExpectingImplicitRubberBandControl = 0x021A0F00; // 538.15.0
115         bool linkedAgainstSafariExpectingImplicitRubberBandControl = NSVersionOfLinkTimeLibrary("Safari") < firstVersionOfSafariNotExpectingImplicitRubberBandControl;
116         return linkedAgainstSafariExpectingImplicitRubberBandControl;
117     }
118
119     const int32_t firstVersionOfWebKit2WithNoImplicitRubberBandControl = 0x021A0200; // 538.2.0
120     int32_t linkedWebKit2Version = NSVersionOfLinkTimeLibrary("WebKit2");
121     return linkedWebKit2Version != -1 && linkedWebKit2Version < firstVersionOfWebKit2WithNoImplicitRubberBandControl;
122 }
123
124 void WebPageProxy::platformInitialize()
125 {
126     static bool clientExpectsLegacyImplicitRubberBandControl = expectsLegacyImplicitRubberBandControl();
127     setShouldUseImplicitRubberBandControl(clientExpectsLegacyImplicitRubberBandControl);
128 }
129
130 String WebPageProxy::standardUserAgent(const String& applicationNameForUserAgent)
131 {
132     return standardUserAgentWithApplicationName(applicationNameForUserAgent);
133 }
134
135 void WebPageProxy::getIsSpeaking(bool& isSpeaking)
136 {
137     ASSERT(hasProcessPrivilege(ProcessPrivilege::CanCommunicateWithWindowServer));
138     isSpeaking = [NSApp isSpeaking];
139 }
140
141 void WebPageProxy::speak(const String& string)
142 {
143     ASSERT(hasProcessPrivilege(ProcessPrivilege::CanCommunicateWithWindowServer));
144     [NSApp speakString:nsStringFromWebCoreString(string)];
145 }
146
147 void WebPageProxy::stopSpeaking()
148 {
149     ASSERT(hasProcessPrivilege(ProcessPrivilege::CanCommunicateWithWindowServer));
150     [NSApp stopSpeaking:nil];
151 }
152
153 void WebPageProxy::searchWithSpotlight(const String& string)
154 {
155     [[NSWorkspace sharedWorkspace] showSearchResultsForQueryString:nsStringFromWebCoreString(string)];
156 }
157     
158 void WebPageProxy::searchTheWeb(const String& string)
159 {
160     NSPasteboard *pasteboard = [NSPasteboard pasteboardWithUniqueName];
161     [pasteboard declareTypes:[NSArray arrayWithObject:legacyStringPasteboardType()] owner:nil];
162     [pasteboard setString:string forType:legacyStringPasteboardType()];
163     
164     NSPerformService(@"Search With %WebSearchProvider@", pasteboard);
165 }
166
167 void WebPageProxy::windowAndViewFramesChanged(const FloatRect& viewFrameInWindowCoordinates, const FloatPoint& accessibilityViewCoordinates)
168 {
169     if (!isValid())
170         return;
171
172     // In case the UI client overrides getWindowFrame(), we call it here to make sure we send the appropriate window frame.
173     m_uiClient->windowFrame(*this, [this, protectedThis = makeRef(*this), viewFrameInWindowCoordinates, accessibilityViewCoordinates] (FloatRect windowFrameInScreenCoordinates) {
174         FloatRect windowFrameInUnflippedScreenCoordinates = pageClient().convertToUserSpace(windowFrameInScreenCoordinates);
175         process().send(Messages::WebPage::WindowAndViewFramesChanged(windowFrameInScreenCoordinates, windowFrameInUnflippedScreenCoordinates, viewFrameInWindowCoordinates, accessibilityViewCoordinates), m_pageID);
176     });
177 }
178
179 void WebPageProxy::setMainFrameIsScrollable(bool isScrollable)
180 {
181     if (!isValid())
182         return;
183
184     process().send(Messages::WebPage::SetMainFrameIsScrollable(isScrollable), m_pageID);
185 }
186
187 void WebPageProxy::insertDictatedTextAsync(const String& text, const EditingRange& replacementRange, const Vector<TextAlternativeWithRange>& dictationAlternativesWithRange, bool registerUndoGroup)
188 {
189 #if USE(DICTATION_ALTERNATIVES)
190     if (!isValid())
191         return;
192
193     Vector<DictationAlternative> dictationAlternatives;
194
195     for (const TextAlternativeWithRange& alternativeWithRange : dictationAlternativesWithRange) {
196         uint64_t dictationContext = pageClient().addDictationAlternatives(alternativeWithRange.alternatives);
197         if (dictationContext)
198             dictationAlternatives.append(DictationAlternative(alternativeWithRange.range.location, alternativeWithRange.range.length, dictationContext));
199     }
200
201     if (dictationAlternatives.isEmpty()) {
202         insertTextAsync(text, replacementRange, registerUndoGroup);
203         return;
204     }
205
206     process().send(Messages::WebPage::InsertDictatedTextAsync(text, replacementRange, dictationAlternatives, registerUndoGroup), m_pageID);
207 #else
208     insertTextAsync(text, replacementRange, registerUndoGroup);
209 #endif
210 }
211
212 void WebPageProxy::attributedSubstringForCharacterRangeAsync(const EditingRange& range, WTF::Function<void (const AttributedString&, const EditingRange&, CallbackBase::Error)>&& callbackFunction)
213 {
214     if (!isValid()) {
215         callbackFunction(AttributedString(), EditingRange(), CallbackBase::Error::Unknown);
216         return;
217     }
218
219     auto callbackID = m_callbacks.put(WTFMove(callbackFunction), m_process->throttler().backgroundActivityToken());
220
221     process().send(Messages::WebPage::AttributedSubstringForCharacterRangeAsync(range, callbackID), m_pageID);
222 }
223
224 void WebPageProxy::attributedStringForCharacterRangeCallback(const AttributedString& string, const EditingRange& actualRange, CallbackID callbackID)
225 {
226     MESSAGE_CHECK(actualRange.isValid());
227
228     auto callback = m_callbacks.take<AttributedStringForCharacterRangeCallback>(callbackID);
229     if (!callback) {
230         // FIXME: Log error or assert.
231         // this can validly happen if a load invalidated the callback, though
232         return;
233     }
234
235     callback->performCallbackWithReturnValue(string, actualRange);
236 }
237
238 void WebPageProxy::fontAtSelection(WTF::Function<void (const String&, double, bool, CallbackBase::Error)>&& callbackFunction)
239 {
240     if (!isValid()) {
241         callbackFunction(String(), 0, false, CallbackBase::Error::Unknown);
242         return;
243     }
244     
245     auto callbackID = m_callbacks.put(WTFMove(callbackFunction), m_process->throttler().backgroundActivityToken());
246     
247     process().send(Messages::WebPage::FontAtSelection(callbackID), m_pageID);
248 }
249
250 void WebPageProxy::fontAtSelectionCallback(const String& fontName, double fontSize, bool selectionHasMultipleFonts, CallbackID callbackID)
251 {
252     auto callback = m_callbacks.take<FontAtSelectionCallback>(callbackID);
253     if (!callback) {
254         // FIXME: Log error or assert.
255         // this can validly happen if a load invalidated the callback, though
256         return;
257     }
258     
259     callback->performCallbackWithReturnValue(fontName, fontSize, selectionHasMultipleFonts);
260 }
261
262 String WebPageProxy::stringSelectionForPasteboard()
263 {
264     String value;
265     if (!isValid())
266         return value;
267     
268     const Seconds messageTimeout(20);
269     process().sendSync(Messages::WebPage::GetStringSelectionForPasteboard(), Messages::WebPage::GetStringSelectionForPasteboard::Reply(value), m_pageID, messageTimeout);
270     return value;
271 }
272
273 RefPtr<WebCore::SharedBuffer> WebPageProxy::dataSelectionForPasteboard(const String& pasteboardType)
274 {
275     if (!isValid())
276         return nullptr;
277     SharedMemory::Handle handle;
278     uint64_t size = 0;
279     const Seconds messageTimeout(20);
280     process().sendSync(Messages::WebPage::GetDataSelectionForPasteboard(pasteboardType),
281                                                 Messages::WebPage::GetDataSelectionForPasteboard::Reply(handle, size), m_pageID, messageTimeout);
282     if (handle.isNull())
283         return nullptr;
284     RefPtr<SharedMemory> sharedMemoryBuffer = SharedMemory::map(handle, SharedMemory::Protection::ReadOnly);
285     return SharedBuffer::create(static_cast<unsigned char *>(sharedMemoryBuffer->data()), size);
286 }
287
288 bool WebPageProxy::readSelectionFromPasteboard(const String& pasteboardName)
289 {
290     if (!isValid())
291         return false;
292
293     bool result = false;
294     const Seconds messageTimeout(20);
295     process().sendSync(Messages::WebPage::ReadSelectionFromPasteboard(pasteboardName), Messages::WebPage::ReadSelectionFromPasteboard::Reply(result), m_pageID, messageTimeout);
296     return result;
297 }
298
299 #if ENABLE(SERVICE_CONTROLS)
300 void WebPageProxy::replaceSelectionWithPasteboardData(const Vector<String>& types, const IPC::DataReference& data)
301 {
302     process().send(Messages::WebPage::ReplaceSelectionWithPasteboardData(types, data), m_pageID);
303 }
304 #endif
305
306 #if ENABLE(DRAG_SUPPORT)
307
308 void WebPageProxy::setPromisedDataForImage(const String& pasteboardName, const SharedMemory::Handle& imageHandle, uint64_t imageSize, const String& filename, const String& extension,
309                                    const String& title, const String& url, const String& visibleURL, const SharedMemory::Handle& archiveHandle, uint64_t archiveSize)
310 {
311     MESSAGE_CHECK_URL(url);
312     MESSAGE_CHECK_URL(visibleURL);
313     RefPtr<SharedMemory> sharedMemoryImage = SharedMemory::map(imageHandle, SharedMemory::Protection::ReadOnly);
314     auto imageBuffer = SharedBuffer::create(static_cast<unsigned char*>(sharedMemoryImage->data()), imageSize);
315     RefPtr<SharedBuffer> archiveBuffer;
316     
317     if (!archiveHandle.isNull()) {
318         RefPtr<SharedMemory> sharedMemoryArchive = SharedMemory::map(archiveHandle, SharedMemory::Protection::ReadOnly);
319         archiveBuffer = SharedBuffer::create(static_cast<unsigned char*>(sharedMemoryArchive->data()), archiveSize);
320     }
321     pageClient().setPromisedDataForImage(pasteboardName, WTFMove(imageBuffer), filename, extension, title, url, visibleURL, WTFMove(archiveBuffer));
322 }
323
324 #endif
325
326 void WebPageProxy::performDictionaryLookupAtLocation(const WebCore::FloatPoint& point)
327 {
328     if (!isValid())
329         return;
330
331     process().send(Messages::WebPage::PerformDictionaryLookupAtLocation(point), m_pageID);
332 }
333
334 void WebPageProxy::performDictionaryLookupOfCurrentSelection()
335 {
336     if (!isValid())
337         return;
338
339     process().send(Messages::WebPage::PerformDictionaryLookupOfCurrentSelection(), m_pageID);
340 }
341
342 // Complex text input support for plug-ins.
343 void WebPageProxy::sendComplexTextInputToPlugin(uint64_t pluginComplexTextInputIdentifier, const String& textInput)
344 {
345     if (!isValid())
346         return;
347     
348     process().send(Messages::WebPage::SendComplexTextInputToPlugin(pluginComplexTextInputIdentifier, textInput), m_pageID);
349 }
350
351 void WebPageProxy::uppercaseWord()
352 {
353     process().send(Messages::WebPage::UppercaseWord(), m_pageID);
354 }
355
356 void WebPageProxy::lowercaseWord()
357 {
358     process().send(Messages::WebPage::LowercaseWord(), m_pageID);
359 }
360
361 void WebPageProxy::capitalizeWord()
362 {
363     process().send(Messages::WebPage::CapitalizeWord(), m_pageID);
364 }
365
366 void WebPageProxy::setSmartInsertDeleteEnabled(bool isSmartInsertDeleteEnabled)
367 {
368     if (m_isSmartInsertDeleteEnabled == isSmartInsertDeleteEnabled)
369         return;
370
371     TextChecker::setSmartInsertDeleteEnabled(isSmartInsertDeleteEnabled);
372     m_isSmartInsertDeleteEnabled = isSmartInsertDeleteEnabled;
373     process().send(Messages::WebPage::SetSmartInsertDeleteEnabled(isSmartInsertDeleteEnabled), m_pageID);
374 }
375
376 void WebPageProxy::didPerformDictionaryLookup(const DictionaryPopupInfo& dictionaryPopupInfo)
377 {
378     pageClient().didPerformDictionaryLookup(dictionaryPopupInfo);
379 }
380     
381 void WebPageProxy::registerWebProcessAccessibilityToken(const IPC::DataReference& data)
382 {
383     if (!isValid())
384         return;
385     
386     pageClient().accessibilityWebProcessTokenReceived(data);
387 }    
388     
389 void WebPageProxy::makeFirstResponder()
390 {
391     pageClient().makeFirstResponder();
392 }
393
394 void WebPageProxy::assistiveTechnologyMakeFirstResponder()
395 {
396     pageClient().assistiveTechnologyMakeFirstResponder();
397 }
398
399 ColorSpaceData WebPageProxy::colorSpace()
400 {
401     return pageClient().colorSpace();
402 }
403
404 void WebPageProxy::registerUIProcessAccessibilityTokens(const IPC::DataReference& elementToken, const IPC::DataReference& windowToken)
405 {
406     if (!isValid())
407         return;
408
409     process().send(Messages::WebPage::RegisterUIProcessAccessibilityTokens(elementToken, windowToken), m_pageID);
410 }
411
412 void WebPageProxy::pluginFocusOrWindowFocusChanged(uint64_t pluginComplexTextInputIdentifier, bool pluginHasFocusAndWindowHasFocus)
413 {
414     pageClient().pluginFocusOrWindowFocusChanged(pluginComplexTextInputIdentifier, pluginHasFocusAndWindowHasFocus);
415 }
416
417 void WebPageProxy::setPluginComplexTextInputState(uint64_t pluginComplexTextInputIdentifier, uint64_t pluginComplexTextInputState)
418 {
419     MESSAGE_CHECK(isValidPluginComplexTextInputState(pluginComplexTextInputState));
420
421     pageClient().setPluginComplexTextInputState(pluginComplexTextInputIdentifier, static_cast<PluginComplexTextInputState>(pluginComplexTextInputState));
422 }
423
424 void WebPageProxy::executeSavedCommandBySelector(const String& selector, bool& handled)
425 {
426     MESSAGE_CHECK(isValidKeypressCommandName(selector));
427
428     handled = pageClient().executeSavedCommandBySelector(selector);
429 }
430
431 bool WebPageProxy::shouldDelayWindowOrderingForEvent(const WebKit::WebMouseEvent& event)
432 {
433     if (process().state() != WebProcessProxy::State::Running)
434         return false;
435
436     bool result = false;
437     const Seconds messageTimeout(3);
438     process().sendSync(Messages::WebPage::ShouldDelayWindowOrderingEvent(event), Messages::WebPage::ShouldDelayWindowOrderingEvent::Reply(result), m_pageID, messageTimeout);
439     return result;
440 }
441
442 bool WebPageProxy::acceptsFirstMouse(int eventNumber, const WebKit::WebMouseEvent& event)
443 {
444     if (!isValid())
445         return false;
446
447     bool result = false;
448     const Seconds messageTimeout(3);
449     process().sendSync(Messages::WebPage::AcceptsFirstMouse(eventNumber, event), Messages::WebPage::AcceptsFirstMouse::Reply(result), m_pageID, messageTimeout);
450     return result;
451 }
452
453 void WebPageProxy::intrinsicContentSizeDidChange(const IntSize& intrinsicContentSize)
454 {
455     pageClient().intrinsicContentSizeDidChange(intrinsicContentSize);
456 }
457
458 void WebPageProxy::setRemoteLayerTreeRootNode(RemoteLayerTreeNode* rootNode)
459 {
460     pageClient().setRemoteLayerTreeRootNode(rootNode);
461     m_frozenRemoteLayerTreeHost = nullptr;
462 }
463
464 CALayer *WebPageProxy::acceleratedCompositingRootLayer() const
465 {
466     return pageClient().acceleratedCompositingRootLayer();
467 }
468
469 static NSString *temporaryPDFDirectoryPath()
470 {
471     static NSString *temporaryPDFDirectoryPath;
472
473     if (!temporaryPDFDirectoryPath) {
474         NSString *temporaryDirectoryTemplate = [NSTemporaryDirectory() stringByAppendingPathComponent:@"WebKitPDFs-XXXXXX"];
475         CString templateRepresentation = [temporaryDirectoryTemplate fileSystemRepresentation];
476
477         if (mkdtemp(templateRepresentation.mutableData()))
478             temporaryPDFDirectoryPath = [[[NSFileManager defaultManager] stringWithFileSystemRepresentation:templateRepresentation.data() length:templateRepresentation.length()] copy];
479     }
480
481     return temporaryPDFDirectoryPath;
482 }
483
484 static NSString *pathToPDFOnDisk(const String& suggestedFilename)
485 {
486     NSString *pdfDirectoryPath = temporaryPDFDirectoryPath();
487     if (!pdfDirectoryPath) {
488         WTFLogAlways("Cannot create temporary PDF download directory.");
489         return nil;
490     }
491
492     NSString *path = [pdfDirectoryPath stringByAppendingPathComponent:suggestedFilename];
493
494     NSFileManager *fileManager = [NSFileManager defaultManager];
495     if ([fileManager fileExistsAtPath:path]) {
496         NSString *pathTemplatePrefix = [pdfDirectoryPath stringByAppendingPathComponent:@"XXXXXX-"];
497         NSString *pathTemplate = [pathTemplatePrefix stringByAppendingString:suggestedFilename];
498         CString pathTemplateRepresentation = [pathTemplate fileSystemRepresentation];
499
500         int fd = mkstemps(pathTemplateRepresentation.mutableData(), pathTemplateRepresentation.length() - strlen([pathTemplatePrefix fileSystemRepresentation]) + 1);
501         if (fd < 0) {
502             WTFLogAlways("Cannot create PDF file in the temporary directory (%s).", suggestedFilename.utf8().data());
503             return nil;
504         }
505
506         close(fd);
507         path = [fileManager stringWithFileSystemRepresentation:pathTemplateRepresentation.data() length:pathTemplateRepresentation.length()];
508     }
509
510     return path;
511 }
512
513 void WebPageProxy::savePDFToTemporaryFolderAndOpenWithNativeApplicationRaw(const String& suggestedFilename, const String& originatingURLString, const uint8_t* data, unsigned long size, const String& pdfUUID)
514 {
515     // FIXME: Write originatingURLString to the file's originating URL metadata (perhaps WebCore::FileSystem::setMetadataURL()?).
516     UNUSED_PARAM(originatingURLString);
517
518     if (!suggestedFilename.endsWithIgnoringASCIICase(".pdf")) {
519         WTFLogAlways("Cannot save file without .pdf extension to the temporary directory.");
520         return;
521     }
522
523     if (!size) {
524         WTFLogAlways("Cannot save empty PDF file to the temporary directory.");
525         return;
526     }
527
528     NSString *nsPath = pathToPDFOnDisk(suggestedFilename);
529
530     if (!nsPath)
531         return;
532
533     RetainPtr<NSNumber> permissions = adoptNS([[NSNumber alloc] initWithInt:S_IRUSR]);
534     RetainPtr<NSDictionary> fileAttributes = adoptNS([[NSDictionary alloc] initWithObjectsAndKeys:permissions.get(), NSFilePosixPermissions, nil]);
535     RetainPtr<NSData> nsData = adoptNS([[NSData alloc] initWithBytesNoCopy:(void*)data length:size freeWhenDone:NO]);
536
537     if (![[NSFileManager defaultManager] createFileAtPath:nsPath contents:nsData.get() attributes:fileAttributes.get()]) {
538         WTFLogAlways("Cannot create PDF file in the temporary directory (%s).", suggestedFilename.utf8().data());
539         return;
540     }
541
542     m_temporaryPDFFiles.add(pdfUUID, nsPath);
543
544     [[NSWorkspace sharedWorkspace] openFile:nsPath];
545 }
546
547 void WebPageProxy::savePDFToTemporaryFolderAndOpenWithNativeApplication(const String& suggestedFilename, const String& originatingURLString, const IPC::DataReference& data, const String& pdfUUID)
548 {
549     if (data.isEmpty()) {
550         WTFLogAlways("Cannot save empty PDF file to the temporary directory.");
551         return;
552     }
553
554     savePDFToTemporaryFolderAndOpenWithNativeApplicationRaw(suggestedFilename, originatingURLString, data.data(), data.size(), pdfUUID);
555 }
556
557 void WebPageProxy::openPDFFromTemporaryFolderWithNativeApplication(const String& pdfUUID)
558 {
559     String pdfFilename = m_temporaryPDFFiles.get(pdfUUID);
560
561     if (!pdfFilename.endsWithIgnoringASCIICase(".pdf"))
562         return;
563
564     [[NSWorkspace sharedWorkspace] openFile:pdfFilename];
565 }
566
567 #if ENABLE(PDFKIT_PLUGIN)
568 void WebPageProxy::showPDFContextMenu(const WebKit::PDFContextMenu& contextMenu, std::optional<int32_t>& selectedIndex)
569 {
570     if (!contextMenu.m_items.size())
571         return;
572     
573     RetainPtr<WKPDFMenuTarget> menuTarget = adoptNS([[WKPDFMenuTarget alloc] init]);
574     RetainPtr<NSMenu> nsMenu = adoptNS([[NSMenu alloc] init]);
575     [nsMenu setAllowsContextMenuPlugIns:false];
576     for (unsigned i = 0; i < contextMenu.m_items.size(); i++) {
577         auto& item = contextMenu.m_items[i];
578         
579         if (item.separator) {
580             [nsMenu insertItem:[NSMenuItem separatorItem] atIndex:i];
581             continue;
582         }
583         
584         RetainPtr<NSMenuItem> nsItem = adoptNS([[NSMenuItem alloc] init]);
585         [nsItem setTitle:item.title];
586         [nsItem setEnabled:item.enabled];
587         [nsItem setState:item.state];
588         if (item.hasAction) {
589             [nsItem setTarget:menuTarget.get()];
590             [nsItem setAction:@selector(contextMenuAction:)];
591         }
592         [nsItem setTag:item.tag];
593         [nsMenu insertItem:nsItem.get() atIndex:i];
594     }
595     NSWindow *window = pageClient().platformWindow();
596     auto windowNumber = [window windowNumber];
597     auto location = [window convertRectFromScreen: { contextMenu.m_point, NSZeroSize }].origin;
598     NSEvent* event = [NSEvent mouseEventWithType:NSEventTypeRightMouseDown location:location modifierFlags:0 timestamp:0 windowNumber:windowNumber context:0 eventNumber:0 clickCount:1 pressure:1];
599
600     auto view = [pageClient().platformWindow() contentView];
601     [NSMenu popUpContextMenu:nsMenu.get() withEvent:event forView:view];
602
603     if (auto selectedMenuItem = [menuTarget selectedMenuItem])
604         selectedIndex = [selectedMenuItem tag];
605 }
606 #endif
607
608 #if ENABLE(TELEPHONE_NUMBER_DETECTION)
609 void WebPageProxy::showTelephoneNumberMenu(const String& telephoneNumber, const WebCore::IntPoint& point)
610 {
611     RetainPtr<NSMenu> menu = menuForTelephoneNumber(telephoneNumber);
612     pageClient().showPlatformContextMenu(menu.get(), point);
613 }
614 #endif
615
616 CGRect WebPageProxy::boundsOfLayerInLayerBackedWindowCoordinates(CALayer *layer) const
617 {
618     return pageClient().boundsOfLayerInLayerBackedWindowCoordinates(layer);
619 }
620
621 bool WebPageProxy::appleMailPaginationQuirkEnabled()
622 {
623     return MacApplication::isAppleMail();
624 }
625
626 bool WebPageProxy::appleMailLinesClampEnabled()
627 {
628     return MacApplication::isAppleMail();
629 }
630
631 void WebPageProxy::editorStateChanged(const EditorState& editorState)
632 {
633     bool couldChangeSecureInputState = m_editorState.isInPasswordField != editorState.isInPasswordField || m_editorState.selectionIsNone;
634     
635     m_editorState = editorState;
636     
637     // Selection being none is a temporary state when editing. Flipping secure input state too quickly was causing trouble (not fully understood).
638     if (couldChangeSecureInputState && !editorState.selectionIsNone)
639         pageClient().updateSecureInputState();
640     
641     if (editorState.shouldIgnoreSelectionChanges)
642         return;
643     
644     pageClient().selectionDidChange();
645     updateFontAttributesAfterEditorStateChange();
646 }
647
648 void WebPageProxy::startWindowDrag()
649 {
650     pageClient().startWindowDrag();
651 }
652
653 NSWindow *WebPageProxy::platformWindow()
654 {
655     return pageClient().platformWindow();
656 }
657
658 void WebPageProxy::rootViewToWindow(const WebCore::IntRect& viewRect, WebCore::IntRect& windowRect)
659 {
660     windowRect = pageClient().rootViewToWindow(viewRect);
661 }
662
663 void WebPageProxy::showValidationMessage(const IntRect& anchorClientRect, const String& message)
664 {
665     m_validationBubble = pageClient().createValidationBubble(message, { m_preferences->minimumFontSize() });
666     m_validationBubble->showRelativeTo(anchorClientRect);
667 }
668
669 #if WK_API_ENABLED
670 NSView *WebPageProxy::inspectorAttachmentView()
671 {
672     return pageClient().inspectorAttachmentView();
673 }
674
675 _WKRemoteObjectRegistry *WebPageProxy::remoteObjectRegistry()
676 {
677     return pageClient().remoteObjectRegistry();
678 }
679 #endif
680
681 } // namespace WebKit
682
683 #endif // PLATFORM(MAC)
684
685 #undef MESSAGE_CHECK
686 #undef MESSAGE_CHECK_URL