Move Lookup Code for better cross platform usage
[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 // Complex text input support for plug-ins.
327 void WebPageProxy::sendComplexTextInputToPlugin(uint64_t pluginComplexTextInputIdentifier, const String& textInput)
328 {
329     if (!isValid())
330         return;
331     
332     process().send(Messages::WebPage::SendComplexTextInputToPlugin(pluginComplexTextInputIdentifier, textInput), m_pageID);
333 }
334
335 void WebPageProxy::uppercaseWord()
336 {
337     process().send(Messages::WebPage::UppercaseWord(), m_pageID);
338 }
339
340 void WebPageProxy::lowercaseWord()
341 {
342     process().send(Messages::WebPage::LowercaseWord(), m_pageID);
343 }
344
345 void WebPageProxy::capitalizeWord()
346 {
347     process().send(Messages::WebPage::CapitalizeWord(), m_pageID);
348 }
349
350 void WebPageProxy::setSmartInsertDeleteEnabled(bool isSmartInsertDeleteEnabled)
351 {
352     if (m_isSmartInsertDeleteEnabled == isSmartInsertDeleteEnabled)
353         return;
354
355     TextChecker::setSmartInsertDeleteEnabled(isSmartInsertDeleteEnabled);
356     m_isSmartInsertDeleteEnabled = isSmartInsertDeleteEnabled;
357     process().send(Messages::WebPage::SetSmartInsertDeleteEnabled(isSmartInsertDeleteEnabled), m_pageID);
358 }
359
360 void WebPageProxy::didPerformDictionaryLookup(const DictionaryPopupInfo& dictionaryPopupInfo)
361 {
362     pageClient().didPerformDictionaryLookup(dictionaryPopupInfo);
363 }
364     
365 void WebPageProxy::registerWebProcessAccessibilityToken(const IPC::DataReference& data)
366 {
367     if (!isValid())
368         return;
369     
370     pageClient().accessibilityWebProcessTokenReceived(data);
371 }    
372     
373 void WebPageProxy::makeFirstResponder()
374 {
375     pageClient().makeFirstResponder();
376 }
377
378 void WebPageProxy::assistiveTechnologyMakeFirstResponder()
379 {
380     pageClient().assistiveTechnologyMakeFirstResponder();
381 }
382
383 ColorSpaceData WebPageProxy::colorSpace()
384 {
385     return pageClient().colorSpace();
386 }
387
388 void WebPageProxy::registerUIProcessAccessibilityTokens(const IPC::DataReference& elementToken, const IPC::DataReference& windowToken)
389 {
390     if (!isValid())
391         return;
392
393     process().send(Messages::WebPage::RegisterUIProcessAccessibilityTokens(elementToken, windowToken), m_pageID);
394 }
395
396 void WebPageProxy::pluginFocusOrWindowFocusChanged(uint64_t pluginComplexTextInputIdentifier, bool pluginHasFocusAndWindowHasFocus)
397 {
398     pageClient().pluginFocusOrWindowFocusChanged(pluginComplexTextInputIdentifier, pluginHasFocusAndWindowHasFocus);
399 }
400
401 void WebPageProxy::setPluginComplexTextInputState(uint64_t pluginComplexTextInputIdentifier, uint64_t pluginComplexTextInputState)
402 {
403     MESSAGE_CHECK(isValidPluginComplexTextInputState(pluginComplexTextInputState));
404
405     pageClient().setPluginComplexTextInputState(pluginComplexTextInputIdentifier, static_cast<PluginComplexTextInputState>(pluginComplexTextInputState));
406 }
407
408 void WebPageProxy::executeSavedCommandBySelector(const String& selector, bool& handled)
409 {
410     MESSAGE_CHECK(isValidKeypressCommandName(selector));
411
412     handled = pageClient().executeSavedCommandBySelector(selector);
413 }
414
415 bool WebPageProxy::shouldDelayWindowOrderingForEvent(const WebKit::WebMouseEvent& event)
416 {
417     if (process().state() != WebProcessProxy::State::Running)
418         return false;
419
420     bool result = false;
421     const Seconds messageTimeout(3);
422     process().sendSync(Messages::WebPage::ShouldDelayWindowOrderingEvent(event), Messages::WebPage::ShouldDelayWindowOrderingEvent::Reply(result), m_pageID, messageTimeout);
423     return result;
424 }
425
426 bool WebPageProxy::acceptsFirstMouse(int eventNumber, const WebKit::WebMouseEvent& event)
427 {
428     if (!isValid())
429         return false;
430
431     bool result = false;
432     const Seconds messageTimeout(3);
433     process().sendSync(Messages::WebPage::AcceptsFirstMouse(eventNumber, event), Messages::WebPage::AcceptsFirstMouse::Reply(result), m_pageID, messageTimeout);
434     return result;
435 }
436
437 void WebPageProxy::intrinsicContentSizeDidChange(const IntSize& intrinsicContentSize)
438 {
439     pageClient().intrinsicContentSizeDidChange(intrinsicContentSize);
440 }
441
442 void WebPageProxy::setRemoteLayerTreeRootNode(RemoteLayerTreeNode* rootNode)
443 {
444     pageClient().setRemoteLayerTreeRootNode(rootNode);
445     m_frozenRemoteLayerTreeHost = nullptr;
446 }
447
448 CALayer *WebPageProxy::acceleratedCompositingRootLayer() const
449 {
450     return pageClient().acceleratedCompositingRootLayer();
451 }
452
453 static NSString *temporaryPDFDirectoryPath()
454 {
455     static NSString *temporaryPDFDirectoryPath;
456
457     if (!temporaryPDFDirectoryPath) {
458         NSString *temporaryDirectoryTemplate = [NSTemporaryDirectory() stringByAppendingPathComponent:@"WebKitPDFs-XXXXXX"];
459         CString templateRepresentation = [temporaryDirectoryTemplate fileSystemRepresentation];
460
461         if (mkdtemp(templateRepresentation.mutableData()))
462             temporaryPDFDirectoryPath = [[[NSFileManager defaultManager] stringWithFileSystemRepresentation:templateRepresentation.data() length:templateRepresentation.length()] copy];
463     }
464
465     return temporaryPDFDirectoryPath;
466 }
467
468 static NSString *pathToPDFOnDisk(const String& suggestedFilename)
469 {
470     NSString *pdfDirectoryPath = temporaryPDFDirectoryPath();
471     if (!pdfDirectoryPath) {
472         WTFLogAlways("Cannot create temporary PDF download directory.");
473         return nil;
474     }
475
476     NSString *path = [pdfDirectoryPath stringByAppendingPathComponent:suggestedFilename];
477
478     NSFileManager *fileManager = [NSFileManager defaultManager];
479     if ([fileManager fileExistsAtPath:path]) {
480         NSString *pathTemplatePrefix = [pdfDirectoryPath stringByAppendingPathComponent:@"XXXXXX-"];
481         NSString *pathTemplate = [pathTemplatePrefix stringByAppendingString:suggestedFilename];
482         CString pathTemplateRepresentation = [pathTemplate fileSystemRepresentation];
483
484         int fd = mkstemps(pathTemplateRepresentation.mutableData(), pathTemplateRepresentation.length() - strlen([pathTemplatePrefix fileSystemRepresentation]) + 1);
485         if (fd < 0) {
486             WTFLogAlways("Cannot create PDF file in the temporary directory (%s).", suggestedFilename.utf8().data());
487             return nil;
488         }
489
490         close(fd);
491         path = [fileManager stringWithFileSystemRepresentation:pathTemplateRepresentation.data() length:pathTemplateRepresentation.length()];
492     }
493
494     return path;
495 }
496
497 void WebPageProxy::savePDFToTemporaryFolderAndOpenWithNativeApplicationRaw(const String& suggestedFilename, const String& originatingURLString, const uint8_t* data, unsigned long size, const String& pdfUUID)
498 {
499     // FIXME: Write originatingURLString to the file's originating URL metadata (perhaps WebCore::FileSystem::setMetadataURL()?).
500     UNUSED_PARAM(originatingURLString);
501
502     if (!suggestedFilename.endsWithIgnoringASCIICase(".pdf")) {
503         WTFLogAlways("Cannot save file without .pdf extension to the temporary directory.");
504         return;
505     }
506
507     if (!size) {
508         WTFLogAlways("Cannot save empty PDF file to the temporary directory.");
509         return;
510     }
511
512     NSString *nsPath = pathToPDFOnDisk(suggestedFilename);
513
514     if (!nsPath)
515         return;
516
517     RetainPtr<NSNumber> permissions = adoptNS([[NSNumber alloc] initWithInt:S_IRUSR]);
518     RetainPtr<NSDictionary> fileAttributes = adoptNS([[NSDictionary alloc] initWithObjectsAndKeys:permissions.get(), NSFilePosixPermissions, nil]);
519     RetainPtr<NSData> nsData = adoptNS([[NSData alloc] initWithBytesNoCopy:(void*)data length:size freeWhenDone:NO]);
520
521     if (![[NSFileManager defaultManager] createFileAtPath:nsPath contents:nsData.get() attributes:fileAttributes.get()]) {
522         WTFLogAlways("Cannot create PDF file in the temporary directory (%s).", suggestedFilename.utf8().data());
523         return;
524     }
525
526     m_temporaryPDFFiles.add(pdfUUID, nsPath);
527
528     [[NSWorkspace sharedWorkspace] openFile:nsPath];
529 }
530
531 void WebPageProxy::savePDFToTemporaryFolderAndOpenWithNativeApplication(const String& suggestedFilename, const String& originatingURLString, const IPC::DataReference& data, const String& pdfUUID)
532 {
533     if (data.isEmpty()) {
534         WTFLogAlways("Cannot save empty PDF file to the temporary directory.");
535         return;
536     }
537
538     savePDFToTemporaryFolderAndOpenWithNativeApplicationRaw(suggestedFilename, originatingURLString, data.data(), data.size(), pdfUUID);
539 }
540
541 void WebPageProxy::openPDFFromTemporaryFolderWithNativeApplication(const String& pdfUUID)
542 {
543     String pdfFilename = m_temporaryPDFFiles.get(pdfUUID);
544
545     if (!pdfFilename.endsWithIgnoringASCIICase(".pdf"))
546         return;
547
548     [[NSWorkspace sharedWorkspace] openFile:pdfFilename];
549 }
550
551 #if ENABLE(PDFKIT_PLUGIN)
552 void WebPageProxy::showPDFContextMenu(const WebKit::PDFContextMenu& contextMenu, std::optional<int32_t>& selectedIndex)
553 {
554     if (!contextMenu.m_items.size())
555         return;
556     
557     RetainPtr<WKPDFMenuTarget> menuTarget = adoptNS([[WKPDFMenuTarget alloc] init]);
558     RetainPtr<NSMenu> nsMenu = adoptNS([[NSMenu alloc] init]);
559     [nsMenu setAllowsContextMenuPlugIns:false];
560     for (unsigned i = 0; i < contextMenu.m_items.size(); i++) {
561         auto& item = contextMenu.m_items[i];
562         
563         if (item.separator) {
564             [nsMenu insertItem:[NSMenuItem separatorItem] atIndex:i];
565             continue;
566         }
567         
568         RetainPtr<NSMenuItem> nsItem = adoptNS([[NSMenuItem alloc] init]);
569         [nsItem setTitle:item.title];
570         [nsItem setEnabled:item.enabled];
571         [nsItem setState:item.state];
572         if (item.hasAction) {
573             [nsItem setTarget:menuTarget.get()];
574             [nsItem setAction:@selector(contextMenuAction:)];
575         }
576         [nsItem setTag:item.tag];
577         [nsMenu insertItem:nsItem.get() atIndex:i];
578     }
579     NSWindow *window = pageClient().platformWindow();
580     auto windowNumber = [window windowNumber];
581     auto location = [window convertRectFromScreen: { contextMenu.m_point, NSZeroSize }].origin;
582     NSEvent* event = [NSEvent mouseEventWithType:NSEventTypeRightMouseDown location:location modifierFlags:0 timestamp:0 windowNumber:windowNumber context:0 eventNumber:0 clickCount:1 pressure:1];
583
584     auto view = [pageClient().platformWindow() contentView];
585     [NSMenu popUpContextMenu:nsMenu.get() withEvent:event forView:view];
586
587     if (auto selectedMenuItem = [menuTarget selectedMenuItem])
588         selectedIndex = [selectedMenuItem tag];
589 }
590 #endif
591
592 #if ENABLE(TELEPHONE_NUMBER_DETECTION)
593 void WebPageProxy::showTelephoneNumberMenu(const String& telephoneNumber, const WebCore::IntPoint& point)
594 {
595     RetainPtr<NSMenu> menu = menuForTelephoneNumber(telephoneNumber);
596     pageClient().showPlatformContextMenu(menu.get(), point);
597 }
598 #endif
599
600 CGRect WebPageProxy::boundsOfLayerInLayerBackedWindowCoordinates(CALayer *layer) const
601 {
602     return pageClient().boundsOfLayerInLayerBackedWindowCoordinates(layer);
603 }
604
605 bool WebPageProxy::appleMailPaginationQuirkEnabled()
606 {
607     return MacApplication::isAppleMail();
608 }
609
610 bool WebPageProxy::appleMailLinesClampEnabled()
611 {
612     return MacApplication::isAppleMail();
613 }
614
615 void WebPageProxy::editorStateChanged(const EditorState& editorState)
616 {
617     bool couldChangeSecureInputState = m_editorState.isInPasswordField != editorState.isInPasswordField || m_editorState.selectionIsNone;
618     
619     m_editorState = editorState;
620     
621     // Selection being none is a temporary state when editing. Flipping secure input state too quickly was causing trouble (not fully understood).
622     if (couldChangeSecureInputState && !editorState.selectionIsNone)
623         pageClient().updateSecureInputState();
624     
625     if (editorState.shouldIgnoreSelectionChanges)
626         return;
627     
628     pageClient().selectionDidChange();
629     updateFontAttributesAfterEditorStateChange();
630 }
631
632 void WebPageProxy::startWindowDrag()
633 {
634     pageClient().startWindowDrag();
635 }
636
637 NSWindow *WebPageProxy::platformWindow()
638 {
639     return pageClient().platformWindow();
640 }
641
642 void WebPageProxy::rootViewToWindow(const WebCore::IntRect& viewRect, WebCore::IntRect& windowRect)
643 {
644     windowRect = pageClient().rootViewToWindow(viewRect);
645 }
646
647 void WebPageProxy::showValidationMessage(const IntRect& anchorClientRect, const String& message)
648 {
649     m_validationBubble = pageClient().createValidationBubble(message, { m_preferences->minimumFontSize() });
650     m_validationBubble->showRelativeTo(anchorClientRect);
651 }
652
653 #if WK_API_ENABLED
654 NSView *WebPageProxy::inspectorAttachmentView()
655 {
656     return pageClient().inspectorAttachmentView();
657 }
658
659 _WKRemoteObjectRegistry *WebPageProxy::remoteObjectRegistry()
660 {
661     return pageClient().remoteObjectRegistry();
662 }
663 #endif
664
665 } // namespace WebKit
666
667 #endif // PLATFORM(MAC)
668
669 #undef MESSAGE_CHECK
670 #undef MESSAGE_CHECK_URL