Move FileSystem to WTF
[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(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(bool& isSpeaking)
135 {
136     ASSERT(hasProcessPrivilege(ProcessPrivilege::CanCommunicateWithWindowServer));
137     isSpeaking = [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 (!isValid())
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 (!isValid())
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 (!isValid())
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 (!isValid()) {
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 (!isValid()) {
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 (!isValid())
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 (!isValid())
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 (!isValid())
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 (!isValid())
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 (!isValid())
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 (!isValid())
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, bool& handled)
408 {
409     MESSAGE_CHECK(isValidKeypressCommandName(selector));
410
411     handled = 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 (!isValid())
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::intrinsicContentSizeDidChange(const IntSize& intrinsicContentSize)
437 {
438     pageClient().intrinsicContentSizeDidChange(intrinsicContentSize);
439 }
440
441 void WebPageProxy::setRemoteLayerTreeRootNode(RemoteLayerTreeNode* rootNode)
442 {
443     pageClient().setRemoteLayerTreeRootNode(rootNode);
444     m_frozenRemoteLayerTreeHost = nullptr;
445 }
446
447 CALayer *WebPageProxy::acceleratedCompositingRootLayer() const
448 {
449     return pageClient().acceleratedCompositingRootLayer();
450 }
451
452 static NSString *temporaryPDFDirectoryPath()
453 {
454     static NSString *temporaryPDFDirectoryPath;
455
456     if (!temporaryPDFDirectoryPath) {
457         NSString *temporaryDirectoryTemplate = [NSTemporaryDirectory() stringByAppendingPathComponent:@"WebKitPDFs-XXXXXX"];
458         CString templateRepresentation = [temporaryDirectoryTemplate fileSystemRepresentation];
459
460         if (mkdtemp(templateRepresentation.mutableData()))
461             temporaryPDFDirectoryPath = [[[NSFileManager defaultManager] stringWithFileSystemRepresentation:templateRepresentation.data() length:templateRepresentation.length()] copy];
462     }
463
464     return temporaryPDFDirectoryPath;
465 }
466
467 static NSString *pathToPDFOnDisk(const String& suggestedFilename)
468 {
469     NSString *pdfDirectoryPath = temporaryPDFDirectoryPath();
470     if (!pdfDirectoryPath) {
471         WTFLogAlways("Cannot create temporary PDF download directory.");
472         return nil;
473     }
474
475     NSString *path = [pdfDirectoryPath stringByAppendingPathComponent:suggestedFilename];
476
477     NSFileManager *fileManager = [NSFileManager defaultManager];
478     if ([fileManager fileExistsAtPath:path]) {
479         NSString *pathTemplatePrefix = [pdfDirectoryPath stringByAppendingPathComponent:@"XXXXXX-"];
480         NSString *pathTemplate = [pathTemplatePrefix stringByAppendingString:suggestedFilename];
481         CString pathTemplateRepresentation = [pathTemplate fileSystemRepresentation];
482
483         int fd = mkstemps(pathTemplateRepresentation.mutableData(), pathTemplateRepresentation.length() - strlen([pathTemplatePrefix fileSystemRepresentation]) + 1);
484         if (fd < 0) {
485             WTFLogAlways("Cannot create PDF file in the temporary directory (%s).", suggestedFilename.utf8().data());
486             return nil;
487         }
488
489         close(fd);
490         path = [fileManager stringWithFileSystemRepresentation:pathTemplateRepresentation.data() length:pathTemplateRepresentation.length()];
491     }
492
493     return path;
494 }
495
496 void WebPageProxy::savePDFToTemporaryFolderAndOpenWithNativeApplicationRaw(const String& suggestedFilename, const String& originatingURLString, const uint8_t* data, unsigned long size, const String& pdfUUID)
497 {
498     // FIXME: Write originatingURLString to the file's originating URL metadata (perhaps FileSystem::setMetadataURL()?).
499     UNUSED_PARAM(originatingURLString);
500
501     if (!suggestedFilename.endsWithIgnoringASCIICase(".pdf")) {
502         WTFLogAlways("Cannot save file without .pdf extension to the temporary directory.");
503         return;
504     }
505
506     if (!size) {
507         WTFLogAlways("Cannot save empty PDF file to the temporary directory.");
508         return;
509     }
510
511     NSString *nsPath = pathToPDFOnDisk(suggestedFilename);
512
513     if (!nsPath)
514         return;
515
516     RetainPtr<NSNumber> permissions = adoptNS([[NSNumber alloc] initWithInt:S_IRUSR]);
517     RetainPtr<NSDictionary> fileAttributes = adoptNS([[NSDictionary alloc] initWithObjectsAndKeys:permissions.get(), NSFilePosixPermissions, nil]);
518     RetainPtr<NSData> nsData = adoptNS([[NSData alloc] initWithBytesNoCopy:(void*)data length:size freeWhenDone:NO]);
519
520     if (![[NSFileManager defaultManager] createFileAtPath:nsPath contents:nsData.get() attributes:fileAttributes.get()]) {
521         WTFLogAlways("Cannot create PDF file in the temporary directory (%s).", suggestedFilename.utf8().data());
522         return;
523     }
524
525     m_temporaryPDFFiles.add(pdfUUID, nsPath);
526
527     [[NSWorkspace sharedWorkspace] openFile:nsPath];
528 }
529
530 void WebPageProxy::savePDFToTemporaryFolderAndOpenWithNativeApplication(const String& suggestedFilename, const String& originatingURLString, const IPC::DataReference& data, const String& pdfUUID)
531 {
532     if (data.isEmpty()) {
533         WTFLogAlways("Cannot save empty PDF file to the temporary directory.");
534         return;
535     }
536
537     savePDFToTemporaryFolderAndOpenWithNativeApplicationRaw(suggestedFilename, originatingURLString, data.data(), data.size(), pdfUUID);
538 }
539
540 void WebPageProxy::openPDFFromTemporaryFolderWithNativeApplication(const String& pdfUUID)
541 {
542     String pdfFilename = m_temporaryPDFFiles.get(pdfUUID);
543
544     if (!pdfFilename.endsWithIgnoringASCIICase(".pdf"))
545         return;
546
547     [[NSWorkspace sharedWorkspace] openFile:pdfFilename];
548 }
549
550 #if ENABLE(PDFKIT_PLUGIN)
551 void WebPageProxy::showPDFContextMenu(const WebKit::PDFContextMenu& contextMenu, Optional<int32_t>& selectedIndex)
552 {
553     if (!contextMenu.m_items.size())
554         return;
555     
556     RetainPtr<WKPDFMenuTarget> menuTarget = adoptNS([[WKPDFMenuTarget alloc] init]);
557     RetainPtr<NSMenu> nsMenu = adoptNS([[NSMenu alloc] init]);
558     [nsMenu setAllowsContextMenuPlugIns:false];
559     for (unsigned i = 0; i < contextMenu.m_items.size(); i++) {
560         auto& item = contextMenu.m_items[i];
561         
562         if (item.separator) {
563             [nsMenu insertItem:[NSMenuItem separatorItem] atIndex:i];
564             continue;
565         }
566         
567         RetainPtr<NSMenuItem> nsItem = adoptNS([[NSMenuItem alloc] init]);
568         [nsItem setTitle:item.title];
569         [nsItem setEnabled:item.enabled];
570         [nsItem setState:item.state];
571         if (item.hasAction) {
572             [nsItem setTarget:menuTarget.get()];
573             [nsItem setAction:@selector(contextMenuAction:)];
574         }
575         [nsItem setTag:item.tag];
576         [nsMenu insertItem:nsItem.get() atIndex:i];
577     }
578     NSWindow *window = pageClient().platformWindow();
579     auto windowNumber = [window windowNumber];
580     auto location = [window convertRectFromScreen: { contextMenu.m_point, NSZeroSize }].origin;
581     NSEvent* event = [NSEvent mouseEventWithType:NSEventTypeRightMouseDown location:location modifierFlags:0 timestamp:0 windowNumber:windowNumber context:0 eventNumber:0 clickCount:1 pressure:1];
582
583     auto view = [pageClient().platformWindow() contentView];
584     [NSMenu popUpContextMenu:nsMenu.get() withEvent:event forView:view];
585
586     if (auto selectedMenuItem = [menuTarget selectedMenuItem])
587         selectedIndex = [selectedMenuItem tag];
588 }
589 #endif
590
591 #if ENABLE(TELEPHONE_NUMBER_DETECTION)
592 void WebPageProxy::showTelephoneNumberMenu(const String& telephoneNumber, const WebCore::IntPoint& point)
593 {
594     RetainPtr<NSMenu> menu = menuForTelephoneNumber(telephoneNumber);
595     pageClient().showPlatformContextMenu(menu.get(), point);
596 }
597 #endif
598
599 CGRect WebPageProxy::boundsOfLayerInLayerBackedWindowCoordinates(CALayer *layer) const
600 {
601     return pageClient().boundsOfLayerInLayerBackedWindowCoordinates(layer);
602 }
603
604 bool WebPageProxy::appleMailPaginationQuirkEnabled()
605 {
606     return MacApplication::isAppleMail();
607 }
608
609 bool WebPageProxy::appleMailLinesClampEnabled()
610 {
611     return MacApplication::isAppleMail();
612 }
613
614 void WebPageProxy::editorStateChanged(const EditorState& editorState)
615 {
616     bool couldChangeSecureInputState = m_editorState.isInPasswordField != editorState.isInPasswordField || m_editorState.selectionIsNone;
617     
618     m_editorState = editorState;
619     
620     // Selection being none is a temporary state when editing. Flipping secure input state too quickly was causing trouble (not fully understood).
621     if (couldChangeSecureInputState && !editorState.selectionIsNone)
622         pageClient().updateSecureInputState();
623     
624     if (editorState.shouldIgnoreSelectionChanges)
625         return;
626     
627     pageClient().selectionDidChange();
628     updateFontAttributesAfterEditorStateChange();
629 }
630
631 void WebPageProxy::startWindowDrag()
632 {
633     pageClient().startWindowDrag();
634 }
635
636 NSWindow *WebPageProxy::platformWindow()
637 {
638     return pageClient().platformWindow();
639 }
640
641 void WebPageProxy::rootViewToWindow(const WebCore::IntRect& viewRect, WebCore::IntRect& windowRect)
642 {
643     windowRect = pageClient().rootViewToWindow(viewRect);
644 }
645
646 void WebPageProxy::showValidationMessage(const IntRect& anchorClientRect, const String& message)
647 {
648     m_validationBubble = pageClient().createValidationBubble(message, { m_preferences->minimumFontSize() });
649     m_validationBubble->showRelativeTo(anchorClientRect);
650 }
651
652 #if WK_API_ENABLED
653 NSView *WebPageProxy::inspectorAttachmentView()
654 {
655     return pageClient().inspectorAttachmentView();
656 }
657
658 _WKRemoteObjectRegistry *WebPageProxy::remoteObjectRegistry()
659 {
660     return pageClient().remoteObjectRegistry();
661 }
662 #endif
663
664 } // namespace WebKit
665
666 #endif // PLATFORM(MAC)
667
668 #undef MESSAGE_CHECK
669 #undef MESSAGE_CHECK_URL