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