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