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