blob: 9f9e514a8306c4bfbdc8e4c8ba562dd2760e0596 [file] [log] [blame]
/*
* Copyright (C) 2010-2020 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "config.h"
#import "WebPageProxy.h"
#if PLATFORM(MAC)
#import "APIUIClient.h"
#import "Connection.h"
#import "DataReference.h"
#import "EditorState.h"
#import "FontInfo.h"
#import "FrameInfoData.h"
#import "InsertTextOptions.h"
#import "MenuUtilities.h"
#import "NativeWebKeyboardEvent.h"
#import "PDFContextMenu.h"
#import "PageClient.h"
#import "PageClientImplMac.h"
#import "RemoteLayerTreeHost.h"
#import "StringUtilities.h"
#import "TextChecker.h"
#import "WKBrowsingContextControllerInternal.h"
#import "WKQuickLookPreviewController.h"
#import "WKSharingServicePickerDelegate.h"
#import "WebContextMenuProxyMac.h"
#import "WebPageMessages.h"
#import "WebPageProxyMessages.h"
#import "WebPreferencesKeys.h"
#import "WebProcessProxy.h"
#import <WebCore/AttributedString.h>
#import <WebCore/DestinationColorSpace.h>
#import <WebCore/DictionaryLookup.h>
#import <WebCore/DragItem.h>
#import <WebCore/GraphicsLayer.h>
#import <WebCore/LegacyNSPasteboardTypes.h>
#import <WebCore/RuntimeApplicationChecks.h>
#import <WebCore/SharedBuffer.h>
#import <WebCore/TextAlternativeWithRange.h>
#import <WebCore/UniversalAccessZoom.h>
#import <WebCore/UserAgent.h>
#import <WebCore/ValidationBubble.h>
#import <mach-o/dyld.h>
#import <pal/spi/mac/NSApplicationSPI.h>
#import <pal/spi/mac/NSMenuSPI.h>
#import <wtf/ProcessPrivilege.h>
#import <wtf/text/StringConcatenate.h>
#define MESSAGE_CHECK(assertion) MESSAGE_CHECK_BASE(assertion, process().connection())
#define MESSAGE_CHECK_URL(url) MESSAGE_CHECK_BASE(checkURLReceivedFromCurrentOrPreviousWebProcess(m_process, url), m_process->connection())
#define MESSAGE_CHECK_WITH_RETURN_VALUE(assertion, returnValue) MESSAGE_CHECK_WITH_RETURN_VALUE_BASE(assertion, process().connection(), returnValue)
@interface NSApplication ()
- (BOOL)isSpeaking;
- (void)speakString:(NSString *)string;
- (void)stopSpeaking:(id)sender;
@end
#if ENABLE(PDFKIT_PLUGIN)
@interface WKPDFMenuTarget : NSObject {
NSMenuItem *_selectedMenuItem;
}
- (NSMenuItem *)selectedMenuItem;
- (void)contextMenuAction:(NSMenuItem *)sender;
@end
@implementation WKPDFMenuTarget
- (instancetype)init
{
self = [super init];
if (!self)
return nil;
_selectedMenuItem = nil;
return self;
}
- (NSMenuItem *)selectedMenuItem
{
return _selectedMenuItem;
}
- (void)contextMenuAction:(NSMenuItem *)sender
{
_selectedMenuItem = sender;
}
@end // implementation WKPDFMenuTarget
#endif
#import <pal/mac/QuickLookUISoftLink.h>
namespace WebKit {
using namespace WebCore;
static inline bool expectsLegacyImplicitRubberBandControl()
{
if (MacApplication::isSafari()) {
const int32_t firstVersionOfSafariNotExpectingImplicitRubberBandControl = 0x021A0F00; // 538.15.0
bool linkedAgainstSafariExpectingImplicitRubberBandControl = NSVersionOfLinkTimeLibrary("Safari") < firstVersionOfSafariNotExpectingImplicitRubberBandControl;
return linkedAgainstSafariExpectingImplicitRubberBandControl;
}
const int32_t firstVersionOfWebKit2WithNoImplicitRubberBandControl = 0x021A0200; // 538.2.0
int32_t linkedWebKit2Version = NSVersionOfLinkTimeLibrary("WebKit2");
return linkedWebKit2Version != -1 && linkedWebKit2Version < firstVersionOfWebKit2WithNoImplicitRubberBandControl;
}
void WebPageProxy::platformInitialize()
{
static bool clientExpectsLegacyImplicitRubberBandControl = expectsLegacyImplicitRubberBandControl();
setShouldUseImplicitRubberBandControl(clientExpectsLegacyImplicitRubberBandControl);
}
String WebPageProxy::userAgentForURL(const URL&)
{
return userAgent();
}
String WebPageProxy::standardUserAgent(const String& applicationNameForUserAgent)
{
return standardUserAgentWithApplicationName(applicationNameForUserAgent);
}
void WebPageProxy::getIsSpeaking(CompletionHandler<void(bool)>&& completionHandler)
{
ASSERT(hasProcessPrivilege(ProcessPrivilege::CanCommunicateWithWindowServer));
completionHandler([NSApp isSpeaking]);
}
void WebPageProxy::speak(const String& string)
{
ASSERT(hasProcessPrivilege(ProcessPrivilege::CanCommunicateWithWindowServer));
[NSApp speakString:nsStringFromWebCoreString(string)];
}
void WebPageProxy::stopSpeaking()
{
ASSERT(hasProcessPrivilege(ProcessPrivilege::CanCommunicateWithWindowServer));
[NSApp stopSpeaking:nil];
}
void WebPageProxy::searchWithSpotlight(const String& string)
{
[[NSWorkspace sharedWorkspace] showSearchResultsForQueryString:nsStringFromWebCoreString(string)];
}
void WebPageProxy::searchTheWeb(const String& string)
{
NSPasteboard *pasteboard = [NSPasteboard pasteboardWithUniqueName];
[pasteboard declareTypes:@[legacyStringPasteboardType()] owner:nil];
[pasteboard setString:string forType:legacyStringPasteboardType()];
NSPerformService(@"Search With %WebSearchProvider@", pasteboard);
}
void WebPageProxy::windowAndViewFramesChanged(const FloatRect& viewFrameInWindowCoordinates, const FloatPoint& accessibilityViewCoordinates)
{
if (!hasRunningProcess())
return;
// In case the UI client overrides getWindowFrame(), we call it here to make sure we send the appropriate window frame.
m_uiClient->windowFrame(*this, [this, protectedThis = Ref { *this }, viewFrameInWindowCoordinates, accessibilityViewCoordinates] (FloatRect windowFrameInScreenCoordinates) {
FloatRect windowFrameInUnflippedScreenCoordinates = pageClient().convertToUserSpace(windowFrameInScreenCoordinates);
send(Messages::WebPage::WindowAndViewFramesChanged(windowFrameInScreenCoordinates, windowFrameInUnflippedScreenCoordinates, viewFrameInWindowCoordinates, accessibilityViewCoordinates));
});
}
void WebPageProxy::setMainFrameIsScrollable(bool isScrollable)
{
if (!hasRunningProcess())
return;
send(Messages::WebPage::SetMainFrameIsScrollable(isScrollable));
}
void WebPageProxy::attributedSubstringForCharacterRangeAsync(const EditingRange& range, CompletionHandler<void(const WebCore::AttributedString&, const EditingRange&)>&& callbackFunction)
{
if (!hasRunningProcess()) {
callbackFunction({ }, { });
return;
}
sendWithAsyncReply(Messages::WebPage::AttributedSubstringForCharacterRangeAsync(range), WTFMove(callbackFunction));
}
String WebPageProxy::stringSelectionForPasteboard()
{
String value;
if (!hasRunningProcess())
return value;
const Seconds messageTimeout(20);
sendSync(Messages::WebPage::GetStringSelectionForPasteboard(), Messages::WebPage::GetStringSelectionForPasteboard::Reply(value), messageTimeout);
return value;
}
RefPtr<WebCore::SharedBuffer> WebPageProxy::dataSelectionForPasteboard(const String& pasteboardType)
{
if (!hasRunningProcess())
return nullptr;
SharedMemory::IPCHandle ipcHandle;
const Seconds messageTimeout(20);
sendSync(Messages::WebPage::GetDataSelectionForPasteboard(pasteboardType), Messages::WebPage::GetDataSelectionForPasteboard::Reply(ipcHandle), messageTimeout);
MESSAGE_CHECK_WITH_RETURN_VALUE(!ipcHandle.handle.isNull(), nullptr);
auto sharedMemoryBuffer = SharedMemory::map(ipcHandle.handle, SharedMemory::Protection::ReadOnly);
if (!sharedMemoryBuffer)
return nullptr;
return sharedMemoryBuffer->createSharedBuffer(ipcHandle.dataSize);
}
bool WebPageProxy::readSelectionFromPasteboard(const String& pasteboardName)
{
if (!hasRunningProcess())
return false;
grantAccessToCurrentPasteboardData(pasteboardName);
bool result = false;
const Seconds messageTimeout(20);
sendSync(Messages::WebPage::ReadSelectionFromPasteboard(pasteboardName), Messages::WebPage::ReadSelectionFromPasteboard::Reply(result), messageTimeout);
return result;
}
#if ENABLE(SERVICE_CONTROLS)
void WebPageProxy::replaceSelectionWithPasteboardData(const Vector<String>& types, const IPC::DataReference& data)
{
send(Messages::WebPage::ReplaceSelectionWithPasteboardData(types, data));
}
#endif
#if ENABLE(DRAG_SUPPORT)
void WebPageProxy::setPromisedDataForImage(const String& pasteboardName, const SharedMemory::IPCHandle& imageHandle, const String& filename, const String& extension,
const String& title, const String& url, const String& visibleURL, const SharedMemory::IPCHandle& archiveHandle, const String& originIdentifier)
{
MESSAGE_CHECK_URL(url);
MESSAGE_CHECK_URL(visibleURL);
MESSAGE_CHECK(!imageHandle.handle.isNull());
auto sharedMemoryImage = SharedMemory::map(imageHandle.handle, SharedMemory::Protection::ReadOnly);
if (!sharedMemoryImage)
return;
auto imageBuffer = sharedMemoryImage->createSharedBuffer(imageHandle.dataSize);
RefPtr<FragmentedSharedBuffer> archiveBuffer;
if (!archiveHandle.handle.isNull()) {
auto sharedMemoryArchive = SharedMemory::map(archiveHandle.handle, SharedMemory::Protection::ReadOnly);
if (!sharedMemoryArchive)
return;
archiveBuffer = sharedMemoryArchive->createSharedBuffer(archiveHandle.dataSize);
}
pageClient().setPromisedDataForImage(pasteboardName, WTFMove(imageBuffer), ResourceResponseBase::sanitizeSuggestedFilename(filename), extension, title, url, visibleURL, WTFMove(archiveBuffer), originIdentifier);
}
#endif
void WebPageProxy::uppercaseWord()
{
send(Messages::WebPage::UppercaseWord());
}
void WebPageProxy::lowercaseWord()
{
send(Messages::WebPage::LowercaseWord());
}
void WebPageProxy::capitalizeWord()
{
send(Messages::WebPage::CapitalizeWord());
}
void WebPageProxy::setSmartInsertDeleteEnabled(bool isSmartInsertDeleteEnabled)
{
if (m_isSmartInsertDeleteEnabled == isSmartInsertDeleteEnabled)
return;
TextChecker::setSmartInsertDeleteEnabled(isSmartInsertDeleteEnabled);
m_isSmartInsertDeleteEnabled = isSmartInsertDeleteEnabled;
send(Messages::WebPage::SetSmartInsertDeleteEnabled(isSmartInsertDeleteEnabled));
}
void WebPageProxy::didPerformDictionaryLookup(const DictionaryPopupInfo& dictionaryPopupInfo)
{
pageClient().didPerformDictionaryLookup(dictionaryPopupInfo);
}
void WebPageProxy::registerWebProcessAccessibilityToken(const IPC::DataReference& data)
{
if (!hasRunningProcess())
return;
pageClient().accessibilityWebProcessTokenReceived(data);
}
void WebPageProxy::makeFirstResponder()
{
pageClient().makeFirstResponder();
}
void WebPageProxy::assistiveTechnologyMakeFirstResponder()
{
pageClient().assistiveTechnologyMakeFirstResponder();
}
WebCore::DestinationColorSpace WebPageProxy::colorSpace()
{
return pageClient().colorSpace();
}
void WebPageProxy::registerUIProcessAccessibilityTokens(const IPC::DataReference& elementToken, const IPC::DataReference& windowToken)
{
if (!hasRunningProcess())
return;
send(Messages::WebPage::RegisterUIProcessAccessibilityTokens(elementToken, windowToken));
}
void WebPageProxy::executeSavedCommandBySelector(const String& selector, CompletionHandler<void(bool)>&& completionHandler)
{
MESSAGE_CHECK(isValidKeypressCommandName(selector));
completionHandler(pageClient().executeSavedCommandBySelector(selector));
}
bool WebPageProxy::shouldDelayWindowOrderingForEvent(const WebKit::WebMouseEvent& event)
{
if (process().state() != WebProcessProxy::State::Running)
return false;
bool result = false;
const Seconds messageTimeout(3);
sendSync(Messages::WebPage::ShouldDelayWindowOrderingEvent(event), Messages::WebPage::ShouldDelayWindowOrderingEvent::Reply(result), messageTimeout);
return result;
}
bool WebPageProxy::acceptsFirstMouse(int eventNumber, const WebKit::WebMouseEvent& event)
{
if (!hasRunningProcess())
return false;
if (!m_process->hasConnection())
return false;
send(Messages::WebPage::RequestAcceptsFirstMouse(eventNumber, event), IPC::SendOption::DispatchMessageEvenWhenWaitingForUnboundedSyncReply);
bool receivedReply = m_process->connection()->waitForAndDispatchImmediately<Messages::WebPageProxy::HandleAcceptsFirstMouse>(webPageID(), 3_s, IPC::WaitForOption::InterruptWaitingIfSyncMessageArrives);
if (!receivedReply)
return false;
return m_acceptsFirstMouse;
}
void WebPageProxy::handleAcceptsFirstMouse(bool acceptsFirstMouse)
{
m_acceptsFirstMouse = acceptsFirstMouse;
}
void WebPageProxy::setRemoteLayerTreeRootNode(RemoteLayerTreeNode* rootNode)
{
pageClient().setRemoteLayerTreeRootNode(rootNode);
m_frozenRemoteLayerTreeHost = nullptr;
}
CALayer *WebPageProxy::acceleratedCompositingRootLayer() const
{
return pageClient().acceleratedCompositingRootLayer();
}
static NSString *temporaryPDFDirectoryPath()
{
static NeverDestroyed path = [] {
auto temporaryDirectoryTemplate = [NSTemporaryDirectory() stringByAppendingPathComponent:@"WebKitPDFs-XXXXXX"];
CString templateRepresentation = [temporaryDirectoryTemplate fileSystemRepresentation];
if (mkdtemp(templateRepresentation.mutableData()))
return adoptNS([[[NSFileManager defaultManager] stringWithFileSystemRepresentation:templateRepresentation.data() length:templateRepresentation.length()] copy]);
return RetainPtr<id> { };
}();
return path.get().get();
}
static NSString *pathToPDFOnDisk(const String& suggestedFilename)
{
NSString *pdfDirectoryPath = temporaryPDFDirectoryPath();
if (!pdfDirectoryPath) {
WTFLogAlways("Cannot create temporary PDF download directory.");
return nil;
}
NSString *path = [pdfDirectoryPath stringByAppendingPathComponent:suggestedFilename];
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:path]) {
NSString *pathTemplatePrefix = [pdfDirectoryPath stringByAppendingPathComponent:@"XXXXXX-"];
NSString *pathTemplate = [pathTemplatePrefix stringByAppendingString:suggestedFilename];
CString pathTemplateRepresentation = [pathTemplate fileSystemRepresentation];
int fd = mkstemps(pathTemplateRepresentation.mutableData(), pathTemplateRepresentation.length() - strlen([pathTemplatePrefix fileSystemRepresentation]) + 1);
if (fd < 0) {
WTFLogAlways("Cannot create PDF file in the temporary directory (%s).", suggestedFilename.utf8().data());
return nil;
}
close(fd);
path = [fileManager stringWithFileSystemRepresentation:pathTemplateRepresentation.data() length:pathTemplateRepresentation.length()];
}
return path;
}
void WebPageProxy::savePDFToTemporaryFolderAndOpenWithNativeApplication(const String& suggestedFilename, FrameInfoData&& frameInfo, const IPC::DataReference& data, const String& pdfUUID)
{
if (data.empty()) {
WTFLogAlways("Cannot save empty PDF file to the temporary directory.");
return;
}
auto sanitizedFilename = ResourceResponseBase::sanitizeSuggestedFilename(suggestedFilename);
if (!sanitizedFilename.endsWithIgnoringASCIICase(".pdf")) {
WTFLogAlways("Cannot save file without .pdf extension to the temporary directory.");
return;
}
auto nsPath = retainPtr(pathToPDFOnDisk(sanitizedFilename));
if (!nsPath)
return;
auto permissions = adoptNS([[NSNumber alloc] initWithInt:S_IRUSR]);
auto fileAttributes = adoptNS([[NSDictionary alloc] initWithObjectsAndKeys:permissions.get(), NSFilePosixPermissions, nil]);
auto nsData = adoptNS([[NSData alloc] initWithBytesNoCopy:(void*)data.data() length:data.size() freeWhenDone:NO]);
if (![[NSFileManager defaultManager] createFileAtPath:nsPath.get() contents:nsData.get() attributes:fileAttributes.get()]) {
WTFLogAlways("Cannot create PDF file in the temporary directory (%s).", sanitizedFilename.utf8().data());
return;
}
auto originatingURLString = frameInfo.request.url().string();
FileSystem::setMetadataURL(nsPath.get(), originatingURLString);
if (TemporaryPDFFileMap::isValidKey(pdfUUID))
m_temporaryPDFFiles.add(pdfUUID, nsPath.get());
auto pdfFileURL = URL::fileURLWithFileSystemPath(String(nsPath.get()));
m_uiClient->confirmPDFOpening(*this, pdfFileURL, WTFMove(frameInfo), [pdfFileURL] (bool allowed) {
if (!allowed)
return;
[[NSWorkspace sharedWorkspace] openURL:pdfFileURL];
});
}
#if ENABLE(PDFKIT_PLUGIN) && !ENABLE(UI_PROCESS_PDF_HUD)
void WebPageProxy::openPDFFromTemporaryFolderWithNativeApplication(FrameInfoData&& frameInfo, const String& pdfUUID)
{
MESSAGE_CHECK(TemporaryPDFFileMap::isValidKey(pdfUUID));
String pdfFilename = m_temporaryPDFFiles.get(pdfUUID);
if (!pdfFilename.endsWithIgnoringASCIICase(".pdf"))
return;
auto pdfFileURL = URL::fileURLWithFileSystemPath(pdfFilename);
m_uiClient->confirmPDFOpening(*this, pdfFileURL, WTFMove(frameInfo), [pdfFileURL] (bool allowed) {
if (!allowed)
return;
[[NSWorkspace sharedWorkspace] openURL:pdfFileURL];
});
}
#endif
#if ENABLE(PDFKIT_PLUGIN)
void WebPageProxy::showPDFContextMenu(const WebKit::PDFContextMenu& contextMenu, PDFPluginIdentifier identifier, CompletionHandler<void(std::optional<int32_t>&&)>&& completionHandler)
{
if (!contextMenu.items.size())
return completionHandler(std::nullopt);
RetainPtr<WKPDFMenuTarget> menuTarget = adoptNS([[WKPDFMenuTarget alloc] init]);
RetainPtr<NSMenu> nsMenu = adoptNS([[NSMenu alloc] init]);
[nsMenu setAllowsContextMenuPlugIns:false];
for (unsigned i = 0; i < contextMenu.items.size(); i++) {
auto& item = contextMenu.items[i];
if (item.separator) {
[nsMenu insertItem:[NSMenuItem separatorItem] atIndex:i];
continue;
}
RetainPtr<NSMenuItem> nsItem = adoptNS([[NSMenuItem alloc] init]);
[nsItem setTitle:item.title];
[nsItem setEnabled:item.enabled];
[nsItem setState:item.state];
if (item.hasAction) {
[nsItem setTarget:menuTarget.get()];
[nsItem setAction:@selector(contextMenuAction:)];
}
[nsItem setTag:item.tag];
[nsMenu insertItem:nsItem.get() atIndex:i];
}
NSWindow *window = pageClient().platformWindow();
auto windowNumber = [window windowNumber];
auto location = [window convertRectFromScreen: { contextMenu.point, NSZeroSize }].origin;
NSEvent* event = [NSEvent mouseEventWithType:NSEventTypeRightMouseDown location:location modifierFlags:0 timestamp:0 windowNumber:windowNumber context:0 eventNumber:0 clickCount:1 pressure:1];
auto view = [pageClient().platformWindow() contentView];
[NSMenu popUpContextMenu:nsMenu.get() withEvent:event forView:view];
if (auto selectedMenuItem = [menuTarget selectedMenuItem]) {
NSInteger tag = selectedMenuItem.tag;
#if ENABLE(UI_PROCESS_PDF_HUD)
if (contextMenu.openInPreviewIndex && *contextMenu.openInPreviewIndex == tag)
pdfOpenWithPreview(identifier);
#else
UNUSED_PARAM(identifier);
#endif
return completionHandler(tag);
}
completionHandler(std::nullopt);
}
#endif
#if ENABLE(TELEPHONE_NUMBER_DETECTION)
void WebPageProxy::showTelephoneNumberMenu(const String& telephoneNumber, const WebCore::IntPoint& point, const WebCore::IntRect& rect)
{
RetainPtr<NSMenu> menu = menuForTelephoneNumber(telephoneNumber, pageClient().viewForPresentingRevealPopover(), rect);
pageClient().showPlatformContextMenu(menu.get(), point);
}
#endif
CGRect WebPageProxy::boundsOfLayerInLayerBackedWindowCoordinates(CALayer *layer) const
{
return pageClient().boundsOfLayerInLayerBackedWindowCoordinates(layer);
}
void WebPageProxy::didUpdateEditorState(const EditorState& oldEditorState, const EditorState& newEditorState)
{
bool couldChangeSecureInputState = newEditorState.isInPasswordField != oldEditorState.isInPasswordField || oldEditorState.selectionIsNone;
// Selection being none is a temporary state when editing. Flipping secure input state too quickly was causing trouble (not fully understood).
if (couldChangeSecureInputState && !newEditorState.selectionIsNone)
pageClient().updateSecureInputState();
if (newEditorState.shouldIgnoreSelectionChanges)
return;
updateFontAttributesAfterEditorStateChange();
pageClient().selectionDidChange();
}
void WebPageProxy::startWindowDrag()
{
pageClient().startWindowDrag();
}
NSWindow *WebPageProxy::platformWindow()
{
return m_pageClient ? m_pageClient->platformWindow() : nullptr;
}
void WebPageProxy::rootViewToWindow(const WebCore::IntRect& viewRect, WebCore::IntRect& windowRect)
{
windowRect = pageClient().rootViewToWindow(viewRect);
}
void WebPageProxy::showValidationMessage(const IntRect& anchorClientRect, const String& message)
{
m_validationBubble = pageClient().createValidationBubble(message, { m_preferences->minimumFontSize() });
m_validationBubble->showRelativeTo(anchorClientRect);
}
NSView *WebPageProxy::inspectorAttachmentView()
{
return pageClient().inspectorAttachmentView();
}
_WKRemoteObjectRegistry *WebPageProxy::remoteObjectRegistry()
{
return pageClient().remoteObjectRegistry();
}
#if ENABLE(APPLE_PAY)
NSWindow *WebPageProxy::paymentCoordinatorPresentingWindow(const WebPaymentCoordinatorProxy&)
{
return platformWindow();
}
#endif
#if ENABLE(CONTEXT_MENUS)
NSMenu *WebPageProxy::platformActiveContextMenu() const
{
if (m_activeContextMenu)
return m_activeContextMenu->platformMenu();
return nil;
}
void WebPageProxy::platformDidSelectItemFromActiveContextMenu(const WebContextMenuItemData& item)
{
if (item.action() == ContextMenuItemTagPaste)
grantAccessToCurrentPasteboardData(NSPasteboardNameGeneral);
}
#endif
void WebPageProxy::willPerformPasteCommand(DOMPasteAccessCategory pasteAccessCategory)
{
switch (pasteAccessCategory) {
case DOMPasteAccessCategory::General:
grantAccessToCurrentPasteboardData(NSPasteboardNameGeneral);
return;
case DOMPasteAccessCategory::Fonts:
grantAccessToCurrentPasteboardData(NSPasteboardNameFont);
return;
}
}
PlatformView* WebPageProxy::platformView() const
{
return [pageClient().platformWindow() contentView];
}
bool WebPageProxy::useiTunesAVOutputContext() const
{
return m_preferences->store().getBoolValueForKey(WebPreferencesKey::useiTunesAVOutputContextKey());
}
#if ENABLE(UI_PROCESS_PDF_HUD)
void WebPageProxy::createPDFHUD(PDFPluginIdentifier identifier, const WebCore::IntRect& rect)
{
pageClient().createPDFHUD(identifier, rect);
}
void WebPageProxy::removePDFHUD(PDFPluginIdentifier identifier)
{
pageClient().removePDFHUD(identifier);
}
void WebPageProxy::updatePDFHUDLocation(PDFPluginIdentifier identifier, const WebCore::IntRect& rect)
{
pageClient().updatePDFHUDLocation(identifier, rect);
}
void WebPageProxy::pdfZoomIn(PDFPluginIdentifier identifier)
{
send(Messages::WebPage::ZoomPDFIn(identifier));
}
void WebPageProxy::pdfZoomOut(PDFPluginIdentifier identifier)
{
send(Messages::WebPage::ZoomPDFOut(identifier));
}
void WebPageProxy::pdfSaveToPDF(PDFPluginIdentifier identifier)
{
sendWithAsyncReply(Messages::WebPage::SavePDF(identifier), [this, protectedThis = Ref { *this }] (String&& suggestedFilename, URL&& originatingURL, const IPC::DataReference& dataReference) {
savePDFToFileInDownloadsFolder(WTFMove(suggestedFilename), WTFMove(originatingURL), dataReference);
});
}
void WebPageProxy::pdfOpenWithPreview(PDFPluginIdentifier identifier)
{
sendWithAsyncReply(Messages::WebPage::OpenPDFWithPreview(identifier), [this, protectedThis = Ref { *this }] (String&& suggestedFilename, FrameInfoData&& frameInfo, const IPC::DataReference& data, const String& pdfUUID) {
savePDFToTemporaryFolderAndOpenWithNativeApplication(WTFMove(suggestedFilename), WTFMove(frameInfo), data, pdfUUID);
});
}
#endif // ENABLE(UI_PROCESS_PDF_HUD)
#if PLATFORM(MAC)
void WebPageProxy::changeUniversalAccessZoomFocus(const WebCore::IntRect& viewRect, const WebCore::IntRect& selectionRect)
{
WebCore::changeUniversalAccessZoomFocus(viewRect, selectionRect);
}
#endif
Color WebPageProxy::platformUnderPageBackgroundColor() const
{
#if ENABLE(DARK_MODE_CSS)
return WebCore::roundAndClampToSRGBALossy(NSColor.controlBackgroundColor.CGColor);
#else
return WebCore::Color::white;
#endif
}
void WebPageProxy::beginPreviewPanelControl(QLPreviewPanel *panel)
{
#if ENABLE(IMAGE_ANALYSIS)
[m_quickLookPreviewController beginControl:panel];
#endif
}
void WebPageProxy::endPreviewPanelControl(QLPreviewPanel *panel)
{
#if ENABLE(IMAGE_ANALYSIS)
if (auto controller = std::exchange(m_quickLookPreviewController, nil))
[controller endControl:panel];
#endif
}
void WebPageProxy::closeSharedPreviewPanelIfNecessary()
{
#if ENABLE(IMAGE_ANALYSIS)
[m_quickLookPreviewController closePanelIfNecessary];
#endif
}
#if ENABLE(IMAGE_ANALYSIS)
void WebPageProxy::handleContextMenuQuickLookImage(QuickLookPreviewActivity activity)
{
ASSERT(m_activeContextMenuContextData.webHitTestResultData());
auto result = m_activeContextMenuContextData.webHitTestResultData().value();
if (!result.imageBitmap)
return;
showImageInQuickLookPreviewPanel(*result.imageBitmap, result.toolTipText, URL { URL { }, result.absoluteImageURL }, activity);
}
void WebPageProxy::showImageInQuickLookPreviewPanel(ShareableBitmap& imageBitmap, const String& tooltip, const URL& imageURL, QuickLookPreviewActivity activity)
{
if (!PAL::isQuickLookUIFrameworkAvailable() || !PAL::getQLPreviewPanelClass() || ![PAL::getQLItemClass() instancesRespondToSelector:@selector(initWithDataProvider:contentType:previewTitle:)])
return;
auto image = imageBitmap.makeCGImage();
if (!image)
return;
auto imageData = adoptCF(CFDataCreateMutable(kCFAllocatorDefault, 0));
auto destination = adoptCF(CGImageDestinationCreateWithData(imageData.get(), (__bridge CFStringRef)UTTypePNG.identifier, 1, nullptr));
if (!destination)
return;
CGImageDestinationAddImage(destination.get(), image.get(), nil);
if (!CGImageDestinationFinalize(destination.get()))
return;
m_quickLookPreviewController = adoptNS([[WKQuickLookPreviewController alloc] initWithPage:*this imageData:(__bridge NSData *)imageData.get() title:tooltip imageURL:imageURL activity:activity]);
// When presenting the shared QLPreviewPanel, QuickLook will search the responder chain for a suitable panel controller.
// Make sure that we (by default) start the search at the web view, which knows how to vend the Visual Search preview
// controller as a delegate and data source for the preview panel.
pageClient().makeFirstResponder();
auto previewPanel = [PAL::getQLPreviewPanelClass() sharedPreviewPanel];
[previewPanel makeKeyAndOrderFront:nil];
if (![m_quickLookPreviewController isControlling:previewPanel]) {
// The WebKit client may have overridden QLPreviewPanelController methods on the view without calling into the superclass.
// In this case, hand over control to the client and clear out our state eagerly, since we don't expect any further delegate
// calls once the preview panel is dismissed.
m_quickLookPreviewController.clear();
}
}
#endif // ENABLE(IMAGE_ANALYSIS)
} // namespace WebKit
#endif // PLATFORM(MAC)
#undef MESSAGE_CHECK_WITH_RETURN_VALUE
#undef MESSAGE_CHECK_URL
#undef MESSAGE_CHECK