blob: 5042d88c32efb0e318bb6e682c79a01cfb148870 [file] [log] [blame]
/*
* Copyright (C) 2010-2021 Apple Inc. All rights reserved.
* Copyright (C) 2012 Intel Corporation. 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.
*/
#include "config.h"
#include "WebPageProxy.h"
#include "APIArray.h"
#include "APIAttachment.h"
#include "APIContentWorld.h"
#include "APIContextMenuClient.h"
#include "APIDictionary.h"
#include "APIFindClient.h"
#include "APIFindMatchesClient.h"
#include "APIFormClient.h"
#include "APIFrameInfo.h"
#include "APIFullscreenClient.h"
#include "APIGeometry.h"
#include "APIHistoryClient.h"
#include "APIHitTestResult.h"
#include "APIIconLoadingClient.h"
#include "APILegacyContextHistoryClient.h"
#include "APILoaderClient.h"
#include "APINavigation.h"
#include "APINavigationAction.h"
#include "APINavigationClient.h"
#include "APINavigationResponse.h"
#include "APIOpenPanelParameters.h"
#include "APIPageConfiguration.h"
#include "APIPolicyClient.h"
#include "APIResourceLoadClient.h"
#include "APISecurityOrigin.h"
#include "APIUIClient.h"
#include "APIURLRequest.h"
#include "APIWebsitePolicies.h"
#include "AuthenticationChallengeProxy.h"
#include "AuthenticationDecisionListener.h"
#include "AuthenticationManager.h"
#include "AuthenticatorManager.h"
#include "DownloadManager.h"
#include "DownloadProxy.h"
#include "DrawingAreaMessages.h"
#include "DrawingAreaProxy.h"
#include "EventDispatcherMessages.h"
#include "FormDataReference.h"
#include "FrameInfoData.h"
#include "LegacyGlobalSettings.h"
#include "LoadParameters.h"
#include "LogInitialization.h"
#include "Logging.h"
#include "NativeWebGestureEvent.h"
#include "NativeWebKeyboardEvent.h"
#include "NativeWebMouseEvent.h"
#include "NativeWebWheelEvent.h"
#include "NavigationActionData.h"
#include "NetworkProcessMessages.h"
#include "NetworkProcessProxy.h"
#include "NotificationManagerMessageHandlerMessages.h"
#include "NotificationPermissionRequest.h"
#include "NotificationPermissionRequestManager.h"
#include "PageClient.h"
#include "PrintInfo.h"
#include "ProcessThrottler.h"
#include "ProvisionalPageProxy.h"
#include "SafeBrowsingWarning.h"
#include "SpeechRecognitionPermissionManager.h"
#include "SpeechRecognitionRemoteRealtimeMediaSource.h"
#include "SpeechRecognitionRemoteRealtimeMediaSourceManager.h"
#include "SyntheticEditingCommandType.h"
#include "TextChecker.h"
#include "TextCheckerState.h"
#include "TextRecognitionUpdateResult.h"
#include "URLSchemeTaskParameters.h"
#include "UndoOrRedo.h"
#include "UserMediaPermissionRequestProxy.h"
#include "UserMediaProcessManager.h"
#include "WKContextPrivate.h"
#include "WebAutomationSession.h"
#include "WebBackForwardCache.h"
#include "WebBackForwardList.h"
#include "WebBackForwardListCounts.h"
#include "WebBackForwardListItem.h"
#include "WebCertificateInfo.h"
#include "WebContextMenuItem.h"
#include "WebContextMenuProxy.h"
#include "WebCoreArgumentCoders.h"
#include "WebEditCommandProxy.h"
#include "WebEventConversion.h"
#include "WebFrame.h"
#include "WebFramePolicyListenerProxy.h"
#include "WebFullScreenManagerProxy.h"
#include "WebFullScreenManagerProxyMessages.h"
#include "WebImage.h"
#include "WebInspectorUIProxy.h"
#include "WebInspectorUtilities.h"
#include "WebKeyboardEvent.h"
#include "WebNavigationDataStore.h"
#include "WebNavigationState.h"
#include "WebNotificationManagerProxy.h"
#include "WebOpenPanelResultListenerProxy.h"
#include "WebPage.h"
#include "WebPageCreationParameters.h"
#include "WebPageDebuggable.h"
#include "WebPageGroup.h"
#include "WebPageGroupData.h"
#include "WebPageInspectorController.h"
#include "WebPageMessages.h"
#include "WebPageNetworkParameters.h"
#include "WebPageProxyMessages.h"
#include "WebPasteboardProxy.h"
#include "WebPaymentCoordinatorProxy.h"
#include "WebPopupItem.h"
#include "WebPopupMenuProxy.h"
#include "WebPreferences.h"
#include "WebPreferencesKeys.h"
#include "WebProcess.h"
#include "WebProcessMessages.h"
#include "WebProcessPool.h"
#include "WebProcessProxy.h"
#include "WebProtectionSpace.h"
#include "WebResourceLoadStatisticsStore.h"
#include "WebURLSchemeHandler.h"
#include "WebUserContentControllerProxy.h"
#include "WebViewDidMoveToWindowObserver.h"
#include "WebWheelEventCoalescer.h"
#include "WebsiteDataStore.h"
#include <WebCore/AppHighlight.h>
#include <WebCore/BitmapImage.h>
#include <WebCore/CompositionHighlight.h>
#include <WebCore/CrossSiteNavigationDataTransfer.h>
#include <WebCore/DOMPasteAccess.h>
#include <WebCore/DeprecatedGlobalSettings.h>
#include <WebCore/DiagnosticLoggingClient.h>
#include <WebCore/DiagnosticLoggingKeys.h>
#include <WebCore/DragController.h>
#include <WebCore/DragData.h>
#include <WebCore/ElementContext.h>
#include <WebCore/EventNames.h>
#include <WebCore/ExceptionDetails.h>
#include <WebCore/FloatRect.h>
#include <WebCore/FocusDirection.h>
#include <WebCore/FontAttributeChanges.h>
#include <WebCore/FrameLoader.h>
#include <WebCore/GlobalFrameIdentifier.h>
#include <WebCore/GlobalWindowIdentifier.h>
#include <WebCore/LengthBox.h>
#include <WebCore/MIMETypeRegistry.h>
#include <WebCore/MediaStreamRequest.h>
#include <WebCore/ModalContainerTypes.h>
#include <WebCore/PerformanceLoggingClient.h>
#include <WebCore/PermissionState.h>
#include <WebCore/PlatformEvent.h>
#include <WebCore/PrivateClickMeasurement.h>
#include <WebCore/PublicSuffix.h>
#include <WebCore/RealtimeMediaSourceCenter.h>
#include <WebCore/RenderEmbeddedObject.h>
#include <WebCore/ResourceLoadStatistics.h>
#include <WebCore/RuntimeApplicationChecks.h>
#include <WebCore/RuntimeEnabledFeatures.h>
#include <WebCore/SSLKeyGenerator.h>
#include <WebCore/SerializedCryptoKeyWrap.h>
#include <WebCore/ShareData.h>
#include <WebCore/SharedBuffer.h>
#include <WebCore/ShouldTreatAsContinuingLoad.h>
#include <WebCore/StoredCredentialsPolicy.h>
#include <WebCore/TextCheckerClient.h>
#include <WebCore/TextIndicator.h>
#include <WebCore/ValidationBubble.h>
#include <WebCore/WindowFeatures.h>
#include <WebCore/WritingDirection.h>
#include <stdio.h>
#include <wtf/CallbackAggregator.h>
#include <wtf/NeverDestroyed.h>
#include <wtf/Scope.h>
#include <wtf/SystemTracing.h>
#include <wtf/URL.h>
#include <wtf/URLParser.h>
#include <wtf/WeakPtr.h>
#include <wtf/text/StringView.h>
#include <wtf/text/TextStream.h>
#if ENABLE(APPLICATION_MANIFEST)
#include "APIApplicationManifest.h"
#endif
#if ENABLE(ASYNC_SCROLLING) && PLATFORM(COCOA)
#include "RemoteScrollingCoordinatorProxy.h"
#endif
#ifndef NDEBUG
#include <wtf/RefCountedLeakCounter.h>
#endif
#if PLATFORM(COCOA)
#include "InsertTextOptions.h"
#include "QuickLookPreviewActivity.h"
#include "RemoteLayerTreeDrawingAreaProxy.h"
#include "RemoteLayerTreeScrollingPerformanceData.h"
#include "UserMediaCaptureManagerProxy.h"
#include "VideoFullscreenManagerProxy.h"
#include "VideoFullscreenManagerProxyMessages.h"
#include <WebCore/AttributedString.h>
#include <WebCore/CoreAudioCaptureDeviceManager.h>
#include <WebCore/RunLoopObserver.h>
#include <WebCore/SystemBattery.h>
#include <wtf/MachSendRight.h>
#include <wtf/cocoa/Entitlements.h>
#include <wtf/cocoa/RuntimeApplicationChecksCocoa.h>
#endif
#if PLATFORM(MAC)
#include "DisplayLink.h"
#include <WebCore/ImageUtilities.h>
#include <WebCore/UTIUtilities.h>
#endif
#if HAVE(TOUCH_BAR)
#include "TouchBarMenuData.h"
#include "TouchBarMenuItemData.h"
#endif
#if PLATFORM(COCOA) || PLATFORM(GTK)
#include "ViewSnapshotStore.h"
#endif
#if PLATFORM(GTK)
#include "GtkSettingsManager.h"
#include <WebCore/SelectionData.h>
#endif
#if USE(CAIRO)
#include <WebCore/CairoUtilities.h>
#endif
#if ENABLE(WIRELESS_PLAYBACK_TARGET) && !PLATFORM(IOS_FAMILY)
#include <WebCore/MediaPlaybackTarget.h>
#include <WebCore/WebMediaSessionManager.h>
#endif
#if PLATFORM(IOS_FAMILY) || (PLATFORM(MAC) && ENABLE(VIDEO_PRESENTATION_MODE))
#include "PlaybackSessionManagerProxy.h"
#endif
#if ENABLE(WEB_AUTHN)
#include "WebAuthenticatorCoordinatorProxy.h"
#endif
#if ENABLE(REMOTE_INSPECTOR)
#include <JavaScriptCore/RemoteInspector.h>
#endif
#if HAVE(SEC_KEY_PROXY)
#include "SecKeyProxyStore.h"
#endif
#if HAVE(APP_SSO)
#include "SOAuthorizationCoordinator.h"
#endif
#if PLATFORM(IOS_FAMILY) && ENABLE(DEVICE_ORIENTATION)
#include "WebDeviceOrientationUpdateProviderProxy.h"
#endif
#if ENABLE(DATA_DETECTION)
#include "DataDetectionResult.h"
#endif
#if ENABLE(MEDIA_USAGE)
#include "MediaUsageManager.h"
#endif
#if PLATFORM(COCOA)
#include "DefaultWebBrowserChecks.h"
#endif
#if ENABLE(DATE_AND_TIME_INPUT_TYPES)
#include "WebDateTimePicker.h"
#endif
#if ENABLE(MEDIA_SESSION_COORDINATOR)
#include "MediaSessionCoordinatorProxyPrivate.h"
#include "RemoteMediaSessionCoordinatorProxy.h"
#endif
#if HAVE(GROUP_ACTIVITIES)
#include "GroupActivitiesSessionNotifier.h"
#endif
#if ENABLE(APP_HIGHLIGHTS)
#include <WebCore/HighlightVisibility.h>
#endif
#if HAVE(SCREEN_CAPTURE_KIT)
#import "DisplayCaptureSessionManager.h"
#endif
#define MESSAGE_CHECK(process, assertion) MESSAGE_CHECK_BASE(assertion, process->connection())
#define MESSAGE_CHECK_URL(process, url) MESSAGE_CHECK_BASE(checkURLReceivedFromCurrentOrPreviousWebProcess(process, url), process->connection())
#define MESSAGE_CHECK_COMPLETION(process, assertion, completion) MESSAGE_CHECK_COMPLETION_BASE(assertion, process->connection(), completion)
#define WEBPAGEPROXY_RELEASE_LOG(channel, fmt, ...) RELEASE_LOG(channel, "%p - [pageProxyID=%" PRIu64 ", webPageID=%" PRIu64 ", PID=%i] WebPageProxy::" fmt, this, m_identifier.toUInt64(), m_webPageID.toUInt64(), m_process->processIdentifier(), ##__VA_ARGS__)
#define WEBPAGEPROXY_RELEASE_LOG_ERROR(channel, fmt, ...) RELEASE_LOG_ERROR(channel, "%p - [pageProxyID=%" PRIu64 ", webPageID=%" PRIu64 ", PID=%i] WebPageProxy::" fmt, this, m_identifier.toUInt64(), m_webPageID.toUInt64(), m_process->processIdentifier(), ##__VA_ARGS__)
// Represents the number of wheel events we can hold in the queue before we start pushing them preemptively.
static const unsigned wheelEventQueueSizeThreshold = 10;
static const Seconds resetRecentCrashCountDelay = 30_s;
static unsigned maximumWebProcessRelaunchAttempts = 1;
static const Seconds audibleActivityClearDelay = 10_s;
static const Seconds tryCloseTimeoutDelay = 50_ms;
namespace WebKit {
using namespace WebCore;
DEFINE_DEBUG_ONLY_GLOBAL(WTF::RefCountedLeakCounter, webPageProxyCounter, ("WebPageProxy"));
class StorageRequests {
WTF_MAKE_NONCOPYABLE(StorageRequests); WTF_MAKE_FAST_ALLOCATED;
friend NeverDestroyed<StorageRequests>;
public:
static StorageRequests& singleton();
void processOrAppend(CompletionHandler<void()>&& completionHandler)
{
if (m_requestsAreBeingProcessed) {
m_requests.append(WTFMove(completionHandler));
return;
}
m_requestsAreBeingProcessed = true;
completionHandler();
}
void processNextIfAny()
{
if (m_requests.isEmpty()) {
m_requestsAreBeingProcessed = false;
return;
}
m_requests.takeFirst()();
}
private:
StorageRequests() { }
~StorageRequests() { }
Deque<CompletionHandler<void()>> m_requests;
bool m_requestsAreBeingProcessed { false };
};
StorageRequests& StorageRequests::singleton()
{
static NeverDestroyed<StorageRequests> requests;
return requests;
}
#if !LOG_DISABLED
static const char* webMouseEventTypeString(WebEvent::Type type)
{
switch (type) {
case WebEvent::MouseDown:
return "MouseDown";
case WebEvent::MouseUp:
return "MouseUp";
case WebEvent::MouseMove:
return "MouseMove";
case WebEvent::MouseForceChanged:
return "MouseForceChanged";
case WebEvent::MouseForceDown:
return "MouseForceDown";
case WebEvent::MouseForceUp:
return "MouseForceUp";
default:
ASSERT_NOT_REACHED();
return "<unknown>";
}
}
static const char* webKeyboardEventTypeString(WebEvent::Type type)
{
switch (type) {
case WebEvent::KeyDown:
return "KeyDown";
case WebEvent::KeyUp:
return "KeyUp";
case WebEvent::RawKeyDown:
return "RawKeyDown";
case WebEvent::Char:
return "Char";
default:
ASSERT_NOT_REACHED();
return "<unknown>";
}
}
#endif // !LOG_DISABLED
class PageClientProtector {
WTF_MAKE_NONCOPYABLE(PageClientProtector);
public:
PageClientProtector(PageClient& pageClient)
: m_pageClient(pageClient)
{
m_pageClient->refView();
}
~PageClientProtector()
{
ASSERT(m_pageClient);
m_pageClient->derefView();
}
private:
WeakPtr<PageClient> m_pageClient;
};
void WebPageProxy::forMostVisibleWebPageIfAny(PAL::SessionID sessionID, const SecurityOriginData& origin, CompletionHandler<void(WebPageProxy*)>&& completionHandler)
{
// FIXME: If not finding right away a visible page, we might want to try again for a given period of time when there is a change of visibility.
WebPageProxy* selectedPage = nullptr;
WebProcessProxy::forWebPagesWithOrigin(sessionID, origin, [&](auto& page) {
if (!page.mainFrame())
return;
if (page.isViewVisible() && (!selectedPage || !selectedPage->isViewVisible())) {
selectedPage = &page;
return;
}
if (page.isViewFocused() && (!selectedPage || !selectedPage->isViewFocused())) {
selectedPage = &page;
return;
}
});
completionHandler(selectedPage);
}
Ref<WebPageProxy> WebPageProxy::create(PageClient& pageClient, WebProcessProxy& process, Ref<API::PageConfiguration>&& configuration)
{
return adoptRef(*new WebPageProxy(pageClient, process, WTFMove(configuration)));
}
WebPageProxy::WebPageProxy(PageClient& pageClient, WebProcessProxy& process, Ref<API::PageConfiguration>&& configuration)
: m_identifier(Identifier::generate())
, m_webPageID(PageIdentifier::generate())
, m_pageClient(pageClient)
, m_configuration(WTFMove(configuration))
, m_navigationClient(makeUniqueRef<API::NavigationClient>())
, m_historyClient(makeUniqueRef<API::HistoryClient>())
, m_iconLoadingClient(makeUnique<API::IconLoadingClient>())
, m_formClient(makeUnique<API::FormClient>())
, m_uiClient(makeUnique<API::UIClient>())
, m_findClient(makeUnique<API::FindClient>())
, m_findMatchesClient(makeUnique<API::FindMatchesClient>())
#if ENABLE(CONTEXT_MENUS)
, m_contextMenuClient(makeUnique<API::ContextMenuClient>())
#endif
, m_navigationState(makeUnique<WebNavigationState>())
, m_process(process)
, m_pageGroup(*m_configuration->pageGroup())
, m_preferences(*m_configuration->preferences())
, m_userContentController(*m_configuration->userContentController())
, m_visitedLinkStore(*m_configuration->visitedLinkStore())
, m_websiteDataStore(*m_configuration->websiteDataStore())
, m_userAgent(standardUserAgent())
, m_overrideContentSecurityPolicy { m_configuration->overrideContentSecurityPolicy() }
#if ENABLE(FULLSCREEN_API)
, m_fullscreenClient(makeUnique<API::FullscreenClient>())
#endif
, m_geolocationPermissionRequestManager(*this)
#if PLATFORM(IOS_FAMILY)
, m_audibleActivityTimer(RunLoop::main(), this, &WebPageProxy::clearAudibleActivity)
#endif
, m_initialCapitalizationEnabled(m_configuration->initialCapitalizationEnabled())
, m_cpuLimit(m_configuration->cpuLimit())
, m_backForwardList(WebBackForwardList::create(*this))
, m_waitsForPaintAfterViewDidMoveToWindow(m_configuration->waitsForPaintAfterViewDidMoveToWindow())
, m_hasRunningProcess(process.state() != WebProcessProxy::State::Terminated)
#if HAVE(CVDISPLAYLINK)
, m_wheelEventActivityHysteresis([this](PAL::HysteresisState state) { wheelEventHysteresisUpdated(state); })
#endif
, m_controlledByAutomation(m_configuration->isControlledByAutomation())
#if PLATFORM(COCOA)
, m_isSmartInsertDeleteEnabled(TextChecker::isSmartInsertDeleteEnabled())
#endif
, m_pageLoadState(*this)
, m_updateReportedMediaCaptureStateTimer(RunLoop::main(), this, &WebPageProxy::updateReportedMediaCaptureState)
, m_inspectorController(makeUnique<WebPageInspectorController>(*this))
#if ENABLE(REMOTE_INSPECTOR)
, m_inspectorDebuggable(makeUnique<WebPageDebuggable>(*this))
#endif
, m_resetRecentCrashCountTimer(RunLoop::main(), this, &WebPageProxy::resetRecentCrashCount)
, m_tryCloseTimeoutTimer(RunLoop::main(), this, &WebPageProxy::tryCloseTimedOut)
, m_corsDisablingPatterns(m_configuration->corsDisablingPatterns())
#if ENABLE(APP_BOUND_DOMAINS)
, m_ignoresAppBoundDomains(m_configuration->ignoresAppBoundDomains())
, m_limitsNavigationsToAppBoundDomains(m_configuration->limitsNavigationsToAppBoundDomains())
#endif
, m_notificationManagerMessageHandler(*this)
{
WEBPAGEPROXY_RELEASE_LOG(Loading, "constructor:");
if (!m_configuration->drawsBackground())
m_backgroundColor = Color(Color::transparentBlack);
updateActivityState();
updateThrottleState();
updateHiddenPageThrottlingAutoIncreases();
#if HAVE(OUT_OF_PROCESS_LAYER_HOSTING)
m_layerHostingMode = m_activityState & ActivityState::IsInWindow ? WebPageProxy::pageClient().viewLayerHostingMode() : LayerHostingMode::OutOfProcess;
#endif
platformInitialize();
#ifndef NDEBUG
webPageProxyCounter.increment();
#endif
WebProcessPool::statistics().wkPageCount++;
m_preferences->addPage(*this);
m_pageGroup->addPage(*this);
m_inspector = WebInspectorUIProxy::create(*this);
if (hasRunningProcess())
didAttachToRunningProcess();
addAllMessageReceivers();
#if PLATFORM(IOS_FAMILY)
DeprecatedGlobalSettings::setDisableScreenSizeOverride(m_preferences->disableScreenSizeOverride());
if (m_configuration->preferences()->serviceWorkerEntitlementDisabledForTesting())
disableServiceWorkerEntitlementInNetworkProcess();
#endif
#if PLATFORM(COCOA)
m_activityStateChangeDispatcher = makeUnique<RunLoopObserver>(static_cast<CFIndex>(RunLoopObserver::WellKnownRunLoopOrders::ActivityStateChange), [this] {
this->dispatchActivityStateChange();
});
#endif
#if ENABLE(REMOTE_INSPECTOR)
m_inspectorDebuggable->setRemoteDebuggingAllowed(true);
m_inspectorDebuggable->init();
#endif
m_inspectorController->init();
#if ENABLE(IPC_TESTING_API)
if (m_preferences->ipcTestingAPIEnabled())
process.setIgnoreInvalidMessageForTesting();
#endif
#if ENABLE(MEDIA_SESSION_COORDINATOR) && HAVE(GROUP_ACTIVITIES)
if (m_preferences->mediaSessionCoordinatorEnabled())
GroupActivitiesSessionNotifier::sharedNotifier().addWebPage(*this);
#endif
}
WebPageProxy::~WebPageProxy()
{
WEBPAGEPROXY_RELEASE_LOG(Loading, "destructor:");
ASSERT(m_process->webPage(m_identifier) != this);
#if ASSERT_ENABLED
for (WebPageProxy* page : m_process->pages())
ASSERT(page != this);
#endif
setPageLoadStateObserver(nullptr);
if (!m_isClosed)
close();
WebProcessPool::statistics().wkPageCount--;
if (m_spellDocumentTag)
TextChecker::closeSpellDocumentWithTag(m_spellDocumentTag.value());
m_preferences->removePage(*this);
m_pageGroup->removePage(*this);
#ifndef NDEBUG
webPageProxyCounter.decrement();
#endif
#if PLATFORM(MACCATALYST)
EndowmentStateTracker::singleton().removeClient(*this);
#endif
for (auto& callback : m_nextActivityStateChangeCallbacks)
callback();
if (auto* networkProcess = websiteDataStore().networkProcessIfExists())
networkProcess->send(Messages::NetworkProcess::RemoveWebPageNetworkParameters(sessionID(), m_identifier), 0);
#if ENABLE(MEDIA_SESSION_COORDINATOR) && HAVE(GROUP_ACTIVITIES)
if (m_preferences->mediaSessionCoordinatorEnabled())
GroupActivitiesSessionNotifier::sharedNotifier().removeWebPage(*this);
#endif
}
void WebPageProxy::addAllMessageReceivers()
{
m_process->addMessageReceiver(Messages::WebPageProxy::messageReceiverName(), m_webPageID, *this);
m_process->addMessageReceiver(Messages::NotificationManagerMessageHandler::messageReceiverName(), m_webPageID, m_notificationManagerMessageHandler);
}
void WebPageProxy::removeAllMessageReceivers()
{
m_process->removeMessageReceiver(Messages::WebPageProxy::messageReceiverName(), m_webPageID);
m_process->removeMessageReceiver(Messages::NotificationManagerMessageHandler::messageReceiverName(), m_webPageID);
}
// FIXME: Should return a const PageClient& and add a separate non-const
// version of this function, but several PageClient methods will need to become
// const for this to be possible.
PageClient& WebPageProxy::pageClient() const
{
ASSERT(m_pageClient);
return *m_pageClient;
}
PAL::SessionID WebPageProxy::sessionID() const
{
return m_websiteDataStore->sessionID();
}
DrawingAreaProxy* WebPageProxy::provisionalDrawingArea() const
{
if (m_provisionalPage && m_provisionalPage->drawingArea())
return m_provisionalPage->drawingArea();
return drawingArea();
}
const API::PageConfiguration& WebPageProxy::configuration() const
{
return m_configuration.get();
}
ProcessID WebPageProxy::processIdentifier() const
{
if (m_isClosed)
return 0;
return m_process->processIdentifier();
}
bool WebPageProxy::hasRunningProcess() const
{
// A page that has been explicitly closed is never valid.
if (m_isClosed)
return false;
return m_hasRunningProcess;
}
void WebPageProxy::notifyProcessPoolToPrewarm()
{
m_process->processPool().didReachGoodTimeToPrewarm();
}
void WebPageProxy::setPreferences(WebPreferences& preferences)
{
if (&preferences == m_preferences.ptr())
return;
m_preferences->removePage(*this);
m_preferences = preferences;
m_preferences->addPage(*this);
preferencesDidChange();
}
void WebPageProxy::setHistoryClient(UniqueRef<API::HistoryClient>&& historyClient)
{
m_historyClient = WTFMove(historyClient);
}
void WebPageProxy::setNavigationClient(UniqueRef<API::NavigationClient>&& navigationClient)
{
m_navigationClient = WTFMove(navigationClient);
}
void WebPageProxy::setLoaderClient(std::unique_ptr<API::LoaderClient>&& loaderClient)
{
m_loaderClient = WTFMove(loaderClient);
}
void WebPageProxy::setPolicyClient(std::unique_ptr<API::PolicyClient>&& policyClient)
{
m_policyClient = WTFMove(policyClient);
}
void WebPageProxy::setFormClient(std::unique_ptr<API::FormClient>&& formClient)
{
if (!formClient) {
m_formClient = makeUnique<API::FormClient>();
return;
}
m_formClient = WTFMove(formClient);
}
void WebPageProxy::setUIClient(std::unique_ptr<API::UIClient>&& uiClient)
{
if (!uiClient) {
m_uiClient = makeUnique<API::UIClient>();
return;
}
m_uiClient = WTFMove(uiClient);
if (hasRunningProcess())
send(Messages::WebPage::SetCanRunBeforeUnloadConfirmPanel(m_uiClient->canRunBeforeUnloadConfirmPanel()));
setCanRunModal(m_uiClient->canRunModal());
setNeedsFontAttributes(m_uiClient->needsFontAttributes());
}
void WebPageProxy::setIconLoadingClient(std::unique_ptr<API::IconLoadingClient>&& iconLoadingClient)
{
bool hasClient = iconLoadingClient.get();
if (!iconLoadingClient)
m_iconLoadingClient = makeUnique<API::IconLoadingClient>();
else
m_iconLoadingClient = WTFMove(iconLoadingClient);
if (!hasRunningProcess())
return;
send(Messages::WebPage::SetUseIconLoadingClient(hasClient));
}
void WebPageProxy::setPageLoadStateObserver(std::unique_ptr<PageLoadState::Observer>&& observer)
{
if (m_pageLoadStateObserver)
pageLoadState().removeObserver(*m_pageLoadStateObserver);
m_pageLoadStateObserver = WTFMove(observer);
if (m_pageLoadStateObserver)
pageLoadState().addObserver(*m_pageLoadStateObserver);
}
void WebPageProxy::setFindClient(std::unique_ptr<API::FindClient>&& findClient)
{
if (!findClient) {
m_findClient = makeUnique<API::FindClient>();
return;
}
m_findClient = WTFMove(findClient);
}
void WebPageProxy::setFindMatchesClient(std::unique_ptr<API::FindMatchesClient>&& findMatchesClient)
{
if (!findMatchesClient) {
m_findMatchesClient = makeUnique<API::FindMatchesClient>();
return;
}
m_findMatchesClient = WTFMove(findMatchesClient);
}
void WebPageProxy::setDiagnosticLoggingClient(std::unique_ptr<API::DiagnosticLoggingClient>&& diagnosticLoggingClient)
{
m_diagnosticLoggingClient = WTFMove(diagnosticLoggingClient);
}
#if ENABLE(CONTEXT_MENUS)
void WebPageProxy::setContextMenuClient(std::unique_ptr<API::ContextMenuClient>&& contextMenuClient)
{
if (!contextMenuClient) {
m_contextMenuClient = makeUnique<API::ContextMenuClient>();
return;
}
m_contextMenuClient = WTFMove(contextMenuClient);
}
#endif
void WebPageProxy::setInjectedBundleClient(const WKPageInjectedBundleClientBase* client)
{
if (!client) {
m_injectedBundleClient = nullptr;
return;
}
m_injectedBundleClient = makeUnique<WebPageInjectedBundleClient>();
m_injectedBundleClient->initialize(client);
}
void WebPageProxy::setResourceLoadClient(std::unique_ptr<API::ResourceLoadClient>&& client)
{
bool hadResourceLoadClient = !!m_resourceLoadClient;
m_resourceLoadClient = WTFMove(client);
bool hasResourceLoadClient = !!m_resourceLoadClient;
if (hadResourceLoadClient != hasResourceLoadClient)
send(Messages::WebPage::SetHasResourceLoadClient(hasResourceLoadClient));
}
void WebPageProxy::handleMessage(IPC::Connection& connection, const String& messageName, const WebKit::UserData& messageBody)
{
ASSERT(m_process->connection() == &connection);
if (!m_injectedBundleClient)
return;
m_injectedBundleClient->didReceiveMessageFromInjectedBundle(this, messageName, m_process->transformHandlesToObjects(messageBody.object()).get());
}
void WebPageProxy::handleSynchronousMessage(IPC::Connection& connection, const String& messageName, const UserData& messageBody, CompletionHandler<void(UserData&&)>&& completionHandler)
{
ASSERT(m_process->connection() == &connection);
if (!m_injectedBundleClient)
return completionHandler({ });
RefPtr<API::Object> returnData;
m_injectedBundleClient->didReceiveSynchronousMessageFromInjectedBundle(this, messageName, m_process->transformHandlesToObjects(messageBody.object()).get(), [completionHandler = WTFMove(completionHandler), process = m_process] (RefPtr<API::Object>&& returnData) mutable {
completionHandler(UserData(process->transformObjectsToHandles(returnData.get())));
});
}
void WebPageProxy::launchProcess(const RegistrableDomain& registrableDomain, ProcessLaunchReason reason)
{
ASSERT(!m_isClosed);
ASSERT(!hasRunningProcess());
WEBPAGEPROXY_RELEASE_LOG(Loading, "launchProcess:");
// In case we are currently connected to the dummy process, we need to make sure the inspector proxy
// disconnects from the dummy process first.
m_inspector->reset();
m_process->removeWebPage(*this, WebProcessProxy::EndsUsingDataStore::Yes);
removeAllMessageReceivers();
auto& processPool = m_process->processPool();
auto* relatedPage = m_configuration->relatedPage();
if (relatedPage && !relatedPage->isClosed())
m_process = relatedPage->ensureRunningProcess();
else
m_process = processPool.processForRegistrableDomain(m_websiteDataStore.get(), registrableDomain, shouldEnableCaptivePortalMode() ? WebProcessProxy::CaptivePortalMode::Enabled : WebProcessProxy::CaptivePortalMode::Disabled);
m_hasRunningProcess = true;
m_isCaptivePortalModeExplicitlySet = m_configuration->isCaptivePortalModeExplicitlySet();
m_process->addExistingWebPage(*this, WebProcessProxy::BeginsUsingDataStore::Yes);
addAllMessageReceivers();
#if ENABLE(IPC_TESTING_API)
if (m_preferences->store().getBoolValueForKey(WebPreferencesKey::ipcTestingAPIEnabledKey()))
m_process->setIgnoreInvalidMessageForTesting();
#endif
finishAttachingToWebProcess(reason);
auto pendingInjectedBundleMessage = WTFMove(m_pendingInjectedBundleMessages);
for (auto& message : pendingInjectedBundleMessage)
send(Messages::WebPage::PostInjectedBundleMessage(message.messageName, UserData(process().transformObjectsToHandles(message.messageBody.get()).get())));
}
bool WebPageProxy::suspendCurrentPageIfPossible(API::Navigation& navigation, std::optional<FrameIdentifier> mainFrameID, ProcessSwapRequestedByClient processSwapRequestedByClient, ShouldDelayClosingUntilFirstLayerFlush shouldDelayClosingUntilFirstLayerFlush)
{
m_suspendedPageKeptToPreventFlashing = nullptr;
m_lastSuspendedPage = nullptr;
if (!mainFrameID)
return false;
if (!hasCommittedAnyProvisionalLoads()) {
WEBPAGEPROXY_RELEASE_LOG(ProcessSwapping, "suspendCurrentPageIfPossible: Not suspending current page for process pid %i because has not committed any load yet", m_process->processIdentifier());
return false;
}
if (isPageOpenedByDOMShowingInitialEmptyDocument()) {
WEBPAGEPROXY_RELEASE_LOG(ProcessSwapping, "suspendCurrentPageIfPossible: Not suspending current page for process pid %i because it is showing the initial empty document", m_process->processIdentifier());
return false;
}
auto* fromItem = navigation.fromItem();
// If the source and the destination back / forward list items are the same, then this is a client-side redirect. In this case,
// there is no need to suspend the previous page as there will be no way to get back to it.
if (fromItem && fromItem == m_backForwardList->currentItem()) {
WEBPAGEPROXY_RELEASE_LOG(ProcessSwapping, "suspendCurrentPageIfPossible: Not suspending current page for process pid %i because this is a client-side redirect", m_process->processIdentifier());
return false;
}
if (fromItem && fromItem->url() != pageLoadState().url()) {
WEBPAGEPROXY_RELEASE_LOG_ERROR(ProcessSwapping, "suspendCurrentPageIfPossible: Not suspending current page for process pid %i because fromItem's URL does not match the page URL.", m_process->processIdentifier());
return false;
}
bool needsSuspendedPageToPreventFlashing = shouldDelayClosingUntilFirstLayerFlush == ShouldDelayClosingUntilFirstLayerFlush::Yes;
if (!needsSuspendedPageToPreventFlashing && (!fromItem || !shouldUseBackForwardCache())) {
if (!fromItem)
WEBPAGEPROXY_RELEASE_LOG(ProcessSwapping, "suspendCurrentPageIfPossible: Not suspending current page for process pid %i there is no associated WebBackForwardListItem", m_process->processIdentifier());
else
WEBPAGEPROXY_RELEASE_LOG(ProcessSwapping, "suspendCurrentPageIfPossible: Not suspending current page for process pid %i the back / forward cache is disabled", m_process->processIdentifier());
return false;
}
WEBPAGEPROXY_RELEASE_LOG(ProcessSwapping, "suspendCurrentPageIfPossible: Suspending current page for process pid %i", m_process->processIdentifier());
auto suspendedPage = makeUnique<SuspendedPageProxy>(*this, m_process.copyRef(), *mainFrameID, shouldDelayClosingUntilFirstLayerFlush);
LOG(ProcessSwapping, "WebPageProxy %" PRIu64 " created suspended page %s for process pid %i, back/forward item %s" PRIu64, identifier().toUInt64(), suspendedPage->loggingString(), m_process->processIdentifier(), fromItem ? fromItem->itemID().logString() : 0);
m_lastSuspendedPage = *suspendedPage;
if (fromItem && shouldUseBackForwardCache())
backForwardCache().addEntry(*fromItem, WTFMove(suspendedPage));
else {
ASSERT(needsSuspendedPageToPreventFlashing);
m_suspendedPageKeptToPreventFlashing = WTFMove(suspendedPage);
}
return true;
}
WebBackForwardCache& WebPageProxy::backForwardCache() const
{
return process().processPool().backForwardCache();
}
bool WebPageProxy::shouldUseBackForwardCache() const
{
return m_preferences->usesBackForwardCache() && backForwardCache().capacity() > 0;
}
void WebPageProxy::swapToProvisionalPage(std::unique_ptr<ProvisionalPageProxy> provisionalPage)
{
ASSERT(!m_isClosed);
WEBPAGEPROXY_RELEASE_LOG(Loading, "swapToProvisionalPage: newWebPageID=%" PRIu64, provisionalPage->webPageID().toUInt64());
m_process = provisionalPage->process();
m_webPageID = provisionalPage->webPageID();
pageClient().didChangeWebPageID();
m_websiteDataStore = m_process->websiteDataStore();
#if HAVE(VISIBILITY_PROPAGATION_VIEW)
m_contextIDForVisibilityPropagationInWebProcess = provisionalPage->contextIDForVisibilityPropagationInWebProcess();
#if ENABLE(GPU_PROCESS)
m_contextIDForVisibilityPropagationInGPUProcess = provisionalPage->contextIDForVisibilityPropagationInGPUProcess();
#endif
#endif
// FIXME: Do we really need to disable this logging in ephemeral sessions?
if (m_logger)
m_logger->setEnabled(this, !sessionID().isEphemeral());
m_hasRunningProcess = true;
ASSERT(!m_drawingArea);
setDrawingArea(provisionalPage->takeDrawingArea());
ASSERT(!m_mainFrame);
m_mainFrame = provisionalPage->mainFrame();
m_process->addExistingWebPage(*this, WebProcessProxy::BeginsUsingDataStore::No);
addAllMessageReceivers();
finishAttachingToWebProcess(ProcessLaunchReason::ProcessSwap);
#if PLATFORM(COCOA)
auto accessibilityToken = provisionalPage->takeAccessibilityToken();
if (!accessibilityToken.isEmpty())
registerWebProcessAccessibilityToken({ accessibilityToken.data(), accessibilityToken.size() });
#endif
#if PLATFORM(GTK) || PLATFORM(WPE)
auto accessibilityPlugID = provisionalPage->accessibilityPlugID();
if (!accessibilityPlugID.isEmpty())
bindAccessibilityTree(accessibilityPlugID);
#endif
}
void WebPageProxy::finishAttachingToWebProcess(ProcessLaunchReason reason)
{
ASSERT(m_process->state() != AuxiliaryProcessProxy::State::Terminated);
updateActivityState();
updateThrottleState();
didAttachToRunningProcess();
// In the process-swap case, the ProvisionalPageProxy already took care of initializing the WebPage in the WebProcess.
if (reason != ProcessLaunchReason::ProcessSwap)
initializeWebPage();
m_inspector->updateForNewPageProcess(*this);
#if ENABLE(REMOTE_INSPECTOR)
remoteInspectorInformationDidChange();
#endif
updateWheelEventActivityAfterProcessSwap();
pageClient().didRelaunchProcess();
m_pageLoadState.didSwapWebProcesses();
if (reason != ProcessLaunchReason::InitialProcess)
m_drawingArea->waitForBackingStoreUpdateOnNextPaint();
}
void WebPageProxy::didAttachToRunningProcess()
{
ASSERT(hasRunningProcess());
#if ENABLE(FULLSCREEN_API)
ASSERT(!m_fullScreenManager);
m_fullScreenManager = makeUnique<WebFullScreenManagerProxy>(*this, pageClient().fullScreenManagerProxyClient());
#endif
#if ENABLE(VIDEO_PRESENTATION_MODE)
ASSERT(!m_playbackSessionManager);
m_playbackSessionManager = PlaybackSessionManagerProxy::create(*this);
ASSERT(!m_videoFullscreenManager);
m_videoFullscreenManager = VideoFullscreenManagerProxy::create(*this, *m_playbackSessionManager);
if (m_videoFullscreenManager)
m_videoFullscreenManager->setMockVideoPresentationModeEnabled(m_mockVideoPresentationModeEnabled);
#endif
#if ENABLE(APPLE_PAY)
ASSERT(!m_paymentCoordinator);
m_paymentCoordinator = makeUnique<WebPaymentCoordinatorProxy>(*this);
#endif
#if USE(SYSTEM_PREVIEW)
ASSERT(!m_systemPreviewController);
m_systemPreviewController = makeUnique<SystemPreviewController>(*this);
#endif
#if ENABLE(ARKIT_INLINE_PREVIEW)
if (m_preferences->modelElementEnabled()) {
ASSERT(!m_modelElementController);
m_modelElementController = makeUnique<ModelElementController>(*this);
}
#endif
#if ENABLE(WEB_AUTHN)
ASSERT(!m_credentialsMessenger);
m_credentialsMessenger = makeUnique<WebAuthenticatorCoordinatorProxy>(*this);
#endif
#if PLATFORM(IOS_FAMILY) && ENABLE(DEVICE_ORIENTATION)
ASSERT(!m_webDeviceOrientationUpdateProviderProxy);
m_webDeviceOrientationUpdateProviderProxy = makeUnique<WebDeviceOrientationUpdateProviderProxy>(*this);
#endif
#if ENABLE(WEBXR) && !USE(OPENXR)
ASSERT(!m_xrSystem);
m_xrSystem = makeUnique<PlatformXRSystem>(*this);
#endif
}
RefPtr<API::Navigation> WebPageProxy::launchProcessForReload()
{
WEBPAGEPROXY_RELEASE_LOG(Loading, "launchProcessForReload:");
if (m_isClosed) {
WEBPAGEPROXY_RELEASE_LOG(Loading, "launchProcessForReload: page is closed");
return nullptr;
}
ASSERT(!hasRunningProcess());
auto registrableDomain = m_backForwardList->currentItem() ? RegistrableDomain { URL(URL(), m_backForwardList->currentItem()->url()) } : RegistrableDomain { };
launchProcess(registrableDomain, ProcessLaunchReason::Crash);
if (!m_backForwardList->currentItem()) {
WEBPAGEPROXY_RELEASE_LOG(Loading, "launchProcessForReload: no current item to reload");
return nullptr;
}
auto navigation = m_navigationState->createReloadNavigation(m_backForwardList->currentItem());
String url = currentURL();
if (!url.isEmpty()) {
auto transaction = m_pageLoadState.transaction();
m_pageLoadState.setPendingAPIRequest(transaction, { navigation->navigationID(), url });
}
// We allow stale content when reloading a WebProcess that's been killed or crashed.
send(Messages::WebPage::GoToBackForwardItem(navigation->navigationID(), m_backForwardList->currentItem()->itemID(), FrameLoadType::IndexedBackForward, ShouldTreatAsContinuingLoad::No, std::nullopt, m_lastNavigationWasAppInitiated));
m_process->startResponsivenessTimer();
return navigation;
}
void WebPageProxy::setDrawingArea(std::unique_ptr<DrawingAreaProxy>&& drawingArea)
{
m_drawingArea = WTFMove(drawingArea);
if (!m_drawingArea)
return;
m_drawingArea->startReceivingMessages();
m_drawingArea->setSize(viewSize());
#if ENABLE(ASYNC_SCROLLING) && PLATFORM(COCOA)
if (m_drawingArea->type() == DrawingAreaType::RemoteLayerTree) {
m_scrollingCoordinatorProxy = makeUnique<RemoteScrollingCoordinatorProxy>(*this);
#if PLATFORM(IOS_FAMILY)
// On iOS, main frame scrolls are sent in terms of visible rect updates.
m_scrollingCoordinatorProxy->setPropagatesMainFrameScrolls(false);
#endif
}
#endif
}
void WebPageProxy::initializeWebPage()
{
if (!hasRunningProcess())
return;
setDrawingArea(pageClient().createDrawingAreaProxy(m_process));
ASSERT(m_drawingArea);
#if ENABLE(REMOTE_INSPECTOR)
// Initialize remote inspector connection now that we have a sub-process that is hosting one of our web views.
Inspector::RemoteInspector::singleton();
#endif
if (auto& attributedBundleIdentifier = m_configuration->attributedBundleIdentifier(); !!attributedBundleIdentifier) {
WebPageNetworkParameters parameters { attributedBundleIdentifier };
websiteDataStore().networkProcess().send(Messages::NetworkProcess::AddWebPageNetworkParameters(sessionID(), m_identifier, WTFMove(parameters)), 0);
}
send(Messages::WebProcess::CreateWebPage(m_webPageID, creationParameters(m_process, *m_drawingArea)), 0);
m_process->addVisitedLinkStoreUser(visitedLinkStore(), m_identifier);
}
void WebPageProxy::close()
{
if (m_isClosed)
return;
WEBPAGEPROXY_RELEASE_LOG(Loading, "close:");
m_isClosed = true;
reportPageLoadResult(ResourceError { ResourceError::Type::Cancellation });
if (m_activePopupMenu)
m_activePopupMenu->cancelTracking();
if (m_controlledByAutomation) {
if (auto* automationSession = process().processPool().automationSession())
automationSession->willClosePage(*this);
}
#if ENABLE(CONTEXT_MENUS)
m_activeContextMenu = nullptr;
#endif
m_provisionalPage = nullptr;
m_inspector->invalidate();
m_backForwardList->pageClosed();
m_inspectorController->pageClosed();
#if ENABLE(REMOTE_INSPECTOR)
m_inspectorDebuggable = nullptr;
#endif
pageClient().pageClosed();
m_process->disconnectFramesFromPage(this);
m_loaderClient = nullptr;
m_navigationClient = makeUniqueRef<API::NavigationClient>();
m_policyClient = nullptr;
m_iconLoadingClient = makeUnique<API::IconLoadingClient>();
m_formClient = makeUnique<API::FormClient>();
m_uiClient = makeUnique<API::UIClient>();
m_findClient = makeUnique<API::FindClient>();
m_findMatchesClient = makeUnique<API::FindMatchesClient>();
m_diagnosticLoggingClient = nullptr;
#if ENABLE(CONTEXT_MENUS)
m_contextMenuClient = makeUnique<API::ContextMenuClient>();
#endif
#if ENABLE(FULLSCREEN_API)
m_fullscreenClient = makeUnique<API::FullscreenClient>();
#endif
resetState(ResetStateReason::PageInvalidated);
m_process->processPool().backForwardCache().removeEntriesForPage(*this);
RunLoop::current().dispatch([destinationID = messageSenderDestinationID(), protectedProcess = m_process.copyRef(), preventProcessShutdownScope = m_process->shutdownPreventingScope()] {
protectedProcess->send(Messages::WebPage::Close(), destinationID);
});
m_process->removeWebPage(*this, WebProcessProxy::EndsUsingDataStore::Yes);
removeAllMessageReceivers();
m_process->processPool().supplement<WebNotificationManagerProxy>()->clearNotifications(this);
// Null out related WebPageProxy to avoid leaks.
m_configuration->setRelatedPage(nullptr);
#if PLATFORM(IOS_FAMILY)
// Make sure we don't hold a process assertion after getting closed.
m_isVisibleActivity = nullptr;
m_isAudibleActivity = nullptr;
m_isCapturingActivity = nullptr;
m_openingAppLinkActivity = nullptr;
m_audibleActivityTimer.stop();
#endif
stopAllURLSchemeTasks();
updatePlayingMediaDidChange(MediaProducer::IsNotPlaying);
}
bool WebPageProxy::tryClose()
{
if (!hasRunningProcess())
return true;
WEBPAGEPROXY_RELEASE_LOG(Process, "tryClose:");
// Close without delay if the process allows it. Our goal is to terminate
// the process, so we check a per-process status bit.
if (m_process->isSuddenTerminationEnabled())
return true;
m_tryCloseTimeoutTimer.startOneShot(tryCloseTimeoutDelay);
sendWithAsyncReply(Messages::WebPage::TryClose(), [this, weakThis = WeakPtr { *this }](bool shouldClose) {
if (!weakThis)
return;
// If we timed out, don't ask the client to close again.
if (!m_tryCloseTimeoutTimer.isActive())
return;
m_tryCloseTimeoutTimer.stop();
if (shouldClose)
closePage();
});
return false;
}
void WebPageProxy::tryCloseTimedOut()
{
WEBPAGEPROXY_RELEASE_LOG_ERROR(Process, "tryCloseTimedOut: Timed out waiting for the process to respond to the WebPage::TryClose IPC, closing the page now");
closePage();
}
void WebPageProxy::maybeInitializeSandboxExtensionHandle(WebProcessProxy& process, const URL& url, const URL& resourceDirectoryURL, SandboxExtension::Handle& sandboxExtensionHandle, bool checkAssumedReadAccessToResourceURL)
{
if (!url.isLocalFile())
return;
#if HAVE(AUDIT_TOKEN)
// If the process is still launching then it does not have a PID yet. We will take care of creating the sandbox extension
// once the process has finished launching.
if (process.isLaunching() || process.wasTerminated())
return;
#endif
if (!resourceDirectoryURL.isEmpty()) {
if (checkAssumedReadAccessToResourceURL && process.hasAssumedReadAccessToURL(resourceDirectoryURL))
return;
bool createdExtension = false;
#if HAVE(AUDIT_TOKEN)
ASSERT(process.connection() && process.connection()->getAuditToken());
if (process.connection() && process.connection()->getAuditToken()) {
if (auto handle = SandboxExtension::createHandleForReadByAuditToken(resourceDirectoryURL.fileSystemPath(), *(process.connection()->getAuditToken()))) {
sandboxExtensionHandle = WTFMove(*handle);
createdExtension = true;
}
} else
#endif
{
if (auto handle = SandboxExtension::createHandle(resourceDirectoryURL.fileSystemPath(), SandboxExtension::Type::ReadOnly)) {
sandboxExtensionHandle = WTFMove(*handle);
createdExtension = true;
}
}
if (createdExtension) {
process.assumeReadAccessToBaseURL(*this, resourceDirectoryURL.string());
return;
}
}
if (process.hasAssumedReadAccessToURL(url))
return;
// Inspector resources are in a directory with assumed access.
RELEASE_ASSERT_WITH_SECURITY_IMPLICATION(!WebKit::isInspectorPage(*this));
bool createdExtension = false;
#if HAVE(AUDIT_TOKEN)
ASSERT(process.connection() && process.connection()->getAuditToken());
if (process.connection() && process.connection()->getAuditToken()) {
if (auto handle = SandboxExtension::createHandleForReadByAuditToken("/", *(process.connection()->getAuditToken()))) {
createdExtension = true;
sandboxExtensionHandle = WTFMove(*handle);
}
} else
#endif
{
if (auto handle = SandboxExtension::createHandle("/", SandboxExtension::Type::ReadOnly)) {
createdExtension = true;
sandboxExtensionHandle = WTFMove(*handle);
}
}
if (createdExtension) {
willAcquireUniversalFileReadSandboxExtension(process);
return;
}
#if PLATFORM(COCOA)
if (!linkedOnOrAfter(SDKVersion::FirstWithoutUnconditionalUniversalSandboxExtension))
willAcquireUniversalFileReadSandboxExtension(process);
#endif
// We failed to issue an universal file read access sandbox, fall back to issuing one for the base URL instead.
auto baseURL = url.truncatedForUseAsBase();
auto basePath = baseURL.fileSystemPath();
if (basePath.isNull())
return;
#if HAVE(AUDIT_TOKEN)
if (process.connection() && process.connection()->getAuditToken()) {
if (auto handle = SandboxExtension::createHandleForReadByAuditToken(basePath, *(process.connection()->getAuditToken()))) {
sandboxExtensionHandle = WTFMove(*handle);
createdExtension = true;
}
} else
#endif
{
if (auto handle = SandboxExtension::createHandle(basePath, SandboxExtension::Type::ReadOnly)) {
sandboxExtensionHandle = WTFMove(*handle);
createdExtension = true;
}
}
if (createdExtension)
process.assumeReadAccessToBaseURL(*this, baseURL.string());
}
#if !PLATFORM(COCOA)
void WebPageProxy::addPlatformLoadParameters(WebProcessProxy&, LoadParameters&)
{
}
#endif
WebProcessProxy& WebPageProxy::ensureRunningProcess()
{
if (!hasRunningProcess())
launchProcess({ }, ProcessLaunchReason::InitialProcess);
return m_process;
}
RefPtr<API::Navigation> WebPageProxy::loadRequest(ResourceRequest&& request, ShouldOpenExternalURLsPolicy shouldOpenExternalURLsPolicy, API::Object* userData)
{
if (m_isClosed)
return nullptr;
WEBPAGEPROXY_RELEASE_LOG(Loading, "loadRequest:");
if (!hasRunningProcess())
launchProcess(RegistrableDomain { request.url() }, ProcessLaunchReason::InitialProcess);
auto navigation = m_navigationState->createLoadRequestNavigation(ResourceRequest(request), m_backForwardList->currentItem());
if (shouldForceForegroundPriorityForClientNavigation())
navigation->setClientNavigationActivity(process().throttler().foregroundActivity("Client navigation"_s));
#if PLATFORM(COCOA)
setLastNavigationWasAppInitiated(request);
#endif
loadRequestWithNavigationShared(m_process.copyRef(), m_webPageID, navigation.get(), WTFMove(request), shouldOpenExternalURLsPolicy, userData, ShouldTreatAsContinuingLoad::No, isNavigatingToAppBoundDomain());
return navigation;
}
void WebPageProxy::loadRequestWithNavigationShared(Ref<WebProcessProxy>&& process, WebCore::PageIdentifier webPageID, API::Navigation& navigation, ResourceRequest&& request, ShouldOpenExternalURLsPolicy shouldOpenExternalURLsPolicy, API::Object* userData, ShouldTreatAsContinuingLoad shouldTreatAsContinuingLoad, std::optional<NavigatingToAppBoundDomain> isNavigatingToAppBoundDomain, std::optional<WebsitePoliciesData>&& websitePolicies, std::optional<NetworkResourceLoadIdentifier> existingNetworkResourceLoadIdentifierToResume)
{
ASSERT(!m_isClosed);
WEBPAGEPROXY_RELEASE_LOG(Loading, "loadRequestWithNavigationShared:");
auto transaction = m_pageLoadState.transaction();
auto url = request.url();
if (shouldTreatAsContinuingLoad == ShouldTreatAsContinuingLoad::No)
m_pageLoadState.setPendingAPIRequest(transaction, { navigation.navigationID(), url.string() });
LoadParameters loadParameters;
loadParameters.navigationID = navigation.navigationID();
loadParameters.request = WTFMove(request);
loadParameters.shouldOpenExternalURLsPolicy = shouldOpenExternalURLsPolicy;
loadParameters.userData = UserData(process->transformObjectsToHandles(userData).get());
loadParameters.shouldTreatAsContinuingLoad = shouldTreatAsContinuingLoad;
loadParameters.websitePolicies = WTFMove(websitePolicies);
loadParameters.lockHistory = navigation.lockHistory();
loadParameters.lockBackForwardList = navigation.lockBackForwardList();
loadParameters.clientRedirectSourceForHistory = navigation.clientRedirectSourceForHistory();
loadParameters.effectiveSandboxFlags = navigation.effectiveSandboxFlags();
loadParameters.isNavigatingToAppBoundDomain = isNavigatingToAppBoundDomain;
loadParameters.existingNetworkResourceLoadIdentifierToResume = existingNetworkResourceLoadIdentifierToResume;
maybeInitializeSandboxExtensionHandle(process, url, m_pageLoadState.resourceDirectoryURL(), loadParameters.sandboxExtensionHandle);
addPlatformLoadParameters(process, loadParameters);
if (shouldTreatAsContinuingLoad == ShouldTreatAsContinuingLoad::No)
preconnectTo(url);
navigation.setIsLoadedWithNavigationShared(true);
process->markProcessAsRecentlyUsed();
if (!process->isLaunching() || !url.isLocalFile())
process->send(Messages::WebPage::LoadRequest(loadParameters), webPageID);
else
process->send(Messages::WebPage::LoadRequestWaitingForProcessLaunch(loadParameters, m_pageLoadState.resourceDirectoryURL(), m_identifier, true), webPageID);
process->startResponsivenessTimer();
}
RefPtr<API::Navigation> WebPageProxy::loadFile(const String& fileURLString, const String& resourceDirectoryURLString, bool isAppInitiated, API::Object* userData)
{
WEBPAGEPROXY_RELEASE_LOG(Loading, "loadFile:");
if (m_isClosed) {
WEBPAGEPROXY_RELEASE_LOG(Loading, "loadFile: page is closed");
return nullptr;
}
#if PLATFORM(MAC)
if (isQuarantinedAndNotUserApproved(fileURLString)) {
WEBPAGEPROXY_RELEASE_LOG(Loading, "loadFile: file cannot be opened because it is from an unidentified developer.");
return nullptr;
}
#endif
if (!hasRunningProcess())
launchProcess({ }, ProcessLaunchReason::InitialProcess);
URL fileURL = URL(URL(), fileURLString);
if (!fileURL.isLocalFile()) {
WEBPAGEPROXY_RELEASE_LOG(Loading, "loadFile: file is not local");
return nullptr;
}
URL resourceDirectoryURL;
if (resourceDirectoryURLString.isNull())
resourceDirectoryURL = URL({ }, "file:///"_s);
else {
resourceDirectoryURL = URL(URL(), resourceDirectoryURLString);
if (!resourceDirectoryURL.isLocalFile()) {
WEBPAGEPROXY_RELEASE_LOG(Loading, "loadFile: resource URL is not local");
return nullptr;
}
}
auto navigation = m_navigationState->createLoadRequestNavigation(ResourceRequest(fileURL), m_backForwardList->currentItem());
if (shouldForceForegroundPriorityForClientNavigation())
navigation->setClientNavigationActivity(process().throttler().foregroundActivity("Client navigation"_s));
auto transaction = m_pageLoadState.transaction();
m_pageLoadState.setPendingAPIRequest(transaction, { navigation->navigationID(), fileURLString }, resourceDirectoryURL);
auto request = ResourceRequest(fileURL);
request.setIsAppInitiated(isAppInitiated);
m_lastNavigationWasAppInitiated = isAppInitiated;
LoadParameters loadParameters;
loadParameters.navigationID = navigation->navigationID();
loadParameters.request = WTFMove(request);
loadParameters.shouldOpenExternalURLsPolicy = ShouldOpenExternalURLsPolicy::ShouldNotAllow;
loadParameters.userData = UserData(process().transformObjectsToHandles(userData).get());
const bool checkAssumedReadAccessToResourceURL = false;
maybeInitializeSandboxExtensionHandle(m_process, fileURL, resourceDirectoryURL, loadParameters.sandboxExtensionHandle, checkAssumedReadAccessToResourceURL);
addPlatformLoadParameters(m_process, loadParameters);
m_process->markProcessAsRecentlyUsed();
if (m_process->isLaunching())
send(Messages::WebPage::LoadRequestWaitingForProcessLaunch(loadParameters, resourceDirectoryURL, m_identifier, checkAssumedReadAccessToResourceURL));
else
send(Messages::WebPage::LoadRequest(loadParameters));
m_process->startResponsivenessTimer();
return navigation;
}
RefPtr<API::Navigation> WebPageProxy::loadData(const IPC::DataReference& data, const String& MIMEType, const String& encoding, const String& baseURL, API::Object* userData, ShouldOpenExternalURLsPolicy shouldOpenExternalURLsPolicy)
{
WEBPAGEPROXY_RELEASE_LOG(Loading, "loadData:");
#if ENABLE(APP_BOUND_DOMAINS)
if (MIMEType == "text/html"_s && !isFullWebBrowser())
m_limitsNavigationsToAppBoundDomains = true;
#endif
if (m_isClosed) {
WEBPAGEPROXY_RELEASE_LOG(Loading, "loadData: page is closed");
return nullptr;
}
if (!hasRunningProcess())
launchProcess({ }, ProcessLaunchReason::InitialProcess);
auto navigation = m_navigationState->createLoadDataNavigation(makeUnique<API::SubstituteData>(Vector(data), MIMEType, encoding, baseURL, userData));
if (shouldForceForegroundPriorityForClientNavigation())
navigation->setClientNavigationActivity(process().throttler().foregroundActivity("Client navigation"_s));
loadDataWithNavigationShared(m_process.copyRef(), m_webPageID, navigation, data, MIMEType, encoding, baseURL, userData, ShouldTreatAsContinuingLoad::No, isNavigatingToAppBoundDomain(), std::nullopt, shouldOpenExternalURLsPolicy, SubstituteData::SessionHistoryVisibility::Hidden);
return navigation;
}
void WebPageProxy::loadDataWithNavigationShared(Ref<WebProcessProxy>&& process, WebCore::PageIdentifier webPageID, API::Navigation& navigation, const IPC::DataReference& data, const String& MIMEType, const String& encoding, const String& baseURL, API::Object* userData, ShouldTreatAsContinuingLoad shouldTreatAsContinuingLoad, std::optional<NavigatingToAppBoundDomain> isNavigatingToAppBoundDomain, std::optional<WebsitePoliciesData>&& websitePolicies, ShouldOpenExternalURLsPolicy shouldOpenExternalURLsPolicy, SubstituteData::SessionHistoryVisibility sessionHistoryVisibility)
{
WEBPAGEPROXY_RELEASE_LOG(Loading, "loadDataWithNavigation");
ASSERT(!m_isClosed);
auto transaction = m_pageLoadState.transaction();
m_pageLoadState.setPendingAPIRequest(transaction, { navigation.navigationID(), !baseURL.isEmpty() ? baseURL : aboutBlankURL().string() });
LoadParameters loadParameters;
loadParameters.sessionHistoryVisibility = sessionHistoryVisibility;
loadParameters.navigationID = navigation.navigationID();
loadParameters.data = data;
loadParameters.MIMEType = MIMEType;
loadParameters.encodingName = encoding;
loadParameters.baseURLString = baseURL;
loadParameters.shouldTreatAsContinuingLoad = shouldTreatAsContinuingLoad;
loadParameters.userData = UserData(process->transformObjectsToHandles(userData).get());
loadParameters.websitePolicies = WTFMove(websitePolicies);
loadParameters.shouldOpenExternalURLsPolicy = shouldOpenExternalURLsPolicy;
loadParameters.isNavigatingToAppBoundDomain = isNavigatingToAppBoundDomain;
loadParameters.isServiceWorkerLoad = isServiceWorkerPage();
addPlatformLoadParameters(process, loadParameters);
process->markProcessAsRecentlyUsed();
process->assumeReadAccessToBaseURL(*this, baseURL);
process->send(Messages::WebPage::LoadData(loadParameters), webPageID);
process->startResponsivenessTimer();
}
RefPtr<API::Navigation> WebPageProxy::loadSimulatedRequest(WebCore::ResourceRequest&& simulatedRequest, WebCore::ResourceResponse&& simulatedResponse, const IPC::DataReference& data)
{
WEBPAGEPROXY_RELEASE_LOG(Loading, "loadSimulatedRequest:");
#if PLATFORM(COCOA)
setLastNavigationWasAppInitiated(simulatedRequest);
#endif
#if ENABLE(APP_BOUND_DOMAINS)
if (simulatedResponse.mimeType() == "text/html"_s && !isFullWebBrowser())
m_limitsNavigationsToAppBoundDomains = true;
#endif
if (m_isClosed) {
WEBPAGEPROXY_RELEASE_LOG(Loading, "loadSimulatedRequest: page is closed");
return nullptr;
}
if (!hasRunningProcess())
launchProcess(RegistrableDomain { simulatedRequest.url() }, ProcessLaunchReason::InitialProcess);
auto navigation = m_navigationState->createSimulatedLoadWithDataNavigation(ResourceRequest(simulatedRequest), makeUnique<API::SubstituteData>(Vector(data), ResourceResponse(simulatedResponse), WebCore::SubstituteData::SessionHistoryVisibility::Visible), m_backForwardList->currentItem());
if (shouldForceForegroundPriorityForClientNavigation())
navigation->setClientNavigationActivity(process().throttler().foregroundActivity("Client navigation"_s));
auto transaction = m_pageLoadState.transaction();
auto baseURL = simulatedRequest.url().string();
simulatedResponse.setURL(simulatedRequest.url()); // These should always match for simulated load
m_pageLoadState.setPendingAPIRequest(transaction, { navigation->navigationID(), !baseURL.isEmpty() ? baseURL : aboutBlankURL().string() });
LoadParameters loadParameters;
loadParameters.navigationID = navigation->navigationID();
loadParameters.request = WTFMove(simulatedRequest);
loadParameters.data = data;
loadParameters.MIMEType = simulatedResponse.mimeType();
loadParameters.encodingName = simulatedResponse.textEncodingName();
loadParameters.baseURLString = baseURL;
loadParameters.shouldOpenExternalURLsPolicy = WebCore::ShouldOpenExternalURLsPolicy::ShouldNotAllow;
loadParameters.shouldTreatAsContinuingLoad = ShouldTreatAsContinuingLoad::No;
loadParameters.lockHistory = navigation->lockHistory();
loadParameters.lockBackForwardList = navigation->lockBackForwardList();
loadParameters.clientRedirectSourceForHistory = navigation->clientRedirectSourceForHistory();
loadParameters.effectiveSandboxFlags = navigation->effectiveSandboxFlags();
loadParameters.isNavigatingToAppBoundDomain = isNavigatingToAppBoundDomain();
simulatedResponse.setExpectedContentLength(data.size());
simulatedResponse.includeCertificateInfo();
addPlatformLoadParameters(m_process, loadParameters);
m_process->markProcessAsRecentlyUsed();
m_process->assumeReadAccessToBaseURL(*this, baseURL);
m_process->send(Messages::WebPage::LoadSimulatedRequestAndResponse(loadParameters, simulatedResponse), m_webPageID);
m_process->startResponsivenessTimer();
return navigation;
}
void WebPageProxy::loadAlternateHTML(const IPC::DataReference& htmlData, const String& encoding, const URL& baseURL, const URL& unreachableURL, API::Object* userData)
{
WEBPAGEPROXY_RELEASE_LOG(Loading, "loadAlternateHTML");
// When the UIProcess is in the process of handling a failing provisional load, do not attempt to
// start a second alternative HTML load as this will prevent the page load state from being
// handled properly.
if (m_isClosed || m_isLoadingAlternateHTMLStringForFailingProvisionalLoad) {
WEBPAGEPROXY_RELEASE_LOG(Loading, "loadAlternateHTML: page is closed (or other)");
return;
}
if (!m_failingProvisionalLoadURL.isEmpty())
m_isLoadingAlternateHTMLStringForFailingProvisionalLoad = true;
if (!hasRunningProcess())
launchProcess(RegistrableDomain { baseURL }, ProcessLaunchReason::InitialProcess);
auto transaction = m_pageLoadState.transaction();
m_pageLoadState.setPendingAPIRequest(transaction, { 0, unreachableURL.string() });
m_pageLoadState.setUnreachableURL(transaction, unreachableURL.string());
if (m_mainFrame)
m_mainFrame->setUnreachableURL(unreachableURL);
LoadParameters loadParameters;
loadParameters.navigationID = 0;
loadParameters.data = htmlData;
loadParameters.MIMEType = "text/html"_s;
loadParameters.encodingName = encoding;
loadParameters.baseURLString = baseURL.string();
loadParameters.unreachableURLString = unreachableURL.string();
loadParameters.provisionalLoadErrorURLString = m_failingProvisionalLoadURL;
loadParameters.userData = UserData(process().transformObjectsToHandles(userData).get());
addPlatformLoadParameters(process(), loadParameters);
m_process->markProcessAsRecentlyUsed();
m_process->assumeReadAccessToBaseURL(*this, baseURL.string());
m_process->assumeReadAccessToBaseURL(*this, unreachableURL.string());
send(Messages::WebPage::LoadAlternateHTML(loadParameters));
m_process->startResponsivenessTimer();
}
void WebPageProxy::loadWebArchiveData(API::Data* webArchiveData, API::Object* userData)
{
WEBPAGEPROXY_RELEASE_LOG(Loading, "loadWebArchiveData:");
if (m_isClosed) {
WEBPAGEPROXY_RELEASE_LOG(Loading, "loadWebArchiveData: page is closed");
return;
}
if (!hasRunningProcess())
launchProcess({ }, ProcessLaunchReason::InitialProcess);
auto transaction = m_pageLoadState.transaction();
m_pageLoadState.setPendingAPIRequest(transaction, { 0, aboutBlankURL().string() });
LoadParameters loadParameters;
loadParameters.navigationID = 0;
loadParameters.data = webArchiveData->dataReference();
loadParameters.MIMEType = "application/x-webarchive"_s;
loadParameters.encodingName = "utf-16"_s;
loadParameters.userData = UserData(process().transformObjectsToHandles(userData).get());
addPlatformLoadParameters(process(), loadParameters);
m_process->markProcessAsRecentlyUsed();
send(Messages::WebPage::LoadData(loadParameters));
m_process->startResponsivenessTimer();
}
void WebPageProxy::navigateToPDFLinkWithSimulatedClick(const String& urlString, IntPoint documentPoint, IntPoint screenPoint)
{
WEBPAGEPROXY_RELEASE_LOG(Loading, "navigateToPDFLinkWithSimulatedClick:");
if (m_isClosed) {
WEBPAGEPROXY_RELEASE_LOG(Loading, "navigateToPDFLinkWithSimulatedClick: page is closed:");
return;
}
if (WTF::protocolIsJavaScript(urlString))
return;
if (!hasRunningProcess())
launchProcess(RegistrableDomain { URL(URL(), urlString) }, ProcessLaunchReason::InitialProcess);
send(Messages::WebPage::NavigateToPDFLinkWithSimulatedClick(urlString, documentPoint, screenPoint));
m_process->startResponsivenessTimer();
}
void WebPageProxy::stopLoading()
{
WEBPAGEPROXY_RELEASE_LOG(Loading, "stopLoading:");
if (!hasRunningProcess()) {
WEBPAGEPROXY_RELEASE_LOG(Loading, "navigateToPDFLinkWithSimulatedClick: page is not valid");
return;
}
send(Messages::WebPage::StopLoading());
if (m_provisionalPage) {
m_provisionalPage->cancel();
m_provisionalPage = nullptr;
}
m_process->startResponsivenessTimer();
}
RefPtr<API::Navigation> WebPageProxy::reload(OptionSet<WebCore::ReloadOption> options)
{
WEBPAGEPROXY_RELEASE_LOG(Loading, "reload:");
// Make sure the Network & GPU processes are still responsive. This is so that reload() gets us out of the bad state if one of these
// processes is hung.
websiteDataStore().networkProcess().checkForResponsiveness();
#if ENABLE(GPU_PROCESS)
if (auto* gpuProcess = process().processPool().gpuProcess())
gpuProcess->checkForResponsiveness();
#endif
SandboxExtension::Handle sandboxExtensionHandle;
String url = currentURL();
if (!url.isEmpty()) {
// We may not have an extension yet if back/forward list was reinstated after a WebProcess crash or a browser relaunch
maybeInitializeSandboxExtensionHandle(m_process, URL(URL(), url), currentResourceDirectoryURL(), sandboxExtensionHandle);
}
if (!hasRunningProcess())
return launchProcessForReload();
auto navigation = m_navigationState->createReloadNavigation(m_backForwardList->currentItem());
if (!url.isEmpty()) {
auto transaction = m_pageLoadState.transaction();
m_pageLoadState.setPendingAPIRequest(transaction, { navigation->navigationID(), url });
}
// Store decision to reload without content blockers on the navigation so that we can later set the corresponding
// WebsitePolicies flag in WebPageProxy::receivedNavigationPolicyDecision().
if (options.contains(WebCore::ReloadOption::DisableContentBlockers))
navigation->setUserContentExtensionsEnabled(false);
m_process->markProcessAsRecentlyUsed();
send(Messages::WebPage::Reload(navigation->navigationID(), options.toRaw(), sandboxExtensionHandle));
m_process->startResponsivenessTimer();
#if ENABLE(SPEECH_SYNTHESIS)
resetSpeechSynthesizer();
#endif
return navigation;
}
void WebPageProxy::recordAutomaticNavigationSnapshot()
{
if (m_shouldSuppressNextAutomaticNavigationSnapshot)
return;
if (WebBackForwardListItem* item = m_backForwardList->currentItem())
recordNavigationSnapshot(*item);
}
void WebPageProxy::recordNavigationSnapshot(WebBackForwardListItem& item)
{
if (!m_shouldRecordNavigationSnapshots)
return;
#if PLATFORM(COCOA) || PLATFORM(GTK)
ViewSnapshotStore::singleton().recordSnapshot(*this, item);
#else
UNUSED_PARAM(item);
#endif
}
RefPtr<API::Navigation> WebPageProxy::goForward()
{
WebBackForwardListItem* forwardItem = m_backForwardList->forwardItem();
if (!forwardItem)
return nullptr;
return goToBackForwardItem(*forwardItem, FrameLoadType::Forward);
}
RefPtr<API::Navigation> WebPageProxy::goBack()
{
WebBackForwardListItem* backItem = m_backForwardList->backItem();
if (!backItem)
return nullptr;
return goToBackForwardItem(*backItem, FrameLoadType::Back);
}
RefPtr<API::Navigation> WebPageProxy::goToBackForwardItem(WebBackForwardListItem& item)
{
return goToBackForwardItem(item, FrameLoadType::IndexedBackForward);
}
RefPtr<API::Navigation> WebPageProxy::goToBackForwardItem(WebBackForwardListItem& item, FrameLoadType frameLoadType)
{
WEBPAGEPROXY_RELEASE_LOG(Loading, "goToBackForwardItem:");
LOG(Loading, "WebPageProxy %p goToBackForwardItem to item URL %s", this, item.url().utf8().data());
if (m_isClosed) {
WEBPAGEPROXY_RELEASE_LOG(Loading, "goToBackForwardItem: page is closed");
return nullptr;
}
if (!hasRunningProcess()) {
launchProcess(RegistrableDomain { URL(URL(), item.url()) }, ProcessLaunchReason::InitialProcess);
if (&item != m_backForwardList->currentItem())
m_backForwardList->goToItem(item);
}
RefPtr<API::Navigation> navigation;
if (!m_backForwardList->currentItem()->itemIsInSameDocument(item))
navigation = m_navigationState->createBackForwardNavigation(item, m_backForwardList->currentItem(), frameLoadType);
auto transaction = m_pageLoadState.transaction();
m_pageLoadState.setPendingAPIRequest(transaction, { navigation ? navigation->navigationID() : 0, item.url() });
m_process->markProcessAsRecentlyUsed();
send(Messages::WebPage::GoToBackForwardItem(navigation ? navigation->navigationID() : 0, item.itemID(), frameLoadType, ShouldTreatAsContinuingLoad::No, std::nullopt, m_lastNavigationWasAppInitiated));
m_process->startResponsivenessTimer();
return navigation;
}
void WebPageProxy::tryRestoreScrollPosition()
{
WEBPAGEPROXY_RELEASE_LOG(Loading, "tryRestoreScrollPosition:");
if (!hasRunningProcess()) {
WEBPAGEPROXY_RELEASE_LOG(Loading, "tryRestoreScrollPosition: page is not valid");
return;
}
send(Messages::WebPage::TryRestoreScrollPosition());
}
void WebPageProxy::didChangeBackForwardList(WebBackForwardListItem* added, Vector<Ref<WebBackForwardListItem>>&& removed)
{
PageClientProtector protector(pageClient());
if (!m_navigationClient->didChangeBackForwardList(*this, added, removed) && m_loaderClient)
m_loaderClient->didChangeBackForwardList(*this, added, WTFMove(removed));
auto transaction = m_pageLoadState.transaction();
m_pageLoadState.setCanGoBack(transaction, m_backForwardList->backItem());
m_pageLoadState.setCanGoForward(transaction, m_backForwardList->forwardItem());
}
void WebPageProxy::willGoToBackForwardListItem(const BackForwardItemIdentifier& itemID, bool inBackForwardCache)
{
PageClientProtector protector(pageClient());
if (auto* item = m_backForwardList->itemForID(itemID))
m_navigationClient->willGoToBackForwardListItem(*this, *item, inBackForwardCache);
}
bool WebPageProxy::shouldKeepCurrentBackForwardListItemInList(WebBackForwardListItem& item)
{
PageClientProtector protector(pageClient());
return !m_loaderClient || m_loaderClient->shouldKeepCurrentBackForwardListItemInList(*this, item);
}
bool WebPageProxy::canShowMIMEType(const String& mimeType)
{
if (MIMETypeRegistry::canShowMIMEType(mimeType))
return true;
#if PLATFORM(COCOA)
// On Mac, we can show PDFs.
if (MIMETypeRegistry::isPDFOrPostScriptMIMEType(mimeType) && !WebProcessPool::omitPDFSupport())
return true;
#endif // PLATFORM(COCOA)
return false;
}
void WebPageProxy::setControlledByAutomation(bool controlled)
{
if (m_controlledByAutomation == controlled)
return;
m_controlledByAutomation = controlled;
if (!hasRunningProcess())
return;
send(Messages::WebPage::SetControlledByAutomation(controlled));
websiteDataStore().networkProcess().send(Messages::NetworkProcess::SetSessionIsControlledByAutomation(m_websiteDataStore->sessionID(), m_controlledByAutomation), 0);
}
void WebPageProxy::createInspectorTarget(const String& targetId, Inspector::InspectorTargetType type)
{
MESSAGE_CHECK(m_process, !targetId.isEmpty());
m_inspectorController->createInspectorTarget(targetId, type);
}
void WebPageProxy::destroyInspectorTarget(const String& targetId)
{
MESSAGE_CHECK(m_process, !targetId.isEmpty());
m_inspectorController->destroyInspectorTarget(targetId);
}
void WebPageProxy::sendMessageToInspectorFrontend(const String& targetId, const String& message)
{
m_inspectorController->sendMessageToInspectorFrontend(targetId, message);
}
#if ENABLE(REMOTE_INSPECTOR)
void WebPageProxy::setIndicating(bool indicating)
{
if (!hasRunningProcess())
return;
send(Messages::WebPage::SetIndicating(indicating));
}
bool WebPageProxy::allowsRemoteInspection() const
{
return m_inspectorDebuggable->remoteDebuggingAllowed();
}
void WebPageProxy::setAllowsRemoteInspection(bool allow)
{
m_inspectorDebuggable->setRemoteDebuggingAllowed(allow);
}
String WebPageProxy::remoteInspectionNameOverride() const
{
return m_inspectorDebuggable->nameOverride();
}
void WebPageProxy::setRemoteInspectionNameOverride(const String& name)
{
m_inspectorDebuggable->setNameOverride(name);
}
void WebPageProxy::remoteInspectorInformationDidChange()
{
m_inspectorDebuggable->update();
}
#endif
void WebPageProxy::setBackgroundColor(const std::optional<Color>& color)
{
if (m_backgroundColor == color)
return;
m_backgroundColor = color;
if (hasRunningProcess())
send(Messages::WebPage::SetBackgroundColor(color));
}
void WebPageProxy::setTopContentInset(float contentInset)
{
if (m_topContentInset == contentInset)
return;
m_topContentInset = contentInset;
if (!hasRunningProcess())
return;
#if PLATFORM(COCOA)
send(Messages::WebPage::SetTopContentInsetFenced(contentInset, m_drawingArea->createFence()));
#else
send(Messages::WebPage::SetTopContentInset(contentInset));
#endif
}
void WebPageProxy::setUnderlayColor(const Color& color)
{
if (m_underlayColor == color)
return;
m_underlayColor = color;
if (hasRunningProcess())
send(Messages::WebPage::SetUnderlayColor(color));
}
Color WebPageProxy::underPageBackgroundColor() const
{
if (m_underPageBackgroundColorOverride.isValid())
return m_underPageBackgroundColorOverride;
if (m_pageExtendedBackgroundColor.isValid())
return m_pageExtendedBackgroundColor;
return platformUnderPageBackgroundColor();
}
void WebPageProxy::setUnderPageBackgroundColorOverride(Color&& newUnderPageBackgroundColorOverride)
{
if (newUnderPageBackgroundColorOverride == m_underPageBackgroundColorOverride)
return;
auto oldUnderPageBackgroundColor = underPageBackgroundColor();
auto oldUnderPageBackgroundColorOverride = std::exchange(m_underPageBackgroundColorOverride, newUnderPageBackgroundColorOverride);
bool changesUnderPageBackgroundColor = !equalIgnoringSemanticColor(oldUnderPageBackgroundColor, underPageBackgroundColor());
m_underPageBackgroundColorOverride = WTFMove(oldUnderPageBackgroundColorOverride);
if (changesUnderPageBackgroundColor)
pageClient().underPageBackgroundColorWillChange();
m_underPageBackgroundColorOverride = WTFMove(newUnderPageBackgroundColorOverride);
if (changesUnderPageBackgroundColor)
pageClient().underPageBackgroundColorDidChange();
if (m_hasPendingUnderPageBackgroundColorOverrideToDispatch)
return;
m_hasPendingUnderPageBackgroundColorOverrideToDispatch = true;
RunLoop::main().dispatch([this, weakThis = WeakPtr { *this }] {
if (!weakThis)
return;
if (!m_hasPendingUnderPageBackgroundColorOverrideToDispatch)
return;
m_hasPendingUnderPageBackgroundColorOverrideToDispatch = false;
if (m_pageClient)
m_pageClient->didChangeBackgroundColor();
if (hasRunningProcess())
send(Messages::WebPage::SetUnderPageBackgroundColorOverride(m_underPageBackgroundColorOverride));
});
}
void WebPageProxy::viewWillStartLiveResize()
{
if (!hasRunningProcess())
return;
closeOverlayedViews();
send(Messages::WebPage::ViewWillStartLiveResize());
}
void WebPageProxy::viewWillEndLiveResize()
{
if (!hasRunningProcess())
return;
send(Messages::WebPage::ViewWillEndLiveResize());
}
void WebPageProxy::setViewNeedsDisplay(const Region& region)
{
pageClient().setViewNeedsDisplay(region);
}
void WebPageProxy::requestScroll(const FloatPoint& scrollPosition, const IntPoint& scrollOrigin, ScrollIsAnimated animated)
{
pageClient().requestScroll(scrollPosition, scrollOrigin, animated);
}
WebCore::FloatPoint WebPageProxy::viewScrollPosition() const
{
return pageClient().viewScrollPosition();
}
void WebPageProxy::setSuppressVisibilityUpdates(bool flag)
{
if (m_suppressVisibilityUpdates == flag)
return;
m_suppressVisibilityUpdates = flag;
if (!m_suppressVisibilityUpdates) {
#if PLATFORM(COCOA)
scheduleActivityStateUpdate();
#else
dispatchActivityStateChange();
#endif
}
}
void WebPageProxy::updateActivityState(OptionSet<ActivityState::Flag> flagsToUpdate)
{
bool wasVisible = isViewVisible();
m_activityState.remove(flagsToUpdate);
if (flagsToUpdate & ActivityState::IsFocused && pageClient().isViewFocused())
m_activityState.add(ActivityState::IsFocused);
if (flagsToUpdate & ActivityState::WindowIsActive && pageClient().isViewWindowActive())
m_activityState.add(ActivityState::WindowIsActive);
if (flagsToUpdate & ActivityState::IsVisible) {
bool isNowVisible = pageClient().isViewVisible();
if (isNowVisible)
m_activityState.add(ActivityState::IsVisible);
if (wasVisible != isNowVisible)
WEBPAGEPROXY_RELEASE_LOG(ViewState, "updateActivityState: view visibility state changed %d -> %d", wasVisible, isNowVisible);
}
if (flagsToUpdate & ActivityState::IsVisibleOrOccluded && pageClient().isViewVisibleOrOccluded())
m_activityState.add(ActivityState::IsVisibleOrOccluded);
if (flagsToUpdate & ActivityState::IsInWindow && pageClient().isViewInWindow())
m_activityState.add(ActivityState::IsInWindow);
if (flagsToUpdate & ActivityState::IsVisuallyIdle && pageClient().isVisuallyIdle())
m_activityState.add(ActivityState::IsVisuallyIdle);
if (flagsToUpdate & ActivityState::IsAudible && m_mediaState.contains(MediaProducerMediaState::IsPlayingAudio) && !(m_mutedState.contains(MediaProducerMutedState::AudioIsMuted)))
m_activityState.add(ActivityState::IsAudible);
if (flagsToUpdate & ActivityState::IsLoading && m_pageLoadState.isLoading())
m_activityState.add(ActivityState::IsLoading);
if (flagsToUpdate & ActivityState::IsCapturingMedia && m_mediaState.containsAny({ MediaProducerMediaState::HasActiveAudioCaptureDevice, MediaProducerMediaState::HasActiveVideoCaptureDevice }))
m_activityState.add(ActivityState::IsCapturingMedia);
}
void WebPageProxy::activityStateDidChange(OptionSet<ActivityState::Flag> mayHaveChanged, ActivityStateChangeDispatchMode dispatchMode, ActivityStateChangeReplyMode replyMode)
{
LOG_WITH_STREAM(ActivityState, stream << "WebPageProxy " << identifier() << " activityStateDidChange - mayHaveChanged " << mayHaveChanged);
m_potentiallyChangedActivityStateFlags.add(mayHaveChanged);
m_activityStateChangeWantsSynchronousReply = m_activityStateChangeWantsSynchronousReply || replyMode == ActivityStateChangeReplyMode::Synchronous;
if (m_suppressVisibilityUpdates && dispatchMode != ActivityStateChangeDispatchMode::Immediate)
return;
#if PLATFORM(COCOA)
bool isNewlyInWindow = !isInWindow() && (mayHaveChanged & ActivityState::IsInWindow) && pageClient().isViewInWindow();
if (dispatchMode == ActivityStateChangeDispatchMode::Immediate || isNewlyInWindow) {
dispatchActivityStateChange();
return;
}
scheduleActivityStateUpdate();
#else
UNUSED_PARAM(dispatchMode);
dispatchActivityStateChange();
#endif
}
void WebPageProxy::viewDidLeaveWindow()
{
closeOverlayedViews();
#if ENABLE(VIDEO_PRESENTATION_MODE)
// When leaving the current page, close the video fullscreen.
if (m_videoFullscreenManager && m_videoFullscreenManager->hasMode(WebCore::HTMLMediaElementEnums::VideoFullscreenModeStandard))
m_videoFullscreenManager->requestHideAndExitFullscreen();
#endif
}
void WebPageProxy::viewDidEnterWindow()
{
LayerHostingMode layerHostingMode = pageClient().viewLayerHostingMode();
if (m_layerHostingMode != layerHostingMode) {
m_layerHostingMode = layerHostingMode;
send(Messages::WebPage::SetLayerHostingMode(layerHostingMode));
}
}
void WebPageProxy::dispatchActivityStateChange()
{
#if PLATFORM(COCOA)
if (m_activityStateChangeDispatcher->isScheduled())
m_activityStateChangeDispatcher->invalidate();
m_hasScheduledActivityStateUpdate = false;
#endif
if (!hasRunningProcess())
return;
LOG_WITH_STREAM(ActivityState, stream << "WebPageProxy " << identifier() << " dispatchActivityStateChange - potentiallyChangedActivityStateFlags " << m_potentiallyChangedActivityStateFlags);
// If the visibility state may have changed, then so may the visually idle & occluded agnostic state.
if (m_potentiallyChangedActivityStateFlags & ActivityState::IsVisible)
m_potentiallyChangedActivityStateFlags.add({ ActivityState::IsVisibleOrOccluded, ActivityState::IsVisuallyIdle });
// Record the prior view state, update the flags that may have changed,
// and check which flags have actually changed.
auto previousActivityState = m_activityState;
updateActivityState(m_potentiallyChangedActivityStateFlags);
auto changed = m_activityState ^ previousActivityState;
if (changed)
LOG_WITH_STREAM(ActivityState, stream << "WebPageProxy " << identifier() << " dispatchActivityStateChange: state changed from " << previousActivityState << " to " << m_activityState);
if ((changed & ActivityState::WindowIsActive) && isViewWindowActive())
updateCurrentModifierState();
if ((m_potentiallyChangedActivityStateFlags & ActivityState::IsVisible)) {
if (isViewVisible())
viewIsBecomingVisible();
else
m_process->pageIsBecomingInvisible(m_webPageID);
}
bool isNowInWindow = (changed & ActivityState::IsInWindow) && isInWindow();
// We always want to wait for the Web process to reply if we've been in-window before and are coming back in-window.
if (m_viewWasEverInWindow && isNowInWindow) {
if (m_drawingArea->hasVisibleContent() && m_waitsForPaintAfterViewDidMoveToWindow && !m_shouldSkipWaitingForPaintAfterNextViewDidMoveToWindow)
m_activityStateChangeWantsSynchronousReply = true;
m_shouldSkipWaitingForPaintAfterNextViewDidMoveToWindow = false;
}
// Don't wait synchronously if the view state is not visible. (This matters in particular on iOS, where a hidden page may be suspended.)
if (!(m_activityState & ActivityState::IsVisible))
m_activityStateChangeWantsSynchronousReply = false;
auto activityStateChangeID = m_activityStateChangeWantsSynchronousReply ? takeNextActivityStateChangeID() : static_cast<ActivityStateChangeID>(ActivityStateChangeAsynchronous);
if (changed || activityStateChangeID != ActivityStateChangeAsynchronous || !m_nextActivityStateChangeCallbacks.isEmpty()) {
sendWithAsyncReply(Messages::WebPage::SetActivityState(m_activityState, activityStateChangeID), [callbacks = std::exchange(m_nextActivityStateChangeCallbacks, { })] () mutable {
for (auto& callback : callbacks)
callback();
});
}
// This must happen after the SetActivityState message is sent, to ensure the page visibility event can fire.
updateThrottleState();
#if ENABLE(POINTER_LOCK)
if (((changed & ActivityState::IsVisible) && !isViewVisible()) || ((changed & ActivityState::WindowIsActive) && !pageClient().isViewWindowActive())
|| ((changed & ActivityState::IsFocused) && !(m_activityState & ActivityState::IsFocused)))
requestPointerUnlock();
#endif
if (changed & ActivityState::IsVisible) {
if (isViewVisible())
m_visiblePageToken = m_process->visiblePageToken();
else {
m_visiblePageToken = nullptr;
// If we've started the responsiveness timer as part of telling the web process to update the backing store
// state, it might not send back a reply (since it won't paint anything if the web page is hidden) so we
// stop the unresponsiveness timer here.
m_process->stopResponsivenessTimer();
}
}
if (changed & ActivityState::IsInWindow) {
if (isInWindow())
viewDidEnterWindow();
else
viewDidLeaveWindow();
}
updateBackingStoreDiscardableState();
if (activityStateChangeID != ActivityStateChangeAsynchronous)
waitForDidUpdateActivityState(activityStateChangeID);
m_potentiallyChangedActivityStateFlags = { };
m_activityStateChangeWantsSynchronousReply = false;
m_viewWasEverInWindow |= isNowInWindow;
#if PLATFORM(COCOA)
for (auto& callback : m_activityStateUpdateCallbacks)
callback();
m_activityStateUpdateCallbacks.clear();
#endif
}
void WebPageProxy::updateThrottleState()
{
bool processSuppressionEnabled = m_preferences->pageVisibilityBasedProcessSuppressionEnabled();
// If process suppression is not enabled take a token on the process pool to disable suppression of support processes.
if (!processSuppressionEnabled)
m_preventProcessSuppressionCount = m_process->processPool().processSuppressionDisabledForPageCount();
else if (!m_preventProcessSuppressionCount)
m_preventProcessSuppressionCount = nullptr;
if (m_activityState & ActivityState::IsVisuallyIdle)
m_pageIsUserObservableCount = nullptr;
else if (!m_pageIsUserObservableCount)
m_pageIsUserObservableCount = m_process->processPool().userObservablePageCount();
#if PLATFORM(IOS_FAMILY)
if (isViewVisible()) {
if (!m_isVisibleActivity || !m_isVisibleActivity->isValid()) {
WEBPAGEPROXY_RELEASE_LOG(ProcessSuspension, "updateThrottleState: UIProcess is taking a foreground assertion because the view is visible");
m_isVisibleActivity = m_process->throttler().foregroundActivity("View is visible"_s).moveToUniquePtr();
}
} else if (m_isVisibleActivity) {
WEBPAGEPROXY_RELEASE_LOG(ProcessSuspension, "updateThrottleState: UIProcess is releasing a foreground assertion because the view is no longer visible");
m_isVisibleActivity = nullptr;
}
bool isAudible = m_activityState.contains(ActivityState::IsAudible);
if (isAudible) {
if (!m_isAudibleActivity || !m_isAudibleActivity->isValid()) {
WEBPAGEPROXY_RELEASE_LOG(ProcessSuspension, "updateThrottleState: UIProcess is taking a foreground assertion because we are playing audio");
m_isAudibleActivity = m_process->throttler().foregroundActivity("View is playing audio"_s).moveToUniquePtr();
}
m_audibleActivityTimer.stop();
} else if (m_isAudibleActivity) {
WEBPAGEPROXY_RELEASE_LOG(ProcessSuspension, "updateThrottleState: UIProcess will release a foreground assertion in %g seconds because we are no longer playing audio", audibleActivityClearDelay.seconds());
if (!m_audibleActivityTimer.isActive())
m_audibleActivityTimer.startOneShot(audibleActivityClearDelay);
}
bool isCapturingMedia = m_activityState.contains(ActivityState::IsCapturingMedia);
if (isCapturingMedia) {
if (!m_isCapturingActivity || !m_isCapturingActivity->isValid()) {
WEBPAGEPROXY_RELEASE_LOG(ProcessSuspension, "updateThrottleState: UIProcess is taking a foreground assertion because media capture is active");
m_isCapturingActivity = m_process->throttler().foregroundActivity("View is capturing media"_s).moveToUniquePtr();
}
} else if (m_isCapturingActivity) {
WEBPAGEPROXY_RELEASE_LOG(ProcessSuspension, "updateThrottleState: UIProcess is releasing a foreground assertion because media capture is no longer active");
m_isCapturingActivity = nullptr;
}
#endif
}
#if PLATFORM(IOS_FAMILY)
void WebPageProxy::clearAudibleActivity()
{
WEBPAGEPROXY_RELEASE_LOG(ProcessSuspension, "updateThrottleState: UIProcess is releasing a foreground assertion because we are no longer playing audio");
m_isAudibleActivity = nullptr;
}
#endif
void WebPageProxy::updateHiddenPageThrottlingAutoIncreases()
{
if (!m_preferences->hiddenPageDOMTimerThrottlingAutoIncreases())
m_hiddenPageDOMTimerThrottlingAutoIncreasesCount = nullptr;
else if (!m_hiddenPageDOMTimerThrottlingAutoIncreasesCount)
m_hiddenPageDOMTimerThrottlingAutoIncreasesCount = m_process->processPool().hiddenPageThrottlingAutoIncreasesCount();
}
void WebPageProxy::layerHostingModeDidChange()
{
LayerHostingMode layerHostingMode = pageClient().viewLayerHostingMode();
if (m_layerHostingMode == layerHostingMode)
return;
m_layerHostingMode = layerHostingMode;
if (hasRunningProcess())
send(Messages::WebPage::SetLayerHostingMode(layerHostingMode));
}
void WebPageProxy::waitForDidUpdateActivityState(ActivityStateChangeID activityStateChangeID)
{
if (!hasRunningProcess())
return;
if (m_process->state() != WebProcessProxy::State::Running)
return;
// If we have previously timed out with no response from the WebProcess, don't block the UIProcess again until it starts responding.
if (m_waitingForDidUpdateActivityState)
return;
#if PLATFORM(IOS_FAMILY)
// Hail Mary check. Should not be possible (dispatchActivityStateChange should force async if not visible,
// and if visible we should be holding an assertion) - but we should never block on a suspended process.
if (!m_isVisibleActivity) {
ASSERT_NOT_REACHED();
return;
}
#endif
m_waitingForDidUpdateActivityState = true;
m_drawingArea->waitForDidUpdateActivityState(activityStateChangeID);
}
IntSize WebPageProxy::viewSize() const
{
return pageClient().viewSize();
}
void WebPageProxy::setInitialFocus(bool forward, bool isKeyboardEventValid, const WebKeyboardEvent& keyboardEvent, CompletionHandler<void()>&& callbackFunction)
{
if (!hasRunningProcess()) {
callbackFunction();
return;
}
sendWithAsyncReply(Messages::WebPage::SetInitialFocus(forward, isKeyboardEventValid, keyboardEvent), [callbackFunction = WTFMove(callbackFunction), backgroundActivity = m_process->throttler().backgroundActivity("WebPageProxy::setInitialFocus"_s)] () mutable {
callbackFunction();
});
}
void WebPageProxy::clearSelection()
{
if (!hasRunningProcess())
return;
send(Messages::WebPage::ClearSelection());
}
void WebPageProxy::restoreSelectionInFocusedEditableElement()
{
if (!hasRunningProcess())
return;
send(Messages::WebPage::RestoreSelectionInFocusedEditableElement());
}
void WebPageProxy::validateCommand(const String& commandName, CompletionHandler<void(bool, int32_t)>&& callbackFunction)
{
if (!hasRunningProcess())
return callbackFunction(false, 0);
sendWithAsyncReply(Messages::WebPage::ValidateCommand(commandName), WTFMove(callbackFunction));
}
void WebPageProxy::increaseListLevel()
{
if (!hasRunningProcess())
return;
send(Messages::WebPage::IncreaseListLevel());
}
void WebPageProxy::decreaseListLevel()
{
if (!hasRunningProcess())
return;
send(Messages::WebPage::DecreaseListLevel());
}
void WebPageProxy::changeListType()
{
if (!hasRunningProcess())
return;
send(Messages::WebPage::ChangeListType());
}
void WebPageProxy::setBaseWritingDirection(WritingDirection direction)
{
if (!hasRunningProcess())
return;
send(Messages::WebPage::SetBaseWritingDirection(direction));
}
void WebPageProxy::updateFontAttributesAfterEditorStateChange()
{
m_cachedFontAttributesAtSelectionStart.reset();
if (m_editorState.isMissingPostLayoutData)
return;
if (auto fontAttributes = m_editorState.postLayoutData().fontAttributes) {
m_uiClient->didChangeFontAttributes(*fontAttributes);
m_cachedFontAttributesAtSelectionStart = WTFMove(fontAttributes);
}
}
void WebPageProxy::setNeedsFontAttributes(bool needsFontAttributes)
{
if (m_needsFontAttributes == needsFontAttributes)
return;
m_needsFontAttributes = needsFontAttributes;
if (hasRunningProcess())
send(Messages::WebPage::SetNeedsFontAttributes(needsFontAttributes));
}
bool WebPageProxy::maintainsInactiveSelection() const
{
// Regardless of what the client wants to do, keep selections if a local Inspector is open.
// Otherwise, there is no way to use the console to inspect the state of a selection.
if (inspector() && inspector()->isVisible())
return true;
return m_maintainsInactiveSelection;
}
void WebPageProxy::setMaintainsInactiveSelection(bool newValue)
{
m_maintainsInactiveSelection = newValue;
}
void WebPageProxy::scheduleFullEditorStateUpdate()
{
if (!hasRunningProcess())
return;
send(Messages::WebPage::ScheduleFullEditorStateUpdate());
}
void WebPageProxy::selectAll()
{
if (!hasRunningProcess())
return;
send(Messages::WebPage::SelectAll());
}
static std::optional<DOMPasteAccessCategory> pasteAccessCategoryForCommand(const String& commandName)
{
static NeverDestroyed<HashMap<String, DOMPasteAccessCategory, ASCIICaseInsensitiveHash>> pasteCommandNames = HashMap<String, DOMPasteAccessCategory, ASCIICaseInsensitiveHash> {
{ "Paste", DOMPasteAccessCategory::General },
{ "PasteAndMatchStyle", DOMPasteAccessCategory::General },
{ "PasteAsQuotation", DOMPasteAccessCategory::General },
{ "PasteAsPlainText", DOMPasteAccessCategory::General },
{ "PasteFont", DOMPasteAccessCategory::Fonts },
};
auto it = pasteCommandNames->find(commandName);
if (it != pasteCommandNames->end())
return it->value;
return std::nullopt;
}
void WebPageProxy::executeEditCommand(const String& commandName, const String& argument, CompletionHandler<void()>&& callbackFunction)
{
if (!hasRunningProcess()) {
callbackFunction();
return;
}
if (auto pasteAccessCategory = pasteAccessCategoryForCommand(commandName))
willPerformPasteCommand(*pasteAccessCategory);
sendWithAsyncReply(Messages::WebPage::ExecuteEditCommandWithCallback(commandName, argument), [callbackFunction = WTFMove(callbackFunction), backgroundActivity = m_process->throttler().backgroundActivity("WebPageProxy::executeEditCommand"_s)] () mutable {
callbackFunction();
});
}
void WebPageProxy::executeEditCommand(const String& commandName, const String& argument)
{
static NeverDestroyed<String> ignoreSpellingCommandName(MAKE_STATIC_STRING_IMPL("ignoreSpelling"));
if (!hasRunningProcess())
return;
if (auto pasteAccessCategory = pasteAccessCategoryForCommand(commandName))
willPerformPasteCommand(*pasteAccessCategory);
if (commandName == ignoreSpellingCommandName)
++m_pendingLearnOrIgnoreWordMessageCount;
send(Messages::WebPage::ExecuteEditCommand(commandName, argument));
}
void WebPageProxy::requestFontAttributesAtSelectionStart(CompletionHandler<void(const WebCore::FontAttributes&)>&& callback)
{
if (!hasRunningProcess())
return callback({ });
if (auto attributes = m_cachedFontAttributesAtSelectionStart) {
callback(*attributes);
return;
}
sendWithAsyncReply(Messages::WebPage::RequestFontAttributesAtSelectionStart(), [this, protectedThis = Ref { *this }, callback = WTFMove(callback)] (const WebCore::FontAttributes& attributes) mutable {
m_cachedFontAttributesAtSelectionStart = attributes;
callback(attributes);
});
}
void WebPageProxy::setEditable(bool editable)
{
if (editable == m_isEditable)
return;
m_isEditable = editable;
if (!hasRunningProcess())
return;
send(Messages::WebPage::SetEditable(editable));
}
void WebPageProxy::setMediaStreamCaptureMuted(bool muted)
{
auto state = m_mutedState;
if (muted)
state.add(WebCore::MediaProducer::MediaStreamCaptureIsMuted);
else
state.remove(WebCore::MediaProducer::MediaStreamCaptureIsMuted);
setMuted(state);
}
void WebPageProxy::activateMediaStreamCaptureInPage()
{
#if ENABLE(MEDIA_STREAM)
WebProcessProxy::muteCaptureInPagesExcept(m_webPageID);
#endif
setMediaStreamCaptureMuted(false);
}
#if !PLATFORM(IOS_FAMILY)
void WebPageProxy::didCommitLayerTree(const RemoteLayerTreeTransaction&)
{
}
void WebPageProxy::layerTreeCommitComplete()
{
}
#endif
void WebPageProxy::didUpdateRenderingAfterCommittingLoad()
{
if (m_hasUpdatedRenderingAfterDidCommitLoad)
return;
m_hasUpdatedRenderingAfterDidCommitLoad = true;
stopMakingViewBlankDueToLackOfRenderingUpdateIfNecessary();
}
void WebPageProxy::stopMakingViewBlankDueToLackOfRenderingUpdateIfNecessary()
{
if (!m_madeViewBlankDueToLackOfRenderingUpdate)
return;
ASSERT(m_hasUpdatedRenderingAfterDidCommitLoad);
WEBPAGEPROXY_RELEASE_LOG(Process, "stopMakingViewBlankDueToLackOfRenderingUpdateIfNecessary:");
pageClient().makeViewBlank(false);
m_madeViewBlankDueToLackOfRenderingUpdate = false;
}
// If we have not painted yet since the last load commit, then we are likely still displaying the previous page.
// Displaying a JS prompt for the new page with the old page behind would be confusing so we make the view blank
// until the next paint in such case.
void WebPageProxy::makeViewBlankIfUnpaintedSinceLastLoadCommit()
{
if (!m_hasUpdatedRenderingAfterDidCommitLoad) {
#if PLATFORM(COCOA)
static bool shouldMakeViewBlank = linkedOnOrAfter(SDKVersion::FirstWithBlankViewOnJSPrompt);
#else
static bool shouldMakeViewBlank = true;
#endif
if (shouldMakeViewBlank) {
WEBPAGEPROXY_RELEASE_LOG(Process, "makeViewBlankIfUnpaintedSinceLastLoadCommit: Making the view blank because of a JS prompt before the first paint for its page");
pageClient().makeViewBlank(true);
m_madeViewBlankDueToLackOfRenderingUpdate = true;
}
}
}
void WebPageProxy::discardQueuedMouseEvents()
{
while (m_mouseEventQueue.size() > 1)
m_mouseEventQueue.removeLast();
}
#if ENABLE(DRAG_SUPPORT)
void WebPageProxy::dragEntered(DragData& dragData, const String& dragStorageName)
{
#if PLATFORM(COCOA)
WebPasteboardProxy::singleton().grantAccessToCurrentTypes(m_process.get(), dragStorageName);
#endif
launchInitialProcessIfNecessary();
performDragControllerAction(DragControllerAction::Entered, dragData, dragStorageName, { }, { });
}
void WebPageProxy::dragUpdated(DragData& dragData, const String& dragStorageName)
{
#if PLATFORM(COCOA)
WebPasteboardProxy::singleton().grantAccessToCurrentTypes(m_process.get(), dragStorageName);
#endif
performDragControllerAction(DragControllerAction::Updated, dragData, dragStorageName, { }, { });
}
void WebPageProxy::dragExited(DragData& dragData, const String& dragStorageName)
{
performDragControllerAction(DragControllerAction::Exited, dragData, dragStorageName, { }, { });
}
void WebPageProxy::performDragOperation(DragData& dragData, const String& dragStorageName, SandboxExtension::Handle&& sandboxExtensionHandle, Vector<SandboxExtension::Handle>&& sandboxExtensionsForUpload)
{
performDragControllerAction(DragControllerAction::PerformDragOperation, dragData, dragStorageName, WTFMove(sandboxExtensionHandle), WTFMove(sandboxExtensionsForUpload));
}
void WebPageProxy::performDragControllerAction(DragControllerAction action, DragData& dragData, const String& dragStorageName, SandboxExtension::Handle&& sandboxExtensionHandle, Vector<SandboxExtension::Handle>&& sandboxExtensionsForUpload)
{
if (!hasRunningProcess())
return;
#if PLATFORM(GTK)
UNUSED_PARAM(dragStorageName);
UNUSED_PARAM(sandboxExtensionHandle);
UNUSED_PARAM(sandboxExtensionsForUpload);
String url = dragData.asURL();
if (!url.isEmpty())
m_process->assumeReadAccessToBaseURL(*this, url);
ASSERT(dragData.platformData());
send(Messages::WebPage::PerformDragControllerAction(action, dragData.clientPosition(), dragData.globalPosition(), dragData.draggingSourceOperationMask(), *dragData.platformData(), dragData.flags()));
#else
send(Messages::WebPage::PerformDragControllerAction(action, dragData, sandboxExtensionHandle, sandboxExtensionsForUpload));
#endif
}
void WebPageProxy::didPerformDragControllerAction(std::optional<WebCore::DragOperation> dragOperation, WebCore::DragHandlingMethod dragHandlingMethod, bool mouseIsOverFileInput, unsigned numberOfItemsToBeAccepted, const IntRect& insertionRect, const IntRect& editableElementRect)
{
m_currentDragOperation = dragOperation;
m_currentDragHandlingMethod = dragHandlingMethod;
m_currentDragIsOverFileInput = mouseIsOverFileInput;
m_currentDragNumberOfFilesToBeAccepted = numberOfItemsToBeAccepted;
m_currentDragCaretEditableElementRect = editableElementRect;
setDragCaretRect(insertionRect);
pageClient().didPerformDragControllerAction();
}
#if PLATFORM(GTK)
void WebPageProxy::startDrag(SelectionData&& selectionData, OptionSet<WebCore::DragOperation> dragOperationMask, const ShareableBitmap::Handle& dragImageHandle)
{
RefPtr<ShareableBitmap> dragImage = !dragImageHandle.isNull() ? ShareableBitmap::create(dragImageHandle) : nullptr;
pageClient().startDrag(WTFMove(selectionData), dragOperationMask, WTFMove(dragImage));
didStartDrag();
}
#endif
void WebPageProxy::dragEnded(const IntPoint& clientPosition, const IntPoint& globalPosition, OptionSet<WebCore::DragOperation> dragOperationMask)
{
if (!hasRunningProcess())
return;
send(Messages::WebPage::DragEnded(clientPosition, globalPosition, dragOperationMask));
setDragCaretRect({ });
}
void WebPageProxy::didPerformDragOperation(bool handled)
{
pageClient().didPerformDragOperation(handled);
}
void WebPageProxy::didStartDrag()
{
if (!hasRunningProcess())
return;
discardQueuedMouseEvents();
send(Messages::WebPage::DidStartDrag());
}
void WebPageProxy::dragCancelled()
{
if (hasRunningProcess())
send(Messages::WebPage::DragCancelled());
}
void WebPageProxy::didEndDragging()
{
resetCurrentDragInformation();
}
void WebPageProxy::resetCurrentDragInformation()
{
m_currentDragOperation = std::nullopt;
m_currentDragHandlingMethod = DragHandlingMethod::None;
m_currentDragIsOverFileInput = false;
m_currentDragNumberOfFilesToBeAccepted = 0;
setDragCaretRect({ });
}
#if !PLATFORM(IOS_FAMILY) || !ENABLE(DRAG_SUPPORT)
void WebPageProxy::setDragCaretRect(const IntRect& dragCaretRect)
{
m_currentDragCaretRect = dragCaretRect;
}
#endif
#endif // ENABLE(DRAG_SUPPORT)
static bool removeOldRedundantEvent(Deque<NativeWebMouseEvent>& queue, WebEvent::Type incomingEventType)
{
if (incomingEventType != WebEvent::MouseMove && incomingEventType != WebEvent::MouseForceChanged)
return false;
auto it = queue.rbegin();
auto end = queue.rend();
// Must not remove the first event in the deque, since it is already being dispatched.
if (it != end)
--end;
for (; it != end; ++it) {
auto type = it->type();
if (type == incomingEventType) {
queue.remove(--it.base());
return true;
}
if (type != WebEvent::MouseMove && type != WebEvent::MouseForceChanged)
break;
}
return false;
}
void WebPageProxy::handleMouseEvent(const NativeWebMouseEvent& event)
{
if (event.type() == WebEvent::MouseDown)
launchInitialProcessIfNecessary();
if (!hasRunningProcess())
return;
#if ENABLE(ASYNC_SCROLLING) && PLATFORM(COCOA)
if (m_scrollingCoordinatorProxy)
m_scrollingCoordinatorProxy->handleMouseEvent(platform(event));
#endif
// If we receive multiple mousemove or mouseforcechanged events and the most recent mousemove or mouseforcechanged event
// (respectively) has not yet been sent to WebProcess for processing, remove the pending mouse event and insert the new
// event in the queue.
bool didRemoveEvent = removeOldRedundantEvent(m_mouseEventQueue, event.type());
m_mouseEventQueue.append(event);
#if LOG_DISABLED
UNUSED_PARAM(didRemoveEvent);
#else
LOG(MouseHandling, "UIProcess: %s mouse event %s (queue size %zu)", didRemoveEvent ? "replaced" : "enqueued", webMouseEventTypeString(event.type()), m_mouseEventQueue.size());
#endif
if (m_mouseEventQueue.size() == 1) // Otherwise, called from DidReceiveEvent message handler.
processNextQueuedMouseEvent();
}
void WebPageProxy::processNextQueuedMouseEvent()
{
if (!hasRunningProcess())
return;
ASSERT(!m_mouseEventQueue.isEmpty());
const NativeWebMouseEvent& event = m_mouseEventQueue.first();
if (pageClient().windowIsFrontWindowUnderMouse(event))
setToolTip(String());
WebEvent::Type eventType = event.type();
if (eventType == WebEvent::MouseDown || eventType == WebEvent::MouseForceChanged || eventType == WebEvent::MouseForceDown)
m_process->startResponsivenessTimer(WebProcessProxy::UseLazyStop::Yes);
else if (eventType != WebEvent::MouseMove) {
// NOTE: This does not start the responsiveness timer because mouse move should not indicate interaction.
m_process->startResponsivenessTimer();
}
std::optional<Vector<SandboxExtension::Handle>> sandboxExtensions;
#if PLATFORM(MAC)
bool eventMayStartDrag = !m_currentDragOperation && eventType == WebEvent::MouseMove && event.button() != WebMouseEvent::Button::NoButton;
if (eventMayStartDrag)
sandboxExtensions = SandboxExtension::createHandlesForMachLookup({ "com.apple.iconservices"_s, "com.apple.iconservices.store"_s }, std::nullopt);
#endif
LOG(MouseHandling, "UIProcess: sent mouse event %s (queue size %zu)", webMouseEventTypeString(eventType), m_mouseEventQueue.size());
send(Messages::WebPage::MouseEvent(event, sandboxExtensions));
}
void WebPageProxy::doAfterProcessingAllPendingMouseEvents(WTF::Function<void ()>&& action)
{
if (!isProcessingMouseEvents()) {
action();
return;
}
m_callbackHandlersAfterProcessingPendingMouseEvents.append(WTFMove(action));
}
void WebPageProxy::didFinishProcessingAllPendingMouseEvents()
{
flushPendingMouseEventCallbacks();
}
void WebPageProxy::flushPendingMouseEventCallbacks()
{
for (auto& callback : m_callbackHandlersAfterProcessingPendingMouseEvents)
callback();
m_callbackHandlersAfterProcessingPendingMouseEvents.clear();
}
void WebPageProxy::dispatchWheelEventWithoutScrolling(const WebWheelEvent& event, CompletionHandler<void(bool)>&& completionHandler)
{
sendWithAsyncReply(Messages::WebPage::DispatchWheelEventWithoutScrolling(event), WTFMove(completionHandler));
}
void WebPageProxy::handleWheelEvent(const NativeWebWheelEvent& event)
{
#if ENABLE(ASYNC_SCROLLING) && PLATFORM(COCOA)
if (m_scrollingCoordinatorProxy && m_scrollingCoordinatorProxy->handleWheelEvent(platform(event)))
return;
#endif
if (!hasRunningProcess())
return;
closeOverlayedViews();
#if ENABLE(MOMENTUM_EVENT_DISPATCHER)
// FIXME: We should not have to look this up repeatedly, but it can also change occasionally.
if (event.momentumPhase() == WebWheelEvent::PhaseBegan && preferences().momentumScrollingAnimatorEnabled())
m_scrollingAccelerationCurve = ScrollingAccelerationCurve::fromNativeWheelEvent(event);
#endif
if (wheelEventCoalescer().shouldDispatchEvent(event)) {
auto event = wheelEventCoalescer().nextEventToDispatch();
sendWheelEvent(*event);
}
}
#if HAVE(CVDISPLAYLINK)
void WebPageProxy::wheelEventHysteresisUpdated(PAL::HysteresisState)
{
updateDisplayLinkFrequency();
}
void WebPageProxy::updateDisplayLinkFrequency()
{
if (!m_process->hasConnection() || !m_displayID)
return;
bool wantsFullSpeedUpdates = m_hasActiveAnimatedScroll || m_wheelEventActivityHysteresis.state() == PAL::HysteresisState::Started;
if (wantsFullSpeedUpdates != m_registeredForFullSpeedUpdates) {
process().processPool().setDisplayLinkForDisplayWantsFullSpeedUpdates(*m_process->connection(), *m_displayID, wantsFullSpeedUpdates);
m_registeredForFullSpeedUpdates = wantsFullSpeedUpdates;
}
}
#endif
void WebPageProxy::updateWheelEventActivityAfterProcessSwap()
{
#if HAVE(CVDISPLAYLINK)
updateDisplayLinkFrequency();
#endif
}
void WebPageProxy::sendWheelEvent(const WebWheelEvent& event)
{
#if HAVE(CVDISPLAYLINK)
m_wheelEventActivityHysteresis.impulse();
#endif
auto* connection = messageSenderConnection();
if (!connection)
return;
auto rubberBandableEdges = this->rubberBandableEdges();
if (shouldUseImplicitRubberBandControl()) {
rubberBandableEdges.setLeft(!m_backForwardList->backItem());
rubberBandableEdges.setRight(!m_backForwardList->forwardItem());
}
#if ENABLE(MOMENTUM_EVENT_DISPATCHER)
if (event.momentumPhase() == WebWheelEvent::PhaseBegan && m_scrollingAccelerationCurve != m_lastSentScrollingAccelerationCurve) {
connection->send(Messages::EventDispatcher::SetScrollingAccelerationCurve(m_webPageID, m_scrollingAccelerationCurve), 0, { }, Thread::QOS::UserInteractive);
m_lastSentScrollingAccelerationCurve = m_scrollingAccelerationCurve;
}
#endif
connection->send(Messages::EventDispatcher::WheelEvent(m_webPageID, event, rubberBandableEdges), 0, { }, Thread::QOS::UserInteractive);
// Manually ping the web process to check for responsiveness since our wheel
// event will dispatch to a non-main thread, which always responds.
m_process->isResponsiveWithLazyStop();
}
WebWheelEventCoalescer& WebPageProxy::wheelEventCoalescer()
{
if (!m_wheelEventCoalescer)
m_wheelEventCoalescer = makeUnique<WebWheelEventCoalescer>();
return *m_wheelEventCoalescer;
}
bool WebPageProxy::hasQueuedKeyEvent() const
{
return !m_keyEventQueue.isEmpty();
}
const NativeWebKeyboardEvent& WebPageProxy::firstQueuedKeyEvent() const
{
return m_keyEventQueue.first();
}
bool WebPageProxy::handleKeyboardEvent(const NativeWebKeyboardEvent& event)
{
if (!hasRunningProcess())
return false;
LOG(KeyHandling, "WebPageProxy::handleKeyboardEvent: %s", webKeyboardEventTypeString(event.type()));
m_keyEventQueue.append(event);
m_process->startResponsivenessTimer(event.type() == WebEvent::KeyDown ? WebProcessProxy::UseLazyStop::Yes : WebProcessProxy::UseLazyStop::No);
if (m_keyEventQueue.size() == 1) { // Otherwise, sent from DidReceiveEvent message handler.
LOG(KeyHandling, " UI process: sent keyEvent from handleKeyboardEvent");
send(Messages::WebPage::KeyEvent(event));
}
return true;
}
WebPreferencesStore WebPageProxy::preferencesStore() const
{
return m_preferences->store();
}
#if ENABLE(TOUCH_EVENTS)
static TrackingType mergeTrackingTypes(TrackingType a, TrackingType b)
{
if (static_cast<uintptr_t>(b) > static_cast<uintptr_t>(a))
return b;
return a;
}
void WebPageProxy::updateTouchEventTracking(const WebTouchEvent& touchStartEvent)
{
#if ENABLE(ASYNC_SCROLLING) && PLATFORM(COCOA)
const EventNames& names = eventNames();
for (auto& touchPoint : touchStartEvent.touchPoints()) {
IntPoint location = touchPoint.location();
auto updateTrackingType = [this, location](TrackingType& trackingType, const AtomString& eventName) {
if (trackingType == TrackingType::Synchronous)
return;
TrackingType trackingTypeForLocation = m_scrollingCoordinatorProxy->eventTrackingTypeForPoint(eventName, location);
trackingType = mergeTrackingTypes(trackingType, trackingTypeForLocation);
};
updateTrackingType(m_touchAndPointerEventTracking.touchForceChangedTracking, names.touchforcechangeEvent);
updateTrackingType(m_touchAndPointerEventTracking.touchStartTracking, names.touchstartEvent);
updateTrackingType(m_touchAndPointerEventTracking.touchMoveTracking, names.touchmoveEvent);
updateTrackingType(m_touchAndPointerEventTracking.touchEndTracking, names.touchendEvent);
updateTrackingType(m_touchAndPointerEventTracking.touchStartTracking, names.pointeroverEvent);
updateTrackingType(m_touchAndPointerEventTracking.touchStartTracking, names.pointerenterEvent);
updateTrackingType(m_touchAndPointerEventTracking.touchStartTracking, names.pointerdownEvent);
updateTrackingType(m_touchAndPointerEventTracking.touchMoveTracking, names.pointermoveEvent);
updateTrackingType(m_touchAndPointerEventTracking.touchEndTracking, names.pointerupEvent);
updateTrackingType(m_touchAndPointerEventTracking.touchEndTracking, names.pointeroutEvent);
updateTrackingType(m_touchAndPointerEventTracking.touchEndTracking, names.pointerleaveEvent);
updateTrackingType(m_touchAndPointerEventTracking.touchStartTracking, names.mousedownEvent);
updateTrackingType(m_touchAndPointerEventTracking.touchMoveTracking, names.mousemoveEvent);
updateTrackingType(m_touchAndPointerEventTracking.touchEndTracking, names.mouseupEvent);
}
#else
UNUSED_PARAM(touchStartEvent);
m_touchAndPointerEventTracking.touchForceChangedTracking = TrackingType::Synchronous;
m_touchAndPointerEventTracking.touchStartTracking = TrackingType::Synchronous;
m_touchAndPointerEventTracking.touchMoveTracking = TrackingType::Synchronous;
m_touchAndPointerEventTracking.touchEndTracking = TrackingType::Synchronous;
#endif // ENABLE(ASYNC_SCROLLING)
}
TrackingType WebPageProxy::touchEventTrackingType(const WebTouchEvent& touchStartEvent) const
{
// We send all events if any type is needed, we just do it asynchronously for the types that are not tracked.
//
// Touch events define a sequence with strong dependencies. For example, we can expect
// a TouchMove to only appear after a TouchStart, and the ids of the touch points is consistent between
// the two.
//
// WebCore should not have to set up its state correctly after some events were dismissed.
// For example, we don't want to send a TouchMoved without a TouchPressed.
// We send everything, WebCore updates its internal state and dispatch what is needed to the page.
TrackingType globalTrackingType = m_touchAndPointerEventTracking.isTrackingAnything() ? TrackingType::Asynchronous : TrackingType::NotTracking;
globalTrackingType = mergeTrackingTypes(globalTrackingType, m_touchAndPointerEventTracking.touchForceChangedTracking);
for (auto& touchPoint : touchStartEvent.touchPoints()) {
switch (touchPoint.state()) {
case WebPlatformTouchPoint::TouchReleased:
globalTrackingType = mergeTrackingTypes(globalTrackingType, m_touchAndPointerEventTracking.touchEndTracking);
break;
case WebPlatformTouchPoint::TouchPressed:
globalTrackingType = mergeTrackingTypes(globalTrackingType, m_touchAndPointerEventTracking.touchStartTracking);
break;
case WebPlatformTouchPoint::TouchMoved:
case WebPlatformTouchPoint::TouchStationary:
globalTrackingType = mergeTrackingTypes(globalTrackingType, m_touchAndPointerEventTracking.touchMoveTracking);
break;
case WebPlatformTouchPoint::TouchCancelled:
globalTrackingType = mergeTrackingTypes(globalTrackingType, TrackingType::Asynchronous);
break;
}
}
return globalTrackingType;
}
#endif
#if ENABLE(MAC_GESTURE_EVENTS)
void WebPageProxy::handleGestureEvent(const NativeWebGestureEvent& event)
{
if (!hasRunningProcess())
return;
m_gestureEventQueue.append(event);
// FIXME: Consider doing some coalescing here.
m_process->startResponsivenessTimer((event.type() == WebEvent::GestureStart || event.type() == WebEvent::GestureChange) ? WebProcessProxy::UseLazyStop::Yes : WebProcessProxy::UseLazyStop::No);
send(Messages::EventDispatcher::GestureEvent(m_webPageID, event), 0);
}
#endif
#if ENABLE(IOS_TOUCH_EVENTS)
void WebPageProxy::handlePreventableTouchEvent(NativeWebTouchEvent& event)
{
if (!hasRunningProcess())
return;
TraceScope scope(SyncTouchEventStart, SyncTouchEventEnd);
updateTouchEventTracking(event);
auto handleAllTouchPointsReleased = WTF::makeScopeExit([&] {
if (!event.allTouchPointsAreReleased())
return;
m_touchAndPointerEventTracking.reset();
didReleaseAllTouchPoints();
});
bool isTouchStart = event.type() == WebEvent::TouchStart;
bool isTouchEnd = event.type() == WebEvent::TouchEnd;
TrackingType touchEventsTrackingType = touchEventTrackingType(event);
if (touchEventsTrackingType == TrackingType::NotTracking) {
if (isTouchStart)
pageClient().doneDeferringTouchStart(false);
if (isTouchEnd)
pageClient().doneDeferringTouchEnd(false);
return;
}
if (touchEventsTrackingType == TrackingType::Asynchronous) {
// We can end up here if a native gesture has not started but the event handlers are passive.
//
// The client of WebPageProxy asks the event to be sent synchronously since the touch event
// can prevent a native gesture.
// But, here we know that all events handlers that can handle this events are passive.
// We can use asynchronous dispatch and pretend to the client that the page does nothing with the events.
event.setCanPreventNativeGestures(false);
handleUnpreventableTouchEvent(event);
didReceiveEvent(event.type(), false);
if (isTouchStart)
pageClient().doneDeferringTouchStart(false);
if (isTouchEnd)
pageClient().doneDeferringTouchEnd(false);
return;
}
if (isTouchStart || isTouchEnd) {
if (isTouchStart)
++m_handlingPreventableTouchStartCount;
if (isTouchEnd)
++m_handlingPreventableTouchEndCount;
sendWithAsyncReply(Messages::EventDispatcher::TouchEvent(m_webPageID, event), [this, weakThis = WeakPtr { *this }, event] (bool handled) {
RefPtr protectedThis { weakThis.get() };
if (!protectedThis)
return;
bool didFinishDeferringTouchStart = false;
ASSERT_IMPLIES(event.type() == WebEvent::TouchStart, m_handlingPreventableTouchStartCount);
if (event.type() == WebEvent::TouchStart && m_handlingPreventableTouchStartCount)
didFinishDeferringTouchStart = !--m_handlingPreventableTouchStartCount;
bool didFinishDeferringTouchEnd = false;
ASSERT_IMPLIES(event.type() == WebEvent::TouchEnd, m_handlingPreventableTouchEndCount);
if (event.type() == WebEvent::TouchEnd && m_handlingPreventableTouchEndCount)
didFinishDeferringTouchEnd = !--m_handlingPreventableTouchEndCount;
bool handledOrFailedWithError = handled || m_handledSynchronousTouchEventWhileDispatchingPreventableTouchStart;
if (!isHandlingPreventableTouchStart())
m_handledSynchronousTouchEventWhileDispatchingPreventableTouchStart = false;
didReceiveEvent(event.type(), handledOrFailedWithError);
if (!m_pageClient)
return;
pageClient().doneWithTouchEvent(event, handledOrFailedWithError);
if (didFinishDeferringTouchStart)
pageClient().doneDeferringTouchStart(handledOrFailedWithError);
if (didFinishDeferringTouchEnd)
pageClient().doneDeferringTouchEnd(handledOrFailedWithError);
});
return;
}
m_process->startResponsivenessTimer();
bool handled = false;
bool replyReceived = !!sendSync(Messages::WebPage::TouchEventSync(event), Messages::WebPage::TouchEventSync::Reply(handled), 1_s, IPC::SendSyncOption::ForceDispatchWhenDestinationIsWaitingForUnboundedSyncReply);
// If the sync request has timed out, we should consider the event handled. The Web Process is too busy to answer any questions, so the default action is also likely to have issues.
if (!replyReceived)
handled = true;
didReceiveEvent(event.type(), handled);
pageClient().doneWithTouchEvent(event, handled);
if (!isHandlingPreventableTouchStart())
pageClient().doneDeferringTouchStart(handled);
else if (handled)
m_handledSynchronousTouchEventWhileDispatchingPreventableTouchStart = true;
m_process->stopResponsivenessTimer();
}
void WebPageProxy::resetPotentialTapSecurityOrigin()
{
if (!hasRunningProcess())
return;
send(Messages::WebPage::ResetPotentialTapSecurityOrigin());
}
void WebPageProxy::handleUnpreventableTouchEvent(const NativeWebTouchEvent& event)
{
if (!hasRunningProcess())
return;
TrackingType touchEventsTrackingType = touchEventTrackingType(event);
if (touchEventsTrackingType == TrackingType::NotTracking)
return;
send(Messages::EventDispatcher::TouchEventWithoutCallback(m_webPageID, event), 0);
if (event.allTouchPointsAreReleased()) {
m_touchAndPointerEventTracking.reset();
didReleaseAllTouchPoints();
}
}
#elif ENABLE(TOUCH_EVENTS)
void WebPageProxy::handleTouchEvent(const NativeWebTouchEvent& event)
{
if (!hasRunningProcess())
return;
updateTouchEventTracking(event);
if (touchEventTrackingType(event) == TrackingType::NotTracking)
return;
// If the page is suspended, which should be the case during panning, pinching
// and animation on the page itself (kinetic scrolling, tap to zoom) etc, then
// we do not send any of the events to the page even if is has listeners.
if (!m_areActiveDOMObjectsAndAnimationsSuspended) {
m_touchEventQueue.append(event);
m_process->startResponsivenessTimer();
send(Messages::WebPage::TouchEvent(event));
} else {
if (m_touchEventQueue.isEmpty()) {
bool isEventHandled = false;
pageClient().doneWithTouchEvent(event, isEventHandled);
} else {
// We attach the incoming events to the newest queued event so that all
// the events are delivered in the correct order when the event is dequed.
QueuedTouchEvents& lastEvent = m_touchEventQueue.last();
lastEvent.deferredTouchEvents.append(event);
}
}
if (event.allTouchPointsAreReleased()) {
m_touchAndPointerEventTracking.reset();
didReleaseAllTouchPoints();
}
}
#endif // ENABLE(TOUCH_EVENTS)
void WebPageProxy::cancelPointer(WebCore::PointerID pointerId, const WebCore::IntPoint& documentPoint)
{
send(Messages::WebPage::CancelPointer(pointerId, documentPoint));
}
void WebPageProxy::touchWithIdentifierWasRemoved(WebCore::PointerID pointerId)
{
send(Messages::WebPage::TouchWithIdentifierWasRemoved(pointerId));
}
void WebPageProxy::scrollBy(ScrollDirection direction, ScrollGranularity granularity)
{
if (!hasRunningProcess())
return;
send(Messages::WebPage::ScrollBy(direction, granularity));
}
void WebPageProxy::centerSelectionInVisibleArea()
{
if (!hasRunningProcess())
return;
send(Messages::WebPage::CenterSelectionInVisibleArea());
}
class WebPageProxy::PolicyDecisionSender : public RefCounted<PolicyDecisionSender> {
public:
using SendFunction = CompletionHandler<void(PolicyDecision&&)>;
static Ref<PolicyDecisionSender> create(PolicyCheckIdentifier identifier, SendFunction&& sendFunction)
{
return adoptRef(*new PolicyDecisionSender(identifier, WTFMove(sendFunction)));
}
void send(PolicyDecision&& policyDecision)
{
if (m_sendFunction)
m_sendFunction(WTFMove(policyDecision));
}
PolicyCheckIdentifier identifier() { return m_identifier; }
private:
PolicyDecisionSender(PolicyCheckIdentifier identifier, SendFunction sendFunction)
: m_sendFunction(WTFMove(sendFunction))
, m_identifier(identifier)
{ }
SendFunction m_sendFunction;
PolicyCheckIdentifier m_identifier;
};
#if ENABLE(APP_BOUND_DOMAINS)
static bool shouldTreatURLProtocolAsAppBound(const URL& requestURL, bool isRunningTest)
{
return !isRunningTest
&& (SecurityOrigin::isLocalHostOrLoopbackIPAddress(requestURL.host())
|| requestURL.protocolIsAbout()
|| requestURL.protocolIsData()
|| requestURL.protocolIsBlob()
|| requestURL.isLocalFile()
|| requestURL.protocolIsJavaScript());
}
bool WebPageProxy::setIsNavigatingToAppBoundDomainAndCheckIfPermitted(bool isMainFrame, const URL& requestURL, std::optional<NavigatingToAppBoundDomain> isNavigatingToAppBoundDomain)
{
if (isFullWebBrowser()) {
if (hasProhibitedUsageStrings())
m_isNavigatingToAppBoundDomain = NavigatingToAppBoundDomain::No;
return true;
}
if (!isNavigatingToAppBoundDomain) {
m_isNavigatingToAppBoundDomain = std::nullopt;
return true;
}
if (m_ignoresAppBoundDomains)
return true;
if (isMainFrame && shouldTreatURLProtocolAsAppBound(requestURL, websiteDataStore().configuration().enableInAppBrowserPrivacyForTesting())) {
isNavigatingToAppBoundDomain = NavigatingToAppBoundDomain::Yes;
m_limitsNavigationsToAppBoundDomains = true;
}
if (m_limitsNavigationsToAppBoundDomains) {
if (*isNavigatingToAppBoundDomain == NavigatingToAppBoundDomain::No) {
if (isMainFrame)
return false;
m_isNavigatingToAppBoundDomain = NavigatingToAppBoundDomain::No;
return true;
}
m_isNavigatingToAppBoundDomain = NavigatingToAppBoundDomain::Yes;
} else {
if (m_hasExecutedAppBoundBehaviorBeforeNavigation)
return false;
m_isNavigatingToAppBoundDomain = NavigatingToAppBoundDomain::No;
}
return true;
}
void WebPageProxy::isNavigatingToAppBoundDomainTesting(CompletionHandler<void(bool)>&& completionHandler)
{
completionHandler(m_isNavigatingToAppBoundDomain && (*m_isNavigatingToAppBoundDomain == NavigatingToAppBoundDomain::Yes));
}
void WebPageProxy::isForcedIntoAppBoundModeTesting(CompletionHandler<void(bool)>&& completionHandler)
{
completionHandler(m_limitsNavigationsToAppBoundDomains);
}
#endif
void WebPageProxy::disableServiceWorkerEntitlementInNetworkProcess()
{
#if ENABLE(APP_BOUND_DOMAINS) && !PLATFORM(MACCATALYST)
websiteDataStore().networkProcess().send(Messages::NetworkProcess::DisableServiceWorkerEntitlement(), 0);
#endif
}
void WebPageProxy::clearServiceWorkerEntitlementOverride(CompletionHandler<void()>&& completionHandler)
{
#if ENABLE(APP_BOUND_DOMAINS) && !PLATFORM(MACCATALYST)
auto callbackAggregator = CallbackAggregator::create(WTFMove(completionHandler));
sendWithAsyncReply(Messages::WebPage::ClearServiceWorkerEntitlementOverride(), [callbackAggregator] { });
websiteDataStore().networkProcess().sendWithAsyncReply(Messages::NetworkProcess::ClearServiceWorkerEntitlementOverride(), [callbackAggregator] { });
#else
completionHandler();
#endif
}
void WebPageProxy::receivedNavigationPolicyDecision(PolicyAction policyAction, API::Navigation* navigation, Ref<API::NavigationAction>&& navigationAction, ProcessSwapRequestedByClient processSwapRequestedByClient, WebFrameProxy& frame, const FrameInfoData& frameInfo, RefPtr<API::WebsitePolicies>&& policies, Ref<PolicyDecisionSender>&& sender)
{
WEBPAGEPROXY_RELEASE_LOG(Loading, "receivedNavigationPolicyDecision: frameID=%llu, navigationID=%llu, policyAction=%u", frame.frameID().toUInt64(), navigation ? navigation->navigationID() : 0, (unsigned)policyAction);
Ref<WebsiteDataStore> websiteDataStore = m_websiteDataStore.copyRef();
if (policies) {
if (policies->websiteDataStore() && policies->websiteDataStore() != websiteDataStore.ptr()) {
websiteDataStore = *policies->websiteDataStore();
processSwapRequestedByClient = ProcessSwapRequestedByClient::Yes;
}
if (policies->userContentController() && policies->userContentController() != m_userContentController.ptr())
processSwapRequestedByClient = ProcessSwapRequestedByClient::Yes;
}
if (navigation && !navigation->userContentExtensionsEnabled()) {
if (!policies)
policies = API::WebsitePolicies::create();
policies->setContentBlockersEnabled(false);
}
#if ENABLE(DEVICE_ORIENTATION)
if (navigation && (!policies || policies->deviceOrientationAndMotionAccessState() == WebCore::DeviceOrientationOrMotionPermissionState::Prompt)) {
auto deviceOrientationPermission = websiteDataStore->deviceOrientationAndMotionAccessController().cachedDeviceOrientationPermission(SecurityOriginData::fromURL(navigation->currentRequest().url()));
if (deviceOrientationPermission != WebCore::DeviceOrientationOrMotionPermissionState::Prompt) {
if (!policies)
policies = API::WebsitePolicies::create();
policies->setDeviceOrientationAndMotionAccessState(deviceOrientationPermission);
}
}
#endif
#if PLATFORM(COCOA)
static const bool forceDownloadFromDownloadAttribute = false;
#else
static const bool forceDownloadFromDownloadAttribute = true;
#endif
if (policyAction == PolicyAction::Use && navigation && (navigation->isSystemPreview() || (forceDownloadFromDownloadAttribute && navigation->shouldPerformDownload())))
policyAction = PolicyAction::Download;
if (policyAction != PolicyAction::Use || !frame.isMainFrame() || !navigation) {
receivedPolicyDecision(policyAction, navigation, WTFMove(policies), WTFMove(navigationAction), WTFMove(sender));
return;
}
Ref<WebProcessProxy> sourceProcess = process();
URL sourceURL = URL { URL(), pageLoadState().url() };
if (auto* provisionalPage = provisionalPageProxy()) {
if (provisionalPage->navigationID() == navigation->navigationID()) {
sourceProcess = provisionalPage->process();
sourceURL = provisionalPage->provisionalURL();
}
}
m_isCaptivePortalModeExplicitlySet = (policies && policies->isCaptivePortalModeExplicitlySet()) || m_configuration->isCaptivePortalModeExplicitlySet();
auto captivePortalMode = (policies ? policies->captivePortalModeEnabled() : shouldEnableCaptivePortalMode()) ? WebProcessProxy::CaptivePortalMode::Enabled : WebProcessProxy::CaptivePortalMode::Disabled;
process().processPool().processForNavigation(*this, *navigation, sourceProcess.copyRef(), sourceURL, processSwapRequestedByClient, captivePortalMode, frameInfo, WTFMove(websiteDataStore), [this, protectedThis = Ref { *this }, policyAction, navigation = Ref { *navigation }, navigationAction = WTFMove(navigationAction), sourceProcess = sourceProcess.copyRef(),
policies = WTFMove(policies), sender = WTFMove(sender), processSwapRequestedByClient] (Ref<WebProcessProxy>&& processForNavigation, SuspendedPageProxy* destinationSuspendedPage, const String& reason) mutable {
// If the navigation has been destroyed, then no need to proceed.
if (isClosed() || !navigationState().hasNavigation(navigation->navigationID())) {
receivedPolicyDecision(policyAction, navigation.ptr(), WTFMove(policies), WTFMove(navigationAction), WTFMove(sender));
return;
}
bool shouldProcessSwap = processForNavigation.ptr() != sourceProcess.ptr();
if (shouldProcessSwap) {
policyAction = PolicyAction::StopAllLoads;
WEBPAGEPROXY_RELEASE_LOG(ProcessSwapping, "decidePolicyForNavigationAction, swapping process %i with process %i for navigation, reason=%" PUBLIC_LOG_STRING, processIdentifier(), processForNavigation->processIdentifier(), reason.utf8().data());
LOG(ProcessSwapping, "(ProcessSwapping) Switching from process %i to new process (%i) for navigation %" PRIu64 " '%s'", processIdentifier(), processForNavigation->processIdentifier(), navigation->navigationID(), navigation->loggingString());
} else
WEBPAGEPROXY_RELEASE_LOG(ProcessSwapping, "decidePolicyForNavigationAction: keep using process %i for navigation, reason=%" PUBLIC_LOG_STRING, processIdentifier(), reason.utf8().data());
std::optional<SandboxExtension::Handle> optionalHandle;
if (shouldProcessSwap) {
// Make sure the process to be used for the navigation does not get shutDown now due to destroying SuspendedPageProxy or ProvisionalPageProxy objects.
auto preventNavigationProcessShutdown = processForNavigation->shutdownPreventingScope();
ASSERT(!destinationSuspendedPage || navigation->targetItem());
auto suspendedPage = destinationSuspendedPage ? backForwardCache().takeSuspendedPage(*navigation->targetItem()) : nullptr;
// It is difficult to get history right if we have several WebPage objects inside a single WebProcess for the same WebPageProxy. As a result, if we make sure to
// clear any SuspendedPageProxy for the current page that are backed by the destination process before we proceed with the navigation. This makes sure the WebPage
// we are about to create in the destination process will be the only one associated with this WebPageProxy.
if (!destinationSuspendedPage)
backForwardCache().removeEntriesForPageAndProcess(*this, processForNavigation);
ASSERT(suspendedPage.get() == destinationSuspendedPage);
if (suspendedPage && suspendedPage->pageIsClosedOrClosing())
suspendedPage = nullptr;
continueNavigationInNewProcess(navigation, WTFMove(suspendedPage), WTFMove(processForNavigation), processSwapRequestedByClient, ShouldTreatAsContinuingLoad::YesAfterNavigationPolicyDecision, std::exchange(policies, nullptr));
} else {
auto item = navigation->reloadItem() ? navigation->reloadItem() : navigation->targetItem();
if (policyAction == PolicyAction::Use && item) {
auto fullURL = URL { URL(), item->url() };
if (fullURL.protocolIs("file"_s)) {
SandboxExtension::Handle sandboxExtensionHandle;
maybeInitializeSandboxExtensionHandle(processForNavigation.get(), fullURL, item->resourceDirectoryURL(), sandboxExtensionHandle);
optionalHandle = WTFMove(sandboxExtensionHandle);
}
}
}
receivedPolicyDecision(policyAction, navigation.ptr(), shouldProcessSwap ? nullptr : WTFMove(policies), WTFMove(navigationAction), WTFMove(sender), WTFMove(optionalHandle), shouldProcessSwap ? WillContinueLoadInNewProcess::Yes : WillContinueLoadInNewProcess::No);
});
}
void WebPageProxy::receivedPolicyDecision(PolicyAction action, API::Navigation* navigation, RefPtr<API::WebsitePolicies>&& websitePolicies, std::variant<Ref<API::NavigationResponse>, Ref<API::NavigationAction>>&& navigationActionOrResponse, Ref<PolicyDecisionSender>&& sender, std::optional<SandboxExtension::Handle> sandboxExtensionHandle, WillContinueLoadInNewProcess willContinueLoadInNewProcess)
{
if (!hasRunningProcess()) {
sender->send(PolicyDecision { sender->identifier(), isNavigatingToAppBoundDomain(), PolicyAction::Ignore, 0, std::nullopt, std::nullopt });
return;
}
auto transaction = m_pageLoadState.transaction();
if (action == PolicyAction::Ignore && willContinueLoadInNewProcess == WillContinueLoadInNewProcess::No && navigation && navigation->navigationID() == m_pageLoadState.pendingAPIRequest().navigationID)
m_pageLoadState.clearPendingAPIRequest(transaction);
std::optional<DownloadID> downloadID;
if (action == PolicyAction::Download) {
// Create a download proxy.
auto& download = m_process->processPool().createDownloadProxy(m_websiteDataStore, m_decidePolicyForResponseRequest, this, navigation ? navigation->originatingFrameInfo() : FrameInfoData { });
download.setDidStartCallback([this, weakThis = WeakPtr { *this }, navigationActionOrResponse = WTFMove(navigationActionOrResponse)] (auto* downloadProxy) {
if (!weakThis || !downloadProxy)
return;
WTF::switchOn(navigationActionOrResponse,
[&] (const Ref<API::NavigationResponse>& response) {
if (!response->downloadAttribute().isNull())
downloadProxy->setSuggestedFilename(response->downloadAttribute());
m_navigationClient->navigationResponseDidBecomeDownload(*this, response.get(), *downloadProxy);
}, [&] (const Ref<API::NavigationAction>& action) {
m_navigationClient->navigationActionDidBecomeDownload(*this, action.get(), *downloadProxy);
}
);
});
if (navigation) {
download.setWasUserInitiated(navigation->wasUserInitiated());
download.setRedirectChain(navigation->takeRedirectChain());
}
downloadID = download.downloadID();
handleDownloadRequest(download);
m_decidePolicyForResponseRequest = { };
}
std::optional<WebsitePoliciesData> websitePoliciesData;
if (websitePolicies)
websitePoliciesData = websitePolicies->data();
sender->send(PolicyDecision { sender->identifier(), isNavigatingToAppBoundDomain(), action, navigation ? navigation->navigationID() : 0, downloadID, WTFMove(websitePoliciesData), WTFMove(sandboxExtensionHandle) });
}
void WebPageProxy::commitProvisionalPage(FrameIdentifier frameID, FrameInfoData&& frameInfo, ResourceRequest&& request, uint64_t navigationID, const String& mimeType, bool frameHasCustomContentProvider, WebCore::FrameLoadType frameLoadType, const WebCore::CertificateInfo& certificateInfo, bool usedLegacyTLS, bool containsPluginDocument, std::optional<WebCore::HasInsecureContent> forcedHasInsecureContent, WebCore::MouseEventPolicy mouseEventPolicy, const UserData& userData)
{
ASSERT(m_provisionalPage);
WEBPAGEPROXY_RELEASE_LOG(Loading, "commitProvisionalPage: newPID=%i", m_provisionalPage->process().processIdentifier());
std::optional<FrameIdentifier> mainFrameIDInPreviousProcess = m_mainFrame ? std::make_optional(m_mainFrame->frameID()) : std::nullopt;
ASSERT(m_process.ptr() != &m_provisionalPage->process());
auto shouldDelayClosingUntilFirstLayerFlush = ShouldDelayClosingUntilFirstLayerFlush::No;
#if PLATFORM(MAC)
// On macOS, when not using UI-side compositing, we need to make sure we do not close the page in the previous process until we've
// entered accelerated compositing for the new page or we will flash on navigation.
if (drawingArea()->type() == DrawingAreaType::TiledCoreAnimation)
shouldDelayClosingUntilFirstLayerFlush = ShouldDelayClosingUntilFirstLayerFlush::Yes;
#endif
if (m_isLayerTreeFrozenDueToSwipeAnimation)
send(Messages::WebPage::UnfreezeLayerTreeDueToSwipeAnimation());
resetStateAfterProcessTermination(ProcessTerminationReason::NavigationSwap);
removeAllMessageReceivers();
auto* navigation = navigationState().navigation(m_provisionalPage->navigationID());
bool didSuspendPreviousPage = navigation && !m_provisionalPage->isProcessSwappingOnNavigationResponse() ? suspendCurrentPageIfPossible(*navigation, mainFrameIDInPreviousProcess, m_provisionalPage->processSwapRequestedByClient(), shouldDelayClosingUntilFirstLayerFlush) : false;
m_process->removeWebPage(*this, m_websiteDataStore.ptr() == &m_provisionalPage->process().websiteDataStore() ? WebProcessProxy::EndsUsingDataStore::No : WebProcessProxy::EndsUsingDataStore::Yes);
// There is no way we'll be able to return to the page in the previous page so close it.
if (!didSuspendPreviousPage)
send(Messages::WebPage::Close());
const auto oldWebPageID = m_webPageID;
swapToProvisionalPage(std::exchange(m_provisionalPage, nullptr));
didCommitLoadForFrame(frameID, WTFMove(frameInfo), WTFMove(request), navigationID, mimeType, frameHasCustomContentProvider, frameLoadType, certificateInfo, usedLegacyTLS, containsPluginDocument, forcedHasInsecureContent, mouseEventPolicy, userData);
m_inspectorController->didCommitProvisionalPage(oldWebPageID, m_webPageID);
}
void WebPageProxy::destroyProvisionalPage()
{
m_provisionalPage = nullptr;
}
void WebPageProxy::continueNavigationInNewProcess(API::Navigation& navigation, std::unique_ptr<SuspendedPageProxy>&& suspendedPage, Ref<WebProcessProxy>&& newProcess, ProcessSwapRequestedByClient processSwapRequestedByClient, ShouldTreatAsContinuingLoad shouldTreatAsContinuingLoad, RefPtr<API::WebsitePolicies>&& websitePolicies, std::optional<NetworkResourceLoadIdentifier> existingNetworkResourceLoadIdentifierToResume)
{
WEBPAGEPROXY_RELEASE_LOG(Loading, "continueNavigationInNewProcess: newProcessPID=%i, hasSuspendedPage=%i", newProcess->processIdentifier(), !!suspendedPage);
LOG(Loading, "Continuing navigation %" PRIu64 " '%s' in a new web process", navigation.navigationID(), navigation.loggingString());
RELEASE_ASSERT(!newProcess->isInProcessCache());
ASSERT(shouldTreatAsContinuingLoad != ShouldTreatAsContinuingLoad::No);
if (m_provisionalPage) {
WEBPAGEPROXY_RELEASE_LOG(ProcessSwapping, "continueNavigationInNewProcess: There is already a pending provisional load, cancelling it (provisonalNavigationID=%llu, navigationID=%llu)", m_provisionalPage->navigationID(), navigation.navigationID());
if (m_provisionalPage->navigationID() != navigation.navigationID())
m_provisionalPage->cancel();
m_provisionalPage = nullptr;
}
bool isServerSideRedirect = shouldTreatAsContinuingLoad == ShouldTreatAsContinuingLoad::YesAfterNavigationPolicyDecision && navigation.currentRequestIsRedirect();
bool isProcessSwappingOnNavigationResponse = shouldTreatAsContinuingLoad == ShouldTreatAsContinuingLoad::YesAfterProvisionalLoadStarted;
m_provisionalPage = makeUnique<ProvisionalPageProxy>(*this, WTFMove(newProcess), WTFMove(suspendedPage), navigation.navigationID(), isServerSideRedirect, navigation.currentRequest(), processSwapRequestedByClient, isProcessSwappingOnNavigationResponse, websitePolicies.get());
auto continuation = [this, protectedThis = Ref { *this }, navigation = Ref { navigation }, shouldTreatAsContinuingLoad, websitePolicies = WTFMove(websitePolicies), existingNetworkResourceLoadIdentifierToResume]() mutable {
if (auto* item = navigation->targetItem()) {
LOG(Loading, "WebPageProxy %p continueNavigationInNewProcess to back item URL %s", this, item->url().utf8().data());
auto transaction = m_pageLoadState.transaction();
m_pageLoadState.setPendingAPIRequest(transaction, { navigation->navigationID(), item->url() });
m_provisionalPage->goToBackForwardItem(navigation, *item, WTFMove(websitePolicies));
return;
}
if (m_backForwardList->currentItem() && (navigation->lockBackForwardList() == LockBackForwardList::Yes || navigation->lockHistory() == LockHistory::Yes)) {
// If WebCore is supposed to lock the history for this load, then the new process needs to know about the current history item so it can update
// it instead of creating a new one.
m_provisionalPage->send(Messages::WebPage::SetCurrentHistoryItemForReattach(m_backForwardList->currentItem()->itemState()));
}
std::optional<WebsitePoliciesData> websitePoliciesData;
if (websitePolicies)
websitePoliciesData = websitePolicies->data();
// FIXME: Work out timing of responding with the last policy delegate, etc
ASSERT(!navigation->currentRequest().isEmpty());
ASSERT(!existingNetworkResourceLoadIdentifierToResume || !navigation->substituteData());
if (auto& substituteData = navigation->substituteData())
m_provisionalPage->loadData(navigation, { substituteData->content.data(), substituteData->content.size() }, substituteData->MIMEType, substituteData->encoding, substituteData->baseURL, substituteData->userData.get(), shouldTreatAsContinuingLoad, isNavigatingToAppBoundDomain(), WTFMove(websitePoliciesData), substituteData->sessionHistoryVisibility);
else
m_provisionalPage->loadRequest(navigation, ResourceRequest { navigation->currentRequest() }, nullptr, shouldTreatAsContinuingLoad, isNavigatingToAppBoundDomain(), WTFMove(websitePoliciesData), existingNetworkResourceLoadIdentifierToResume);
};
if (m_inspectorController->shouldPauseLoading(*m_provisionalPage))
m_inspectorController->setContinueLoadingCallback(*m_provisionalPage, WTFMove(continuation));
else
continuation();
}
bool WebPageProxy::isPageOpenedByDOMShowingInitialEmptyDocument() const
{
return openedByDOM() && !hasCommittedAnyProvisionalLoads();
}
// MSVC gives a redeclaration error if noreturn is used on the definition and not the declaration, while
// Cocoa tests segfault if it is moved to the declaration site (even if we move the definition with it!).
#if !COMPILER(MSVC)
NO_RETURN_DUE_TO_ASSERT
#endif
void WebPageProxy::didFailToSuspendAfterProcessSwap()
{
// Only the SuspendedPageProxy should be getting this call.
ASSERT_NOT_REACHED();
}
#if !COMPILER(MSVC)
NO_RETURN_DUE_TO_ASSERT
#endif
void WebPageProxy::didSuspendAfterProcessSwap()
{
// Only the SuspendedPageProxy should be getting this call.
ASSERT_NOT_REACHED();
}
void WebPageProxy::setUserAgent(String&& userAgent)
{
if (m_userAgent == userAgent)
return;
m_userAgent = WTFMove(userAgent);
#if ENABLE(SERVICE_WORKER)
// We update the service worker there at the moment to be sure we use values used by actual web pages.
// FIXME: Refactor this when we have a better User-Agent story.
process().processPool().updateServiceWorkerUserAgent(m_userAgent);
#endif
if (!hasRunningProcess())
return;
send(Messages::WebPage::SetUserAgent(m_userAgent));
}
void WebPageProxy::setApplicationNameForUserAgent(const String& applicationName)
{
if (m_applicationNameForUserAgent == applicationName)
return;
m_applicationNameForUserAgent = applicationName;
if (!m_customUserAgent.isEmpty())
return;
setUserAgent(standardUserAgent(m_applicationNameForUserAgent));
}
void WebPageProxy::setCustomUserAgent(const String& customUserAgent)
{
if (m_customUserAgent == customUserAgent)
return;
m_customUserAgent = customUserAgent;
if (m_customUserAgent.isEmpty()) {
setUserAgent(standardUserAgent(m_applicationNameForUserAgent));
return;
}
setUserAgent(String { m_customUserAgent });
}
void WebPageProxy::resumeActiveDOMObjectsAndAnimations()
{
if (!hasRunningProcess() || !m_areActiveDOMObjectsAndAnimationsSuspended)
return;
m_areActiveDOMObjectsAndAnimationsSuspended = false;
send(Messages::WebPage::ResumeActiveDOMObjectsAndAnimations());
}
void WebPageProxy::suspendActiveDOMObjectsAndAnimations()
{
if (!hasRunningProcess() || m_areActiveDOMObjectsAndAnimationsSuspended)
return;
m_areActiveDOMObjectsAndAnimationsSuspended = true;
send(Messages::WebPage::SuspendActiveDOMObjectsAndAnimations());
}
void WebPageProxy::suspend(CompletionHandler<void(bool)>&& completionHandler)
{
WEBPAGEPROXY_RELEASE_LOG(Loading, "suspend:");
if (!hasRunningProcess() || m_isSuspended)
return completionHandler(false);
m_isSuspended = true;
sendWithAsyncReply(Messages::WebPage::Suspend(), WTFMove(completionHandler));
}
void WebPageProxy::resume(CompletionHandler<void(bool)>&& completionHandler)
{
WEBPAGEPROXY_RELEASE_LOG(Loading, "resume:");
if (!hasRunningProcess() || !m_isSuspended)
return completionHandler(false);
m_isSuspended = false;
sendWithAsyncReply(Messages::WebPage::Resume(), WTFMove(completionHandler));
}
bool WebPageProxy::supportsTextEncoding() const
{
// FIXME (118840): We should probably only support this for text documents, not all non-image documents.
return m_mainFrame && !m_mainFrame->isDisplayingStandaloneImageDocument();
}
void WebPageProxy::setCustomTextEncodingName(const String& encodingName)
{
if (m_customTextEncodingName == encodingName)
return;
m_customTextEncodingName = encodingName;
if (!hasRunningProcess())
return;
send(Messages::WebPage::SetCustomTextEncodingName(encodingName));
}
SessionState WebPageProxy::sessionState(WTF::Function<bool (WebBackForwardListItem&)>&& filter) const
{
RELEASE_ASSERT(RunLoop::isMain());
SessionState sessionState;
sessionState.backForwardListState = m_backForwardList->backForwardListState(WTFMove(filter));
String provisionalURLString = m_pageLoadState.pendingAPIRequestURL();
if (provisionalURLString.isEmpty())
provisionalURLString = m_pageLoadState.provisionalURL();
if (!provisionalURLString.isEmpty())
sessionState.provisionalURL = URL(URL(), provisionalURLString);
sessionState.renderTreeSize = renderTreeSize();
sessionState.isAppInitiated = m_lastNavigationWasAppInitiated;
return sessionState;
}
RefPtr<API::Navigation> WebPageProxy::restoreFromSessionState(SessionState sessionState, bool navigate)
{
WEBPAGEPROXY_RELEASE_LOG(Loading, "restoreFromSessionState:");
m_lastNavigationWasAppInitiated = sessionState.isAppInitiated;
m_sessionRestorationRenderTreeSize = 0;
m_hitRenderTreeSizeThreshold = false;
bool hasBackForwardList = !!sessionState.backForwardListState.currentIndex;
if (hasBackForwardList) {
m_sessionStateWasRestoredByAPIRequest = true;
m_backForwardList->restoreFromState(WTFMove(sessionState.backForwardListState));
// If the process is not launched yet, the session will be restored when sending the WebPageCreationParameters;
if (hasRunningProcess())
send(Messages::WebPage::RestoreSession(m_backForwardList->itemStates()));
auto transaction = m_pageLoadState.transaction();
m_pageLoadState.setCanGoBack(transaction, m_backForwardList->backItem());
m_pageLoadState.setCanGoForward(transaction, m_backForwardList->forwardItem());
// The back / forward list was restored from a sessionState so we don't want to snapshot the current
// page when navigating away. Suppress navigation snapshotting until the next load has committed
suppressNextAutomaticNavigationSnapshot();
}
// FIXME: Navigating should be separate from state restoration.
if (navigate) {
m_sessionRestorationRenderTreeSize = sessionState.renderTreeSize;
if (!m_sessionRestorationRenderTreeSize)
m_hitRenderTreeSizeThreshold = true; // If we didn't get data on renderTreeSize, just don't fire the milestone.
if (!sessionState.provisionalURL.isNull())
return loadRequest(sessionState.provisionalURL);
if (hasBackForwardList) {
if (WebBackForwardListItem* item = m_backForwardList->currentItem())
return goToBackForwardItem(*item);
}
}
return nullptr;
}
bool WebPageProxy::supportsTextZoom() const
{
// FIXME (118840): This should also return false for standalone media and plug-in documents.
if (!m_mainFrame || m_mainFrame->isDisplayingStandaloneImageDocument())
return false;
return true;
}
void WebPageProxy::setTextZoomFactor(double zoomFactor)
{
if (m_textZoomFactor == zoomFactor)
return;
m_textZoomFactor = zoomFactor;
if (!hasRunningProcess())
return;
send(Messages::WebPage::SetTextZoomFactor(m_textZoomFactor));
}
void WebPageProxy::setPageZoomFactor(double zoomFactor)
{
if (m_pageZoomFactor == zoomFactor)
return;
closeOverlayedViews();
m_pageZoomFactor = zoomFactor;
if (!hasRunningProcess())
return;
send(Messages::WebPage::SetPageZoomFactor(m_pageZoomFactor));
}
void WebPageProxy::setPageAndTextZoomFactors(double pageZoomFactor, double textZoomFactor)
{
if (m_pageZoomFactor == pageZoomFactor && m_textZoomFactor == textZoomFactor)
return;
closeOverlayedViews();
m_pageZoomFactor = pageZoomFactor;
m_textZoomFactor = textZoomFactor;
if (!hasRunningProcess())
return;
send(Messages::WebPage::SetPageAndTextZoomFactors(m_pageZoomFactor, m_textZoomFactor));
}
double WebPageProxy::pageZoomFactor() const
{
// Zoom factor for non-PDF pages persists across page loads. We maintain a separate member variable for PDF
// zoom which ensures that we don't use the PDF zoom for a normal page.
if (m_mainFramePluginHandlesPageScaleGesture)
return m_pluginZoomFactor;
return m_pageZoomFactor;
}
double WebPageProxy::pageScaleFactor() const
{
// PDF documents use zoom and scale factors to size themselves appropriately in the window. We store them
// separately but decide which to return based on the main frame.
if (m_mainFramePluginHandlesPageScaleGesture)
return m_pluginScaleFactor;
return m_pageScaleFactor;
}
void WebPageProxy::scalePage(double scale, const IntPoint& origin)
{
ASSERT(scale > 0);
m_pageScaleFactor = scale;
if (!hasRunningProcess())
return;
send(Messages::WebPage::ScalePage(scale, origin));
}
void WebPageProxy::scalePageInViewCoordinates(double scale, const IntPoint& centerInViewCoordinates)
{
ASSERT(scale > 0);
m_pageScaleFactor = scale;
if (!hasRunningProcess())
return;
send(Messages::WebPage::ScalePageInViewCoordinates(scale, centerInViewCoordinates));
}
void WebPageProxy::scaleView(double scale)
{
ASSERT(scale > 0);
m_viewScaleFactor = scale;
if (!hasRunningProcess())
return;
send(Messages::WebPage::ScaleView(scale));
}
void WebPageProxy::setIntrinsicDeviceScaleFactor(float scaleFactor)
{
if (m_intrinsicDeviceScaleFactor == scaleFactor)
return;
m_intrinsicDeviceScaleFactor = scaleFactor;
if (m_drawingArea)
m_drawingArea->deviceScaleFactorDidChange();
}
void WebPageProxy::windowScreenDidChange(PlatformDisplayID displayID, std::optional<unsigned> nominalFramesPerSecond)
{
#if HAVE(CVDISPLAYLINK)
if (hasRunningProcess() && m_displayID && m_registeredForFullSpeedUpdates)
process().processPool().setDisplayLinkForDisplayWantsFullSpeedUpdates(*m_process->connection(), *m_displayID, false);
m_registeredForFullSpeedUpdates = false;
#endif
m_displayID = displayID;
if (!hasRunningProcess())
return;
send(Messages::EventDispatcher::PageScreenDidChange(m_webPageID, displayID, nominalFramesPerSecond));
send(Messages::WebPage::WindowScreenDidChange(displayID, nominalFramesPerSecond));
#if HAVE(CVDISPLAYLINK)
updateDisplayLinkFrequency();
#endif
}
float WebPageProxy::deviceScaleFactor() const
{
return m_customDeviceScaleFactor.value_or(m_intrinsicDeviceScaleFactor);
}
void WebPageProxy::setCustomDeviceScaleFactor(float customScaleFactor)
{
if (m_customDeviceScaleFactor && m_customDeviceScaleFactor.value() == customScaleFactor)
return;
float oldScaleFactor = deviceScaleFactor();
// A value of 0 clears the customScaleFactor.
if (customScaleFactor)
m_customDeviceScaleFactor = customScaleFactor;
else
m_customDeviceScaleFactor = std::nullopt;
if (!hasRunningProcess())
return;
if (deviceScaleFactor() != oldScaleFactor)
m_drawingArea->deviceScaleFactorDidChange();
}
void WebPageProxy::accessibilitySettingsDidChange()
{
if (!hasRunningProcess())
return;
// Also update screen properties which encodes invert colors.
process().processPool().screenPropertiesStateChanged();
send(Messages::WebPage::AccessibilitySettingsDidChange());
}
void WebPageProxy::setUseFixedLayout(bool fixed)
{
// This check is fine as the value is initialized in the web
// process as part of the creation parameters.
if (fixed == m_useFixedLayout)
return;
m_useFixedLayout = fixed;
if (!fixed)
m_fixedLayoutSize = IntSize();
if (!hasRunningProcess())
return;
send(Messages::WebPage::SetUseFixedLayout(fixed));
}
void WebPageProxy::setFixedLayoutSize(const IntSize& size)
{
if (size == m_fixedLayoutSize)
return;
m_fixedLayoutSize = size;
if (!hasRunningProcess())
return;
send(Messages::WebPage::SetFixedLayoutSize(size));
}
void WebPageProxy::setViewExposedRect(std::optional<WebCore::FloatRect> viewExposedRect)
{
if (m_viewExposedRect == viewExposedRect)
return;
m_viewExposedRect = viewExposedRect;
#if PLATFORM(MAC)
if (m_drawingArea)
m_drawingArea->didChangeViewExposedRect();
#endif
}
void WebPageProxy::setAlwaysShowsHorizontalScroller(bool alwaysShowsHorizontalScroller)
{
if (alwaysShowsHorizontalScroller == m_alwaysShowsHorizontalScroller)
return;
m_alwaysShowsHorizontalScroller = alwaysShowsHorizontalScroller;
if (!hasRunningProcess())
return;
send(Messages::WebPage::SetAlwaysShowsHorizontalScroller(alwaysShowsHorizontalScroller));
}
void WebPageProxy::setAlwaysShowsVerticalScroller(bool alwaysShowsVerticalScroller)
{
if (alwaysShowsVerticalScroller == m_alwaysShowsVerticalScroller)
return;
m_alwaysShowsVerticalScroller = alwaysShowsVerticalScroller;
if (!hasRunningProcess())
return;
send(Messages::WebPage::SetAlwaysShowsVerticalScroller(alwaysShowsVerticalScroller));
}
void WebPageProxy::listenForLayoutMilestones(OptionSet<WebCore::LayoutMilestone> milestones)
{
if (milestones == m_observedLayoutMilestones)
return;
m_observedLayoutMilestones = milestones;
if (!hasRunningProcess())
return;
send(Messages::WebPage::ListenForLayoutMilestones(milestones));
}
void WebPageProxy::setSuppressScrollbarAnimations(bool suppressAnimations)
{
if (suppressAnimations == m_suppressScrollbarAnimations)
return;
m_suppressScrollbarAnimations = suppressAnimations;
if (!hasRunningProcess())
return;
send(Messages::WebPage::SetSuppressScrollbarAnimations(suppressAnimations));
}
void WebPageProxy::setRubberBandsAtLeft(bool rubberBandsAtLeft)
{
m_rubberBandableEdges.setLeft(rubberBandsAtLeft);
}
void WebPageProxy::setRubberBandsAtRight(bool rubberBandsAtRight)
{
m_rubberBandableEdges.setRight(rubberBandsAtRight);
}
void WebPageProxy::setRubberBandsAtTop(bool rubberBandsAtTop)
{
m_rubberBandableEdges.setTop(rubberBandsAtTop);
}
void WebPageProxy::setRubberBandsAtBottom(bool rubberBandsAtBottom)
{
m_rubberBandableEdges.setBottom(rubberBandsAtBottom);
}
void WebPageProxy::setEnableVerticalRubberBanding(bool enableVerticalRubberBanding)
{
if (enableVerticalRubberBanding == m_enableVerticalRubberBanding)
return;
m_enableVerticalRubberBanding = enableVerticalRubberBanding;
if (!hasRunningProcess())
return;
send(Messages::WebPage::SetEnableVerticalRubberBanding(enableVerticalRubberBanding));
}
bool WebPageProxy::verticalRubberBandingIsEnabled() const
{
return m_enableVerticalRubberBanding;
}
void WebPageProxy::setEnableHorizontalRubberBanding(bool enableHorizontalRubberBanding)
{
if (enableHorizontalRubberBanding == m_enableHorizontalRubberBanding)
return;
m_enableHorizontalRubberBanding = enableHorizontalRubberBanding;
if (!hasRunningProcess())
return;
send(Messages::WebPage::SetEnableHorizontalRubberBanding(enableHorizontalRubberBanding));
}
bool WebPageProxy::horizontalRubberBandingIsEnabled() const
{
return m_enableHorizontalRubberBanding;
}
void WebPageProxy::setBackgroundExtendsBeyondPage(bool backgroundExtendsBeyondPage)
{
if (backgroundExtendsBeyondPage == m_backgroundExtendsBeyondPage)
return;
m_backgroundExtendsBeyondPage = backgroundExtendsBeyondPage;
if (!hasRunningProcess())
return;
send(Messages::WebPage::SetBackgroundExtendsBeyondPage(backgroundExtendsBeyondPage));
}
bool WebPageProxy::backgroundExtendsBeyondPage() const
{
return m_backgroundExtendsBeyondPage;
}
void WebPageProxy::setPaginationMode(WebCore::Pagination::Mode mode)
{
if (mode == m_paginationMode)
return;
m_paginationMode = mode;
if (!hasRunningProcess())
return;
send(Messages::WebPage::SetPaginationMode(mode));
}
void WebPageProxy::setPaginationBehavesLikeColumns(bool behavesLikeColumns)
{
if (behavesLikeColumns == m_paginationBehavesLikeColumns)
return;
m_paginationBehavesLikeColumns = behavesLikeColumns;
if (!hasRunningProcess())
return;
send(Messages::WebPage::SetPaginationBehavesLikeColumns(behavesLikeColumns));
}
void WebPageProxy::setPageLength(double pageLength)
{
if (pageLength == m_pageLength)
return;
m_pageLength = pageLength;
if (!hasRunningProcess())
return;
send(Messages::WebPage::SetPageLength(pageLength));
}
void WebPageProxy::setGapBetweenPages(double gap)
{
if (gap == m_gapBetweenPages)
return;
m_gapBetweenPages = gap;
if (!hasRunningProcess())
return;
send(Messages::WebPage::SetGapBetweenPages(gap));
}
void WebPageProxy::setPaginationLineGridEnabled(bool lineGridEnabled)
{
if (lineGridEnabled == m_paginationLineGridEnabled)
return;
m_paginationLineGridEnabled = lineGridEnabled;
if (!hasRunningProcess())
return;
send(Messages::WebPage::SetPaginationLineGridEnabled(lineGridEnabled));
}
void WebPageProxy::pageScaleFactorDidChange(double scaleFactor)
{
m_pageScaleFactor = scaleFactor;
}
void WebPageProxy::pluginScaleFactorDidChange(double pluginScaleFactor)
{
m_pluginScaleFactor = pluginScaleFactor;
}
void WebPageProxy::pluginZoomFactorDidChange(double pluginZoomFactor)
{
m_pluginZoomFactor = pluginZoomFactor;
}
void WebPageProxy::findStringMatches(const String& string, OptionSet<FindOptions> options, unsigned maxMatchCount)
{
if (string.isEmpty()) {
didFindStringMatches(string, Vector<Vector<WebCore::IntRect>> (), 0);
return;
}
send(Messages::WebPage::FindStringMatches(string, options, maxMatchCount));
}
void WebPageProxy::findString(const String& string, OptionSet<FindOptions> options, unsigned maxMatchCount, CompletionHandler<void(bool)>&& callbackFunction)
{
sendWithAsyncReply(Messages::WebPage::FindString(string, options, maxMatchCount), WTFMove(callbackFunction));
}
void WebPageProxy::findRectsForStringMatches(const String& string, OptionSet<WebKit::FindOptions> options, unsigned maxMatchCount, CompletionHandler<void(Vector<WebCore::FloatRect>&&)>&& callbackFunction)
{
sendWithAsyncReply(Messages::WebPage::FindRectsForStringMatches(string, options, maxMatchCount), WTFMove(callbackFunction));
}
void WebPageProxy::getImageForFindMatch(int32_t matchIndex)
{
send(Messages::WebPage::GetImageForFindMatch(matchIndex));
}
void WebPageProxy::selectFindMatch(int32_t matchIndex)
{
send(Messages::WebPage::SelectFindMatch(matchIndex));
}
void WebPageProxy::indicateFindMatch(int32_t matchIndex)
{
send(Messages::WebPage::IndicateFindMatch(matchIndex));
}
void WebPageProxy::hideFindUI()
{
send(Messages::WebPage::HideFindUI());
}
void WebPageProxy::hideFindIndicator()
{
send(Messages::WebPage::HideFindIndicator());
}
void WebPageProxy::countStringMatches(const String& string, OptionSet<FindOptions> options, unsigned maxMatchCount)
{
if (!hasRunningProcess())
return;
send(Messages::WebPage::CountStringMatches(string, options, maxMatchCount));
}
void WebPageProxy::replaceMatches(Vector<uint32_t>&& matchIndices, const String& replacementText, bool selectionOnly, CompletionHandler<void(uint64_t)>&& callback)
{
sendWithAsyncReply(Messages::WebPage::ReplaceMatches(WTFMove(matchIndices), replacementText, selectionOnly), WTFMove(callback));
}
void WebPageProxy::launchInitialProcessIfNecessary()
{
if (process().isDummyProcessProxy())
launchProcess({ }, ProcessLaunchReason::InitialProcess);
}
void WebPageProxy::runJavaScriptInMainFrame(RunJavaScriptParameters&& parameters, CompletionHandler<void(Expected<RefPtr<API::SerializedScriptValue>, WebCore::ExceptionDetails>&&)>&& callbackFunction)
{
runJavaScriptInFrameInScriptWorld(WTFMove(parameters), std::nullopt, API::ContentWorld::pageContentWorld(), WTFMove(callbackFunction));
}
void WebPageProxy::runJavaScriptInFrameInScriptWorld(RunJavaScriptParameters&& parameters, std::optional<WebCore::FrameIdentifier> frameID, API::ContentWorld& world, CompletionHandler<void(Expected<RefPtr<API::SerializedScriptValue>, WebCore::ExceptionDetails>&&)>&& callbackFunction)
{
// For backward-compatibility support running script in a WebView which has not done any loads yets.
launchInitialProcessIfNecessary();
if (!hasRunningProcess())
return callbackFunction({ nullptr });
ProcessThrottler::ActivityVariant activity;
#if PLATFORM(IOS_FAMILY)
if (pageClient().canTakeForegroundAssertions())
activity = m_process->throttler().foregroundActivity("WebPageProxy::runJavaScriptInFrameInScriptWorld"_s);
#endif
sendWithAsyncReply(Messages::WebPage::RunJavaScriptInFrameInScriptWorld(parameters, frameID, world.worldData()), [activity = WTFMove(activity), callbackFunction = WTFMove(callbackFunction)] (const IPC::DataReference& dataReference, std::optional<ExceptionDetails>&& details) mutable {
if (details)
return callbackFunction(makeUnexpected(WTFMove(*details)));
if (dataReference.empty())
return callbackFunction({ nullptr });
Vector<uint8_t> data;
data.reserveInitialCapacity(dataReference.size());
data.append(dataReference.data(), dataReference.size());
callbackFunction({ API::SerializedScriptValue::adopt(WTFMove(data)).ptr() });
});
}
void WebPageProxy::getRenderTreeExternalRepresentation(CompletionHandler<void(const String&)>&& callback)
{
sendWithAsyncReply(Messages::WebPage::GetRenderTreeExternalRepresentation(), WTFMove(callback));
}
void WebPageProxy::getSourceForFrame(WebFrameProxy* frame, CompletionHandler<void(const String&)>&& callback)
{
sendWithAsyncReply(Messages::WebPage::GetSourceForFrame(frame->frameID()), WTFMove(callback));
}
void WebPageProxy::getContentsAsString(ContentAsStringIncludesChildFrames includesChildFrames, CompletionHandler<void(const String&)>&& callback)
{
sendWithAsyncReply(Messages::WebPage::GetContentsAsString(includesChildFrames), WTFMove(callback));
}
#if PLATFORM(COCOA)
void WebPageProxy::getContentsAsAttributedString(CompletionHandler<void(const WebCore::AttributedString&)>&& completionHandler)
{
if (!hasRunningProcess()) {
completionHandler({ });
return;
}
sendWithAsyncReply(Messages::WebPage::GetContentsAsAttributedString(), WTFMove(completionHandler));
}
#endif
void WebPageProxy::getAllFrames(CompletionHandler<void(FrameTreeNodeData&&)>&& completionHandler)
{
sendWithAsyncReply(Messages::WebPage::GetAllFrames(), WTFMove(completionHandler));
}
void WebPageProxy::getBytecodeProfile(CompletionHandler<void(const String&)>&& callback)
{
sendWithAsyncReply(Messages::WebPage::GetBytecodeProfile(), WTFMove(callback));
}
void WebPageProxy::getSamplingProfilerOutput(CompletionHandler<void(const String&)>&& callback)
{
sendWithAsyncReply(Messages::WebPage::GetSamplingProfilerOutput(), WTFMove(callback));
}
static CompletionHandler<void(const std::optional<IPC::SharedBufferCopy>& data)> toAPIDataCallback(CompletionHandler<void(API::Data*)>&& callback)
{
return [callback = WTFMove(callback)] (const std::optional<IPC::SharedBufferCopy>& data) mutable {
if (!data) {
callback(nullptr);
return;
}
callback(API::Data::create(data->data(), data->size()).ptr());
};
}
#if ENABLE(MHTML)
void WebPageProxy::getContentsAsMHTMLData(CompletionHandler<void(API::Data*)>&& callback)
{
sendWithAsyncReply(Messages::WebPage::GetContentsAsMHTMLData(), toAPIDataCallback(WTFMove(callback)));
}
#endif
void WebPageProxy::getSelectionOrContentsAsString(CompletionHandler<void(const String&)>&& callback)
{
sendWithAsyncReply(Messages::WebPage::GetSelectionOrContentsAsString(), callback);
}
void WebPageProxy::getSelectionAsWebArchiveData(CompletionHandler<void(API::Data*)>&& callback)
{
sendWithAsyncReply(Messages::WebPage::GetSelectionAsWebArchiveData(), toAPIDataCallback(WTFMove(callback)));
}
void WebPageProxy::getMainResourceDataOfFrame(WebFrameProxy* frame, CompletionHandler<void(API::Data*)>&& callback)
{
if (!frame)
return callback(nullptr);
sendWithAsyncReply(Messages::WebPage::GetMainResourceDataOfFrame(frame->frameID()), toAPIDataCallback(WTFMove(callback)));
}
void WebPageProxy::getResourceDataFromFrame(WebFrameProxy& frame, API::URL* resourceURL, CompletionHandler<void(API::Data*)>&& callback)
{
sendWithAsyncReply(Messages::WebPage::GetResourceDataFromFrame(frame.frameID(), resourceURL->string()), toAPIDataCallback(WTFMove(callback)));
}
void WebPageProxy::getWebArchiveOfFrame(WebFrameProxy* frame, CompletionHandler<void(API::Data*)>&& callback)
{
if (!frame)
return callback(nullptr);
sendWithAsyncReply(Messages::WebPage::GetWebArchiveOfFrame(frame->frameID()), toAPIDataCallback(WTFMove(callback)));
}
void WebPageProxy::forceRepaint(CompletionHandler<void()>&& callback)
{
if (!hasRunningProcess())
return callback();
m_drawingArea->waitForBackingStoreUpdateOnNextPaint();
sendWithAsyncReply(Messages::WebPage::ForceRepaint(), [this, protectedThis = Ref { *this }, callback = WTFMove(callback)] () mutable {
callAfterNextPresentationUpdate([callback = WTFMove(callback)] (auto) mutable {
callback();
});
});
}
static OptionSet<IPC::SendOption> printingSendOptions(bool isPerformingDOMPrintOperation)
{
if (isPerformingDOMPrintOperation)
return IPC::SendOption::DispatchMessageEvenWhenWaitingForSyncReply;
return { };
}
void WebPageProxy::preferencesDidChange()
{
if (!hasRunningProcess())
return;
updateThrottleState();
updateHiddenPageThrottlingAutoIncreases();
pageClient().preferencesDidChange();
// FIXME: It probably makes more sense to send individual preference changes.
// However, WebKitTestRunner depends on getting a preference change notification
// even if nothing changed in UI process, so that overrides get removed.
// Preferences need to be updated during synchronous printing to make "print backgrounds" preference work when toggled from a print dialog checkbox.
send(Messages::WebPage::PreferencesDidChange(preferencesStore()), printingSendOptions(m_isPerformingDOMPrintOperation));
}
void WebPageProxy::didCreateMainFrame(FrameIdentifier frameID)
{
// The DecidePolicyForNavigationActionSync IPC is synchronous and may therefore get processed before the DidCreateMainFrame one.
// When this happens, decidePolicyForNavigationActionSync() calls didCreateMainFrame() and we need to ignore the DidCreateMainFrame
// IPC when it later gets processed.
if (m_mainFrame && m_mainFrame->frameID() == frameID)
return;
PageClientProtector protector(pageClient());
MESSAGE_CHECK(m_process, !m_mainFrame);
MESSAGE_CHECK(m_process, m_process->canCreateFrame(frameID));
m_mainFrame = WebFrameProxy::create(*this, frameID);
// Add the frame to the process wide map.
m_process->frameCreated(frameID, *m_mainFrame);
}
void WebPageProxy::didCreateSubframe(FrameIdentifier frameID)
{
PageClientProtector protector(pageClient());
MESSAGE_CHECK(m_process, m_mainFrame);
// The DecidePolicyForNavigationActionSync IPC is synchronous and may therefore get processed before the DidCreateSubframe one.
// When this happens, decidePolicyForNavigationActionSync() calls didCreateSubframe() and we need to ignore the DidCreateSubframe
// IPC when it later gets processed.
if (m_process->webFrame(frameID))
return;
MESSAGE_CHECK(m_process, m_process->canCreateFrame(frameID));
auto subFrame = WebFrameProxy::create(*this, frameID);
// Add the frame to the process wide map.
m_process->frameCreated(frameID, subFrame.get());
}
void WebPageProxy::didCreateWindow(FrameIdentifier frameID, GlobalWindowIdentifier&& windowIdentifier)
{
}
double WebPageProxy::estimatedProgress() const
{
return m_pageLoadState.estimatedProgress();
}
void WebPageProxy::didStartProgress()
{
ASSERT(!m_isClosed);
PageClientProtector protector(pageClient());
auto transaction = m_pageLoadState.transaction();
m_pageLoadState.didStartProgress(transaction);
m_pageLoadState.commitChanges();
}
void WebPageProxy::didChangeProgress(double value)
{
PageClientProtector protector(pageClient());
auto transaction = m_pageLoadState.transaction();
m_pageLoadState.didChangeProgress(transaction, value);
m_pageLoadState.commitChanges();
}
void WebPageProxy::didFinishProgress()
{
PageClientProtector protector(pageClient());
auto transaction = m_pageLoadState.transaction();
m_pageLoadState.didFinishProgress(transaction);
m_pageLoadState.commitChanges();
}
void WebPageProxy::setNetworkRequestsInProgress(bool networkRequestsInProgress)
{
auto transaction = m_pageLoadState.transaction();
m_pageLoadState.setNetworkRequestsInProgress(transaction, networkRequestsInProgress);
}
void WebPageProxy::preconnectTo(const URL& url)
{
if (!m_websiteDataStore->configuration().allowsServerPreconnect())
return;
auto storedCredentialsPolicy = m_canUseCredentialStorage ? WebCore::StoredCredentialsPolicy::Use : WebCore::StoredCredentialsPolicy::DoNotUse;
websiteDataStore().networkProcess().preconnectTo(sessionID(), identifier(), webPageID(), url, userAgent(), storedCredentialsPolicy, isNavigatingToAppBoundDomain(), m_lastNavigationWasAppInitiated ? LastNavigationWasAppInitiated::Yes : LastNavigationWasAppInitiated::No);
}
void WebPageProxy::setCanUseCredentialStorage(bool canUseCredentialStorage)
{
m_canUseCredentialStorage = canUseCredentialStorage;
send(Messages::WebPage::SetCanUseCredentialStorage(canUseCredentialStorage));
}
void WebPageProxy::didDestroyNavigation(uint64_t navigationID)
{
MESSAGE_CHECK(m_process, WebNavigationState::NavigationMap::isValidKey(navigationID));
PageClientProtector protector(pageClient());
// On process-swap, the previous process tries to destroy the navigation but the provisional process is actually taking over the navigation.
if (m_provisionalPage && m_provisionalPage->navigationID() == navigationID)
return;
m_navigationState->didDestroyNavigation(navigationID);
}
void WebPageProxy::didStartProvisionalLoadForFrame(FrameIdentifier frameID, FrameInfoData&& frameInfo, ResourceRequest&& request, uint64_t navigationID, URL&& url, URL&& unreachableURL, const UserData& userData)
{
didStartProvisionalLoadForFrameShared(m_process.copyRef(), frameID, WTFMove(frameInfo), WTFMove(request), navigationID, WTFMove(url), WTFMove(unreachableURL), userData);
}
void WebPageProxy::didStartProvisionalLoadForFrameShared(Ref<WebProcessProxy>&& process, FrameIdentifier frameID, FrameInfoData&& frameInfo, ResourceRequest&& request, uint64_t navigationID, URL&& url, URL&& unreachableURL, const UserData& userData)
{
PageClientProtector protector(pageClient());
RefPtr frame = process->webFrame(frameID);
MESSAGE_CHECK(process, frame);
MESSAGE_CHECK_URL(process, url);
// If the page starts a new main frame provisional load, then cancel any pending one in a provisional process.
if (frame->isMainFrame() && m_provisionalPage && m_provisionalPage->mainFrame() != frame) {
m_provisionalPage->cancel();
m_provisionalPage = nullptr;
}
// FIXME: We should message check that navigationID is not zero here, but it's currently zero for some navigations through the back/forward cache.
RefPtr<API::Navigation> navigation;
if (frame->isMainFrame() && navigationID)
navigation = navigationState().navigation(navigationID);
LOG(Loading, "WebPageProxy %" PRIu64 " in process pid %i didStartProvisionalLoadForFrame to frameID %" PRIu64 ", navigationID %" PRIu64 ", url %s", m_identifier.toUInt64(), process->processIdentifier(), frameID.toUInt64(), navigationID, url.string().utf8().data());
WEBPAGEPROXY_RELEASE_LOG(Loading, "didStartProvisionalLoadForFrame: frameID=%" PRIu64, frameID.toUInt64());
auto transaction = m_pageLoadState.transaction();
m_pageLoadState.clearPendingAPIRequest(transaction);
if (frame->isMainFrame()) {
process->didStartProvisionalLoadForMainFrame(url);
reportPageLoadResult(ResourceError { ResourceError::Type::Cancellation });
m_pageLoadStart = MonotonicTime::now();
m_pageLoadState.didStartProvisionalLoad(transaction, url.string(), unreachableURL.string());
pageClient().didStartProvisionalLoadForMainFrame();
closeOverlayedViews();
}
frame->setUnreachableURL(unreachableURL);
frame->didStartProvisionalLoad(url);
m_pageLoadState.commitChanges();
if (m_loaderClient)
m_loaderClient->didStartProvisionalLoadForFrame(*this, *frame, navigation.get(), process->transformHandlesToObjects(userData.object()).get());
else {
if (frameInfo.isMainFrame)
m_navigationClient->didStartProvisionalNavigation(*this, request, navigation.get(), process->transformHandlesToObjects(userData.object()).get());
m_navigationClient->didStartProvisionalLoadForFrame(*this, WTFMove(request), WTFMove(frameInfo));
}
#if ENABLE(WEB_AUTHN)
m_websiteDataStore->authenticatorManager().cancelRequest(m_webPageID, frameID);
#endif
}
void WebPageProxy::didExplicitOpenForFrame(FrameIdentifier frameID, URL&& url, String&& mimeType)
{
auto* frame = m_process->webFrame(frameID);
MESSAGE_CHECK(m_process, frame);
if (!checkURLReceivedFromCurrentOrPreviousWebProcess(m_process, url)) {
WEBPAGEPROXY_RELEASE_LOG_ERROR(Process, "Ignoring WebPageProxy::DidExplicitOpenForFrame() IPC from the WebContent process because the file URL is outside the sandbox");
return;
}
auto transaction = m_pageLoadState.transaction();
if (frame->isMainFrame())
m_pageLoadState.didExplicitOpen(transaction, url.string());
frame->didExplicitOpen(WTFMove(url), WTFMove(mimeType));
m_hasCommittedAnyProvisionalLoads = true;
m_process->didCommitProvisionalLoad();
m_pageLoadState.commitChanges();
}
void WebPageProxy::didReceiveServerRedirectForProvisionalLoadForFrame(FrameIdentifier frameID, uint64_t navigationID, ResourceRequest&& request, const UserData& userData)
{
didReceiveServerRedirectForProvisionalLoadForFrameShared(m_process.copyRef(), frameID, navigationID, WTFMove(request), userData);
}
void WebPageProxy::didReceiveServerRedirectForProvisionalLoadForFrameShared(Ref<WebProcessProxy>&& process, FrameIdentifier frameID, uint64_t navigationID, ResourceRequest&& request, const UserData& userData)
{
LOG(Loading, "WebPageProxy::didReceiveServerRedirectForProvisionalLoadForFrame to frameID %" PRIu64 ", navigationID %" PRIu64 ", url %s", frameID.toUInt64(), navigationID, request.url().string().utf8().data());
WEBPAGEPROXY_RELEASE_LOG(Loading, "didReceiveServerRedirectForProvisionalLoadForFrame: frameID=%" PRIu64, frameID.toUInt64());
PageClientProtector protector(pageClient());
WebFrameProxy* frame = process->webFrame(frameID);
MESSAGE_CHECK(process, frame);
MESSAGE_CHECK_URL(process, request.url());
// FIXME: We should message check that navigationID is not zero here, but it's currently zero for some navigations through the back/forward cache.
RefPtr<API::Navigation> navigation = navigationID ? navigationState().navigation(navigationID) : nullptr;
if (navigation)
navigation->appendRedirectionURL(request.url());
auto transaction = m_pageLoadState.transaction();
if (frame->isMainFrame())
m_pageLoadState.didReceiveServerRedirectForProvisionalLoad(transaction, request.url().string());
frame->didReceiveServerRedirectForProvisionalLoad(request.url());
m_pageLoadState.commitChanges();
if (m_loaderClient)
m_loaderClient->didReceiveServerRedirectForProvisionalLoadForFrame(*this, *frame, frame->isMainFrame() ? navigation.get() : nullptr, process->transformHandlesToObjects(userData.object()).get());
else if (frame->isMainFrame())
m_navigationClient->didReceiveServerRedirectForProvisionalNavigation(*this, navigation.get(), process->transformHandlesToObjects(userData.object()).get());
}
void WebPageProxy::willPerformClientRedirectForFrame(FrameIdentifier frameID, const String& url, double delay, WebCore::LockBackForwardList)
{
WEBPAGEPROXY_RELEASE_LOG(Loading, "willPerformClientRedirectForFrame: frameID=%" PRIu64, frameID.toUInt64());
PageClientProtector protector(pageClient());
WebFrameProxy* frame = m_process->webFrame(frameID);
MESSAGE_CHECK(m_process, frame);
if (frame->isMainFrame())
m_navigationClient->willPerformClientRedirect(*this, url, delay);
}
void WebPageProxy::didCancelClientRedirectForFrame(FrameIdentifier frameID)
{
WEBPAGEPROXY_RELEASE_LOG(Loading, "didCancelClientRedirectForFrame: frameID=%" PRIu64, frameID.toUInt64());
PageClientProtector protector(pageClient());
WebFrameProxy* frame = m_process->webFrame(frameID);
MESSAGE_CHECK(m_process, frame);
if (frame->isMainFrame())
m_navigationClient->didCancelClientRedirect(*this);
}
void WebPageProxy::didChangeProvisionalURLForFrame(FrameIdentifier frameID, uint64_t navigationID, URL&& url)
{
didChangeProvisionalURLForFrameShared(m_process.copyRef(), frameID, navigationID, WTFMove(url));
}
void WebPageProxy::didChangeProvisionalURLForFrameShared(Ref<WebProcessProxy>&& process, FrameIdentifier frameID, uint64_t, URL&& url)
{
PageClientProtector protector(pageClient());
WebFrameProxy* frame = process->webFrame(frameID);
MESSAGE_CHECK(process, frame);
MESSAGE_CHECK(process, frame->frameLoadState().state() == FrameLoadState::State::Provisional);
MESSAGE_CHECK_URL(process, url);
auto transaction = m_pageLoadState.transaction();
// Internally, we handle this the same way we handle a server redirect. There are no client callbacks
// for this, but if this is the main frame, clients may observe a change to the page's URL.
if (frame->isMainFrame())
m_pageLoadState.didReceiveServerRedirectForProvisionalLoad(transaction, url.string());
frame->didReceiveServerRedirectForProvisionalLoad(url);
}
void WebPageProxy::didFailProvisionalLoadForFrame(FrameIdentifier frameID, FrameInfoData&& frameInfo, WebCore::ResourceRequest&& request, uint64_t navigationID, const String& provisionalURL, const ResourceError& error, WillContinueLoading willContinueLoading, const UserData& userData)
{
WebFrameProxy* frame = m_process->webFrame(frameID);
MESSAGE_CHECK(m_process, frame);
if (m_provisionalPage && frame->isMainFrame()) {
// The load did not fail, it is merely happening in a new provisional process.
return;
}
didFailProvisionalLoadForFrameShared(m_process.copyRef(), *frame, WTFMove(frameInfo), WTFMove(request), navigationID, provisionalURL, error, willContinueLoading, userData);
}
void WebPageProxy::didFailProvisionalLoadForFrameShared(Ref<WebProcessProxy>&& process, WebFrameProxy& frame, FrameInfoData&& frameInfo, WebCore::ResourceRequest&& request, uint64_t navigationID, const String& provisionalURL, const ResourceError& error, WillContinueLoading willContinueLoading, const UserData& userData)
{
LOG(Loading, "(Loading) WebPageProxy %" PRIu64 " in web process pid %i didFailProvisionalLoadForFrame to provisionalURL %s", m_identifier.toUInt64(), process->processIdentifier(), provisionalURL.utf8().data());
WEBPAGEPROXY_RELEASE_LOG_ERROR(Process, "didFailProvisionalLoadForFrame: frameID=%" PRIu64 ", domain=%s, code=%d, isMainFrame=%d", frame.frameID().toUInt64(), error.domain().utf8().data(), error.errorCode(), frame.isMainFrame());
PageClientProtector protector(pageClient());
if (m_controlledByAutomation) {
if (auto* automationSession = process->processPool().automationSession())
automationSession->navigationOccurredForFrame(frame);
}
// FIXME: We should message check that navigationID is not zero here, but it's currently zero for some navigations through the back/forward cache.
RefPtr<API::Navigation> navigation;
if (frame.isMainFrame() && navigationID)
navigation = navigationState().takeNavigation(navigationID);
auto transaction = m_pageLoadState.transaction();
if (frame.isMainFrame()) {
reportPageLoadResult(error);
m_pageLoadState.didFailProvisionalLoad(transaction);
pageClient().didFailProvisionalLoadForMainFrame();
if (navigation)
navigation->setClientNavigationActivity(nullptr);
callServiceWorkerLaunchCompletionHandlerIfNecessary();
}
frame.didFailProvisionalLoad();
m_pageLoadState.commitChanges();
ASSERT(!m_failingProvisionalLoadURL);
m_failingProvisionalLoadURL = provisionalURL;
if (m_loaderClient)
m_loaderClient->didFailProvisionalLoadWithErrorForFrame(*this, frame, navigation.get(), error, process->transformHandlesToObjects(userData.object()).get());
else {
m_navigationClient->didFailProvisionalNavigationWithError(*this, FrameInfoData { frameInfo }, navigation.get(), error, process->transformHandlesToObjects(userData.object()).get());
m_navigationClient->didFailProvisionalLoadWithErrorForFrame(*this, WTFMove(request), error, WTFMove(frameInfo));
}
m_failingProvisionalLoadURL = { };
// If the provisional page's load fails then we destroy the provisional page.
if (m_provisionalPage && m_provisionalPage->mainFrame() == &frame && willContinueLoading == WillContinueLoading::No)
m_provisionalPage = nullptr;
}
#if ENABLE(SERVICE_WORKER)
void WebPageProxy::didFinishServiceWorkerPageRegistration(bool success)
{
ASSERT(m_isServiceWorkerPage);
ASSERT(m_serviceWorkerLaunchCompletionHandler);
if (m_serviceWorkerLaunchCompletionHandler)
m_serviceWorkerLaunchCompletionHandler(success);
}
#endif
void WebPageProxy::callServiceWorkerLaunchCompletionHandlerIfNecessary()
{
#if ENABLE(SERVICE_WORKER)
if (m_isServiceWorkerPage && m_serviceWorkerLaunchCompletionHandler)
m_serviceWorkerLaunchCompletionHandler(false);
#endif
}
#if ENABLE(INTELLIGENT_TRACKING_PREVENTION)
static OptionSet<CrossSiteNavigationDataTransfer::Flag> checkIfNavigationContainsDataTransfer(const SecurityOriginData requesterOrigin, const ResourceRequest& currentRequest)
{
OptionSet<CrossSiteNavigationDataTransfer::Flag> navigationDataTransfer;
if (requesterOrigin.securityOrigin()->isUnique())
return navigationDataTransfer;
auto currentURL = currentRequest.url();
if (!currentURL.query().isEmpty() || !currentURL.fragmentIdentifier().isEmpty())
navigationDataTransfer.add(CrossSiteNavigationDataTransfer::Flag::DestinationLinkDecoration);
URL referrerURL { URL(), currentRequest.httpReferrer() };
if (!referrerURL.query().isEmpty() || !referrerURL.fragmentIdentifier().isEmpty())
navigationDataTransfer.add(CrossSiteNavigationDataTransfer::Flag::ReferrerLinkDecoration);
return navigationDataTransfer;
}
#endif
void WebPageProxy::didCommitLoadForFrame(FrameIdentifier frameID, FrameInfoData&& frameInfo, ResourceRequest&& request, uint64_t navigationID, const String& mimeType, bool frameHasCustomContentProvider, WebCore::FrameLoadType frameLoadType, const WebCore::CertificateInfo& certificateInfo, bool usedLegacyTLS, bool containsPluginDocument, std::optional<HasInsecureContent> hasInsecureContent, WebCore::MouseEventPolicy mouseEventPolicy, const UserData& userData)
{
LOG(Loading, "(Loading) WebPageProxy %" PRIu64 " didCommitLoadForFrame in navigation %" PRIu64, m_identifier.toUInt64(), navigationID);
LOG(BackForward, "(Back/Forward) After load commit, back/forward list is now:%s", m_backForwardList->loggingString());
WEBPAGEPROXY_RELEASE_LOG(Loading, "didCommitLoadForFrame: frameID=%" PRIu64, frameID.toUInt64());
PageClientProtector protector(pageClient());
WebFrameProxy* frame = m_process->webFrame(frameID);
MESSAGE_CHECK(m_process, frame);
// FIXME: We should message check that navigationID is not zero here, but it's currently zero for some navigations through the back/forward cache.
RefPtr<API::Navigation> navigation;
if (frame->isMainFrame() && navigationID && (navigation = navigationState().navigation(navigationID))) {
#if ENABLE(INTELLIGENT_TRACKING_PREVENTION)
auto requesterOrigin = navigation->lastNavigationAction().requesterOrigin;
auto currentRequest = navigation->currentRequest();
auto navigationDataTransfer = checkIfNavigationContainsDataTransfer(requesterOrigin, currentRequest);
if (!navigationDataTransfer.isEmpty()) {
RegistrableDomain currentDomain { currentRequest.url() };
URL requesterURL { URL(), requesterOrigin.toString() };
if (!currentDomain.matches(requesterURL))
m_websiteDataStore->networkProcess().didCommitCrossSiteLoadWithDataTransfer(m_websiteDataStore->sessionID(), RegistrableDomain { requesterURL }, currentDomain, navigationDataTransfer, m_identifier, m_webPageID);
}
#endif
}
m_hasCommittedAnyProvisionalLoads = true;
m_process->didCommitProvisionalLoad();
if (frame->isMainFrame()) {
m_hasUpdatedRenderingAfterDidCommitLoad = false;
#if PLATFORM(IOS_FAMILY)
m_firstLayerTreeTransactionIdAfterDidCommitLoad = downcast<RemoteLayerTreeDrawingAreaProxy>(*drawingArea()).nextLayerTreeTransactionID();
#endif
}
auto transaction = m_pageLoadState.transaction();
Ref<WebCertificateInfo> webCertificateInfo = WebCertificateInfo::create(certificateInfo);
bool markPageInsecure = hasInsecureContent ? hasInsecureContent.value() == HasInsecureContent::Yes : certificateInfo.containsNonRootSHA1SignedCertificate();
if (frame->isMainFrame()) {
m_pageLoadState.didCommitLoad(transaction, webCertificateInfo, markPageInsecure, usedLegacyTLS);
m_shouldSuppressNextAutomaticNavigationSnapshot = false;
} else if (markPageInsecure)
m_pageLoadState.didDisplayOrRunInsecureContent(transaction);
#if USE(APPKIT)
// FIXME (bug 59111): didCommitLoadForFrame comes too late when restoring a page from b/f cache, making us disable secure event mode in password fields.
// FIXME: A load going on in one frame shouldn't affect text editing in other frames on the page.
pageClient().resetSecureInputState();
#endif
frame->didCommitLoad(mimeType, webCertificateInfo, containsPluginDocument);
if (frame->isMainFrame()) {
std::optional<WebCore::PrivateClickMeasurement> privateClickMeasurement;
if (m_privateClickMeasurement)
privateClickMeasurement = m_privateClickMeasurement->pcm;
else if (navigation && navigation->privateClickMeasurement())
privateClickMeasurement = navigation->privateClickMeasurement();
if (privateClickMeasurement) {
if (privateClickMeasurement->destinationSite().matches(frame->url()))
websiteDataStore().networkProcess().send(Messages::NetworkProcess::StorePrivateClickMeasurement(m_websiteDataStore->sessionID(), *privateClickMeasurement), 0);
}
}
m_privateClickMeasurement.reset();
if (frame->isMainFrame()) {
m_mainFrameHasCustomContentProvider = frameHasCustomContentProvider;
if (m_mainFrameHasCustomContentProvider) {
// Always assume that the main frame is pinned here, since the custom representation view will handle
// any wheel events and dispatch them to the WKView when necessary.
m_mainFramePinnedState = { true, true, true, true };
m_uiClient->pinnedStateDidChange(*this);
}
pageClient().didCommitLoadForMainFrame(mimeType, frameHasCustomContentProvider);
}
// Even if WebPage has the default pageScaleFactor (and therefore doesn't reset it),
// WebPageProxy's cache of the value can get out of sync (e.g. in the case where a
// plugin is handling page scaling itself) so we should reset it to the default
// for standard main frame loads.
if (frame->isMainFrame()) {
m_pageScaleFactor = 1;
m_pluginScaleFactor = 1;
m_mainFramePluginHandlesPageScaleGesture = false;
#if ENABLE(POINTER_LOCK)
requestPointerUnlock();
#endif
pageClient().setMouseEventPolicy(mouseEventPolicy);
#if ENABLE(UI_PROCESS_PDF_HUD)
pageClient().removeAllPDFHUDs();
#endif
}
m_pageLoadState.commitChanges();
if (m_loaderClient)
m_loaderClient->didCommitLoadForFrame(*this, *frame, navigation.get(), m_process->transformHandlesToObjects(userData.object()).get());
else {
if (frameInfo.isMainFrame)
m_navigationClient->didCommitNavigation(*this, navigation.get(), m_process->transformHandlesToObjects(userData.object()).get());
m_navigationClient->didCommitLoadForFrame(*this, WTFMove(request), WTFMove(frameInfo));
}
if (frame->isMainFrame()) {
#if ENABLE(ATTACHMENT_ELEMENT)
invalidateAllAttachments();
#endif
#if ENABLE(REMOTE_INSPECTOR)
remoteInspectorInformationDidChange();
#endif
#if USE(APPKIT)
closeSharedPreviewPanelIfNecessary();
#endif
}
#if ENABLE(MEDIA_SESSION_COORDINATOR) && HAVE(GROUP_ACTIVITIES)
if (frame->isMainFrame() && m_preferences->mediaSessionCoordinatorEnabled())
GroupActivitiesSessionNotifier::sharedNotifier().webPageURLChanged(*this);
#endif
}
void WebPageProxy::didFinishDocumentLoadForFrame(FrameIdentifier frameID, uint64_t navigationID, const UserData& userData)
{
WEBPAGEPROXY_RELEASE_LOG(Loading, "didFinishDocumentLoadForFrame: frameID=%" PRIu64, frameID.toUInt64());
PageClientProtector protector(pageClient());
WebFrameProxy* frame = m_process->webFrame(frameID);
MESSAGE_CHECK(m_process, frame);
if (m_controlledByAutomation) {
if (auto* automationSession = process().processPool().automationSession())
automationSession->documentLoadedForFrame(*frame);
}
// FIXME: We should message check that navigationID is not zero here, but it's currently zero for some navigations through the back/forward cache.
RefPtr<API::Navigation> navigation;
if (frame->isMainFrame() && navigationID)
navigation = navigationState().navigation(navigationID);
if (frame->isMainFrame()) {
m_navigationClient->didFinishDocumentLoad(*this, navigation.get(), m_process->transformHandlesToObjects(userData.object()).get());
#if ENABLE(INTELLIGENT_TRACKING_PREVENTION)
m_didFinishDocumentLoadForMainFrameTimestamp = MonotonicTime::now();
#endif
}
}
void WebPageProxy::didFinishLoadForFrame(FrameIdentifier frameID, FrameInfoData&& frameInfo, ResourceRequest&& request, uint64_t navigationID, const UserData& userData)
{
LOG(Loading, "WebPageProxy::didFinishLoadForFrame - WebPageProxy %p with navigationID %" PRIu64 " didFinishLoad", this, navigationID);
WEBPAGEPROXY_RELEASE_LOG(Loading, "didFinishLoadForFrame: frameID=%" PRIu64, frameID.toUInt64());
PageClientProtector protector(pageClient());
WebFrameProxy* frame = m_process->webFrame(frameID);
MESSAGE_CHECK(m_process, frame);
// FIXME: We should message check that navigationID is not zero here, but it's currently zero for some navigations through the back/forward cache.
RefPtr<API::Navigation> navigation;
if (frame->isMainFrame() && navigationID && navigationState().hasNavigation(navigationID))
navigation = navigationState().navigation(navigationID);
bool isMainFrame = frame->isMainFrame();
if (!isMainFrame || !navigationID || navigation) {
auto transaction = m_pageLoadState.transaction();
if (isMainFrame)
m_pageLoadState.didFinishLoad(transaction);
if (m_controlledByAutomation) {
if (auto* automationSession = process().processPool().automationSession())
automationSession->navigationOccurredForFrame(*frame);
}
frame->didFinishLoad();
m_pageLoadState.commitChanges();
}
if (m_loaderClient)
m_loaderClient->didFinishLoadForFrame(*this, *frame, navigation.get(), m_process->transformHandlesToObjects(userData.object()).get());
else {
if (frameInfo.isMainFrame)
m_navigationClient->didFinishNavigation(*this, navigation.get(), m_process->transformHandlesToObjects(userData.object()).get());
m_navigationClient->didFinishLoadForFrame(*this, WTFMove(request), WTFMove(frameInfo));
}
if (isMainFrame) {
reportPageLoadResult();
pageClient().didFinishNavigation(navigation.get());
if (navigation)
navigation->setClientNavigationActivity(nullptr);
resetRecentCrashCountSoon();
notifyProcessPoolToPrewarm();
}
m_isLoadingAlternateHTMLStringForFailingProvisionalLoad = false;
}
void WebPageProxy::didFailLoadForFrame(FrameIdentifier frameID, FrameInfoData&& frameInfo, ResourceRequest&& request, uint64_t navigationID, const ResourceError& error, const UserData& userData)
{
WEBPAGEPROXY_RELEASE_LOG_ERROR(Loading, "didFailLoadForFrame: frameID=%" PRIu64 ", domain=%s, code=%d", frameID.toUInt64(), error.domain().utf8().data(), error.errorCode());
PageClientProtector protector(pageClient());
WebFrameProxy* frame = m_process->webFrame(frameID);
MESSAGE_CHECK(m_process, frame);
// FIXME: We should message check that navigationID is not zero here, but it's currently zero for some navigations through the back/forward cache.
RefPtr<API::Navigation> navigation;
if (frame->isMainFrame() && navigationID)
navigation = navigationState().navigation(navigationID);
auto transaction = m_pageLoadState.transaction();
bool isMainFrame = frame->isMainFrame();
if (isMainFrame)
m_pageLoadState.didFailLoad(transaction);
if (m_controlledByAutomation) {
if (auto* automationSession = process().processPool().automationSession())
automationSession->navigationOccurredForFrame(*frame);
}
frame->didFailLoad();
m_pageLoadState.commitChanges();
if (m_loaderClient)
m_loaderClient->didFailLoadWithErrorForFrame(*this, *frame, navigation.get(), error, m_process->transformHandlesToObjects(userData.object()).get());
else {
if (frameInfo.isMainFrame)
m_navigationClient->didFailNavigationWithError(*this, frameInfo, navigation.get(), error, m_process->transformHandlesToObjects(userData.object()).get());
m_navigationClient->didFailLoadWithErrorForFrame(*this, WTFMove(request), error, WTFMove(frameInfo));
}
if (isMainFrame) {
reportPageLoadResult(error);
pageClient().didFailNavigation(navigation.get());
if (navigation)
navigation->setClientNavigationActivity(nullptr);
callServiceWorkerLaunchCompletionHandlerIfNecessary();
}
}
void WebPageProxy::didSameDocumentNavigationForFrame(FrameIdentifier frameID, uint64_t navigationID, uint32_t opaqueSameDocumentNavigationType, URL&& url, const UserData& userData)
{
WEBPAGEPROXY_RELEASE_LOG(Loading, "didSameDocumentNavigationForFrame: frameID=%" PRIu64, frameID.toUInt64());
PageClientProtector protector(pageClient());
WebFrameProxy* frame = m_process->webFrame(frameID);
MESSAGE_CHECK(m_process, frame);
MESSAGE_CHECK_URL(m_process, url);
// FIXME: We should message check that navigationID is not zero here, but it's currently zero for some navigations through the back/forward cache.
RefPtr<API::Navigation> navigation;
if (frame->isMainFrame() && navigationID)
navigation = navigationState().navigation(navigationID);
auto transaction = m_pageLoadState.transaction();
bool isMainFrame = frame->isMainFrame();
if (isMainFrame)
m_pageLoadState.didSameDocumentNavigation(transaction, url.string());
if (m_controlledByAutomation) {
if (auto* automationSession = process().processPool().automationSession())
automationSession->navigationOccurredForFrame(*frame);
}
m_pageLoadState.clearPendingAPIRequest(transaction);
frame->didSameDocumentNavigation(url);
m_pageLoadState.commitChanges();
SameDocumentNavigationType navigationType = static_cast<SameDocumentNavigationType>(opaqueSameDocumentNavigationType);
if (isMainFrame)
m_navigationClient->didSameDocumentNavigation(*this, navigation.get(), navigationType, m_process->transformHandlesToObjects(userData.object()).get());
if (isMainFrame)
pageClient().didSameDocumentNavigationForMainFrame(navigationType);
}
void WebPageProxy::didChangeMainDocument(FrameIdentifier frameID)
{
#if ENABLE(MEDIA_STREAM)
if (m_userMediaPermissionRequestManager) {
m_userMediaPermissionRequestManager->resetAccess(frameID);
#if ENABLE(GPU_PROCESS)
if (auto* gpuProcess = m_process->processPool().gpuProcess()) {
if (auto* frame = m_process->webFrame(frameID))
gpuProcess->updateCaptureOrigin(SecurityOriginData::fromURL(frame->url()), m_process->coreProcessIdentifier());
}
#endif
}
#else
UNUSED_PARAM(frameID);
#endif
m_isQuotaIncreaseDenied = false;
m_speechRecognitionPermissionManager = nullptr;
}
void WebPageProxy::viewIsBecomingVisible()
{
m_process->markProcessAsRecentlyUsed();
#if ENABLE(MEDIA_STREAM)
if (m_userMediaPermissionRequestManager)
m_userMediaPermissionRequestManager->viewIsBecomingVisible();
#endif
}
void WebPageProxy::didReceiveTitleForFrame(FrameIdentifier frameID, const String& title, const UserData& userData)
{
PageClientProtector protector(pageClient());
WebFrameProxy* frame = m_process->webFrame(frameID);
MESSAGE_CHECK(m_process, frame);
auto transaction = m_pageLoadState.transaction();
if (frame->isMainFrame())
m_pageLoadState.setTitle(transaction, title);
frame->didChangeTitle(title);
m_pageLoadState.commitChanges();
#if ENABLE(REMOTE_INSPECTOR)
if (frame->isMainFrame())
remoteInspectorInformationDidChange();
#endif
}
void WebPageProxy::didFirstLayoutForFrame(FrameIdentifier, const UserData& userData)
{
}
void WebPageProxy::didFirstVisuallyNonEmptyLayoutForFrame(FrameIdentifier frameID, const UserData& userData)
{
PageClientProtector protector(pageClient());
WebFrameProxy* frame = m_process->webFrame(frameID);
MESSAGE_CHECK(m_process, frame);
if (m_loaderClient)
m_loaderClient->didFirstVisuallyNonEmptyLayoutForFrame(*this, *frame, m_process->transformHandlesToObjects(userData.object()).get());
if (frame->isMainFrame())
pageClient().didFirstVisuallyNonEmptyLayoutForMainFrame();
}
void WebPageProxy::didLayoutForCustomContentProvider()
{
didReachLayoutMilestone({ DidFirstLayout, DidFirstVisuallyNonEmptyLayout, DidHitRelevantRepaintedObjectsAreaThreshold });
}
void WebPageProxy::didReachLayoutMilestone(OptionSet<WebCore::LayoutMilestone> layoutMilestones)
{
PageClientProtector protector(pageClient());
if (layoutMilestones.contains(DidFirstVisuallyNonEmptyLayout))
pageClient().clearSafeBrowsingWarningIfForMainFrameNavigation();
if (m_loaderClient)
m_loaderClient->didReachLayoutMilestone(*this, layoutMilestones);
m_navigationClient->renderingProgressDidChange(*this, layoutMilestones);
}
void WebPageProxy::didDisplayInsecureContentForFrame(FrameIdentifier frameID, const UserData& userData)
{
PageClientProtector protector(pageClient());
WebFrameProxy* frame = m_process->webFrame(frameID);
MESSAGE_CHECK(m_process, frame);
auto transaction = m_pageLoadState.transaction();
m_pageLoadState.didDisplayOrRunInsecureContent(transaction);
m_pageLoadState.commitChanges();
m_navigationClient->didDisplayInsecureContent(*this, m_process->transformHandlesToObjects(userData.object()).get());
}
void WebPageProxy::didRunInsecureContentForFrame(FrameIdentifier frameID, const UserData& userData)
{
PageClientProtector protector(pageClient());
WebFrameProxy* frame = m_process->webFrame(frameID);
MESSAGE_CHECK(m_process, frame);
auto transaction = m_pageLoadState.transaction();
m_pageLoadState.didDisplayOrRunInsecureContent(transaction);
m_pageLoadState.commitChanges();
m_navigationClient->didRunInsecureContent(*this, m_process->transformHandlesToObjects(userData.object()).get());
}
void WebPageProxy::didDetectXSSForFrame(FrameIdentifier, const UserData&)
{
}
void WebPageProxy::mainFramePluginHandlesPageScaleGestureDidChange(bool mainFramePluginHandlesPageScaleGesture)
{
m_mainFramePluginHandlesPageScaleGesture = mainFramePluginHandlesPageScaleGesture;
}
#if !PLATFORM(COCOA)
void WebPageProxy::beginSafeBrowsingCheck(const URL&, bool, WebFramePolicyListenerProxy& listener)
{
listener.didReceiveSafeBrowsingResults({ });
}
#endif
void WebPageProxy::decidePolicyForNavigationActionAsync(FrameIdentifier frameID, FrameInfoData&& frameInfo, PolicyCheckIdentifier identifier, uint64_t navigationID,
NavigationActionData&& navigationActionData, FrameInfoData&& originatingFrameInfo, std::optional<WebPageProxyIdentifier> originatingPageID, const WebCore::ResourceRequest& originalRequest, WebCore::ResourceRequest&& request,
IPC::FormDataReference&& requestBody, WebCore::ResourceResponse&& redirectResponse, const UserData& userData, uint64_t listenerID)
{
decidePolicyForNavigationActionAsyncShared(m_process.copyRef(), m_webPageID, frameID, WTFMove(frameInfo), identifier, navigationID, WTFMove(navigationActionData), WTFMove(originatingFrameInfo), originatingPageID, originalRequest, WTFMove(request), WTFMove(requestBody), WTFMove(redirectResponse), userData, listenerID);
}
void WebPageProxy::decidePolicyForNavigationActionAsyncShared(Ref<WebProcessProxy>&& process, PageIdentifier webPageID, FrameIdentifier frameID, FrameInfoData&& frameInfo,
WebCore::PolicyCheckIdentifier identifier, uint64_t navigationID, NavigationActionData&& navigationActionData, FrameInfoData&& originatingFrameInfo, std::optional<WebPageProxyIdentifier> originatingPageID,
const WebCore::ResourceRequest& originalRequest, WebCore::ResourceRequest&& request, IPC::FormDataReference&& requestBody, WebCore::ResourceResponse&& redirectResponse,
const UserData& userData, uint64_t listenerID)
{
auto* frame = process->webFrame(frameID);
MESSAGE_CHECK(process, frame);
auto sender = PolicyDecisionSender::create(identifier, [webPageID, frameID, listenerID, process] (const auto& policyDecision) {
process->send(Messages::WebPage::DidReceivePolicyDecision(frameID, listenerID, policyDecision, createNetworkExtensionsSandboxExtensions(process)), webPageID);
});
decidePolicyForNavigationAction(process.copyRef(), webPageID, *frame, WTFMove(frameInfo), navigationID, WTFMove(navigationActionData), WTFMove(originatingFrameInfo), originatingPageID,
originalRequest, WTFMove(request), WTFMove(requestBody), WTFMove(redirectResponse), userData, WTFMove(sender));
}
#if PLATFORM(COCOA)
// https://html.spec.whatwg.org/#hand-off-to-external-software
static bool frameSandboxAllowsOpeningExternalCustomProtocols(SandboxFlags sandboxFlags, bool hasUserGesture)
{
if (!(sandboxFlags & SandboxPopups) || !(sandboxFlags & SandboxTopNavigation))
return true;
return !(sandboxFlags & SandboxTopNavigationByUserActivation) && hasUserGesture;
}
#endif
void WebPageProxy::decidePolicyForNavigationAction(Ref<WebProcessProxy>&& process, PageIdentifier webPageID, WebFrameProxy& frame, FrameInfoData&& frameInfo, uint64_t navigationID,
NavigationActionData&& navigationActionData, FrameInfoData&& originatingFrameInfoData, std::optional<WebPageProxyIdentifier> originatingPageID, const WebCore::ResourceRequest& originalRequest, WebCore::ResourceRequest&& request,
IPC::FormDataReference&& requestBody, WebCore::ResourceResponse&& redirectResponse, const UserData& userData, Ref<PolicyDecisionSender>&& sender)
{
WEBPAGEPROXY_RELEASE_LOG(Loading, "decidePolicyForNavigationAction: frameID=%llu, navigationID=%llu", frame.frameID().toUInt64(), navigationID);
LOG(Loading, "WebPageProxy::decidePolicyForNavigationAction - Original URL %s, current target URL %s", originalRequest.url().string().utf8().data(), request.url().string().utf8().data());
PageClientProtector protector(pageClient());
// Make the request whole again as we do not normally encode the request's body when sending it over IPC, for performance reasons.
request.setHTTPBody(requestBody.takeData());
auto transaction = m_pageLoadState.transaction();
bool fromAPI = request.url() == m_pageLoadState.pendingAPIRequestURL();
if (navigationID && !fromAPI)
m_pageLoadState.clearPendingAPIRequest(transaction);
if (!checkURLReceivedFromCurrentOrPreviousWebProcess(process, request.url())) {
WEBPAGEPROXY_RELEASE_LOG_ERROR(Process, "Ignoring request to load this main resource because it is outside the sandbox");
sender->send(PolicyDecision { sender->identifier(), isNavigatingToAppBoundDomain(), PolicyAction::Ignore, 0, std::nullopt, std::nullopt });
return;
}
MESSAGE_CHECK_URL(process, originalRequest.url());
RefPtr<API::Navigation> navigation;
if (navigationID)
navigation = m_navigationState->navigation(navigationID);
// When process-swapping on a redirect, the navigationActionData / originatingFrameInfoData provided by the fresh new WebProcess are inaccurate since
// the new process does not have sufficient information. To address the issue, we restore the information we stored on the NavigationAction during the original request
// policy decision.
if (navigationActionData.isRedirect && navigation) {
navigationActionData = navigation->lastNavigationAction();
navigationActionData.isRedirect = true;
originatingFrameInfoData = navigation->originatingFrameInfo();
frameInfo.securityOrigin = navigation->destinationFrameSecurityOrigin();
}
if (!navigation) {
if (auto targetBackForwardItemIdentifier = navigationActionData.targetBackForwardItemIdentifier) {
if (auto* item = m_backForwardList->itemForID(*targetBackForwardItemIdentifier)) {
auto* fromItem = navigationActionData.sourceBackForwardItemIdentifier ? m_backForwardList->itemForID(*navigationActionData.sourceBackForwardItemIdentifier) : nullptr;
if (!fromItem)
fromItem = m_backForwardList->currentItem();
navigation = m_navigationState->createBackForwardNavigation(*item, fromItem, FrameLoadType::IndexedBackForward);
}
}
if (!navigation)
navigation = m_navigationState->createLoadRequestNavigation(ResourceRequest(request), m_backForwardList->currentItem());
}
navigationID = navigation->navigationID();
// Make sure the provisional page always has the latest navigationID.
if (m_provisionalPage && &m_provisionalPage->process() == process.ptr())
m_provisionalPage->setNavigationID(navigationID);
navigation->setCurrentRequest(ResourceRequest(request), process->coreProcessIdentifier());
navigation->setLastNavigationAction(navigationActionData);
navigation->setOriginatingFrameInfo(originatingFrameInfoData);
navigation->setDestinationFrameSecurityOrigin(frameInfo.securityOrigin);
API::Navigation* mainFrameNavigation = frame.isMainFrame() ? navigation.get() : nullptr;
WebFrameProxy* originatingFrame = originatingFrameInfoData.frameID ? process->webFrame(*originatingFrameInfoData.frameID) : nullptr;
auto destinationFrameInfo = API::FrameInfo::create(FrameInfoData { frameInfo }, this);
RefPtr<API::FrameInfo> sourceFrameInfo;
if (!fromAPI && originatingFrame == &frame)
sourceFrameInfo = destinationFrameInfo.copyRef();
else if (!fromAPI) {
auto* originatingPage = originatingPageID ? process->webPage(*originatingPageID) : nullptr;
sourceFrameInfo = API::FrameInfo::create(WTFMove(originatingFrameInfoData), originatingPage);
}
bool shouldOpenAppLinks = !m_shouldSuppressAppLinksInNextNavigationPolicyDecision
&& destinationFrameInfo->isMainFrame()
&& (m_mainFrame && m_mainFrame->url().host() != request.url().host())
&& navigationActionData.navigationType != WebCore::NavigationType::BackForward;
auto userInitiatedActivity = process->userInitiatedActivity(navigationActionData.userGestureTokenIdentifier);
auto navigationAction = API::NavigationAction::create(WTFMove(navigationActionData), sourceFrameInfo.get(), destinationFrameInfo.ptr(), std::nullopt, ResourceRequest(request), originalRequest.url(), shouldOpenAppLinks, WTFMove(userInitiatedActivity), mainFrameNavigation);
#if ENABLE(CONTENT_FILTERING)
if (frame.didHandleContentFilterUnblockNavigation(request)) {
WEBPAGEPROXY_RELEASE_LOG_ERROR(Process, "Ignoring request to load this main resource because it was handled by content filter");
return receivedPolicyDecision(PolicyAction::Ignore, m_navigationState->navigation(navigationID), nullptr, WTFMove(navigationAction), WTFMove(sender));
}
#endif
// Other ports do not implement WebPage::platformCanHandleRequest().
#if PLATFORM(COCOA)
// Sandboxed iframes should be allowed to open external apps via custom protocols unless explicitely allowed (https://html.spec.whatwg.org/#hand-off-to-external-software).
bool canHandleRequest = navigationActionData.canHandleRequest || m_urlSchemeHandlersByScheme.contains(request.url().protocol().toStringWithoutCopying());
if (!canHandleRequest && !destinationFrameInfo->isMainFrame() && !frameSandboxAllowsOpeningExternalCustomProtocols(navigationActionData.effectiveSandboxFlags, !!navigationActionData.userGestureTokenIdentifier)) {
WEBPAGEPROXY_RELEASE_LOG_ERROR(Process, "Ignoring request to load this main resource because it has a custom protocol and comes from a sandboxed iframe");
process->send(Messages::WebPage::AddConsoleMessage(frame.frameID(), MessageSource::Security, MessageLevel::Error, "Ignoring request to load this main resource because it has a custom protocol and comes from a sandboxed iframe"_s, std::nullopt), webPageID);
return receivedPolicyDecision(PolicyAction::Ignore, m_navigationState->navigation(navigationID), nullptr, WTFMove(navigationAction), WTFMove(sender));
}
#endif
ShouldExpectSafeBrowsingResult shouldExpectSafeBrowsingResult = ShouldExpectSafeBrowsingResult::Yes;
if (!m_preferences->safeBrowsingEnabled())
shouldExpectSafeBrowsingResult = ShouldExpectSafeBrowsingResult::No;
ShouldExpectAppBoundDomainResult shouldExpectAppBoundDomainResult = ShouldExpectAppBoundDomainResult::No;
#if ENABLE(APP_BOUND_DOMAINS)
shouldExpectAppBoundDomainResult = ShouldExpectAppBoundDomainResult::Yes;
#endif
Ref listener = frame.setUpPolicyListenerProxy([this, protectedThis = Ref { *this }, frame = Ref { frame }, sender = WTFMove(sender), navigation, navigationAction, frameInfo, userDataObject = process->transformHandlesToObjects(userData.object()).get()] (PolicyAction policyAction, API::WebsitePolicies* policies, ProcessSwapRequestedByClient processSwapRequestedByClient, RefPtr<SafeBrowsingWarning>&& safeBrowsingWarning, std::optional<NavigatingToAppBoundDomain> isAppBoundDomain) mutable {
WEBPAGEPROXY_RELEASE_LOG(Loading, "decidePolicyForNavigationAction: listener called: frameID=%llu, navigationID=%llu, policyAction=%u, safeBrowsingWarning=%d, isAppBoundDomain=%d", frame->frameID().toUInt64(), navigation ? navigation->navigationID() : 0, (unsigned)policyAction, !!safeBrowsingWarning, !!isAppBoundDomain);
auto completionHandler = [this, protectedThis, frame, frameInfo, sender = WTFMove(sender), navigation, navigationAction = WTFMove(navigationAction), processSwapRequestedByClient, policies = RefPtr { policies }] (PolicyAction policyAction) mutable {
if (frame->isMainFrame()) {
if (!policies) {
if (auto* defaultPolicies = m_configuration->defaultWebsitePolicies())
policies = defaultPolicies->copy();
}
if (policies)
navigation->setEffectiveContentMode(effectiveContentModeAfterAdjustingPolicies(*policies, navigation->currentRequest()));
}
receivedNavigationPolicyDecision(policyAction, navigation.get(), WTFMove(navigationAction), processSwapRequestedByClient, frame, frameInfo, WTFMove(policies), WTFMove(sender));
};
#if ENABLE(APP_BOUND_DOMAINS)
if (policyAction != PolicyAction::Ignore) {
if (!setIsNavigatingToAppBoundDomainAndCheckIfPermitted(frame->isMainFrame(), navigation->currentRequest().url(), isAppBoundDomain)) {
auto error = errorForUnpermittedAppBoundDomainNavigation(navigation->currentRequest().url());
m_navigationClient->didFailProvisionalNavigationWithError(*this, FrameInfoData { frameInfo }, navigation.get(), error, userDataObject);
WEBPAGEPROXY_RELEASE_LOG_ERROR(Loading, "Ignoring request to load this main resource because it is attempting to navigate away from an app-bound domain or navigate after using restricted APIs");
completionHandler(PolicyAction::Ignore);
return;
}
if (frame->isMainFrame())
m_isTopFrameNavigatingToAppBoundDomain = m_isNavigatingToAppBoundDomain;
}
#endif
if (!m_pageClient)
return completionHandler(policyAction);
m_pageClient->clearSafeBrowsingWarning();
if (safeBrowsingWarning) {
if (frame->isMainFrame() && safeBrowsingWarning->url().isValid()) {
auto transaction = m_pageLoadState.transaction();
m_pageLoadState.setPendingAPIRequest(transaction, { navigation->navigationID(), safeBrowsingWarning->url().string() });
m_pageLoadState.commitChanges();
}
auto transaction = m_pageLoadState.transaction();
m_pageLoadState.setTitleFromSafeBrowsingWarning(transaction, safeBrowsingWarning->title());
m_pageClient->showSafeBrowsingWarning(*safeBrowsingWarning, [this, protectedThis = WTFMove(protectedThis), completionHandler = WTFMove(completionHandler), policyAction] (auto&& result) mutable {
auto transaction = m_pageLoadState.transaction();
m_pageLoadState.setTitleFromSafeBrowsingWarning(transaction, { });
switchOn(result, [&] (const URL& url) {
completionHandler(PolicyAction::Ignore);
loadRequest({ url });
}, [&] (ContinueUnsafeLoad continueUnsafeLoad) {
switch (continueUnsafeLoad) {
case ContinueUnsafeLoad::No:
if (!hasCommittedAnyProvisionalLoads())
m_uiClient->close(protectedThis.ptr());
completionHandler(PolicyAction::Ignore);
break;
case ContinueUnsafeLoad::Yes:
completionHandler(policyAction);
break;
}
});
});
m_uiClient->didShowSafeBrowsingWarning();
return;
}
completionHandler(policyAction);
}, shouldExpectSafeBrowsingResult, shouldExpectAppBoundDomainResult);
if (shouldExpectSafeBrowsingResult == ShouldExpectSafeBrowsingResult::Yes)
beginSafeBrowsingCheck(request.url(), frame.isMainFrame(), listener);
#if ENABLE(APP_BOUND_DOMAINS)
bool shouldSendSecurityOriginData = !frame.isMainFrame() && shouldTreatURLProtocolAsAppBound(request.url(), websiteDataStore().configuration().enableInAppBrowserPrivacyForTesting());
auto host = shouldSendSecurityOriginData ? frameInfo.securityOrigin.host : request.url().host();
auto protocol = shouldSendSecurityOriginData ? frameInfo.securityOrigin.protocol : request.url().protocol();
m_websiteDataStore->beginAppBoundDomainCheck(host.toString(), protocol.toString(), listener);
#endif
#if ENABLE(INTELLIGENT_TRACKING_PREVENTION)
auto wasPotentiallyInitiatedByUser = navigation->isLoadedWithNavigationShared() || userInitiatedActivity;
if (!sessionID().isEphemeral())
logFrameNavigation(frame, URL(URL(), m_pageLoadState.url()), request, redirectResponse.url(), wasPotentiallyInitiatedByUser);
#endif
if (m_policyClient)
m_policyClient->decidePolicyForNavigationAction(*this, &frame, WTFMove(navigationAction), originatingFrame, originalRequest, WTFMove(request), WTFMove(listener), process->transformHandlesToObjects(userData.object()).get());
else {
#if HAVE(APP_SSO)
if (m_shouldSuppressSOAuthorizationInNextNavigationPolicyDecision || !m_preferences->isExtensibleSSOEnabled())
navigationAction->unsetShouldPerformSOAuthorization();
#endif
m_navigationClient->decidePolicyForNavigationAction(*this, WTFMove(navigationAction), WTFMove(listener), process->transformHandlesToObjects(userData.object()).get());
}
m_shouldSuppressAppLinksInNextNavigationPolicyDecision = false;
#if HAVE(APP_SSO)
m_shouldSuppressSOAuthorizationInNextNavigationPolicyDecision = false;
#endif
}
WebPageProxy* WebPageProxy::nonEphemeralWebPageProxy()
{
auto processPools = WebProcessPool::allProcessPools();
if (processPools.isEmpty())
return nullptr;
for (auto& webProcess : processPools[0]->processes()) {
for (auto& page : webProcess->pages()) {
if (page->sessionID().isEphemeral())
continue;
return page;
}
}
return nullptr;
}
#if ENABLE(INTELLIGENT_TRACKING_PREVENTION)
void WebPageProxy::logFrameNavigation(const WebFrameProxy& frame, const URL& pageURL, const WebCore::ResourceRequest& request, const URL& redirectURL, bool wasPotentiallyInitiatedByUser)
{
ASSERT(RunLoop::isMain());
auto sourceURL = redirectURL;
bool isRedirect = !redirectURL.isNull();
if (!isRedirect) {
sourceURL = frame.url();
if (sourceURL.isNull())
sourceURL = pageURL;
}
auto& targetURL = request.url();
if (!targetURL.isValid() || !pageURL.isValid())
return;
auto targetHost = targetURL.host();
auto mainFrameHost = pageURL.host();
if (targetHost.isEmpty() || mainFrameHost.isEmpty() || targetHost == sourceURL.host())
return;
websiteDataStore().networkProcess().send(Messages::NetworkProcess::LogFrameNavigation(m_websiteDataStore->sessionID(), RegistrableDomain { targetURL }, RegistrableDomain { pageURL }, RegistrableDomain { sourceURL }, isRedirect, frame.isMainFrame(), MonotonicTime::now() - m_didFinishDocumentLoadForMainFrameTimestamp, wasPotentiallyInitiatedByUser), 0);
}
#endif
void WebPageProxy::decidePolicyForNavigationActionSync(FrameIdentifier frameID, bool isMainFrame, FrameInfoData&& frameInfo, PolicyCheckIdentifier identifier,
uint64_t navigationID, NavigationActionData&& navigationActionData, FrameInfoData&& originatingFrameInfo, std::optional<WebPageProxyIdentifier> originatingPageID,
const WebCore::ResourceRequest& originalRequest, WebCore::ResourceRequest&& request, IPC::FormDataReference&& requestBody, WebCore::ResourceResponse&& redirectResponse,
const UserData& userData, Messages::WebPageProxy::DecidePolicyForNavigationActionSync::DelayedReply&& reply)
{
auto* frame = m_process->webFrame(frameID);
if (!frame) {
// This synchronous IPC message was processed before the asynchronous DidCreateMainFrame / DidCreateSubframe one so we do not know about this frameID yet.
if (isMainFrame)
didCreateMainFrame(frameID);
else
didCreateSubframe(frameID);
}
decidePolicyForNavigationActionSyncShared(m_process.copyRef(), m_webPageID, frameID, isMainFrame, WTFMove(frameInfo), identifier, navigationID, WTFMove(navigationActionData), WTFMove(originatingFrameInfo), originatingPageID, originalRequest, WTFMove(request), WTFMove(requestBody), WTFMove(redirectResponse), userData, WTFMove(reply));
}
void WebPageProxy::decidePolicyForNavigationActionSyncShared(Ref<WebProcessProxy>&& process, PageIdentifier webPageID, FrameIdentifier frameID, bool isMainFrame, FrameInfoData&& frameInfo, PolicyCheckIdentifier identifier, uint64_t navigationID, NavigationActionData&& navigationActionData, FrameInfoData&& originatingFrameInfo, std::optional<WebPageProxyIdentifier> originatingPageID, const WebCore::ResourceRequest& originalRequest, WebCore::ResourceRequest&& request, IPC::FormDataReference&& requestBody, WebCore::ResourceResponse&& redirectResponse, const UserData& userData, Messages::WebPageProxy::DecidePolicyForNavigationActionSync::DelayedReply&& reply)
{
auto sender = PolicyDecisionSender::create(identifier, WTFMove(reply));
auto* frame = process->webFrame(frameID);
MESSAGE_CHECK(process, frame);
decidePolicyForNavigationAction(WTFMove(process), webPageID, *frame, WTFMove(frameInfo), navigationID, WTFMove(navigationActionData), WTFMove(originatingFrameInfo), originatingPageID, originalRequest, WTFMove(request), WTFMove(requestBody), WTFMove(redirectResponse), userData, sender.copyRef());
// If the client did not respond synchronously, proceed with the load.
sender->send(PolicyDecision { sender->identifier(), isNavigatingToAppBoundDomain(), PolicyAction::Use, navigationID, std::nullopt, std::nullopt });
}
void WebPageProxy::decidePolicyForNewWindowAction(FrameIdentifier frameID, FrameInfoData&& frameInfo, PolicyCheckIdentifier identifier, NavigationActionData&& navigationActionData, ResourceRequest&& request, const String& frameName, uint64_t listenerID, const UserData& userData)
{
PageClientProtector protector(pageClient());
WebFrameProxy* frame = m_process->webFrame(frameID);
MESSAGE_CHECK(m_process, frame);
MESSAGE_CHECK_URL(m_process, request.url());
RefPtr<API::FrameInfo> sourceFrameInfo;
if (frame)
sourceFrameInfo = API::FrameInfo::create(WTFMove(frameInfo), this);
auto userInitiatedActivity = m_process->userInitiatedActivity(navigationActionData.userGestureTokenIdentifier);
bool shouldOpenAppLinks = m_mainFrame && m_mainFrame->url().host() != request.url().host();
auto navigationAction = API::NavigationAction::create(WTFMove(navigationActionData), sourceFrameInfo.get(), nullptr, frameName, ResourceRequest(request), URL { }, shouldOpenAppLinks, WTFMove(userInitiatedActivity));
Ref listener = frame->setUpPolicyListenerProxy([this, protectedThis = Ref { *this }, identifier, listenerID, frameID, navigationAction] (PolicyAction policyAction, API::WebsitePolicies*, ProcessSwapRequestedByClient processSwapRequestedByClient, RefPtr<SafeBrowsingWarning>&& safeBrowsingWarning, std::optional<NavigatingToAppBoundDomain> isNavigatingToAppBoundDomain) mutable {
// FIXME: Assert the API::WebsitePolicies* is nullptr here once clients of WKFramePolicyListenerUseWithPolicies go away.
RELEASE_ASSERT(processSwapRequestedByClient == ProcessSwapRequestedByClient::No);
ASSERT_UNUSED(safeBrowsingWarning, !safeBrowsingWarning);
auto sender = PolicyDecisionSender::create(identifier, [this, protectedThis = WTFMove(protectedThis), frameID, listenerID] (const auto& policyDecision) {
send(Messages::WebPage::DidReceivePolicyDecision(frameID, listenerID, policyDecision, createNetworkExtensionsSandboxExtensions(m_process)));
});
receivedPolicyDecision(policyAction, nullptr, nullptr, WTFMove(navigationAction), WTFMove(sender));
}, ShouldExpectSafeBrowsingResult::No, ShouldExpectAppBoundDomainResult::No);
if (m_policyClient)
m_policyClient->decidePolicyForNewWindowAction(*this, *frame, navigationAction.get(), request, frameName, WTFMove(listener), m_process->transformHandlesToObjects(userData.object()).get());
else
m_navigationClient->decidePolicyForNavigationAction(*this, navigationAction.get(), WTFMove(listener), m_process->transformHandlesToObjects(userData.object()).get());
}
void WebPageProxy::decidePolicyForResponse(FrameIdentifier frameID, FrameInfoData&& frameInfo, PolicyCheckIdentifier identifier,
uint64_t navigationID, const ResourceResponse& response, const ResourceRequest& request, bool canShowMIMEType, const String& downloadAttribute,
bool wasAllowedByInjectedBundle, uint64_t listenerID, const UserData& userData)
{
decidePolicyForResponseShared(m_process.copyRef(), m_webPageID, frameID, WTFMove(frameInfo), identifier, navigationID, response, request, canShowMIMEType, downloadAttribute, wasAllowedByInjectedBundle, listenerID, userData);
}
void WebPageProxy::decidePolicyForResponseShared(Ref<WebProcessProxy>&& process, PageIdentifier webPageID, FrameIdentifier frameID, FrameInfoData&& frameInfo, PolicyCheckIdentifier identifier, uint64_t navigationID, const ResourceResponse& response, const ResourceRequest& request, bool canShowMIMEType, const String& downloadAttribute, bool wasAllowedByInjectedBundle, uint64_t listenerID, const UserData& userData)
{
PageClientProtector protector(pageClient());
m_decidePolicyForResponseRequest = request;
WebFrameProxy* frame = process->webFrame(frameID);
MESSAGE_CHECK(process, frame);
MESSAGE_CHECK_URL(process, request.url());
MESSAGE_CHECK_URL(process, response.url());
RefPtr<API::Navigation> navigation = navigationID ? m_navigationState->navigation(navigationID) : nullptr;
auto navigationResponse = API::NavigationResponse::create(API::FrameInfo::create(WTFMove(frameInfo), this).get(), request, response, canShowMIMEType, downloadAttribute);
Ref listener = frame->setUpPolicyListenerProxy([this, protectedThis = Ref { *this }, webPageID, frameID, identifier, listenerID, navigation = WTFMove(navigation),
process, navigationResponse] (PolicyAction policyAction, API::WebsitePolicies*, ProcessSwapRequestedByClient processSwapRequestedByClient, RefPtr<SafeBrowsingWarning>&& safeBrowsingWarning, std::optional<NavigatingToAppBoundDomain> isNavigatingToAppBoundDomain) mutable {
// FIXME: Assert the API::WebsitePolicies* is nullptr here once clients of WKFramePolicyListenerUseWithPolicies go away.
RELEASE_ASSERT(processSwapRequestedByClient == ProcessSwapRequestedByClient::No);
ASSERT_UNUSED(safeBrowsingWarning, !safeBrowsingWarning);
auto sender = PolicyDecisionSender::create(identifier, [webPageID, frameID, listenerID, process] (const auto& policyDecision) {
process->send(Messages::WebPage::DidReceivePolicyDecision(frameID, listenerID, policyDecision, createNetworkExtensionsSandboxExtensions(process)), webPageID);
});
receivedPolicyDecision(policyAction, navigation.get(), nullptr, WTFMove(navigationResponse), WTFMove(sender));
}, ShouldExpectSafeBrowsingResult::No, ShouldExpectAppBoundDomainResult::No);
if (wasAllowedByInjectedBundle) {
listener->use();
return;
}
if (m_policyClient)
m_policyClient->decidePolicyForResponse(*this, *frame, response, request, canShowMIMEType, WTFMove(listener), process->transformHandlesToObjects(userData.object()).get());
else
m_navigationClient->decidePolicyForNavigationResponse(*this, WTFMove(navigationResponse), WTFMove(listener), process->transformHandlesToObjects(userData.object()).get());
}
void WebPageProxy::triggerBrowsingContextGroupSwitchForNavigation(uint64_t navigationID, BrowsingContextGroupSwitchDecision browsingContextGroupSwitchDecision, const RegistrableDomain& responseDomain, NetworkResourceLoadIdentifier existingNetworkResourceLoadIdentifierToResume, CompletionHandler<void(bool success)>&& completionHandler)
{
ASSERT(browsingContextGroupSwitchDecision != BrowsingContextGroupSwitchDecision::StayInGroup);
RefPtr<API::Navigation> navigation = navigationID ? m_navigationState->navigation(navigationID) : nullptr;
WEBPAGEPROXY_RELEASE_LOG(ProcessSwapping, "triggerBrowsingContextGroupSwitchForNavigation: Process-swapping due to Cross-Origin-Opener-Policy, newProcessIsCrossOriginIsolated=%d, navigation=%p", browsingContextGroupSwitchDecision == BrowsingContextGroupSwitchDecision::NewIsolatedGroup, navigation.get());
if (!navigation)
return completionHandler(false);
RefPtr<WebProcessProxy> processForNavigation;
if (browsingContextGroupSwitchDecision == BrowsingContextGroupSwitchDecision::NewIsolatedGroup)
processForNavigation = m_process->processPool().createNewWebProcess(&websiteDataStore(), m_process->captivePortalMode(), WebProcessProxy::IsPrewarmed::No, CrossOriginMode::Isolated);
else
processForNavigation = m_process->processPool().processForRegistrableDomain(websiteDataStore(), responseDomain, m_process->captivePortalMode());
continueNavigationInNewProcess(*navigation, nullptr, processForNavigation.releaseNonNull(), ProcessSwapRequestedByClient::No, ShouldTreatAsContinuingLoad::YesAfterProvisionalLoadStarted, nullptr, existingNetworkResourceLoadIdentifierToResume);
completionHandler(true);
}
void WebPageProxy::unableToImplementPolicy(FrameIdentifier frameID, const ResourceError& error, const UserData& userData)
{
PageClientProtector protector(pageClient());
WebFrameProxy* frame = m_process->webFrame(frameID);
MESSAGE_CHECK(m_process, frame);
if (!m_policyClient)
return;
m_policyClient->unableToImplementPolicy(*this, *frame, error, m_process->transformHandlesToObjects(userData.object()).get());
}
// FormClient
void WebPageProxy::willSubmitForm(FrameIdentifier frameID, FrameIdentifier sourceFrameID, const Vector<std::pair<String, String>>& textFieldValues, FormSubmitListenerIdentifier listenerID, const UserData& userData)
{
WebFrameProxy* frame = m_process->webFrame(frameID);
MESSAGE_CHECK(m_process, frame);
WebFrameProxy* sourceFrame = m_process->webFrame(sourceFrameID);
MESSAGE_CHECK(m_process, sourceFrame);
for (auto& pair : textFieldValues)
MESSAGE_CHECK(m_process, API::Dictionary::MapType::isValidKey(pair.first));
m_formClient->willSubmitForm(*this, *frame, *sourceFrame, textFieldValues, m_process->transformHandlesToObjects(userData.object()).get(), [this, protectedThis = Ref { *this }, frameID, listenerID]() {
send(Messages::WebPage::ContinueWillSubmitForm(frameID, listenerID));
});
}
#if ENABLE(CONTENT_EXTENSIONS)
void WebPageProxy::contentRuleListNotification(URL&& url, ContentRuleListResults&& results)
{
m_navigationClient->contentRuleListNotification(*this, WTFMove(url), WTFMove(results));
}
#endif
void WebPageProxy::didNavigateWithNavigationData(const WebNavigationDataStore& store, FrameIdentifier frameID)
{
didNavigateWithNavigationDataShared(m_process.copyRef(), store, frameID);
}
void WebPageProxy::didNavigateWithNavigationDataShared(Ref<WebProcessProxy>&& process, const WebNavigationDataStore& store, FrameIdentifier frameID)
{
WEBPAGEPROXY_RELEASE_LOG(Loading, "didNavigateWithNavigationDataShared:");
PageClientProtector protector(pageClient());
WebFrameProxy* frame = process->webFrame(frameID);
MESSAGE_CHECK(process, frame);
MESSAGE_CHECK(process, frame->page() == this);
if (frame->isMainFrame())
m_historyClient->didNavigateWithNavigationData(*this, store);
process->processPool().historyClient().didNavigateWithNavigationData(process->processPool(), *this, store, *frame);
}
void WebPageProxy::didPerformClientRedirect(const String& sourceURLString, const String& destinationURLString, FrameIdentifier frameID)
{
didPerformClientRedirectShared(m_process.copyRef(), sourceURLString, destinationURLString, frameID);
}
void WebPageProxy::didPerformClientRedirectShared(Ref<WebProcessProxy>&& process, const String& sourceURLString, const String& destinationURLString, FrameIdentifier frameID)
{
WEBPAGEPROXY_RELEASE_LOG(Loading, "didPerformClientRedirectShared: frameID=%" PRIu64, frameID.toUInt64());
PageClientProtector protector(pageClient());
if (sourceURLString.isEmpty() || destinationURLString.isEmpty())
return;
WebFrameProxy* frame = process->webFrame(frameID);
MESSAGE_CHECK(process, frame);
MESSAGE_CHECK(process, frame->page() == this);
MESSAGE_CHECK_URL(process, sourceURLString);
MESSAGE_CHECK_URL(process, destinationURLString);
if (frame->isMainFrame()) {
m_historyClient->didPerformClientRedirect(*this, sourceURLString, destinationURLString);
m_navigationClient->didPerformClientRedirect(*this, sourceURLString, destinationURLString);
}
process->processPool().historyClient().didPerformClientRedirect(process->processPool(), *this, sourceURLString, destinationURLString, *frame);
}
void WebPageProxy::didPerformServerRedirect(const String& sourceURLString, const String& destinationURLString, FrameIdentifier frameID)
{
didPerformServerRedirectShared(m_process.copyRef(), sourceURLString, destinationURLString, frameID);
}
void WebPageProxy::didPerformServerRedirectShared(Ref<WebProcessProxy>&& process, const String& sourceURLString, const String& destinationURLString, FrameIdentifier frameID)
{
WEBPAGEPROXY_RELEASE_LOG(Loading, "didPerformServerRedirect:");
PageClientProtector protector(pageClient());
if (sourceURLString.isEmpty() || destinationURLString.isEmpty())
return;
WebFrameProxy* frame = process->webFrame(frameID);
MESSAGE_CHECK(process, frame);
MESSAGE_CHECK(process, frame->page() == this);
MESSAGE_CHECK_URL(process, sourceURLString);
MESSAGE_CHECK_URL(process, destinationURLString);
if (frame->isMainFrame())
m_historyClient->didPerformServerRedirect(*this, sourceURLString, destinationURLString);
process->processPool().historyClient().didPerformServerRedirect(process->processPool(), *this, sourceURLString, destinationURLString, *frame);
}
void WebPageProxy::didUpdateHistoryTitle(const String& title, const String& url, FrameIdentifier frameID)
{
PageClientProtector protector(pageClient());
WebFrameProxy* frame = m_process->webFrame(frameID);
MESSAGE_CHECK(m_process, frame);
MESSAGE_CHECK(m_process, frame->page() == this);
MESSAGE_CHECK_URL(m_process, url);
if (frame->isMainFrame())
m_historyClient->didUpdateHistoryTitle(*this, title, url);
process().processPool().historyClient().didUpdateHistoryTitle(process().processPool(), *this, title, url, *frame);
}
// UIClient
using NewPageCallback = CompletionHandler<void(RefPtr<WebPageProxy>&&)>;
using UIClientCallback = Function<void(Ref<API::NavigationAction>&&, NewPageCallback&&)>;
static void trySOAuthorization(Ref<API::NavigationAction>&& navigationAction, WebPageProxy& page, NewPageCallback&& newPageCallback, UIClientCallback&& uiClientCallback)
{
#if HAVE(APP_SSO)
page.websiteDataStore().soAuthorizationCoordinator().tryAuthorize(WTFMove(navigationAction), page, WTFMove(newPageCallback), WTFMove(uiClientCallback));
#else
uiClientCallback(WTFMove(navigationAction), WTFMove(newPageCallback));
#endif
}
void WebPageProxy::createNewPage(FrameInfoData&& originatingFrameInfoData, WebPageProxyIdentifier originatingPageID, ResourceRequest&& request, WindowFeatures&& windowFeatures, NavigationActionData&& navigationActionData, Messages::WebPageProxy::CreateNewPage::DelayedReply&& reply)
{
MESSAGE_CHECK(m_process, originatingFrameInfoData.frameID);
MESSAGE_CHECK(m_process, m_process->webFrame(*originatingFrameInfoData.frameID));
auto* originatingPage = m_process->webPage(originatingPageID);
auto originatingFrameInfo = API::FrameInfo::create(WTFMove(originatingFrameInfoData), originatingPage);
auto mainFrameURL = m_mainFrame ? m_mainFrame->url() : URL();
std::optional<bool> openerAppInitiatedState;
if (originatingPage)
openerAppInitiatedState = originatingPage->lastNavigationWasAppInitiated();
auto completionHandler = [this, protectedThis = Ref { *this }, mainFrameURL, request, reply = WTFMove(reply), privateClickMeasurement = navigationActionData.privateClickMeasurement, openerAppInitiatedState = WTFMove(openerAppInitiatedState)] (RefPtr<WebPageProxy> newPage) mutable {
if (!newPage) {
reply(std::nullopt, std::nullopt);
return;
}
newPage->setOpenedByDOM();
if (openerAppInitiatedState)
newPage->m_lastNavigationWasAppInitiated = *openerAppInitiatedState;
reply(newPage->webPageID(), newPage->creationParameters(m_process, *newPage->drawingArea()));
newPage->m_shouldSuppressAppLinksInNextNavigationPolicyDecision = mainFrameURL.host() == request.url().host();
if (privateClickMeasurement)
newPage->m_privateClickMeasurement = {{ WTFMove(*privateClickMeasurement), { }, { }}};
#if HAVE(APP_SSO)
newPage->m_shouldSuppressSOAuthorizationInNextNavigationPolicyDecision = true;
#endif
};
RefPtr<API::UserInitiatedAction> userInitiatedActivity;
#if ENABLE(INTELLIGENT_TRACKING_PREVENTION)
// WebKit cancels the original gesture to open the BBC radio player so
// we can call the Storage Access API first. When we re-initiate the open,
// we should make sure the client knows that this was user initiated so it
// does not block the popup.
if (request.url().string() == Quirks::staticRadioPlayerURLString())
userInitiatedActivity = API::UserInitiatedAction::create();
else
#endif
userInitiatedActivity = m_process->userInitiatedActivity(navigationActionData.userGestureTokenIdentifier);
bool shouldOpenAppLinks = originatingFrameInfo->request().url().host() != request.url().host();
auto navigationAction = API::NavigationAction::create(WTFMove(navigationActionData), originatingFrameInfo.ptr(), nullptr, std::nullopt, WTFMove(request), URL(), shouldOpenAppLinks, WTFMove(userInitiatedActivity));
trySOAuthorization(WTFMove(navigationAction), *this, WTFMove(completionHandler), [this, protectedThis = Ref { *this }, windowFeatures = WTFMove(windowFeatures)] (Ref<API::NavigationAction>&& navigationAction, CompletionHandler<void(RefPtr<WebPageProxy>&&)>&& completionHandler) mutable {
m_uiClient->createNewPage(*this, WTFMove(windowFeatures), WTFMove(navigationAction), WTFMove(completionHandler));
});
}
void WebPageProxy::showPage()
{
m_uiClient->showPage(this);
}
void WebPageProxy::exitFullscreenImmediately()
{
#if ENABLE(FULLSCREEN_API)
if (fullScreenManager())
fullScreenManager()->close();
#endif
#if ENABLE(VIDEO_PRESENTATION_MODE)
if (videoFullscreenManager())
videoFullscreenManager()->requestHideAndExitFullscreen();
#endif
}
void WebPageProxy::fullscreenMayReturnToInline()
{
m_uiClient->fullscreenMayReturnToInline(this);
}
void WebPageProxy::didEnterFullscreen()
{
m_uiClient->didEnterFullscreen(this);
}
void WebPageProxy::didExitFullscreen()
{
m_uiClient->didExitFullscreen(this);
}
void WebPageProxy::closePage()
{
if (isClosed())
return;
WEBPAGEPROXY_RELEASE_LOG(Process, "closePage:");
pageClient().clearAllEditCommands();
m_uiClient->close(this);
}
void WebPageProxy::runModalJavaScriptDialog(RefPtr<WebFrameProxy>&& frame, FrameInfoData&& frameInfo, const String& message, CompletionHandler<void(WebPageProxy&, WebFrameProxy* frame, FrameInfoData&& frameInfo, const String& message, CompletionHandler<void()>&&)>&& runDialogCallback)
{
pageClient().runModalJavaScriptDialog([weakThis = WeakPtr { *this }, frameInfo = WTFMove(frameInfo), frame = WTFMove(frame), message, runDialogCallback = WTFMove(runDialogCallback)]() mutable {
RefPtr protectedThis { weakThis.get() };
if (!protectedThis)
return;
protectedThis->m_isRunningModalJavaScriptDialog = true;
runDialogCallback(*protectedThis, frame.get(), WTFMove(frameInfo), message, [weakThis = WTFMove(weakThis)]() mutable {
if (RefPtr protectedThis = weakThis.get())
protectedThis->m_isRunningModalJavaScriptDialog = false;
});
});
}
void WebPageProxy::runJavaScriptAlert(FrameIdentifier frameID, FrameInfoData&& frameInfo, const String& message, Messages::WebPageProxy::RunJavaScriptAlert::DelayedReply&& reply)
{
RefPtr frame = m_process->webFrame(frameID);
MESSAGE_CHECK(m_process, frame);
exitFullscreenImmediately();
// Since runJavaScriptAlert() can spin a nested run loop we need to turn off the responsiveness timer.
m_process->stopResponsivenessTimer();
if (m_controlledByAutomation) {
if (auto* automationSession = process().processPool().automationSession())
automationSession->willShowJavaScriptDialog(*this);
}
runModalJavaScriptDialog(WTFMove(frame), WTFMove(frameInfo), message, [reply = WTFMove(reply)](WebPageProxy& page, WebFrameProxy* frame, FrameInfoData&& frameInfo, const String& message, CompletionHandler<void()>&& completion) mutable {
page.m_uiClient->runJavaScriptAlert(page, message, frame, WTFMove(frameInfo), [reply = WTFMove(reply), completion = WTFMove(completion)]() mutable {
reply();
completion();
});
});
}
void WebPageProxy::runJavaScriptConfirm(FrameIdentifier frameID, FrameInfoData&& frameInfo, const String& message, Messages::WebPageProxy::RunJavaScriptConfirm::DelayedReply&& reply)
{
RefPtr frame = m_process->webFrame(frameID);
MESSAGE_CHECK(m_process, frame);
exitFullscreenImmediately();
// Since runJavaScriptConfirm() can spin a nested run loop we need to turn off the responsiveness timer.
m_process->stopResponsivenessTimer();
if (m_controlledByAutomation) {
if (auto* automationSession = process().processPool().automationSession())
automationSession->willShowJavaScriptDialog(*this);
}
runModalJavaScriptDialog(WTFMove(frame), WTFMove(frameInfo), message, [reply = WTFMove(reply)](WebPageProxy& page, WebFrameProxy* frame, FrameInfoData&& frameInfo, const String& message, CompletionHandler<void()>&& completion) mutable {
page.m_uiClient->runJavaScriptConfirm(page, message, frame, WTFMove(frameInfo), [reply = WTFMove(reply), completion = WTFMove(completion)](bool result) mutable {
reply(result);
completion();
});
});
}
void WebPageProxy::runJavaScriptPrompt(FrameIdentifier frameID, FrameInfoData&& frameInfo, const String& message, const String& defaultValue, Messages::WebPageProxy::RunJavaScriptPrompt::DelayedReply&& reply)
{
RefPtr frame = m_process->webFrame(frameID);
MESSAGE_CHECK(m_process, frame);
exitFullscreenImmediately();
// Since runJavaScriptPrompt() can spin a nested run loop we need to turn off the responsiveness timer.
m_process->stopResponsivenessTimer();
if (m_controlledByAutomation) {
if (auto* automationSession = process().processPool().automationSession())
automationSession->willShowJavaScriptDialog(*this);
}
runModalJavaScriptDialog(WTFMove(frame), WTFMove(frameInfo), message, [reply = WTFMove(reply), defaultValue](WebPageProxy& page, WebFrameProxy* frame, FrameInfoData&& frameInfo, const String& message, CompletionHandler<void()>&& completion) mutable {
page.m_uiClient->runJavaScriptPrompt(page, message, defaultValue, frame, WTFMove(frameInfo), [reply = WTFMove(reply), completion = WTFMove(completion)](auto& result) mutable {
reply(result);
completion();
});
});
}
void WebPageProxy::setStatusText(const String& text)
{
m_uiClient->setStatusText(this, text);
}
void WebPageProxy::mouseDidMoveOverElement(WebHitTestResultData&& hitTestResultData, uint32_t opaqueModifiers, UserData&& userData)
{
m_lastMouseMoveHitTestResult = API::HitTestResult::create(hitTestResultData);
auto modifiers = OptionSet<WebEvent::Modifier>::fromRaw(opaqueModifiers);
m_uiClient->mouseDidMoveOverElement(*this, hitTestResultData, modifiers, m_process->transformHandlesToObjects(userData.object()).get());
setToolTip(hitTestResultData.toolTipText);
}
#if ENABLE(WEBGL)
void WebPageProxy::webGLPolicyForURL(URL&& url, Messages::WebPageProxy::WebGLPolicyForURL::DelayedReply&& reply)
{
m_navigationClient->webGLLoadPolicy(*this, url, WTFMove(reply));
}
void WebPageProxy::resolveWebGLPolicyForURL(URL&& url, Messages::WebPageProxy::ResolveWebGLPolicyForURL::DelayedReply&& reply)
{
m_navigationClient->resolveWebGLLoadPolicy(*this, url, WTFMove(reply));
}
#endif // ENABLE(WEBGL)
void WebPageProxy::setToolbarsAreVisible(bool toolbarsAreVisible)
{
m_uiClient->setToolbarsAreVisible(*this, toolbarsAreVisible);
}
void WebPageProxy::getToolbarsAreVisible(Messages::WebPageProxy::GetToolbarsAreVisible::DelayedReply&& reply)
{
m_uiClient->toolbarsAreVisible(*this, WTFMove(reply));
}
void WebPageProxy::setMenuBarIsVisible(bool menuBarIsVisible)
{
m_uiClient->setMenuBarIsVisible(*this, menuBarIsVisible);
}
void WebPageProxy::getMenuBarIsVisible(Messages::WebPageProxy::GetMenuBarIsVisible::DelayedReply&& reply)
{
m_uiClient->menuBarIsVisible(*this, WTFMove(reply));
}
void WebPageProxy::setStatusBarIsVisible(bool statusBarIsVisible)
{
m_uiClient->setStatusBarIsVisible(*this, statusBarIsVisible);
}
void WebPageProxy::getStatusBarIsVisible(Messages::WebPageProxy::GetStatusBarIsVisible::DelayedReply&& reply)
{
m_uiClient->statusBarIsVisible(*this, WTFMove(reply));
}
void WebPageProxy::setIsResizable(bool isResizable)
{
m_uiClient->setIsResizable(*this, isResizable);
}
void WebPageProxy::setWindowFrame(const FloatRect& newWindowFrame)
{
m_uiClient->setWindowFrame(*this, pageClient().convertToDeviceSpace(newWindowFrame));
}
void WebPageProxy::getWindowFrame(Messages::WebPageProxy::GetWindowFrame::DelayedReply&& reply)
{
m_uiClient->windowFrame(*this, [this, protectedThis = Ref { *this }, reply = WTFMove(reply)] (FloatRect frame) mutable {
reply(pageClient().convertToUserSpace(frame));
});
}
void WebPageProxy::getWindowFrameWithCallback(Function<void(FloatRect)>&& completionHandler)
{
m_uiClient->windowFrame(*this, [this, protectedThis = Ref { *this }, completionHandler = WTFMove(completionHandler)] (FloatRect frame) {
completionHandler(pageClient().convertToUserSpace(frame));
});
}
void WebPageProxy::screenToRootView(const IntPoint& screenPoint, Messages::WebPageProxy::ScreenToRootView::DelayedReply&& reply)
{
reply(pageClient().screenToRootView(screenPoint));
}
void WebPageProxy::rootViewToScreen(const IntRect& viewRect, Messages::WebPageProxy::RootViewToScreen::DelayedReply&& reply)
{
reply(pageClient().rootViewToScreen(viewRect));
}
IntRect WebPageProxy::syncRootViewToScreen(const IntRect& viewRect)
{
return pageClient().rootViewToScreen(viewRect);
}
void WebPageProxy::accessibilityScreenToRootView(const IntPoint& screenPoint, CompletionHandler<void(IntPoint)>&& completionHandler)
{
completionHandler(pageClient().accessibilityScreenToRootView(screenPoint));
}
void WebPageProxy::rootViewToAccessibilityScreen(const IntRect& viewRect, CompletionHandler<void(IntRect)>&& completionHandler)
{
completionHandler(pageClient().rootViewToAccessibilityScreen(viewRect));
}
void WebPageProxy::runBeforeUnloadConfirmPanel(FrameIdentifier frameID, FrameInfoData&& frameInfo, const String& message, Messages::WebPageProxy::RunBeforeUnloadConfirmPanel::DelayedReply&& reply)
{
WebFrameProxy* frame = m_process->webFrame(frameID);
MESSAGE_CHECK(m_process, frame);
// Per §18 User Prompts in the WebDriver spec, "User prompts that are spawned from beforeunload
// event handlers, are dismissed implicitly upon navigation or close window, regardless of the
// defined user prompt handler." So, always allow the unload to proceed if the page is being automated.
if (m_controlledByAutomation) {
if (!!process().processPool().automationSession()) {
reply(true);
return;
}
}
// Since runBeforeUnloadConfirmPanel() can spin a nested run loop we need to turn off the responsiveness timer and the tryClose timer.
m_process->stopResponsivenessTimer();
bool shouldResumeTimerAfterPrompt = m_tryCloseTimeoutTimer.isActive();
m_tryCloseTimeoutTimer.stop();
m_uiClient->runBeforeUnloadConfirmPanel(*this, message, frame, WTFMove(frameInfo),
[this, weakThis = WeakPtr { *this }, completionHandler = WTFMove(reply), shouldResumeTimerAfterPrompt](bool shouldClose) mutable {
if (weakThis && shouldResumeTimerAfterPrompt)
m_tryCloseTimeoutTimer.startOneShot(tryCloseTimeoutDelay);
completionHandler(shouldClose);
});
}
void WebPageProxy::didChangeViewportProperties(const ViewportAttributes& attr)
{
pageClient().didChangeViewportProperties(attr);
}
void WebPageProxy::pageDidScroll(const WebCore::IntPoint& scrollPosition)
{
m_uiClient->pageDidScroll(this);
pageClient().pageDidScroll(scrollPosition);
#if PLATFORM(IOS_FAMILY)
// Do not hide the validation message if the scrolling was caused by the keyboard showing up.
if (m_isKeyboardAnimatingIn)
return;
#endif
#if !PLATFORM(IOS_FAMILY)
closeOverlayedViews();
#endif
}
void WebPageProxy::setHasActiveAnimatedScrolls(bool isRunning)
{
m_hasActiveAnimatedScroll = isRunning;
#if HAVE(CVDISPLAYLINK)
updateDisplayLinkFrequency();
#endif
}
void WebPageProxy::runOpenPanel(FrameIdentifier frameID, FrameInfoData&& frameInfo, const FileChooserSettings& settings)
{
if (m_openPanelResultListener) {
m_openPanelResultListener->invalidate();
m_openPanelResultListener = nullptr;
}
WebFrameProxy* frame = m_process->webFrame(frameID);
MESSAGE_CHECK(m_process, frame);
Ref<API::OpenPanelParameters> parameters = API::OpenPanelParameters::create(settings);
m_openPanelResultListener = WebOpenPanelResultListenerProxy::create(this);
if (m_controlledByAutomation) {
if (auto* automationSession = process().processPool().automationSession())
automationSession->handleRunOpenPanel(*this, *frame, parameters.get(), *m_openPanelResultListener);
// Don't show a file chooser, since automation will be unable to interact with it.
return;
}
// Since runOpenPanel() can spin a nested run loop we need to turn off the responsiveness timer.
m_process->stopResponsivenessTimer();
const auto frameInfoForPageClient = frameInfo;
if (!m_uiClient->runOpenPanel(*this, frame, WTFMove(frameInfo), parameters.ptr(), m_openPanelResultListener.get())) {
if (!pageClient().handleRunOpenPanel(this, frame, frameInfoForPageClient, parameters.ptr(), m_openPanelResultListener.get()))
didCancelForOpenPanel();
}
}
void WebPageProxy::showShareSheet(const ShareDataWithParsedURL& shareData, CompletionHandler<void(bool)>&& completionHandler)
{
MESSAGE_CHECK(m_process, !shareData.url || shareData.url->protocolIsInHTTPFamily() || shareData.url->protocolIsData());
MESSAGE_CHECK(m_process, shareData.files.isEmpty() || m_preferences->webShareFileAPIEnabled());
pageClient().showShareSheet(shareData, WTFMove(completionHandler));
}
void WebPageProxy::showContactPicker(const WebCore::ContactsRequestData& requestData, CompletionHandler<void(std::optional<Vector<WebCore::ContactInfo>>&&)>&& completionHandler)
{
MESSAGE_CHECK(m_process, m_preferences->contactPickerAPIEnabled());
pageClient().showContactPicker(requestData, WTFMove(completionHandler));
}
void WebPageProxy::printFrame(FrameIdentifier frameID, const String& title, const WebCore::FloatSize& pdfFirstPageSize, CompletionHandler<void()>&& completionHandler)
{
ASSERT(!m_isPerformingDOMPrintOperation);
m_isPerformingDOMPrintOperation = true;
WebFrameProxy* frame = m_process->webFrame(frameID);
MESSAGE_CHECK(m_process, frame);
frame->didChangeTitle(title);
m_uiClient->printFrame(*this, *frame, pdfFirstPageSize, [this, protectedThis = Ref { *this }, completionHandler = WTFMove(completionHandler)] () mutable {
endPrinting(); // Send a message synchronously while m_isPerformingDOMPrintOperation is still true.
m_isPerformingDOMPrintOperation = false;
completionHandler();
});
}
void WebPageProxy::setMediaVolume(float volume)
{
if (volume == m_mediaVolume)
return;
m_mediaVolume = volume;
if (!hasRunningProcess())
return;
send(Messages::WebPage::SetMediaVolume(volume));
}
void WebPageProxy::setMuted(WebCore::MediaProducerMutedStateFlags state, CompletionHandler<void()>&& completionHandler)
{
m_mutedState = state;
if (!hasRunningProcess())
return completionHandler();
#if ENABLE(MEDIA_STREAM)
bool hasMutedCaptureStreams = m_mediaState.containsAny(WebCore::MediaProducer::MutedCaptureMask);
if (hasMutedCaptureStreams && !(state.containsAny(WebCore::MediaProducer::MediaStreamCaptureIsMuted)))
WebProcessProxy::muteCaptureInPagesExcept(m_webPageID);
#endif
m_process->pageMutedStateChanged(m_webPageID, state);
sendWithAsyncReply(Messages::WebPage::SetMuted(state), WTFMove(completionHandler));
activityStateDidChange({ ActivityState::IsAudible, ActivityState::IsCapturingMedia });
}
void WebPageProxy::setMediaCaptureEnabled(bool enabled)
{
m_mediaCaptureEnabled = enabled;
if (!hasRunningProcess())
return;
#if ENABLE(MEDIA_STREAM)
UserMediaProcessManager::singleton().setCaptureEnabled(enabled);
#endif
}
void WebPageProxy::stopMediaCapture(MediaProducerMediaCaptureKind kind, CompletionHandler<void()>&& completionHandler)
{
if (!hasRunningProcess())
return completionHandler();
#if ENABLE(MEDIA_STREAM)
if (m_userMediaPermissionRequestManager)
m_userMediaPermissionRequestManager->resetAccess();
sendWithAsyncReply(Messages::WebPage::StopMediaCapture(kind), WTFMove(completionHandler));
#endif
}
void WebPageProxy::requestMediaPlaybackState(CompletionHandler<void(MediaPlaybackState)>&& completionHandler)
{
if (!hasRunningProcess()) {
completionHandler({ });
return;
}
sendWithAsyncReply(Messages::WebPage::RequestMediaPlaybackState(), WTFMove(completionHandler));
}
void WebPageProxy::pauseAllMediaPlayback(CompletionHandler<void()>&& completionHandler)
{
if (!hasRunningProcess()) {
completionHandler();
return;
}
sendWithAsyncReply(Messages::WebPage::PauseAllMediaPlayback(), WTFMove(completionHandler));
}
void WebPageProxy::suspendAllMediaPlayback(CompletionHandler<void()>&& completionHandler)
{
m_suspendMediaPlaybackCounter++;
if (m_mediaPlaybackIsSuspended) {
completionHandler();
return;
}
m_mediaPlaybackIsSuspended = true;
if (!hasRunningProcess()) {
completionHandler();
return;
}
sendWithAsyncReply(Messages::WebPage::SuspendAllMediaPlayback(), WTFMove(completionHandler));
}
void WebPageProxy::resumeAllMediaPlayback(CompletionHandler<void()>&& completionHandler)
{
if (m_suspendMediaPlaybackCounter > 0)
m_suspendMediaPlaybackCounter--;
if (!m_mediaPlaybackIsSuspended || m_suspendMediaPlaybackCounter) {
completionHandler();
return;
}
m_mediaPlaybackIsSuspended = false;
if (!hasRunningProcess()) {
completionHandler();
return;
}
sendWithAsyncReply(Messages::WebPage::ResumeAllMediaPlayback(), WTFMove(completionHandler));
}
void WebPageProxy::setMayStartMediaWhenInWindow(bool mayStartMedia)
{
if (mayStartMedia == m_mayStartMediaWhenInWindow)
return;
m_mayStartMediaWhenInWindow = mayStartMedia;
if (!hasRunningProcess())
return;
send(Messages::WebPage::SetMayStartMediaWhenInWindow(mayStartMedia));
}
void WebPageProxy::handleDownloadRequest(DownloadProxy& download)
{
pageClient().handleDownloadRequest(download);
}
void WebPageProxy::resumeDownload(const API::Data& resumeData, const String& path, CompletionHandler<void(DownloadProxy*)>&& completionHandler)
{
auto& download = process().processPool().resumeDownload(websiteDataStore(), this, resumeData, path, CallDownloadDidStart::Yes);
download.setDestinationFilename(path);
download.setDidStartCallback(WTFMove(completionHandler));
}
void WebPageProxy::downloadRequest(WebCore::ResourceRequest&& request, CompletionHandler<void(DownloadProxy*)>&& completionHandler)
{
auto& download = process().processPool().download(websiteDataStore(), this, request, { });
download.setDidStartCallback(WTFMove(completionHandler));
}
void WebPageProxy::requestResource(WebCore::ResourceRequest&& request, CompletionHandler<void(Ref<WebCore::SharedBuffer>&&, WebCore::ResourceResponse&&, WebCore::ResourceError&&)>&& completionHandler)
{
websiteDataStore().networkProcess().requestResource(identifier(), sessionID(), WTFMove(request), WTFMove(completionHandler));
}
void WebPageProxy::didChangeContentSize(const IntSize& size)
{
pageClient().didChangeContentSize(size);
}
void WebPageProxy::didChangeIntrinsicContentSize(const IntSize& intrinsicContentSize)
{
#if USE(APPKIT)
pageClient().intrinsicContentSizeDidChange(intrinsicContentSize);
#endif
}
#if ENABLE(INPUT_TYPE_COLOR)
void WebPageProxy::showColorPicker(const WebCore::Color& initialColor, const IntRect& elementRect, Vector<WebCore::Color>&& suggestions)
{
m_colorPicker = pageClient().createColorPicker(this, initialColor, elementRect, WTFMove(suggestions));
m_colorPicker->showColorPicker(initialColor);
}
void WebPageProxy::setColorPickerColor(const WebCore::Color& color)
{
if (m_colorPicker)
m_colorPicker->setSelectedColor(color);
}
void WebPageProxy::endColorPicker()
{
if (auto colorPicker = std::exchange(m_colorPicker, nullptr))
colorPicker->endPicker();
}
void WebPageProxy::didChooseColor(const WebCore::Color& color)
{
if (!hasRunningProcess())
return;
send(Messages::WebPage::DidChooseColor(color));
}
void WebPageProxy::didEndColorPicker()
{
if (std::exchange(m_colorPicker, nullptr)) {
if (!hasRunningProcess())
return;
send(Messages::WebPage::DidEndColorPicker());
}
}
#endif
#if ENABLE(DATALIST_ELEMENT)
void WebPageProxy::showDataListSuggestions(WebCore::DataListSuggestionInformation&& info)
{
if (!m_dataListSuggestionsDropdown)
m_dataListSuggestionsDropdown = pageClient().createDataListSuggestionsDropdown(*this);
m_dataListSuggestionsDropdown->show(WTFMove(info));
}
void WebPageProxy::handleKeydownInDataList(const String& key)
{
if (!m_dataListSuggestionsDropdown)
return;
m_dataListSuggestionsDropdown->handleKeydownWithIdentifier(key);
}
void WebPageProxy::endDataListSuggestions()
{
if (m_dataListSuggestionsDropdown)
m_dataListSuggestionsDropdown->close();
}
void WebPageProxy::didCloseSuggestions()
{
if (!m_dataListSuggestionsDropdown)
return;
m_dataListSuggestionsDropdown = nullptr;
send(Messages::WebPage::DidCloseSuggestions());
}
void WebPageProxy::didSelectOption(const String& selectedOption)
{
if (!hasRunningProcess())
return;
send(Messages::WebPage::DidSelectDataListOption(selectedOption));
}
#endif
#if ENABLE(DATE_AND_TIME_INPUT_TYPES)
void WebPageProxy::showDateTimePicker(WebCore::DateTimeChooserParameters&& params)
{
if (!m_dateTimePicker)
m_dateTimePicker = pageClient().createDateTimePicker(*this);
m_dateTimePicker->showDateTimePicker(WTFMove(params));
}
void WebPageProxy::endDateTimePicker()
{
if (!m_dateTimePicker)
return;
m_dateTimePicker->endPicker();
}
void WebPageProxy::didChooseDate(StringView date)
{
if (!hasRunningProcess())
return;
send(Messages::WebPage::DidChooseDate(date.toString()));
}
void WebPageProxy::didEndDateTimePicker()
{
m_dateTimePicker = nullptr;
if (!hasRunningProcess())
return;
send(Messages::WebPage::DidEndDateTimePicker());
}
#endif
WebInspectorUIProxy* WebPageProxy::inspector() const
{
if (isClosed())
return nullptr;
return m_inspector.get();
}
void WebPageProxy::resourceLoadDidSendRequest(ResourceLoadInfo&& loadInfo, WebCore::ResourceRequest&& request)
{
if (m_resourceLoadClient)
m_resourceLoadClient->didSendRequest(WTFMove(loadInfo), WTFMove(request));
}
void WebPageProxy::resourceLoadDidPerformHTTPRedirection(ResourceLoadInfo&& loadInfo, WebCore::ResourceResponse&& response, WebCore::ResourceRequest&& request)
{
if (m_resourceLoadClient)
m_resourceLoadClient->didPerformHTTPRedirection(WTFMove(loadInfo), WTFMove(response), WTFMove(request));
}
void WebPageProxy::resourceLoadDidReceiveChallenge(ResourceLoadInfo&& loadInfo, WebCore::AuthenticationChallenge&& challenge)
{
if (m_resourceLoadClient)
m_resourceLoadClient->didReceiveChallenge(WTFMove(loadInfo), WTFMove(challenge));
}
void WebPageProxy::resourceLoadDidReceiveResponse(ResourceLoadInfo&& loadInfo, WebCore::ResourceResponse&& response)
{
if (m_resourceLoadClient)
m_resourceLoadClient->didReceiveResponse(WTFMove(loadInfo), WTFMove(response));
}
void WebPageProxy::resourceLoadDidCompleteWithError(ResourceLoadInfo&& loadInfo, WebCore::ResourceResponse&& response, WebCore::ResourceError&& error)
{
if (m_resourceLoadClient)
m_resourceLoadClient->didCompleteWithError(WTFMove(loadInfo), WTFMove(response), WTFMove(error));
}
#if ENABLE(FULLSCREEN_API)
WebFullScreenManagerProxy* WebPageProxy::fullScreenManager()
{
return m_fullScreenManager.get();
}
void WebPageProxy::setFullscreenClient(std::unique_ptr<API::FullscreenClient>&& client)
{
if (!client) {
m_fullscreenClient = makeUnique<API::FullscreenClient>();
return;
}
m_fullscreenClient = WTFMove(client);
}
#endif
#if ENABLE(VIDEO_PRESENTATION_MODE)
PlaybackSessionManagerProxy* WebPageProxy::playbackSessionManager()
{
return m_playbackSessionManager.get();
}
VideoFullscreenManagerProxy* WebPageProxy::videoFullscreenManager()
{
return m_videoFullscreenManager.get();
}
void WebPageProxy::setMockVideoPresentationModeEnabled(bool enabled)
{
m_mockVideoPresentationModeEnabled = enabled;
if (m_videoFullscreenManager)
m_videoFullscreenManager->setMockVideoPresentationModeEnabled(enabled);
}
#endif
#if PLATFORM(IOS_FAMILY)
bool WebPageProxy::allowsMediaDocumentInlinePlayback() const
{
return m_allowsMediaDocumentInlinePlayback;
}
void WebPageProxy::setAllowsMediaDocumentInlinePlayback(bool allows)
{
if (m_allowsMediaDocumentInlinePlayback == allows)
return;
m_allowsMediaDocumentInlinePlayback = allows;
send(Messages::WebPage::SetAllowsMediaDocumentInlinePlayback(allows));
}
#endif
void WebPageProxy::setHasHadSelectionChangesFromUserInteraction(bool hasHadUserSelectionChanges)
{
m_hasHadSelectionChangesFromUserInteraction = hasHadUserSelectionChanges;
}
#if HAVE(TOUCH_BAR)
void WebPageProxy::setIsTouchBarUpdateSupressedForHiddenContentEditable(bool ignoreTouchBarUpdate)
{
m_isTouchBarUpdateSupressedForHiddenContentEditable = ignoreTouchBarUpdate;
}
void WebPageProxy::setIsNeverRichlyEditableForTouchBar(bool isNeverRichlyEditable)
{
m_isNeverRichlyEditableForTouchBar = isNeverRichlyEditable;
}
#endif
void WebPageProxy::requestDOMPasteAccess(WebCore::DOMPasteAccessCategory pasteAccessCategory, const WebCore::IntRect& elementRect, const String& originIdentifier, CompletionHandler<void(WebCore::DOMPasteAccessResponse)>&& completionHandler)
{
m_pageClient->requestDOMPasteAccess(pasteAccessCategory, elementRect, originIdentifier, WTFMove(completionHandler));
}
// BackForwardList
void WebPageProxy::backForwardAddItem(BackForwardListItemState&& itemState)
{
auto item = WebBackForwardListItem::create(WTFMove(itemState), identifier());
item->setResourceDirectoryURL(currentResourceDirectoryURL());
m_backForwardList->addItem(WTFMove(item));
}
void WebPageProxy::backForwardGoToItem(const BackForwardItemIdentifier& itemID, CompletionHandler<void(const WebBackForwardListCounts&)>&& completionHandler)
{
// On process swap, we tell the previous process to ignore the load, which causes it so restore its current back forward item to its previous
// value. Since the load is really going on in a new provisional process, we want to ignore such requests from the committed process.
// Any real new load in the committed process would have cleared m_provisionalPage.
if (m_provisionalPage)
return completionHandler(m_backForwardList->counts());
backForwardGoToItemShared(m_process.copyRef(), itemID, WTFMove(completionHandler));
}
void WebPageProxy::backForwardGoToItemShared(Ref<WebProcessProxy>&& process, const BackForwardItemIdentifier& itemID, CompletionHandler<void(const WebBackForwardListCounts&)>&& completionHandler)
{
MESSAGE_CHECK_COMPLETION(m_process, !WebKit::isInspectorPage(*this), completionHandler(m_backForwardList->counts()));
auto* item = m_backForwardList->itemForID(itemID);
if (!item)
return completionHandler(m_backForwardList->counts());
m_backForwardList->goToItem(*item);
completionHandler(m_backForwardList->counts());
}
void WebPageProxy::backForwardItemAtIndex(int32_t index, CompletionHandler<void(std::optional<BackForwardItemIdentifier>&&)>&& completionHandler)
{
if (auto* item = m_backForwardList->itemAtIndex(index))
completionHandler(item->itemID());
else
completionHandler(std::nullopt);
}
void WebPageProxy::backForwardListCounts(Messages::WebPageProxy::BackForwardListCountsDelayedReply&& completionHandler)
{
completionHandler(m_backForwardList->counts());
}
void WebPageProxy::compositionWasCanceled()
{
#if PLATFORM(COCOA)
pageClient().notifyInputContextAboutDiscardedComposition();
#endif
}
// Undo management
void WebPageProxy::registerEditCommandForUndo(WebUndoStepID commandID, const String& label)
{
registerEditCommand(WebEditCommandProxy::create(commandID, label, *this), UndoOrRedo::Undo);
}
void WebPageProxy::registerInsertionUndoGrouping()
{
#if USE(INSERTION_UNDO_GROUPING)
pageClient().registerInsertionUndoGrouping();
#endif
}
void WebPageProxy::canUndoRedo(UndoOrRedo action, CompletionHandler<void(bool)>&& completionHandler)
{
completionHandler(pageClient().canUndoRedo(action));
}
void WebPageProxy::executeUndoRedo(UndoOrRedo action, CompletionHandler<void()>&& completionHandler)
{
pageClient().executeUndoRedo(action);
completionHandler();
}
void WebPageProxy::clearAllEditCommands()
{
pageClient().clearAllEditCommands();
}
void WebPageProxy::didCountStringMatches(const String& string, uint32_t matchCount)
{
m_findClient->didCountStringMatches(this, string, matchCount);
}
void WebPageProxy::didGetImageForFindMatch(const ShareableBitmap::Handle& contentImageHandle, uint32_t matchIndex)
{
auto bitmap = ShareableBitmap::create(contentImageHandle);
if (!bitmap) {
ASSERT_NOT_REACHED();
return;
}
m_findMatchesClient->didGetImageForMatchResult(this, WebImage::create(bitmap.releaseNonNull()).ptr(), matchIndex);
}
void WebPageProxy::setTextIndicator(const TextIndicatorData& indicatorData, uint64_t lifetime)
{
// FIXME: Make TextIndicatorWindow a platform-independent presentational thing ("TextIndicatorPresentation"?).
#if PLATFORM(COCOA)
pageClient().setTextIndicator(TextIndicator::create(indicatorData), static_cast<WebCore::TextIndicatorLifetime>(lifetime));
#else
ASSERT_NOT_REACHED();
#endif
}
void WebPageProxy::clearTextIndicator()
{
#if PLATFORM(COCOA)
pageClient().clearTextIndicator(WebCore::TextIndicatorDismissalAnimation::FadeOut);
#else
ASSERT_NOT_REACHED();
#endif
}
void WebPageProxy::setTextIndicatorAnimationProgress(float progress)
{
#if PLATFORM(COCOA)
pageClient().setTextIndicatorAnimationProgress(progress);
#else
ASSERT_NOT_REACHED();
#endif
}
void WebPageProxy::didFindString(const String& string, const Vector<WebCore::IntRect>& matchRects, uint32_t matchCount, int32_t matchIndex, bool didWrapAround)
{
m_findClient->didFindString(this, string, matchRects, matchCount, matchIndex, didWrapAround);
}
void WebPageProxy::didFindStringMatches(const String& string, const Vector<Vector<WebCore::IntRect>>& matchRects, int32_t firstIndexAfterSelection)
{
m_findMatchesClient->didFindStringMatches(this, string, matchRects, firstIndexAfterSelection);
}
void WebPageProxy::didFailToFindString(const String& string)
{
m_findClient->didFailToFindString(this, string);
}
bool WebPageProxy::sendMessage(UniqueRef<IPC::Encoder>&& encoder, OptionSet<IPC::SendOption> sendOptions, std::optional<std::pair<CompletionHandler<void(IPC::Decoder*)>, uint64_t>>&& asyncReplyInfo)
{
return m_process->sendMessage(WTFMove(encoder), sendOptions, WTFMove(asyncReplyInfo));
}
IPC::Connection* WebPageProxy::messageSenderConnection() const
{
return m_process->hasConnection() ? m_process->connection() : nullptr;
}
uint64_t WebPageProxy::messageSenderDestinationID() const
{
return m_webPageID.toUInt64();
}
void WebPageProxy::valueChangedForPopupMenu(WebPopupMenuProxy*, int32_t newSelectedIndex)
{
send(Messages::WebPage::DidChangeSelectedIndexForActivePopupMenu(newSelectedIndex));
}
void WebPageProxy::setTextFromItemForPopupMenu(WebPopupMenuProxy*, int32_t index)
{
send(Messages::WebPage::SetTextForActivePopupMenu(index));
}
bool WebPageProxy::isProcessingKeyboardEvents() const
{
return !m_keyEventQueue.isEmpty();
}
bool WebPageProxy::isProcessingMouseEvents() const
{
return !m_mouseEventQueue.isEmpty();
}
bool WebPageProxy::isProcessingWheelEvents() const
{
return m_wheelEventCoalescer && m_wheelEventCoalescer->hasEventsBeingProcessed();
}
NativeWebMouseEvent* WebPageProxy::currentlyProcessedMouseDownEvent()
{
// <https://bugs.webkit.org/show_bug.cgi?id=57904> We need to keep track of the mouse down event in the case where we
// display a popup menu for select elements. When the user changes the selected item, we fake a mouseup event by
// using this stored mousedown event and changing the event type. This trickery happens when WebProcess handles
// a mousedown event that runs the default handler for HTMLSelectElement, so the triggering mousedown must be the first event.
if (m_mouseEventQueue.isEmpty())
return nullptr;
auto& event = m_mouseEventQueue.first();
if (event.type() != WebEvent::Type::MouseDown)
return nullptr;
return &event;
}
void WebPageProxy::postMessageToInjectedBundle(const String& messageName, API::Object* messageBody)
{
if (!hasRunningProcess()) {
m_pendingInjectedBundleMessages.append(InjectedBundleMessage { messageName, messageBody });
return;
}
send(Messages::WebPage::PostInjectedBundleMessage(messageName, UserData(process().transformObjectsToHandles(messageBody).get())));
}
#if PLATFORM(GTK)
void WebPageProxy::failedToShowPopupMenu()
{
send(Messages::WebPage::FailedToShowPopupMenu());
}
#endif
void WebPageProxy::showPopupMenu(const IntRect& rect, uint64_t textDirection, const Vector<WebPopupItem>& items, int32_t selectedIndex, const PlatformPopupMenuData& data)
{
MESSAGE_CHECK(m_process, selectedIndex == -1 || static_cast<uint32_t>(selectedIndex) < items.size());
if (m_activePopupMenu) {
m_activePopupMenu->hidePopupMenu();
m_activePopupMenu->invalidate();
m_activePopupMenu = nullptr;
}
// If the page is controlled by automation, entering a nested run loop while the menu is open
// can hang the page / WebDriver test. Since <option> elements are selected via a different
// code path anyway, just don't show the native popup menu.
if (auto* automationSession = process().processPool().automationSession()) {
if (m_controlledByAutomation && automationSession->isSimulatingUserInteraction())
return;
}
m_activePopupMenu = pageClient().createPopupMenuProxy(*this);
if (!m_activePopupMenu)
return;
// Since showPopupMenu() can spin a nested run loop we need to turn off the responsiveness timer.
m_process->stopResponsivenessTimer();
// Showing a popup menu runs a nested runloop, which can handle messages that cause |this| to get closed.
Ref<WebPageProxy> protect(*this);
m_activePopupMenu->showPopupMenu(rect, static_cast<TextDirection>(textDirection), m_pageScaleFactor, items, data, selectedIndex);
}
void WebPageProxy::hidePopupMenu()
{
if (!m_activePopupMenu)
return;
m_activePopupMenu->hidePopupMenu();
m_activePopupMenu->invalidate();
m_activePopupMenu = nullptr;
}
#if ENABLE(CONTEXT_MENUS)
void WebPageProxy::showContextMenu(ContextMenuContextData&& contextMenuContextData, const UserData& userData)
{
// Showing a context menu runs a nested runloop, which can handle messages that cause |this| to get closed.
Ref<WebPageProxy> protect(*this);
// If the page is controlled by automation, entering a nested run loop while the menu is open
// can hang the page / WebDriver test. Pretend to show and immediately dismiss the context menu.
if (auto* automationSession = process().processPool().automationSession()) {
if (m_controlledByAutomation && automationSession->isSimulatingUserInteraction()) {
send(Messages::WebPage::DidShowContextMenu());
return;
}
}
// Discard any enqueued mouse events that have been delivered to the UIProcess whilst the WebProcess is still processing the
// MouseDown event that triggered this ShowContextMenu message. This can happen if we take too long to enter the nested runloop.
discardQueuedMouseEvents();
m_activeContextMenuContextData = contextMenuContextData;
m_activeContextMenu = pageClient().createContextMenuProxy(*this, WTFMove(contextMenuContextData), userData);
m_activeContextMenu->show();
}
void WebPageProxy::didShowContextMenu()
{
// Don't send `Messages::WebPage::DidShowContextMenu` as that should've already been eagerly
// sent when requesting the context menu to show, regardless of the result of that request.
pageClient().didShowContextMenu();
}
void WebPageProxy::didDismissContextMenu()
{
send(Messages::WebPage::DidDismissContextMenu());
pageClient().didDismissContextMenu();
}
void WebPageProxy::contextMenuItemSelected(const WebContextMenuItemData& item)
{
// Application custom items don't need to round-trip through to WebCore in the WebProcess.
if (item.action() >= ContextMenuItemBaseApplicationTag) {
m_contextMenuClient->customContextMenuItemSelected(*this, item);
return;
}
struct DownloadInfo {
String url;
String suggestedFilename;
};
std::optional<DownloadInfo> downloadInfo;
ASSERT(m_activeContextMenuContextData.webHitTestResultData());
auto hitTestData = m_activeContextMenuContextData.webHitTestResultData().value();
switch (item.action()) {
#if PLATFORM(COCOA)
case ContextMenuItemTagSmartCopyPaste:
setSmartInsertDeleteEnabled(!isSmartInsertDeleteEnabled());
return;
case ContextMenuItemTagSmartQuotes:
TextChecker::setAutomaticQuoteSubstitutionEnabled(!TextChecker::state().isAutomaticQuoteSubstitutionEnabled);
m_process->updateTextCheckerState();
return;
case ContextMenuItemTagSmartDashes:
TextChecker::setAutomaticDashSubstitutionEnabled(!TextChecker::state().isAutomaticDashSubstitutionEnabled);
m_process->updateTextCheckerState();
return;
case ContextMenuItemTagSmartLinks:
TextChecker::setAutomaticLinkDetectionEnabled(!TextChecker::state().isAutomaticLinkDetectionEnabled);
m_process->updateTextCheckerState();
return;
case ContextMenuItemTagTextReplacement:
TextChecker::setAutomaticTextReplacementEnabled(!TextChecker::state().isAutomaticTextReplacementEnabled);
m_process->updateTextCheckerState();
return;
case ContextMenuItemTagCorrectSpellingAutomatically:
TextChecker::setAutomaticSpellingCorrectionEnabled(!TextChecker::state().isAutomaticSpellingCorrectionEnabled);
m_process->updateTextCheckerState();
return;
case ContextMenuItemTagShowSubstitutions:
TextChecker::toggleSubstitutionsPanelIsShowing();
return;
#endif
case ContextMenuItemTagDownloadImageToDisk:
downloadInfo = { { hitTestData.absoluteImageURL, { } } };
break;
case ContextMenuItemTagDownloadLinkToDisk: {
downloadInfo = { { hitTestData.absoluteLinkURL, hitTestData.linkSuggestedFilename } };
break;
}
case ContextMenuItemTagDownloadMediaToDisk:
downloadInfo = { { hitTestData.absoluteMediaURL, { } } };
break;
case ContextMenuItemTagCheckSpellingWhileTyping:
TextChecker::setContinuousSpellCheckingEnabled(!TextChecker::state().isContinuousSpellCheckingEnabled);
m_process->updateTextCheckerState();
return;
case ContextMenuItemTagCheckGrammarWithSpelling:
TextChecker::setGrammarCheckingEnabled(!TextChecker::state().isGrammarCheckingEnabled);
m_process->updateTextCheckerState();
return;
case ContextMenuItemTagShowSpellingPanel:
if (!TextChecker::spellingUIIsShowing())
advanceToNextMisspelling(true);
TextChecker::toggleSpellingUIIsShowing();
return;
case ContextMenuItemTagAddHighlightToNewQuickNote:
#if ENABLE(APP_HIGHLIGHTS)
createAppHighlightInSelectedRange(CreateNewGroupForHighlight::Yes, HighlightRequestOriginatedInApp::No);
#endif
return;
case ContextMenuItemTagAddHighlightToCurrentQuickNote:
#if ENABLE(APP_HIGHLIGHTS)
createAppHighlightInSelectedRange(CreateNewGroupForHighlight::No, HighlightRequestOriginatedInApp::No);
#endif
return;
case ContextMenuItemTagLearnSpelling:
case ContextMenuItemTagIgnoreSpelling:
++m_pendingLearnOrIgnoreWordMessageCount;
break;
case ContextMenuItemTagQuickLookImage:
#if ENABLE(IMAGE_ANALYSIS)
if (m_activeContextMenu)
handleContextMenuQuickLookImage(m_activeContextMenu->quickLookPreviewActivity());
#endif
return;
default:
break;
}
if (downloadInfo) {
auto& download = m_process->processPool().download(m_websiteDataStore, this, URL(URL(), downloadInfo->url), downloadInfo->suggestedFilename);
download.setDidStartCallback([this, weakThis = WeakPtr { *this }] (auto* download) {
if (!weakThis || !download)
return;
m_navigationClient->contextMenuDidCreateDownload(*this, *download);
});
}
platformDidSelectItemFromActiveContextMenu(item);
send(Messages::WebPage::DidSelectItemFromActiveContextMenu(item));
}
void WebPageProxy::handleContextMenuKeyEvent()
{
send(Messages::WebPage::ContextMenuForKeyEvent());
}
#endif // ENABLE(CONTEXT_MENUS)
#if PLATFORM(IOS_FAMILY)
void WebPageProxy::didChooseFilesForOpenPanelWithDisplayStringAndIcon(const Vector<String>& fileURLs, const String& displayString, const API::Data* iconData)
{
if (!hasRunningProcess())
return;
#if ENABLE(SANDBOX_EXTENSIONS)
auto sandboxExtensionHandles = SandboxExtension::createReadOnlyHandlesForFiles("WebPageProxy::didChooseFilesForOpenPanelWithDisplayStringAndIcon"_s, fileURLs);
send(Messages::WebPage::ExtendSandboxForFilesFromOpenPanel(WTFMove(sandboxExtensionHandles)));
#endif
SandboxExtension::Handle frontboardServicesSandboxExtension, iconServicesSandboxExtension;
#if HAVE(FRONTBOARD_SYSTEM_APP_SERVICES)
if (auto handle = SandboxExtension::createHandleForMachLookup("com.apple.frontboard.systemappservices"_s, std::nullopt))
frontboardServicesSandboxExtension = WTFMove(*handle);
#endif
if (auto handle = SandboxExtension::createHandleForMachLookup("com.apple.iconservices"_s, std::nullopt))
iconServicesSandboxExtension = WTFMove(*handle);
send(Messages::WebPage::DidChooseFilesForOpenPanelWithDisplayStringAndIcon(fileURLs, displayString, iconData ? iconData->dataReference() : IPC::DataReference(), frontboardServicesSandboxExtension, iconServicesSandboxExtension));
m_openPanelResultListener->invalidate();
m_openPanelResultListener = nullptr;
}
#endif
bool WebPageProxy::didChooseFilesForOpenPanelWithImageTranscoding(const Vector<String>& fileURLs, const Vector<String>& allowedMIMETypes)
{
#if PLATFORM(MAC)
auto transcodingMIMEType = WebCore::MIMETypeRegistry::preferredImageMIMETypeForEncoding(allowedMIMETypes, { });
if (transcodingMIMEType.isNull())
return false;
auto transcodingURLs = findImagesForTranscoding(fileURLs, allowedMIMETypes);
if (transcodingURLs.isEmpty())
return false;
auto transcodingUTI = WebCore::UTIFromMIMEType(transcodingMIMEType);
auto transcodingExtension = WebCore::MIMETypeRegistry::preferredExtensionForMIMEType(transcodingMIMEType);
sharedImageTranscodingQueue().dispatch([this, protectedThis = Ref { *this }, fileURLs = fileURLs.isolatedCopy(), transcodingURLs = transcodingURLs.isolatedCopy(), transcodingUTI = transcodingUTI.isolatedCopy(), transcodingExtension = transcodingExtension.isolatedCopy()]() mutable {
ASSERT(!RunLoop::isMain());
auto transcodedURLs = transcodeImages(transcodingURLs, transcodingUTI, transcodingExtension);
ASSERT(transcodingURLs.size() == transcodedURLs.size());
RunLoop::main().dispatch([this, protectedThis = WTFMove(protectedThis), fileURLs = fileURLs.isolatedCopy(), transcodedURLs = transcodedURLs.isolatedCopy()]() {
#if ENABLE(SANDBOX_EXTENSIONS)
Vector<String> sandboxExtensionFiles;
for (size_t i = 0, size = fileURLs.size(); i < size; ++i)
sandboxExtensionFiles.append(!transcodedURLs[i].isNull() ? transcodedURLs[i] : fileURLs[i]);
auto sandboxExtensionHandles = SandboxExtension::createReadOnlyHandlesForFiles("WebPageProxy::didChooseFilesForOpenPanel"_s, sandboxExtensionFiles);
send(Messages::WebPage::ExtendSandboxForFilesFromOpenPanel(WTFMove(sandboxExtensionHandles)));
#endif
send(Messages::WebPage::DidChooseFilesForOpenPanel(fileURLs, transcodedURLs));
});
});
return true;
#else
UNUSED_PARAM(fileURLs);
UNUSED_PARAM(allowedMIMETypes);
return false;
#endif
}
void WebPageProxy::didChooseFilesForOpenPanel(const Vector<String>& fileURLs, const Vector<String>& allowedMIMETypes)
{
if (!hasRunningProcess())
return;
if (!didChooseFilesForOpenPanelWithImageTranscoding(fileURLs, allowedMIMETypes)) {
#if ENABLE(SANDBOX_EXTENSIONS)
auto sandboxExtensionHandles = SandboxExtension::createReadOnlyHandlesForFiles("WebPageProxy::didChooseFilesForOpenPanel"_s, fileURLs);
send(Messages::WebPage::ExtendSandboxForFilesFromOpenPanel(WTFMove(sandboxExtensionHandles)));
#endif
send(Messages::WebPage::DidChooseFilesForOpenPanel(fileURLs, { }));
}
m_openPanelResultListener->invalidate();
m_openPanelResultListener = nullptr;
}
void WebPageProxy::didCancelForOpenPanel()
{
if (!hasRunningProcess())
return;
send(Messages::WebPage::DidCancelForOpenPanel());
m_openPanelResultListener->invalidate();
m_openPanelResultListener = nullptr;
}
void WebPageProxy::advanceToNextMisspelling(bool startBeforeSelection)
{
send(Messages::WebPage::AdvanceToNextMisspelling(startBeforeSelection));
}
void WebPageProxy::changeSpellingToWord(const String& word)
{
if (word.isEmpty())
return;
send(Messages::WebPage::ChangeSpellingToWord(word));
}
void WebPageProxy::registerEditCommand(Ref<WebEditCommandProxy>&& commandProxy, UndoOrRedo undoOrRedo)
{
MESSAGE_CHECK(m_process, commandProxy->commandID());
pageClient().registerEditCommand(WTFMove(commandProxy), undoOrRedo);
}
void WebPageProxy::addEditCommand(WebEditCommandProxy& command)
{
m_editCommandSet.add(&command);
}
void WebPageProxy::removeEditCommand(WebEditCommandProxy& command)
{
m_editCommandSet.remove(&command);
if (!hasRunningProcess())
return;
send(Messages::WebPage::DidRemoveEditCommand(command.commandID()));
}
bool WebPageProxy::canUndo()
{
return pageClient().canUndoRedo(UndoOrRedo::Undo);
}
bool WebPageProxy::canRedo()
{
return pageClient().canUndoRedo(UndoOrRedo::Redo);
}
SpellDocumentTag WebPageProxy::spellDocumentTag()
{
if (!m_spellDocumentTag)
m_spellDocumentTag = TextChecker::uniqueSpellDocumentTag(this);
return m_spellDocumentTag.value();
}
#if USE(UNIFIED_TEXT_CHECKING)
void WebPageProxy::checkTextOfParagraph(const String& text, OptionSet<TextCheckingType> checkingTypes, int32_t insertionPoint, CompletionHandler<void(Vector<WebCore::TextCheckingResult>&&)>&& completionHandler)
{
completionHandler(TextChecker::checkTextOfParagraph(spellDocumentTag(), text, insertionPoint, checkingTypes, m_initialCapitalizationEnabled));
}
#endif
void WebPageProxy::checkSpellingOfString(const String& text, CompletionHandler<void(int32_t misspellingLocation, int32_t misspellingLength)>&& completionHandler)
{
int32_t misspellingLocation = 0;
int32_t misspellingLength = 0;
TextChecker::checkSpellingOfString(spellDocumentTag(), text, misspellingLocation, misspellingLength);
completionHandler(misspellingLocation, misspellingLength);
}
void WebPageProxy::checkGrammarOfString(const String& text, CompletionHandler<void(Vector<WebCore::GrammarDetail>&&, int32_t badGrammarLocation, int32_t badGrammarLength)>&& completionHandler)
{
Vector<GrammarDetail> grammarDetails;
int32_t badGrammarLocation = 0;
int32_t badGrammarLength = 0;
TextChecker::checkGrammarOfString(spellDocumentTag(), text, grammarDetails, badGrammarLocation, badGrammarLength);
completionHandler(WTFMove(grammarDetails), badGrammarLocation, badGrammarLength);
}
void WebPageProxy::spellingUIIsShowing(CompletionHandler<void(bool)>&& completionHandler)
{
completionHandler(TextChecker::spellingUIIsShowing());
}
void WebPageProxy::updateSpellingUIWithMisspelledWord(const String& misspelledWord)
{
TextChecker::updateSpellingUIWithMisspelledWord(spellDocumentTag(), misspelledWord);
}
void WebPageProxy::updateSpellingUIWithGrammarString(const String& badGrammarPhrase, const GrammarDetail& grammarDetail)
{
TextChecker::updateSpellingUIWithGrammarString(spellDocumentTag(), badGrammarPhrase, grammarDetail);
}
void WebPageProxy::getGuessesForWord(const String& word, const String& context, int32_t insertionPoint, CompletionHandler<void(Vector<String>&&)>&& completionHandler)
{
Vector<String> guesses;
TextChecker::getGuessesForWord(spellDocumentTag(), word, context, insertionPoint, guesses, m_initialCapitalizationEnabled);
completionHandler(WTFMove(guesses));
}
void WebPageProxy::learnWord(const String& word)
{
MESSAGE_CHECK(m_process, m_pendingLearnOrIgnoreWordMessageCount);
--m_pendingLearnOrIgnoreWordMessageCount;
TextChecker::learnWord(spellDocumentTag(), word);
}
void WebPageProxy::ignoreWord(const String& word)
{
MESSAGE_CHECK(m_process, m_pendingLearnOrIgnoreWordMessageCount);
--m_pendingLearnOrIgnoreWordMessageCount;
TextChecker::ignoreWord(spellDocumentTag(), word);
}
void WebPageProxy::requestCheckingOfString(TextCheckerRequestID requestID, const TextCheckingRequestData& request, int32_t insertionPoint)
{
TextChecker::requestCheckingOfString(TextCheckerCompletion::create(requestID, request, this), insertionPoint);
}
void WebPageProxy::didFinishCheckingText(TextCheckerRequestID requestID, const Vector<WebCore::TextCheckingResult>& result)
{
send(Messages::WebPage::DidFinishCheckingText(requestID, result));
}
void WebPageProxy::didCancelCheckingText(TextCheckerRequestID requestID)
{
send(Messages::WebPage::DidCancelCheckingText(requestID));
}
// Other
void WebPageProxy::setFocus(bool focused)
{
if (focused)
m_uiClient->focus(this);
else
m_uiClient->unfocus(this);
}
void WebPageProxy::takeFocus(uint8_t direction)
{
if (m_uiClient->takeFocus(this, (static_cast<FocusDirection>(direction) == FocusDirection::Forward) ? kWKFocusDirectionForward : kWKFocusDirectionBackward))
return;
pageClient().takeFocus(static_cast<FocusDirection>(direction));
}
void WebPageProxy::setToolTip(const String& toolTip)
{
if (m_toolTip == toolTip)
return;
String oldToolTip = m_toolTip;
m_toolTip = toolTip;
pageClient().toolTipChanged(oldToolTip, m_toolTip);
}
void WebPageProxy::setCursor(const WebCore::Cursor& cursor)
{
pageClient().setCursor(cursor);
}
void WebPageProxy::setCursorHiddenUntilMouseMoves(bool hiddenUntilMouseMoves)
{
pageClient().setCursorHiddenUntilMouseMoves(hiddenUntilMouseMoves);
}
void WebPageProxy::didReceiveEvent(uint32_t opaqueType, bool handled)
{
WebEvent::Type type = static_cast<WebEvent::Type>(opaqueType);
switch (type) {
case WebEvent::NoType:
case WebEvent::MouseMove:
case WebEvent::Wheel:
break;
case WebEvent::MouseDown:
case WebEvent::MouseUp:
case WebEvent::MouseForceChanged:
case WebEvent::MouseForceDown:
case WebEvent::MouseForceUp:
case WebEvent::KeyDown:
case WebEvent::KeyUp:
case WebEvent::RawKeyDown:
case WebEvent::Char:
#if ENABLE(TOUCH_EVENTS)
case WebEvent::TouchStart:
case WebEvent::TouchMove:
case WebEvent::TouchEnd:
case WebEvent::TouchCancel:
#endif
#if ENABLE(MAC_GESTURE_EVENTS)
case WebEvent::GestureStart:
case WebEvent::GestureChange:
case WebEvent::GestureEnd:
#endif
m_process->stopResponsivenessTimer();
break;
}
switch (type) {
case WebEvent::NoType:
break;
case WebEvent::MouseForceChanged:
case WebEvent::MouseForceDown:
case WebEvent::MouseForceUp:
case WebEvent::MouseMove:
case WebEvent::MouseDown:
case WebEvent::MouseUp: {
LOG(MouseHandling, "WebPageProxy::didReceiveEvent: %s (queue size %zu)", webMouseEventTypeString(type), m_mouseEventQueue.size());
// Retire the last sent event now that WebProcess is done handling it.
MESSAGE_CHECK(m_process, !m_mouseEventQueue.isEmpty());
auto event = m_mouseEventQueue.takeFirst();
MESSAGE_CHECK(m_process, type == event.type());
if (!m_mouseEventQueue.isEmpty()) {
LOG(MouseHandling, " UIProcess: handling a queued mouse event from didReceiveEvent");
processNextQueuedMouseEvent();
} else {
if (auto* automationSession = process().processPool().automationSession())
automationSession->mouseEventsFlushedForPage(*this);
didFinishProcessingAllPendingMouseEvents();
}
break;
}
case WebEvent::Wheel: {
MESSAGE_CHECK(m_process, wheelEventCoalescer().hasEventsBeingProcessed());
auto oldestProcessedEvent = wheelEventCoalescer().takeOldestEventBeingProcessed();
// FIXME: Dispatch additional events to the didNotHandleWheelEvent client function.
if (!handled) {
m_uiClient->didNotHandleWheelEvent(this, oldestProcessedEvent);
pageClient().wheelEventWasNotHandledByWebCore(oldestProcessedEvent);
}
if (auto eventToSend = wheelEventCoalescer().nextEventToDispatch())
sendWheelEvent(*eventToSend);
else if (auto* automationSession = process().processPool().automationSession())
automationSession->wheelEventsFlushedForPage(*this);
break;
}
case WebEvent::KeyDown:
case WebEvent::KeyUp:
case WebEvent::RawKeyDown:
case WebEvent::Char: {
LOG(KeyHandling, "WebPageProxy::didReceiveEvent: %s (queue empty %d)", webKeyboardEventTypeString(type), m_keyEventQueue.isEmpty());
MESSAGE_CHECK(m_process, !m_keyEventQueue.isEmpty());
auto event = m_keyEventQueue.takeFirst();
MESSAGE_CHECK(m_process, type == event.type());
#if PLATFORM(WIN)
if (!handled && type == WebEvent::RawKeyDown)
dispatchPendingCharEvents(event);
#endif
bool canProcessMoreKeyEvents = !m_keyEventQueue.isEmpty();
if (canProcessMoreKeyEvents) {
LOG(KeyHandling, " UI process: sent keyEvent from didReceiveEvent");
send(Messages::WebPage::KeyEvent(m_keyEventQueue.first()));
}
// The call to doneWithKeyEvent may close this WebPage.
// Protect against this being destroyed.
Ref<WebPageProxy> protect(*this);
pageClient().doneWithKeyEvent(event, handled);
if (!handled)
m_uiClient->didNotHandleKeyEvent(this, event);
// Notify the session after -[NSApp sendEvent:] has a crack at turning the event into an action.
if (!canProcessMoreKeyEvents) {
if (auto* automationSession = process().processPool().automationSession())
automationSession->keyboardEventsFlushedForPage(*this);
}
break;
}
#if ENABLE(MAC_GESTURE_EVENTS)
case WebEvent::GestureStart:
case WebEvent::GestureChange:
case WebEvent::GestureEnd: {
MESSAGE_CHECK(m_process, !m_gestureEventQueue.isEmpty());
auto event = m_gestureEventQueue.takeFirst();
MESSAGE_CHECK(m_process, type == event.type());
if (!handled)
pageClient().gestureEventWasNotHandledByWebCore(event);
break;
}
#endif
#if ENABLE(IOS_TOUCH_EVENTS)
case WebEvent::TouchStart:
case WebEvent::TouchMove:
case WebEvent::TouchEnd:
case WebEvent::TouchCancel:
break;
#elif ENABLE(TOUCH_EVENTS)
case WebEvent::TouchStart:
case WebEvent::TouchMove:
case WebEvent::TouchEnd:
case WebEvent::TouchCancel: {
MESSAGE_CHECK(m_process, !m_touchEventQueue.isEmpty());
auto queuedEvents = m_touchEventQueue.takeFirst();
MESSAGE_CHECK(m_process, type == queuedEvents.forwardedEvent.type());
pageClient().doneWithTouchEvent(queuedEvents.forwardedEvent, handled);
for (size_t i = 0; i < queuedEvents.deferredTouchEvents.size(); ++i) {
bool isEventHandled = false;
pageClient().doneWithTouchEvent(queuedEvents.deferredTouchEvents.at(i), isEventHandled);
}
break;
}
#endif
}
}
void WebPageProxy::editorStateChanged(const EditorState& editorState)
{
if (updateEditorState(editorState))
dispatchDidUpdateEditorState();
}
bool WebPageProxy::updateEditorState(const EditorState& newEditorState)
{
if (newEditorState.identifier < m_editorState.identifier)
return false;
auto oldEditorState = std::exchange(m_editorState, newEditorState);
didUpdateEditorState(oldEditorState, newEditorState);
return true;
}
#if !PLATFORM(IOS_FAMILY)
void WebPageProxy::dispatchDidUpdateEditorState()
{
}
#endif
inline API::DiagnosticLoggingClient* WebPageProxy::effectiveDiagnosticLoggingClient(ShouldSample shouldSample)
{
// Diagnostic logging is disabled for ephemeral sessions for privacy reasons.
if (sessionID().isEphemeral())
return nullptr;
return DiagnosticLoggingClient::shouldLogAfterSampling(shouldSample) ? diagnosticLoggingClient() : nullptr;
}
void WebPageProxy::logDiagnosticMessage(const String& message, const String& description, WebCore::ShouldSample shouldSample)
{
auto* effectiveClient = effectiveDiagnosticLoggingClient(shouldSample);
if (!effectiveClient)
return;
effectiveClient->logDiagnosticMessage(this, message, description);
}
void WebPageProxy::logDiagnosticMessageFromWebProcess(const String& message, const String& description, WebCore::ShouldSample shouldSample)
{
MESSAGE_CHECK(m_process, message.isAllASCII());
logDiagnosticMessage(message, description, shouldSample);
}
void WebPageProxy::logDiagnosticMessageWithResult(const String& message, const String& description, uint32_t result, WebCore::ShouldSample shouldSample)
{
auto* effectiveClient = effectiveDiagnosticLoggingClient(shouldSample);
if (!effectiveClient)
return;
effectiveClient->logDiagnosticMessageWithResult(this, message, description, static_cast<WebCore::DiagnosticLoggingResultType>(result));
}
void WebPageProxy::logDiagnosticMessageWithResultFromWebProcess(const String& message, const String& description, uint32_t result, WebCore::ShouldSample shouldSample)
{
MESSAGE_CHECK(m_process, message.isAllASCII());
logDiagnosticMessageWithResult(message, description, result, shouldSample);
}
void WebPageProxy::logDiagnosticMessageWithValue(const String& message, const String& description, double value, unsigned significantFigures, ShouldSample shouldSample)
{
auto* effectiveClient = effectiveDiagnosticLoggingClient(shouldSample);
if (!effectiveClient)
return;
effectiveClient->logDiagnosticMessageWithValue(this, message, description, String::numberToStringFixedPrecision(value, significantFigures));
}
void WebPageProxy::logDiagnosticMessageWithValueFromWebProcess(const String& message, const String& description, double value, unsigned significantFigures, ShouldSample shouldSample)
{
MESSAGE_CHECK(m_process, message.isAllASCII());
logDiagnosticMessageWithValue(message, description, value, significantFigures, shouldSample);
}
void WebPageProxy::logDiagnosticMessageWithEnhancedPrivacy(const String& message, const String& description, ShouldSample shouldSample)
{
auto* effectiveClient = effectiveDiagnosticLoggingClient(shouldSample);
if (!effectiveClient)
return;
effectiveClient->logDiagnosticMessageWithEnhancedPrivacy(this, message, description);
}
void WebPageProxy::logDiagnosticMessageWithEnhancedPrivacyFromWebProcess(const String& message, const String& description, WebCore::ShouldSample shouldSample)
{
MESSAGE_CHECK(m_process, message.isAllASCII());
logDiagnosticMessageWithEnhancedPrivacy(message, description, shouldSample);
}
void WebPageProxy::logDiagnosticMessageWithValueDictionary(const String& message, const String& description, const WebCore::DiagnosticLoggingClient::ValueDictionary& valueDictionary, WebCore::ShouldSample shouldSample)
{
auto* effectiveClient = effectiveDiagnosticLoggingClient(shouldSample);
if (!effectiveClient)
return;
auto apiDictionary = API::Dictionary::create();
for (auto& keyValuePair : valueDictionary) {
apiDictionary->add(keyValuePair.key, WTF::switchOn(keyValuePair.value,
[](const String& value) -> Ref<Object> { return API::String::create(value); },
[](uint64_t value) -> Ref<Object> { return API::UInt64::create(value); },
[](int64_t value) -> Ref<Object> { return API::Int64::create(value); },
[](bool value) -> Ref<Object> { return API::Boolean::create(value); },
[](double value) -> Ref<Object> { return API::Double::create(value); }
));
}
effectiveClient->logDiagnosticMessageWithValueDictionary(this, message, description, WTFMove(apiDictionary));
}
void WebPageProxy::logDiagnosticMessageWithValueDictionaryFromWebProcess(const String& message, const String& description, const WebCore::DiagnosticLoggingClient::ValueDictionary& valueDictionary, WebCore::ShouldSample shouldSample)
{
MESSAGE_CHECK(m_process, message.isAllASCII());
logDiagnosticMessageWithValueDictionary(message, description, valueDictionary, shouldSample);
}
void WebPageProxy::logDiagnosticMessageWithDomain(const String& message, WebCore::DiagnosticLoggingDomain domain)
{
auto* effectiveClient = effectiveDiagnosticLoggingClient(ShouldSample::No);
if (!effectiveClient)
return;
effectiveClient->logDiagnosticMessageWithDomain(this, message, domain);
}
void WebPageProxy::logDiagnosticMessageWithDomainFromWebProcess(const String& message, WebCore::DiagnosticLoggingDomain domain)
{
MESSAGE_CHECK(m_process, message.isAllASCII());
logDiagnosticMessageWithDomain(message, domain);
}
void WebPageProxy::logScrollingEvent(uint32_t eventType, MonotonicTime timestamp, uint64_t data)
{
PerformanceLoggingClient::ScrollingEvent event = static_cast<PerformanceLoggingClient::ScrollingEvent>(eventType);
switch (event) {
case PerformanceLoggingClient::ScrollingEvent::LoggingEnabled:
WTFLogAlways("SCROLLING: ScrollingPerformanceTestingEnabled\n");
break;
case PerformanceLoggingClient::ScrollingEvent::ExposedTilelessArea:
WTFLogAlways("SCROLLING: Exposed tileless area. Time: %f Unfilled Pixels: %llu\n", timestamp.secondsSinceEpoch().value(), (unsigned long long)data);
break;
case PerformanceLoggingClient::ScrollingEvent::FilledTile:
WTFLogAlways("SCROLLING: Filled visible fresh tile. Time: %f Unfilled Pixels: %llu\n", timestamp.secondsSinceEpoch().value(), (unsigned long long)data);
break;
case PerformanceLoggingClient::ScrollingEvent::SwitchedScrollingMode:
if (data)
WTFLogAlways("SCROLLING: Switching to main-thread scrolling mode. Time: %f Reason(s): %s\n", timestamp.secondsSinceEpoch().value(), PerformanceLoggingClient::synchronousScrollingReasonsAsString(OptionSet<SynchronousScrollingReason>::fromRaw(data)).utf8().data());
else
WTFLogAlways("SCROLLING: Switching to threaded scrolling mode. Time: %f\n", timestamp.secondsSinceEpoch().value());
break;
}
}
void WebPageProxy::focusedFrameChanged(const std::optional<FrameIdentifier>& frameID)
{
if (!frameID) {
m_focusedFrame = nullptr;
return;
}
WebFrameProxy* frame = m_process->webFrame(*frameID);
MESSAGE_CHECK(m_process, frame);
m_focusedFrame = frame;
}
void WebPageProxy::processDidBecomeUnresponsive()
{
WEBPAGEPROXY_RELEASE_LOG_ERROR(Process, "processDidBecomeUnresponsive:");
if (!hasRunningProcess())
return;
updateBackingStoreDiscardableState();
m_navigationClient->processDidBecomeUnresponsive(*this);
}
void WebPageProxy::processDidBecomeResponsive()
{
WEBPAGEPROXY_RELEASE_LOG(Process, "processDidBecomeResponsive:");
if (!hasRunningProcess())
return;
updateBackingStoreDiscardableState();
m_navigationClient->processDidBecomeResponsive(*this);
}
void WebPageProxy::willChangeProcessIsResponsive()
{
m_pageLoadState.willChangeProcessIsResponsive();
}
void WebPageProxy::didChangeProcessIsResponsive()
{
m_pageLoadState.didChangeProcessIsResponsive();
}
String WebPageProxy::currentURL() const
{
String url = m_pageLoadState.activeURL();
if (url.isEmpty() && m_backForwardList->currentItem())
url = m_backForwardList->currentItem()->url();
return url;
}
URL WebPageProxy::currentResourceDirectoryURL() const
{
auto resourceDirectoryURL = m_pageLoadState.resourceDirectoryURL();
if (!resourceDirectoryURL.isEmpty())
return resourceDirectoryURL;
if (auto* item = m_backForwardList->currentItem())
return item->resourceDirectoryURL();
return { };
}
void WebPageProxy::resetStateAfterProcessTermination(ProcessTerminationReason reason)
{
if (reason != ProcessTerminationReason::NavigationSwap)
WEBPAGEPROXY_RELEASE_LOG_ERROR(Process, "processDidTerminate: (pid %d), reason %d", processIdentifier(), reason);
ASSERT(m_hasRunningProcess);
#if PLATFORM(IOS_FAMILY)
if (m_process->isUnderMemoryPressure()) {
String domain = WebCore::topPrivatelyControlledDomain(URL({ }, currentURL()).host().toString());
if (!domain.isEmpty())
logDiagnosticMessageWithEnhancedPrivacy(WebCore::DiagnosticLoggingKeys::domainCausingJetsamKey(), domain, WebCore::ShouldSample::No);
}
#endif
resetStateAfterProcessExited(reason);
stopAllURLSchemeTasks(m_process.ptr());
#if ENABLE(UI_PROCESS_PDF_HUD)
pageClient().removeAllPDFHUDs();
#endif
// For bringup of process swapping, NavigationSwap termination will not go out to clients.
// If it does *during* process swapping, and the client triggers a reload, that causes bizarre WebKit re-entry.
// FIXME: This might have to change
if (reason != ProcessTerminationReason::NavigationSwap)
navigationState().clearAllNavigations();
if (m_controlledByAutomation) {
if (auto* automationSession = process().processPool().automationSession())
automationSession->terminate();
}
}
void WebPageProxy::provisionalProcessDidTerminate()
{
ASSERT(m_provisionalPage);
m_provisionalPage = nullptr;
}
static bool shouldReloadAfterProcessTermination(ProcessTerminationReason reason)
{
switch (reason) {
case ProcessTerminationReason::ExceededMemoryLimit:
case ProcessTerminationReason::ExceededCPULimit:
case ProcessTerminationReason::RequestedByNetworkProcess:
case ProcessTerminationReason::RequestedByGPUProcess:
case ProcessTerminationReason::Crash:
return true;
case ProcessTerminationReason::ExceededProcessCountLimit:
case ProcessTerminationReason::NavigationSwap:
case ProcessTerminationReason::RequestedByClient:
break;
}
return false;
}
static const char* processTerminationReasonToString(ProcessTerminationReason reason)
{
switch (reason) {
case ProcessTerminationReason::ExceededMemoryLimit:
return "ExceededMemoryLimit";
case ProcessTerminationReason::ExceededCPULimit:
return "ExceededCPULimit";
case ProcessTerminationReason::RequestedByNetworkProcess:
return "RequestedByNetworkProcess";
case ProcessTerminationReason::RequestedByGPUProcess:
return "RequestedByGPUProcess";
case ProcessTerminationReason::ExceededProcessCountLimit:
return "ExceededProcessCountLimit";
case ProcessTerminationReason::NavigationSwap:
return "NavigationSwap";
case ProcessTerminationReason::RequestedByClient:
return "RequestedByClient";
case ProcessTerminationReason::Crash:
break;
}
return "Crash";
}
void WebPageProxy::dispatchProcessDidTerminate(ProcessTerminationReason reason)
{
WEBPAGEPROXY_RELEASE_LOG_ERROR(Loading, "dispatchProcessDidTerminate: reason=%{public}s", processTerminationReasonToString(reason));
bool handledByClient = false;
if (m_loaderClient)
handledByClient = reason != ProcessTerminationReason::RequestedByClient && m_loaderClient->processDidCrash(*this);
else
handledByClient = m_navigationClient->processDidTerminate(*this, reason);
if (!handledByClient && shouldReloadAfterProcessTermination(reason))
tryReloadAfterProcessTermination();
}
void WebPageProxy::tryReloadAfterProcessTermination()
{
m_resetRecentCrashCountTimer.stop();
if (++m_recentCrashCount > maximumWebProcessRelaunchAttempts) {
WEBPAGEPROXY_RELEASE_LOG_ERROR(Process, "tryReloadAfterProcessTermination: process crashed and the client did not handle it, not reloading the page because we reached the maximum number of attempts");
m_recentCrashCount = 0;
return;
}
WEBPAGEPROXY_RELEASE_LOG(Process, "tryReloadAfterProcessTermination: process crashed and the client did not handle it, reloading the page");
reload(ReloadOption::ExpiredOnly);
}
void WebPageProxy::resetRecentCrashCountSoon()
{
m_resetRecentCrashCountTimer.startOneShot(resetRecentCrashCountDelay);
}
void WebPageProxy::resetRecentCrashCount()
{
m_recentCrashCount = 0;
}
void WebPageProxy::stopAllURLSchemeTasks(WebProcessProxy* process)
{
for (auto& handler : copyToVectorOf<Ref<WebURLSchemeHandler>>(m_urlSchemeHandlersByScheme.values()))
handler->stopAllTasksForPage(*this, process);
}
void WebPageProxy::resetState(ResetStateReason resetStateReason)
{
m_mainFrame = nullptr;
m_focusedFrame = nullptr;
m_suspendedPageKeptToPreventFlashing = nullptr;
m_lastSuspendedPage = nullptr;
#if PLATFORM(COCOA)
m_scrollingPerformanceData = nullptr;
#endif
if (m_drawingArea) {
#if PLATFORM(COCOA)
if (resetStateReason == ResetStateReason::NavigationSwap && is<RemoteLayerTreeDrawingAreaProxy>(*m_drawingArea)) {
// Keep layers around in frozen state to avoid flashing during process swaps.
m_frozenRemoteLayerTreeHost = downcast<RemoteLayerTreeDrawingAreaProxy>(*m_drawingArea).detachRemoteLayerTreeHost();
}
#endif
m_drawingArea = nullptr;
}
closeOverlayedViews();
m_inspector->reset();
#if ENABLE(FULLSCREEN_API)
if (m_fullScreenManager) {
m_fullScreenManager->close();
m_fullScreenManager = nullptr;
}
#endif
#if ENABLE(MEDIA_USAGE)
if (m_mediaUsageManager)
m_mediaUsageManager->reset();
#endif
#if HAVE(VISIBILITY_PROPAGATION_VIEW)
if (resetStateReason != ResetStateReason::NavigationSwap)
m_contextIDForVisibilityPropagationInWebProcess = 0;
#endif
if (resetStateReason != ResetStateReason::NavigationSwap)
callServiceWorkerLaunchCompletionHandlerIfNecessary();
if (m_openPanelResultListener) {
m_openPanelResultListener->invalidate();
m_openPanelResultListener = nullptr;
}
#if ENABLE(TOUCH_EVENTS)
m_touchAndPointerEventTracking.reset();
#endif
#if ENABLE(GEOLOCATION)
m_geolocationPermissionRequestManager.invalidateRequests();
#endif
setToolTip({ });
m_mainFrameHasHorizontalScrollbar = false;
m_mainFrameHasVerticalScrollbar = false;
m_mainFramePinnedState = { true, true, true, true };
m_visibleScrollerThumbRect = IntRect();
#if ENABLE(VIDEO_PRESENTATION_MODE)
if (m_playbackSessionManager) {
m_playbackSessionManager->invalidate();
m_playbackSessionManager = nullptr;
}
if (m_videoFullscreenManager) {
m_videoFullscreenManager->invalidate();
m_videoFullscreenManager = nullptr;
}
#endif
#if PLATFORM(IOS_FAMILY)
m_firstLayerTreeTransactionIdAfterDidCommitLoad = { };
m_lastVisibleContentRectUpdate = { };
m_hasNetworkRequestsOnSuspended = false;
m_isKeyboardAnimatingIn = false;
m_isScrollingOrZooming = false;
m_lastObservedStateWasBackground = false;
#endif
#if ENABLE(WIRELESS_PLAYBACK_TARGET) && !PLATFORM(IOS_FAMILY)
pageClient().mediaSessionManager().removeAllPlaybackTargetPickerClients(*this);
#endif
#if ENABLE(APPLE_PAY)
m_paymentCoordinator = nullptr;
#endif
#if USE(SYSTEM_PREVIEW)
m_systemPreviewController = nullptr;
#endif
#if ENABLE(ARKIT_INLINE_PREVIEW)
m_modelElementController = nullptr;
#endif
#if ENABLE(WEB_AUTHN)
m_credentialsMessenger = nullptr;
#endif
#if PLATFORM(IOS_FAMILY) && ENABLE(DEVICE_ORIENTATION)
m_webDeviceOrientationUpdateProviderProxy = nullptr;
#endif
for (auto& editCommand : std::exchange(m_editCommandSet, { }))
editCommand->invalidate();
m_activePopupMenu = nullptr;
updatePlayingMediaDidChange(MediaProducer::IsNotPlaying);
#if ENABLE(MEDIA_STREAM)
m_userMediaPermissionRequestManager = nullptr;
#endif
#if ENABLE(POINTER_LOCK)
requestPointerUnlock();
#endif
#if ENABLE(SPEECH_SYNTHESIS)
resetSpeechSynthesizer();
#endif
#if ENABLE(WEB_AUTHN)
m_websiteDataStore->authenticatorManager().cancelRequest(m_webPageID, std::nullopt);
#endif
m_speechRecognitionPermissionManager = nullptr;
#if ENABLE(WEBXR) && !USE(OPENXR)
if (m_xrSystem) {
m_xrSystem->invalidate();
m_xrSystem = nullptr;
}
#endif
#if ENABLE(MOMENTUM_EVENT_DISPATCHER)
m_lastSentScrollingAccelerationCurve = std::nullopt;
#endif
}
void WebPageProxy::resetStateAfterProcessExited(ProcessTerminationReason terminationReason)
{
if (!hasRunningProcess())
return;
PageClientProtector protector(pageClient());
#if ASSERT_ENABLED
// FIXME: It's weird that resetStateAfterProcessExited() is called even though the process is launching.
if (terminationReason != ProcessTerminationReason::NavigationSwap)
ASSERT(m_process->state() == WebProcessProxy::State::Launching || m_process->state() == WebProcessProxy::State::Terminated);
#endif
#if PLATFORM(IOS_FAMILY)
m_waitingForPostLayoutEditorStateUpdateAfterFocusingElement = false;
m_isVisibleActivity = nullptr;
m_isAudibleActivity = nullptr;
m_isCapturingActivity = nullptr;
m_openingAppLinkActivity = nullptr;
#endif
m_pageIsUserObservableCount = nullptr;
m_visiblePageToken = nullptr;
m_hasRunningProcess = false;
m_areActiveDOMObjectsAndAnimationsSuspended = false;
#if ENABLE(SERVICE_WORKER)
m_isServiceWorkerPage = false;
#endif
m_userScriptsNotified = false;
m_hasActiveAnimatedScroll = false;
m_registeredForFullSpeedUpdates = false;
m_editorState = EditorState();
m_cachedFontAttributesAtSelectionStart.reset();
if (terminationReason != ProcessTerminationReason::NavigationSwap)
m_provisionalPage = nullptr;
if (terminationReason == ProcessTerminationReason::NavigationSwap)
pageClient().processWillSwap();
else
pageClient().processDidExit();
pageClient().clearAllEditCommands();
#if PLATFORM(COCOA)
WebPasteboardProxy::singleton().revokeAccess(m_process.get());
#endif
auto resetStateReason = terminationReason == ProcessTerminationReason::NavigationSwap ? ResetStateReason::NavigationSwap : ResetStateReason::WebProcessExited;
resetState(resetStateReason);
m_pendingLearnOrIgnoreWordMessageCount = 0;
// Can't expect DidReceiveEvent notifications from a crashed web process.
m_mouseEventQueue.clear();
m_keyEventQueue.clear();
if (m_wheelEventCoalescer)
m_wheelEventCoalescer->clear();
#if ENABLE(TOUCH_EVENTS) && !ENABLE(IOS_TOUCH_EVENTS)
m_touchEventQueue.clear();
#endif
#if ENABLE(ATTACHMENT_ELEMENT)
invalidateAllAttachments();
#endif
#if ENABLE(ASYNC_SCROLLING) && PLATFORM(COCOA)
if (m_scrollingCoordinatorProxy)
m_scrollingCoordinatorProxy->resetStateAfterProcessExited();
#endif
if (terminationReason != ProcessTerminationReason::NavigationSwap) {
PageLoadState::Transaction transaction = m_pageLoadState.transaction();
m_pageLoadState.reset(transaction);
}
updatePlayingMediaDidChange(MediaProducer::IsNotPlaying);
// FIXME: <rdar://problem/38676604> In case of process swaps, the old process should gracefully suspend instead of terminating.
m_process->processTerminated();
}
#if ENABLE(ATTACHMENT_ELEMENT) && PLATFORM(COCOA)
static Span<const ASCIILiteral> attachmentElementServices()
{
static constexpr std::array services {
"com.apple.iconservices"_s,
#if PLATFORM(MAC)
"com.apple.iconservices.store"_s,
#endif
};
return services;
}
#endif
#if PLATFORM(COCOA)
static Span<const ASCIILiteral> gpuIOKitClasses()
{
static constexpr std::array services {
#if PLATFORM(IOS_FAMILY)
"AGXDeviceUserClient"_s,
"IOGPU"_s,
"IOSurfaceRootUserClient"_s,
#endif
#if PLATFORM(MAC) || PLATFORM(MACCATALYST)
"AGPMClient"_s,
"AppleGraphicsControlClient"_s,
"AppleGraphicsPolicyClient"_s,
"AppleIntelMEUserClient"_s,
"AppleMGPUPowerControlClient"_s,
"AppleSNBFBUserClient"_s,
"AppleUpstreamUserClient"_s,
"AudioAUUC"_s,
"IOAccelerationUserClient"_s,
"IOAccelerator"_s,
"IOAudioControlUserClient"_s,
"IOAudioEngineUserClient"_s,
"IOSurfaceRootUserClient"_s,
#endif
// FIXME: Is this also needed in PLATFORM(MACCATALYST)?
#if PLATFORM(MAC) && CPU(ARM64)
"IOMobileFramebufferUserClient"_s,
#endif
#if (PLATFORM(MAC) && CPU(ARM64)) || PLATFORM(IOS_FAMILY)
"IOSurfaceAcceleratorClient"_s,
#endif
};
return services;
}
static Span<const ASCIILiteral> gpuMachServices()
{
static constexpr std::array services {
"com.apple.MTLCompilerService"_s,
#if PLATFORM(MAC) || PLATFORM(MACCATALYST)
"com.apple.cvmsServ"_s,
#endif
};
return services;
}
// FIXME(207716): The following should be removed when the GPU process is complete.
static Span<const ASCIILiteral> mediaRelatedMachServices()
{
static constexpr std::array services {
"com.apple.audio.AudioComponentPrefs"_s, "com.apple.audio.AudioComponentRegistrar"_s,
"com.apple.audio.AudioQueueServer"_s, "com.apple.coremedia.endpoint.xpc"_s,
"com.apple.coremedia.routediscoverer.xpc"_s, "com.apple.coremedia.routingcontext.xpc"_s,
"com.apple.coremedia.volumecontroller.xpc"_s, "com.apple.accessibility.mediaaccessibilityd"_s,
"com.apple.mediaremoted.xpc"_s,
#if PLATFORM(IOS_FAMILY)
"com.apple.audio.AudioSession"_s, "com.apple.MediaPlayer.RemotePlayerService"_s,
"com.apple.coremedia.admin"_s,
"com.apple.coremedia.asset.xpc"_s, "com.apple.coremedia.assetimagegenerator.xpc"_s,
"com.apple.coremedia.audiodeviceclock.xpc"_s, "com.apple.coremedia.audioprocessingtap.xpc"_s,
"com.apple.coremedia.capturesession"_s, "com.apple.coremedia.capturesource"_s,
"com.apple.coremedia.compressionsession"_s, "com.apple.coremedia.cpe.xpc"_s,
"com.apple.coremedia.cpeprotector.xpc"_s, "com.apple.coremedia.customurlloader.xpc"_s,
"com.apple.coremedia.decompressionsession"_s, "com.apple.coremedia.figcontentkeysession.xpc"_s,
"com.apple.coremedia.figcpecryptor"_s, "com.apple.coremedia.formatreader.xpc"_s,
"com.apple.coremedia.player.xpc"_s, "com.apple.coremedia.remaker"_s,
"com.apple.coremedia.remotequeue"_s, "com.apple.coremedia.routingsessionmanager.xpc"_s,
"com.apple.coremedia.samplebufferaudiorenderer.xpc"_s, "com.apple.coremedia.samplebufferrendersynchronizer.xpc"_s,
"com.apple.coremedia.sandboxserver.xpc"_s, "com.apple.coremedia.sts"_s,
"com.apple.coremedia.systemcontroller.xpc"_s, "com.apple.coremedia.videoqueue"_s,
"com.apple.coremedia.visualcontext.xpc"_s, "com.apple.airplay.apsynccontroller.xpc"_s,
"com.apple.audio.AURemoteIOServer"_s,
#endif
#if PLATFORM(MAC) || PLATFORM(MACCATALYST)
"com.apple.audio.audiohald"_s, "com.apple.audio.SandboxHelper"_s, "com.apple.coremedia.endpointstream.xpc"_s, "com.apple.coremedia.endpointplaybacksession.xpc"_s,
"com.apple.coremedia.endpointremotecontrolsession.xpc"_s, "com.apple.coremedia.videodecoder"_s,
"com.apple.coremedia.videoencoder"_s, "com.apple.lskdd"_s, "com.apple.trustd.agent"_s,
#endif
// FIXME: Is this also needed in PLATFORM(MACCATALYST)?
#if PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED > 120000
"com.apple.coremedia.samplebufferconsumer.xpc"_s,
#endif
};
return services;
}
static Span<const ASCIILiteral> mediaRelatedIOKitClasses()
{
#if !(PLATFORM(MAC) || PLATFORM(MACCATALYST))
return { };
#else
static constexpr std::array services {
#if CPU(ARM64)
"AppleAVDUserClient"_s,
#endif
"RootDomainUserClient"_s,
};
return services;
#endif
}
#endif
WebPageCreationParameters WebPageProxy::creationParameters(WebProcessProxy& process, DrawingAreaProxy& drawingArea, RefPtr<API::WebsitePolicies>&& websitePolicies)
{
WebPageCreationParameters parameters;
parameters.processDisplayName = configuration().processDisplayName();
parameters.viewSize = pageClient().viewSize();
parameters.activityState = m_activityState;
parameters.drawingAreaType = drawingArea.type();
parameters.drawingAreaIdentifier = drawingArea.identifier();
parameters.webPageProxyIdentifier = m_identifier;
parameters.store = preferencesStore();
parameters.pageGroupData = m_pageGroup->data();
parameters.isEditable = m_isEditable;
parameters.underlayColor = m_underlayColor;
parameters.useFixedLayout = m_useFixedLayout;
parameters.fixedLayoutSize = m_fixedLayoutSize;
parameters.viewExposedRect = m_viewExposedRect;
parameters.alwaysShowsHorizontalScroller = m_alwaysShowsHorizontalScroller;
parameters.alwaysShowsVerticalScroller = m_alwaysShowsVerticalScroller;
parameters.suppressScrollbarAnimations = m_suppressScrollbarAnimations;
parameters.paginationMode = m_paginationMode;
parameters.paginationBehavesLikeColumns = m_paginationBehavesLikeColumns;
parameters.pageLength = m_pageLength;
parameters.gapBetweenPages = m_gapBetweenPages;
parameters.paginationLineGridEnabled = m_paginationLineGridEnabled;
parameters.userAgent = userAgent();
parameters.itemStatesWereRestoredByAPIRequest = m_sessionStateWasRestoredByAPIRequest;
parameters.itemStates = m_backForwardList->itemStates();
parameters.visitedLinkTableID = m_visitedLinkStore->identifier();
parameters.canRunBeforeUnloadConfirmPanel = m_uiClient->canRunBeforeUnloadConfirmPanel();
parameters.canRunModal = m_canRunModal;
parameters.deviceScaleFactor = deviceScaleFactor();
parameters.viewScaleFactor = m_viewScaleFactor;
parameters.textZoomFactor = m_textZoomFactor;
parameters.pageZoomFactor = m_pageZoomFactor;
parameters.topContentInset = m_topContentInset;
parameters.mediaVolume = m_mediaVolume;
parameters.muted = m_mutedState;
parameters.openedByDOM = m_openedByDOM;
parameters.mayStartMediaWhenInWindow = m_mayStartMediaWhenInWindow;
parameters.mediaPlaybackIsSuspended = m_mediaPlaybackIsSuspended;
parameters.minimumSizeForAutoLayout = m_minimumSizeForAutoLayout;
parameters.sizeToContentAutoSizeMaximumSize = m_sizeToContentAutoSizeMaximumSize;
parameters.autoSizingShouldExpandToViewHeight = m_autoSizingShouldExpandToViewHeight;
parameters.viewportSizeForCSSViewportUnits = m_viewportSizeForCSSViewportUnits;
parameters.scrollPinningBehavior = m_scrollPinningBehavior;
if (m_scrollbarOverlayStyle)
parameters.scrollbarOverlayStyle = m_scrollbarOverlayStyle.value();
else
parameters.scrollbarOverlayStyle = std::nullopt;
parameters.backgroundExtendsBeyondPage = m_backgroundExtendsBeyondPage;
parameters.layerHostingMode = m_layerHostingMode;
parameters.controlledByAutomation = m_controlledByAutomation;
parameters.useDarkAppearance = useDarkAppearance();
parameters.useElevatedUserInterfaceLevel = useElevatedUserInterfaceLevel();
#if PLATFORM(MAC)
parameters.colorSpace = pageClient().colorSpace();
parameters.useSystemAppearance = m_useSystemAppearance;
#endif
#if ENABLE(META_VIEWPORT)
parameters.ignoresViewportScaleLimits = m_forceAlwaysUserScalable;
parameters.viewportConfigurationViewLayoutSize = m_viewportConfigurationViewLayoutSize;
parameters.viewportConfigurationLayoutSizeScaleFactor = m_viewportConfigurationLayoutSizeScaleFactor;
parameters.viewportConfigurationMinimumEffectiveDeviceWidth = m_viewportConfigurationMinimumEffectiveDeviceWidth;
parameters.overrideViewportArguments = m_overrideViewportArguments;
#endif
#if PLATFORM(IOS_FAMILY)
parameters.screenSize = screenSize();
parameters.availableScreenSize = availableScreenSize();
parameters.overrideScreenSize = overrideScreenSize();
parameters.textAutosizingWidth = textAutosizingWidth();
parameters.mimeTypesWithCustomContentProviders = pageClient().mimeTypesWithCustomContentProviders();
parameters.minimumUnobscuredSize = m_minimumUnobscuredSize;
parameters.maximumUnobscuredSize = m_maximumUnobscuredSize;
parameters.deviceOrientation = m_deviceOrientation;
parameters.keyboardIsAttached = isInHardwareKeyboardMode();
parameters.canShowWhileLocked = m_configuration->canShowWhileLocked();
#endif
#if PLATFORM(COCOA)
parameters.smartInsertDeleteEnabled = m_isSmartInsertDeleteEnabled;
parameters.additionalSupportedImageTypes = m_configuration->additionalSupportedImageTypes();
bool needWebProcessExtensions = !preferences().useGPUProcessForMediaEnabled()
|| !preferences().captureAudioInGPUProcessEnabled()
|| !preferences().captureVideoInGPUProcessEnabled()
|| !preferences().webRTCPlatformCodecsInGPUProcessEnabled();
if (needWebProcessExtensions) {
// FIXME(207716): The following should be removed when the GPU process is complete.
parameters.mediaExtensionHandles = SandboxExtension::createHandlesForMachLookup(mediaRelatedMachServices(), std::nullopt);
parameters.mediaIOKitExtensionHandles = SandboxExtension::createHandlesForIOKitClassExtensions(mediaRelatedIOKitClasses(), std::nullopt);
}
if (!preferences().useGPUProcessForMediaEnabled()
|| (!preferences().captureVideoInGPUProcessEnabled() && !preferences().captureVideoInUIProcessEnabled())
|| (!preferences().captureAudioInGPUProcessEnabled() && !preferences().captureAudioInUIProcessEnabled())
|| !preferences().webRTCPlatformCodecsInGPUProcessEnabled()
|| !preferences().useGPUProcessForCanvasRenderingEnabled()
|| !preferences().useGPUProcessForWebGLEnabled()) {
parameters.gpuIOKitExtensionHandles = SandboxExtension::createHandlesForIOKitClassExtensions(gpuIOKitClasses(), std::nullopt);
parameters.gpuMachExtensionHandles = SandboxExtension::createHandlesForMachLookup(gpuMachServices(), std::nullopt);
}
#endif
#if HAVE(STATIC_FONT_REGISTRY)
if (preferences().shouldAllowUserInstalledFonts())
parameters.fontMachExtensionHandle = fontdMachExtensionHandle();
#endif
#if HAVE(APP_ACCENT_COLORS)
parameters.accentColor = pageClient().accentColor();
#endif
parameters.shouldScaleViewToFitDocument = m_shouldScaleViewToFitDocument;
parameters.userInterfaceLayoutDirection = pageClient().userInterfaceLayoutDirection();
parameters.observedLayoutMilestones = m_observedLayoutMilestones;
parameters.overrideContentSecurityPolicy = m_overrideContentSecurityPolicy;
parameters.cpuLimit = m_cpuLimit;
#if USE(WPE_RENDERER)
parameters.hostFileDescriptor = pageClient().hostFileDescriptor();
#endif
#if PLATFORM(WIN)
parameters.nativeWindowHandle = reinterpret_cast<uint64_t>(viewWidget());
#endif
for (auto& iterator : m_urlSchemeHandlersByScheme)
parameters.urlSchemeHandlers.set(iterator.key, iterator.value->identifier());
parameters.urlSchemesWithLegacyCustomProtocolHandlers = WebProcessPool::urlSchemesWithCustomProtocolHandlers();
#if ENABLE(WEB_RTC)
// FIXME: This is also being passed over the to WebProcess via the PreferencesStore.
parameters.iceCandidateFilteringEnabled = m_preferences->iceCandidateFilteringEnabled();
#if USE(LIBWEBRTC)
// FIXME: This is also being passed over the to WebProcess via the PreferencesStore.
parameters.enumeratingAllNetworkInterfacesEnabled = m_preferences->enumeratingAllNetworkInterfacesEnabled();
#endif
#endif
#if ENABLE(APPLICATION_MANIFEST)
parameters.applicationManifest = m_configuration->applicationManifest() ? std::optional<WebCore::ApplicationManifest>(m_configuration->applicationManifest()->applicationManifest()) : std::nullopt;
#endif
parameters.needsFontAttributes = m_needsFontAttributes;
parameters.backgroundColor = m_backgroundColor;
parameters.overriddenMediaType = m_overriddenMediaType;
parameters.corsDisablingPatterns = corsDisablingPatterns();
parameters.userScriptsShouldWaitUntilNotification = m_configuration->userScriptsShouldWaitUntilNotification();
parameters.allowedNetworkHosts = m_configuration->allowedNetworkHosts();
parameters.loadsSubresources = m_configuration->loadsSubresources();
parameters.crossOriginAccessControlCheckEnabled = m_configuration->crossOriginAccessControlCheckEnabled();
parameters.hasResourceLoadClient = !!m_resourceLoadClient;
std::reference_wrapper<WebUserContentControllerProxy> userContentController(m_userContentController.get());
if (auto* userContentControllerFromWebsitePolicies = websitePolicies ? websitePolicies->userContentController() : nullptr)
userContentController = *userContentControllerFromWebsitePolicies;
process.addWebUserContentControllerProxy(userContentController);
parameters.userContentControllerParameters = userContentController.get().parameters();
// FIXME: This is also being passed over the to WebProcess via the PreferencesStore.
parameters.shouldCaptureAudioInUIProcess = preferences().captureAudioInUIProcessEnabled();
// FIXME: This is also being passed over the to WebProcess via the PreferencesStore.
parameters.shouldCaptureAudioInGPUProcess = preferences().captureAudioInGPUProcessEnabled();
// FIXME: This is also being passed over the to WebProcess via the PreferencesStore.
parameters.shouldCaptureVideoInUIProcess = preferences().captureVideoInUIProcessEnabled();
// FIXME: This is also being passed over the to WebProcess via the PreferencesStore.
parameters.shouldCaptureVideoInGPUProcess = preferences().captureVideoInGPUProcessEnabled();
// FIXME: This is also being passed over the to WebProcess via the PreferencesStore.
parameters.shouldRenderCanvasInGPUProcess = preferences().useGPUProcessForCanvasRenderingEnabled();
// FIXME: This is also being passed over the to WebProcess via the PreferencesStore.
parameters.shouldRenderDOMInGPUProcess = preferences().useGPUProcessForDOMRenderingEnabled();
// FIXME: This is also being passed over the to WebProcess via the PreferencesStore.
parameters.shouldPlayMediaInGPUProcess = preferences().useGPUProcessForMediaEnabled();
#if ENABLE(WEBGL)
// FIXME: This is also being passed over the to WebProcess via the PreferencesStore.
parameters.shouldRenderWebGLInGPUProcess = preferences().useGPUProcessForWebGLEnabled();
#endif
// FIXME: This is also being passed over the to WebProcess via the PreferencesStore.
parameters.shouldEnableVP9Decoder = preferences().vp9DecoderEnabled();
#if ENABLE(VP9) && PLATFORM(COCOA)
// FIXME: This is also being passed over the to WebProcess via the PreferencesStore.
parameters.shouldEnableVP8Decoder = preferences().vp8DecoderEnabled();
// FIXME: This is also being passed over the to WebProcess via the PreferencesStore.
parameters.shouldEnableVP9SWDecoder = preferences().vp9DecoderEnabled() && (!WebCore::systemHasBattery() || preferences().vp9SWDecoderEnabledOnBattery());
#endif
parameters.shouldCaptureDisplayInUIProcess = m_process->processPool().configuration().shouldCaptureDisplayInUIProcess();
#if ENABLE(APP_BOUND_DOMAINS)
parameters.limitsNavigationsToAppBoundDomains = m_limitsNavigationsToAppBoundDomains;
#endif
parameters.lastNavigationWasAppInitiated = m_lastNavigationWasAppInitiated;
parameters.shouldRelaxThirdPartyCookieBlocking = m_configuration->shouldRelaxThirdPartyCookieBlocking();
parameters.canUseCredentialStorage = m_canUseCredentialStorage;
#if PLATFORM(GTK)
parameters.gtkSettings = GtkSettingsManager::singleton().settingsState();
#endif
#if ENABLE(ATTACHMENT_ELEMENT) && PLATFORM(COCOA)
if (m_preferences->attachmentElementEnabled() && !process.hasIssuedAttachmentElementRelatedSandboxExtensions()) {
parameters.attachmentElementExtensionHandles = SandboxExtension::createHandlesForMachLookup(attachmentElementServices(), std::nullopt);
process.setHasIssuedAttachmentElementRelatedSandboxExtensions();
}
#endif
parameters.httpsUpgradeEnabled = preferences().upgradeKnownHostsToHTTPSEnabled() ? m_configuration->httpsUpgradeEnabled() : false;
#if PLATFORM(IOS)
// FIXME: This is also being passed over the to WebProcess via the PreferencesStore.
parameters.allowsDeprecatedSynchronousXMLHttpRequestDuringUnload = allowsDeprecatedSynchronousXMLHttpRequestDuringUnload();
#endif
#if ENABLE(APP_HIGHLIGHTS)
parameters.appHighlightsVisible = appHighlightsVisibility() ? HighlightVisibility::Visible : HighlightVisibility::Hidden;
#endif
#if HAVE(TOUCH_BAR)
parameters.requiresUserActionForEditingControlsManager = m_configuration->requiresUserActionForEditingControlsManager();
#endif
return parameters;
}
void WebPageProxy::isJITEnabled(CompletionHandler<void(bool)>&& completionHandler)
{
launchInitialProcessIfNecessary();
sendWithAsyncReply(Messages::WebProcess::IsJITEnabled(), WTFMove(completionHandler), 0);
}
void WebPageProxy::enterAcceleratedCompositingMode(const LayerTreeContext& layerTreeContext)
{
#if PLATFORM(MAC)
ASSERT(m_drawingArea->type() == DrawingAreaType::TiledCoreAnimation);
#endif
pageClient().enterAcceleratedCompositingMode(layerTreeContext);
}
void WebPageProxy::didFirstLayerFlush(const LayerTreeContext& layerTreeContext)
{
#if PLATFORM(MAC)
ASSERT(m_drawingArea->type() == DrawingAreaType::TiledCoreAnimation);
#endif
pageClient().didFirstLayerFlush(layerTreeContext);
if (m_lastSuspendedPage)
m_lastSuspendedPage->pageDidFirstLayerFlush();
m_suspendedPageKeptToPreventFlashing = nullptr;
}
void WebPageProxy::exitAcceleratedCompositingMode()
{
pageClient().exitAcceleratedCompositingMode();
}
void WebPageProxy::updateAcceleratedCompositingMode(const LayerTreeContext& layerTreeContext)
{
pageClient().updateAcceleratedCompositingMode(layerTreeContext);
}
void WebPageProxy::backForwardClear()
{
m_backForwardList->clear();
}
#if ENABLE(GAMEPAD)
void WebPageProxy::gamepadActivity(const Vector<GamepadData>& gamepadDatas, EventMakesGamepadsVisible eventVisibility)
{
send(Messages::WebPage::GamepadActivity(gamepadDatas, eventVisibility));
}
#endif
void WebPageProxy::didReceiveAuthenticationChallengeProxy(Ref<AuthenticationChallengeProxy>&& authenticationChallenge, NegotiatedLegacyTLS negotiatedLegacyTLS)
{
if (negotiatedLegacyTLS == NegotiatedLegacyTLS::Yes) {
m_navigationClient->shouldAllowLegacyTLS(*this, authenticationChallenge.get(), [this, protectedThis = Ref { *this }, authenticationChallenge] (bool shouldAllowLegacyTLS) {
if (shouldAllowLegacyTLS)
m_navigationClient->didReceiveAuthenticationChallenge(*this, authenticationChallenge.get());
else
authenticationChallenge->listener().completeChallenge(AuthenticationChallengeDisposition::Cancel);
});
return;
}
m_navigationClient->didReceiveAuthenticationChallenge(*this, authenticationChallenge.get());
}
void WebPageProxy::negotiatedLegacyTLS()
{
auto transaction = m_pageLoadState.transaction();
m_pageLoadState.negotiatedLegacyTLS(transaction);
}
void WebPageProxy::didNegotiateModernTLS(const URL& url)
{
m_navigationClient->didNegotiateModernTLS(url);
}
void WebPageProxy::exceededDatabaseQuota(FrameIdentifier frameID, const String& originIdentifier, const String& databaseName, const String& displayName, uint64_t currentQuota, uint64_t currentOriginUsage, uint64_t currentDatabaseUsage, uint64_t expectedUsage, Messages::WebPageProxy::ExceededDatabaseQuota::DelayedReply&& reply)
{
requestStorageSpace(frameID, originIdentifier, databaseName, displayName, currentQuota, currentOriginUsage, currentDatabaseUsage, expectedUsage, [reply = WTFMove(reply)](auto quota) mutable {
reply(quota);
});
}
void WebPageProxy::requestStorageSpace(FrameIdentifier frameID, const String& originIdentifier, const String& databaseName, const String& displayName, uint64_t currentQuota, uint64_t currentOriginUsage, uint64_t currentDatabaseUsage, uint64_t expectedUsage, CompletionHandler<void(uint64_t)>&& completionHandler)
{
WEBPAGEPROXY_RELEASE_LOG(Storage, "requestStorageSpace for frame %" PRIu64 ", current quota %" PRIu64 " current usage %" PRIu64 " expected usage %" PRIu64, frameID.toUInt64(), currentQuota, currentDatabaseUsage, expectedUsage);
StorageRequests::singleton().processOrAppend([this, protectedThis = Ref { *this }, pageURL = currentURL(), frameID, originIdentifier, databaseName, displayName, currentQuota, currentOriginUsage, currentDatabaseUsage, expectedUsage, completionHandler = WTFMove(completionHandler)]() mutable {
this->makeStorageSpaceRequest(frameID, originIdentifier, databaseName, displayName, currentQuota, currentOriginUsage, currentDatabaseUsage, expectedUsage, [this, protectedThis = WTFMove(protectedThis), frameID, pageURL = WTFMove(pageURL), completionHandler = WTFMove(completionHandler), currentQuota](auto quota) mutable {
WEBPAGEPROXY_RELEASE_LOG(Storage, "requestStorageSpace response for frame %" PRIu64 ", quota %" PRIu64, frameID.toUInt64(), quota);
UNUSED_VARIABLE(frameID);
if (quota <= currentQuota && this->currentURL() == pageURL) {
WEBPAGEPROXY_RELEASE_LOG(Storage, "storage space increase denied");
m_isQuotaIncreaseDenied = true;
}
completionHandler(quota);
StorageRequests::singleton().processNextIfAny();
});
});
}
void WebPageProxy::makeStorageSpaceRequest(FrameIdentifier frameID, const String& originIdentifier, const String& databaseName, const String& displayName, uint64_t currentQuota, uint64_t currentOriginUsage, uint64_t currentDatabaseUsage, uint64_t expectedUsage, CompletionHandler<void(uint64_t)>&& completionHandler)
{
if (m_isQuotaIncreaseDenied) {
completionHandler(currentQuota);
return;
}
WebFrameProxy* frame = m_process->webFrame(frameID);
MESSAGE_CHECK(m_process, frame);
auto originData = SecurityOriginData::fromDatabaseIdentifier(originIdentifier);
if (originData != SecurityOriginData::fromURL(URL { { }, currentURL() })) {
completionHandler(currentQuota);
return;
}
auto origin = API::SecurityOrigin::create(originData->securityOrigin());
m_uiClient->exceededDatabaseQuota(this, frame, origin.ptr(), databaseName, displayName, currentQuota, currentOriginUsage, currentDatabaseUsage, expectedUsage, WTFMove(completionHandler));
}
void WebPageProxy::reachedApplicationCacheOriginQuota(const String& originIdentifier, uint64_t currentQuota, uint64_t totalBytesNeeded, Messages::WebPageProxy::ReachedApplicationCacheOriginQuota::DelayedReply&& reply)
{
auto securityOriginData = SecurityOriginData::fromDatabaseIdentifier(originIdentifier);
MESSAGE_CHECK(m_process, securityOriginData);
Ref<SecurityOrigin> securityOrigin = securityOriginData->securityOrigin();
m_uiClient->reachedApplicationCacheOriginQuota(this, securityOrigin.get(), currentQuota, totalBytesNeeded, WTFMove(reply));
}
void WebPageProxy::requestGeolocationPermissionForFrame(GeolocationIdentifier geolocationID, FrameInfoData&& frameInfo)
{
MESSAGE_CHECK(m_process, frameInfo.frameID);
auto* frame = m_process->webFrame(*frameInfo.frameID);
MESSAGE_CHECK(m_process, frame);
auto request = m_geolocationPermissionRequestManager.createRequest(geolocationID);
Function<void(bool)> completionHandler = [request = WTFMove(request)](bool allowed) {
if (allowed)
request->allow();
else
request->deny();
};
// FIXME: Once iOS migrates to the new WKUIDelegate SPI, clean this up
// and make it one UIClient call that calls the completionHandler with false
// if there is no delegate instead of returning the completionHandler
// for other code paths to try.
m_uiClient->decidePolicyForGeolocationPermissionRequest(*this, *frame, frameInfo, completionHandler);
#if PLATFORM(IOS_FAMILY)
if (completionHandler)
pageClient().decidePolicyForGeolocationPermissionRequest(*frame, frameInfo, completionHandler);
#endif
if (completionHandler)
completionHandler(false);
}
void WebPageProxy::revokeGeolocationAuthorizationToken(const String& authorizationToken)
{
m_geolocationPermissionRequestManager.revokeAuthorizationToken(authorizationToken);
}
void WebPageProxy::requestPermission(const ClientOrigin&, const PermissionDescriptor&, CompletionHandler<void(PermissionState)>&& completionHandler)
{
// FIXME: Show a prompt for user input.
completionHandler(PermissionState::Granted);
}
#if ENABLE(MEDIA_STREAM)
UserMediaPermissionRequestManagerProxy& WebPageProxy::userMediaPermissionRequestManager()
{
if (m_userMediaPermissionRequestManager)
return *m_userMediaPermissionRequestManager;
m_userMediaPermissionRequestManager = makeUnique<UserMediaPermissionRequestManagerProxy>(*this);
return *m_userMediaPermissionRequestManager;
}
void WebPageProxy::setMockCaptureDevicesEnabledOverride(std::optional<bool> enabled)
{
userMediaPermissionRequestManager().setMockCaptureDevicesEnabledOverride(enabled);
}
void WebPageProxy::willStartCapture(const UserMediaPermissionRequestProxy& request, CompletionHandler<void()>&& callback)
{
#if ENABLE(GPU_PROCESS)
if (!preferences().captureVideoInGPUProcessEnabled() && !preferences().captureAudioInGPUProcessEnabled())
return callback();
auto& gpuProcess = process().processPool().ensureGPUProcess();
gpuProcess.updateCaptureAccess(request.requiresAudioCapture(), request.requiresVideoCapture(), request.requiresDisplayCapture(), m_process->coreProcessIdentifier(), WTFMove(callback));
gpuProcess.updateCaptureOrigin(request.topLevelDocumentSecurityOrigin().data(), m_process->coreProcessIdentifier());
#if PLATFORM(IOS_FAMILY)
gpuProcess.setOrientationForMediaCapture(m_deviceOrientation);
#endif
#else
callback();
#endif
}
#endif
void WebPageProxy::requestUserMediaPermissionForFrame(UserMediaRequestIdentifier userMediaID, FrameIdentifier frameID, const WebCore::SecurityOriginData& userMediaDocumentOriginData, const WebCore::SecurityOriginData& topLevelDocumentOriginData, WebCore::MediaStreamRequest&& request)
{
#if ENABLE(MEDIA_STREAM)
MESSAGE_CHECK(m_process, m_process->webFrame(frameID));
#if PLATFORM(MAC)
CoreAudioCaptureDeviceManager::singleton().setFilterTapEnabledDevices(!preferences().captureAudioInGPUProcessEnabled());
#endif
userMediaPermissionRequestManager().requestUserMediaPermissionForFrame(userMediaID, frameID, userMediaDocumentOriginData.securityOrigin(), topLevelDocumentOriginData.securityOrigin(), WTFMove(request));
#else
UNUSED_PARAM(userMediaID);
UNUSED_PARAM(frameID);
UNUSED_PARAM(userMediaDocumentOriginData);
UNUSED_PARAM(topLevelDocumentOriginData);
UNUSED_PARAM(request);
#endif
}
void WebPageProxy::enumerateMediaDevicesForFrame(FrameIdentifier frameID, const WebCore::SecurityOriginData& userMediaDocumentOriginData, const WebCore::SecurityOriginData& topLevelDocumentOriginData, CompletionHandler<void(const Vector<CaptureDevice>&, const String&)>&& completionHandler)
{
#if ENABLE(MEDIA_STREAM)
WebFrameProxy* frame = m_process->webFrame(frameID);
MESSAGE_CHECK(m_process, frame);
userMediaPermissionRequestManager().enumerateMediaDevicesForFrame(frameID, userMediaDocumentOriginData.securityOrigin(), topLevelDocumentOriginData.securityOrigin(), WTFMove(completionHandler));
#else
UNUSED_PARAM(frameID);
UNUSED_PARAM(userMediaDocumentOriginData);
UNUSED_PARAM(topLevelDocumentOriginData);
UNUSED_PARAM(completionHandler);
#endif
}
void WebPageProxy::syncIfMockDevicesEnabledChanged()
{
#if ENABLE(MEDIA_STREAM)
userMediaPermissionRequestManager().syncWithWebCorePrefs();
#endif
}
void WebPageProxy::beginMonitoringCaptureDevices()
{
#if ENABLE(MEDIA_STREAM)
userMediaPermissionRequestManager().syncWithWebCorePrefs();
UserMediaProcessManager::singleton().beginMonitoringCaptureDevices();
#endif
}
void WebPageProxy::clearUserMediaState()
{
#if ENABLE(MEDIA_STREAM)
if (m_userMediaPermissionRequestManager)
m_userMediaPermissionRequestManager->clearCachedState();
#endif
}
void WebPageProxy::requestMediaKeySystemPermissionForFrame(MediaKeySystemRequestIdentifier mediaKeySystemID, FrameIdentifier frameID, const WebCore::SecurityOriginData& topLevelDocumentOriginData, const String& keySystem)
{
#if ENABLE(ENCRYPTED_MEDIA)
MESSAGE_CHECK(m_process, m_process->webFrame(frameID));
auto origin = API::SecurityOrigin::create(topLevelDocumentOriginData.securityOrigin());
auto request = mediaKeySystemPermissionRequestManager().createRequestForFrame(mediaKeySystemID, frameID, topLevelDocumentOriginData.securityOrigin(), keySystem);
m_uiClient->decidePolicyForMediaKeySystemPermissionRequest(*this, origin, keySystem, [request = WTFMove(request)](bool allowed) {
if (allowed)
request->allow();
else
request->deny();
});
#else
UNUSED_PARAM(mediaKeySystemID);
UNUSED_PARAM(frameID);
UNUSED_PARAM(topLevelDocumentOriginData);
UNUSED_PARAM(keySystem);
#endif
}
#if ENABLE(DEVICE_ORIENTATION)
void WebPageProxy::shouldAllowDeviceOrientationAndMotionAccess(FrameIdentifier frameID, FrameInfoData&& frameInfo, bool mayPrompt, CompletionHandler<void(DeviceOrientationOrMotionPermissionState)>&& completionHandler)
{
WebFrameProxy* frame = m_process->webFrame(frameID);
MESSAGE_CHECK(m_process, frame);
websiteDataStore().deviceOrientationAndMotionAccessController().shouldAllowAccess(*this, *frame, WTFMove(frameInfo), mayPrompt, WTFMove(completionHandler));
}
#endif
#if ENABLE(IMAGE_ANALYSIS)
void WebPageProxy::requestTextRecognition(const URL& imageURL, const ShareableBitmap::Handle& imageData, const String& identifier, CompletionHandler<void(TextRecognitionResult&&)>&& completionHandler)
{
pageClient().requestTextRecognition(imageURL, imageData, identifier, WTFMove(completionHandler));
}
void WebPageProxy::computeHasImageAnalysisResults(const URL& imageURL, ShareableBitmap& imageBitmap, ImageAnalysisType type, CompletionHandler<void(bool)>&& completion)
{
pageClient().computeHasImageAnalysisResults(imageURL, imageBitmap, type, WTFMove(completion));
}
void WebPageProxy::updateWithTextRecognitionResult(TextRecognitionResult&& results, const ElementContext& context, const FloatPoint& location, CompletionHandler<void(TextRecognitionUpdateResult)>&& completionHandler)
{
if (!hasRunningProcess()) {
completionHandler(TextRecognitionUpdateResult::NoText);
return;
}
sendWithAsyncReply(Messages::WebPage::UpdateWithTextRecognitionResult(WTFMove(results), context, location), WTFMove(completionHandler));
}
void WebPageProxy::startImageAnalysis(const String& identifier)
{
if (hasRunningProcess())
send(Messages::WebPage::StartImageAnalysis(identifier));
}
#endif // ENABLE(IMAGE_ANALYSIS)
#if ENABLE(ENCRYPTED_MEDIA)
MediaKeySystemPermissionRequestManagerProxy& WebPageProxy::mediaKeySystemPermissionRequestManager()
{
if (m_mediaKeySystemPermissionRequestManager)
return *m_mediaKeySystemPermissionRequestManager;
m_mediaKeySystemPermissionRequestManager = makeUnique<MediaKeySystemPermissionRequestManagerProxy>(*this);
return *m_mediaKeySystemPermissionRequestManager;
}
#endif
#if ENABLE(MEDIA_CONTROLS_CONTEXT_MENUS) && USE(UICONTEXTMENU)
void WebPageProxy::showMediaControlsContextMenu(FloatRect&& targetFrame, Vector<MediaControlsContextMenuItem>&& items, CompletionHandler<void(MediaControlsContextMenuItem::ID)>&& completionHandler)
{
pageClient().showMediaControlsContextMenu(WTFMove(targetFrame), WTFMove(items), WTFMove(completionHandler));
}
#endif // ENABLE(MEDIA_CONTROLS_CONTEXT_MENUS) && USE(UICONTEXTMENU)
void WebPageProxy::requestNotificationPermission(const String& originString, CompletionHandler<void(bool allowed)>&& completionHandler)
{
auto origin = API::SecurityOrigin::createFromString(originString);
m_uiClient->decidePolicyForNotificationPermissionRequest(*this, origin.get(), WTFMove(completionHandler));
}
void WebPageProxy::showNotification(const WebCore::NotificationData& notificationData)
{
m_process->processPool().supplement<WebNotificationManagerProxy>()->show(this, notificationData);
}
void WebPageProxy::cancelNotification(const UUID& notificationID)
{
m_process->processPool().supplement<WebNotificationManagerProxy>()->cancel(this, notificationID);
}
void WebPageProxy::clearNotifications(const Vector<UUID>& notificationIDs)
{
m_process->processPool().supplement<WebNotificationManagerProxy>()->clearNotifications(this, notificationIDs);
}
void WebPageProxy::didDestroyNotification(const UUID& notificationID)
{
m_process->processPool().supplement<WebNotificationManagerProxy>()->didDestroyNotification(this, notificationID);
}
float WebPageProxy::headerHeightForPrinting(WebFrameProxy& frame)
{
if (frame.isDisplayingPDFDocument())
return 0;
return m_uiClient->headerHeight(*this, frame);
}
float WebPageProxy::footerHeightForPrinting(WebFrameProxy& frame)
{
if (frame.isDisplayingPDFDocument())
return 0;
return m_uiClient->footerHeight(*this, frame);
}
void WebPageProxy::drawHeaderForPrinting(WebFrameProxy& frame, FloatRect&& rect)
{
if (frame.isDisplayingPDFDocument())
return;
m_uiClient->drawHeader(*this, frame, WTFMove(rect));
}
void WebPageProxy::drawFooterForPrinting(WebFrameProxy& frame, FloatRect&& rect)
{
if (frame.isDisplayingPDFDocument())
return;
m_uiClient->drawFooter(*this, frame, WTFMove(rect));
}
void WebPageProxy::runModal()
{
// Since runModal() can (and probably will) spin a nested run loop we need to turn off the responsiveness timer.
m_process->stopResponsivenessTimer();
// Our Connection's run loop might have more messages waiting to be handled after this RunModal message.
// To make sure they are handled inside of the nested modal run loop we must first signal the Connection's
// run loop so we're guaranteed that it has a chance to wake up.
// See http://webkit.org/b/89590 for more discussion.
m_process->connection()->wakeUpRunLoop();
m_uiClient->runModal(*this);
}
void WebPageProxy::notifyScrollerThumbIsVisibleInRect(const IntRect& scrollerThumb)
{
m_visibleScrollerThumbRect = scrollerThumb;
}
void WebPageProxy::recommendedScrollbarStyleDidChange(int32_t newStyle)
{
#if USE(APPKIT)
pageClient().recommendedScrollbarStyleDidChange(static_cast<WebCore::ScrollbarStyle>(newStyle));
#else
UNUSED_PARAM(newStyle);
#endif
}
void WebPageProxy::didChangeScrollbarsForMainFrame(bool hasHorizontalScrollbar, bool hasVerticalScrollbar)
{
m_mainFrameHasHorizontalScrollbar = hasHorizontalScrollbar;
m_mainFrameHasVerticalScrollbar = hasVerticalScrollbar;
}
void WebPageProxy::didChangeScrollOffsetPinningForMainFrame(RectEdges<bool> pinnedState)
{
pageClient().pinnedStateWillChange();
m_mainFramePinnedState = pinnedState;
pageClient().pinnedStateDidChange();
m_uiClient->pinnedStateDidChange(*this);
}
void WebPageProxy::didChangePageCount(unsigned pageCount)
{
m_pageCount = pageCount;
}
void WebPageProxy::themeColorChanged(const Color& themeColor)
{
if (m_themeColor == themeColor)
return;
pageClient().themeColorWillChange();
m_themeColor = themeColor;
pageClient().themeColorDidChange();
}
void WebPageProxy::pageExtendedBackgroundColorDidChange(const Color& newPageExtendedBackgroundColor)
{
if (m_pageExtendedBackgroundColor == newPageExtendedBackgroundColor)
return;
auto oldUnderPageBackgroundColor = underPageBackgroundColor();
auto oldPageExtendedBackgroundColor = std::exchange(m_pageExtendedBackgroundColor, newPageExtendedBackgroundColor);
bool changesUnderPageBackgroundColor = !equalIgnoringSemanticColor(oldUnderPageBackgroundColor, underPageBackgroundColor());
m_pageExtendedBackgroundColor = WTFMove(oldPageExtendedBackgroundColor);
if (changesUnderPageBackgroundColor)
pageClient().underPageBackgroundColorWillChange();
pageClient().pageExtendedBackgroundColorWillChange();
m_pageExtendedBackgroundColor = newPageExtendedBackgroundColor;
if (changesUnderPageBackgroundColor)
pageClient().underPageBackgroundColorDidChange();
pageClient().pageExtendedBackgroundColorDidChange();
}
void WebPageProxy::sampledPageTopColorChanged(const Color& sampledPageTopColor)
{
if (m_sampledPageTopColor == sampledPageTopColor)
return;
pageClient().sampledPageTopColorWillChange();
m_sampledPageTopColor = sampledPageTopColor;
pageClient().sampledPageTopColorDidChange();
}
#if !PLATFORM(COCOA)
Color WebPageProxy::platformUnderPageBackgroundColor() const
{
return Color::transparentBlack;
}
#endif // !PLATFORM(COCOA)
bool WebPageProxy::willHandleHorizontalScrollEvents() const
{
return !m_canShortCircuitHorizontalWheelEvents;
}
void WebPageProxy::updateWebsitePolicies(WebsitePoliciesData&& websitePolicies)
{
send(Messages::WebPage::UpdateWebsitePolicies(websitePolicies));
}
void WebPageProxy::notifyUserScripts()
{
m_userScriptsNotified = true;
send(Messages::WebPage::NotifyUserScripts());
}
bool WebPageProxy::userScriptsNeedNotification() const
{
if (!m_configuration->userScriptsShouldWaitUntilNotification())
return false;
return !m_userScriptsNotified;
}
void WebPageProxy::didFinishLoadingDataForCustomContentProvider(const String& suggestedFilename, const IPC::DataReference& dataReference)
{
pageClient().didFinishLoadingDataForCustomContentProvider(ResourceResponseBase::sanitizeSuggestedFilename(suggestedFilename), dataReference);
}
void WebPageProxy::backForwardRemovedItem(const BackForwardItemIdentifier& itemID)
{
send(Messages::WebPage::DidRemoveBackForwardItem(itemID));
}
void WebPageProxy::setCanRunModal(bool canRunModal)
{
// It's only possible to change the state for a WebPage which
// already qualifies for running modal child web pages, otherwise
// there's no other possibility than not allowing it.
m_canRunModal = m_uiClient->canRunModal() && canRunModal;
if (!hasRunningProcess())
return;
send(Messages::WebPage::SetCanRunModal(m_canRunModal));
}
bool WebPageProxy::canRunModal()
{
return hasRunningProcess() ? m_canRunModal : false;
}
void WebPageProxy::beginPrinting(WebFrameProxy* frame, const PrintInfo& printInfo)
{
if (m_isInPrintingMode)
return;
m_isInPrintingMode = true;
send(Messages::WebPage::BeginPrinting(frame->frameID(), printInfo), printingSendOptions(m_isPerformingDOMPrintOperation));
}
void WebPageProxy::endPrinting()
{
if (!m_isInPrintingMode)
return;
m_isInPrintingMode = false;
send(Messages::WebPage::EndPrinting(), printingSendOptions(m_isPerformingDOMPrintOperation));
}
uint64_t WebPageProxy::computePagesForPrinting(WebFrameProxy* frame, const PrintInfo& printInfo, CompletionHandler<void(const Vector<WebCore::IntRect>&, double, const WebCore::FloatBoxExtent&)>&& callback)
{
m_isInPrintingMode = true;
return sendWithAsyncReply(Messages::WebPage::ComputePagesForPrinting(frame->frameID(), printInfo), WTFMove(callback), printingSendOptions(m_isPerformingDOMPrintOperation));
}
#if PLATFORM(COCOA)
uint64_t WebPageProxy::drawRectToImage(WebFrameProxy* frame, const PrintInfo& printInfo, const IntRect& rect, const WebCore::IntSize& imageSize, CompletionHandler<void(const WebKit::ShareableBitmap::Handle&)>&& callback)
{
return sendWithAsyncReply(Messages::WebPage::DrawRectToImage(frame->frameID(), printInfo, rect, imageSize), WTFMove(callback), printingSendOptions(m_isPerformingDOMPrintOperation));
}
uint64_t WebPageProxy::drawPagesToPDF(WebFrameProxy* frame, const PrintInfo& printInfo, uint32_t first, uint32_t count, CompletionHandler<void(API::Data*)>&& callback)
{
return sendWithAsyncReply(Messages::WebPage::DrawPagesToPDF(frame->frameID(), printInfo, first, count), toAPIDataCallback(WTFMove(callback)), printingSendOptions(m_isPerformingDOMPrintOperation));
}
#elif PLATFORM(GTK)
void WebPageProxy::drawPagesForPrinting(WebFrameProxy* frame, const PrintInfo& printInfo, CompletionHandler<void(API::Error*)>&& callback)
{
auto callbackWrapper = [callback = WTFMove(callback)] (const WebCore::ResourceError& error) mutable {
if (error.isNull())
return callback(nullptr);
callback(API::Error::create(error).ptr());
};
m_isInPrintingMode = true;
sendWithAsyncReply(Messages::WebPage::DrawPagesForPrinting(frame->frameID(), printInfo), WTFMove(callbackWrapper), printingSendOptions(m_isPerformingDOMPrintOperation));
}
#endif
#if PLATFORM(COCOA)
void WebPageProxy::drawToPDF(FrameIdentifier frameID, const std::optional<FloatRect>& rect, CompletionHandler<void(const IPC::SharedBufferCopy&)>&& callback)
{
if (!hasRunningProcess()) {
callback({ });
return;
}
sendWithAsyncReply(Messages::WebPage::DrawToPDF(frameID, rect), WTFMove(callback));
}
#endif // PLATFORM(COCOA)
void WebPageProxy::getPDFFirstPageSize(WebCore::FrameIdentifier frameID, CompletionHandler<void(WebCore::FloatSize)>&& completionHandler)
{
sendWithAsyncReply(Messages::WebPage::GetPDFFirstPageSize(frameID), WTFMove(completionHandler));
}
void WebPageProxy::updateBackingStoreDiscardableState()
{
ASSERT(hasRunningProcess());
if (!m_drawingArea)
return;
bool isDiscardable;
if (!m_process->isResponsive())
isDiscardable = false;
else
isDiscardable = !pageClient().isViewWindowActive() || !isViewVisible();
m_drawingArea->setBackingStoreIsDiscardable(isDiscardable);
}
void WebPageProxy::saveDataToFileInDownloadsFolder(String&& suggestedFilename, String&& mimeType, URL&& originatingURLString, API::Data& data)
{
m_uiClient->saveDataToFileInDownloadsFolder(this, ResourceResponseBase::sanitizeSuggestedFilename(suggestedFilename), mimeType, originatingURLString, data);
}
void WebPageProxy::savePDFToFileInDownloadsFolder(String&& suggestedFilename, URL&& originatingURL, const IPC::DataReference& dataReference)
{
String sanitizedFilename = ResourceResponseBase::sanitizeSuggestedFilename(suggestedFilename);
if (!sanitizedFilename.endsWithIgnoringASCIICase(".pdf"))
return;
saveDataToFileInDownloadsFolder(WTFMove(sanitizedFilename), "application/pdf"_s, WTFMove(originatingURL),
API::Data::create(dataReference.data(), dataReference.size()).get());
}
void WebPageProxy::setMinimumSizeForAutoLayout(const IntSize& size)
{
if (m_minimumSizeForAutoLayout == size)
return;
m_minimumSizeForAutoLayout = size;
if (!hasRunningProcess())
return;
send(Messages::WebPage::SetMinimumSizeForAutoLayout(size));
m_drawingArea->minimumSizeForAutoLayoutDidChange();
#if USE(APPKIT)
if (m_minimumSizeForAutoLayout.width() <= 0)
didChangeIntrinsicContentSize(IntSize(-1, -1));
#endif
}
void WebPageProxy::setSizeToContentAutoSizeMaximumSize(const IntSize& size)
{
if (m_sizeToContentAutoSizeMaximumSize == size)
return;
m_sizeToContentAutoSizeMaximumSize = size;
if (!hasRunningProcess())
return;
send(Messages::WebPage::SetSizeToContentAutoSizeMaximumSize(size));
m_drawingArea->sizeToContentAutoSizeMaximumSizeDidChange();
#if USE(APPKIT)
if (m_sizeToContentAutoSizeMaximumSize.width() <= 0)
didChangeIntrinsicContentSize(IntSize(-1, -1));
#endif
}
void WebPageProxy::setAutoSizingShouldExpandToViewHeight(bool shouldExpand)
{
if (m_autoSizingShouldExpandToViewHeight == shouldExpand)
return;
m_autoSizingShouldExpandToViewHeight = shouldExpand;
if (!hasRunningProcess())
return;
send(Messages::WebPage::SetAutoSizingShouldExpandToViewHeight(shouldExpand));
}
void WebPageProxy::setViewportSizeForCSSViewportUnits(const FloatSize& viewportSize)
{
if (m_viewportSizeForCSSViewportUnits && *m_viewportSizeForCSSViewportUnits == viewportSize)
return;
m_viewportSizeForCSSViewportUnits = viewportSize;
if (!hasRunningProcess())
return;
send(Messages::WebPage::SetViewportSizeForCSSViewportUnits(viewportSize));
}
#if USE(AUTOMATIC_TEXT_REPLACEMENT)
void WebPageProxy::toggleSmartInsertDelete()
{
if (TextChecker::isTestingMode())
TextChecker::setSmartInsertDeleteEnabled(!TextChecker::isSmartInsertDeleteEnabled());
}
void WebPageProxy::toggleAutomaticQuoteSubstitution()
{
if (TextChecker::isTestingMode())
TextChecker::setAutomaticQuoteSubstitutionEnabled(!TextChecker::state().isAutomaticQuoteSubstitutionEnabled);
}
void WebPageProxy::toggleAutomaticLinkDetection()
{
if (TextChecker::isTestingMode())
TextChecker::setAutomaticLinkDetectionEnabled(!TextChecker::state().isAutomaticLinkDetectionEnabled);
}
void WebPageProxy::toggleAutomaticDashSubstitution()
{
if (TextChecker::isTestingMode())
TextChecker::setAutomaticDashSubstitutionEnabled(!TextChecker::state().isAutomaticDashSubstitutionEnabled);
}
void WebPageProxy::toggleAutomaticTextReplacement()
{
if (TextChecker::isTestingMode())
TextChecker::setAutomaticTextReplacementEnabled(!TextChecker::state().isAutomaticTextReplacementEnabled);
}
#endif
#if USE(DICTATION_ALTERNATIVES)
void WebPageProxy::showDictationAlternativeUI(const WebCore::FloatRect& boundingBoxOfDictatedText, WebCore::DictationContext dictationContext)
{
pageClient().showDictationAlternativeUI(boundingBoxOfDictatedText, dictationContext);
}
void WebPageProxy::removeDictationAlternatives(WebCore::DictationContext dictationContext)
{
pageClient().removeDictationAlternatives(dictationContext);
}
void WebPageProxy::dictationAlternatives(WebCore::DictationContext dictationContext, CompletionHandler<void(Vector<String>&&)>&& completionHandler)
{
completionHandler(pageClient().dictationAlternatives(dictationContext));
}
#endif
#if PLATFORM(MAC)
void WebPageProxy::substitutionsPanelIsShowing(CompletionHandler<void(bool)>&& completionHandler)
{
completionHandler(TextChecker::substitutionsPanelIsShowing());
}
void WebPageProxy::showCorrectionPanel(int32_t panelType, const FloatRect& boundingBoxOfReplacedString, const String& replacedString, const String& replacementString, const Vector<String>& alternativeReplacementStrings)
{
// FIXME: Make AlternativeTextType an enum class with EnumTraits and serialize it instead of casting to/from an int32_t.
pageClient().showCorrectionPanel((AlternativeTextType)panelType, boundingBoxOfReplacedString, replacedString, replacementString, alternativeReplacementStrings);
}
void WebPageProxy::dismissCorrectionPanel(int32_t reason)
{
// FIXME: Make ReasonForDismissingAlternativeText an enum class with EnumTraits and serialize it instead of casting to/from an int32_t.
pageClient().dismissCorrectionPanel((ReasonForDismissingAlternativeText)reason);
}
void WebPageProxy::dismissCorrectionPanelSoon(int32_t reason, CompletionHandler<void(String)>&& completionHandler)
{
// FIXME: Make ReasonForDismissingAlternativeText an enum class with EnumTraits and serialize it instead of casting to/from an int32_t.
completionHandler(pageClient().dismissCorrectionPanelSoon((ReasonForDismissingAlternativeText)reason));
}
void WebPageProxy::recordAutocorrectionResponse(int32_t response, const String& replacedString, const String& replacementString)
{
// FIXME: Make AutocorrectionResponse an enum class with EnumTraits and serialize it instead of casting to/from an int32_t.
pageClient().recordAutocorrectionResponse(static_cast<AutocorrectionResponse>(response), replacedString, replacementString);
}
void WebPageProxy::handleAlternativeTextUIResult(const String& result)
{
if (!isClosed())
send(Messages::WebPage::HandleAlternativeTextUIResult(result));
}
void WebPageProxy::setEditableElementIsFocused(bool editableElementIsFocused)
{
pageClient().setEditableElementIsFocused(editableElementIsFocused);
}
#endif // PLATFORM(MAC)
#if PLATFORM(COCOA) || PLATFORM(GTK)
RefPtr<ViewSnapshot> WebPageProxy::takeViewSnapshot(std::optional<WebCore::IntRect>&& clipRect)
{
return pageClient().takeViewSnapshot(WTFMove(clipRect));
}
#endif
#if PLATFORM(GTK) || PLATFORM(WPE)
void WebPageProxy::cancelComposition(const String& compositionString)
{
if (!hasRunningProcess())
return;
// Remove any pending composition key event.
if (m_keyEventQueue.size() > 1) {
auto event = m_keyEventQueue.takeFirst();
m_keyEventQueue.removeAllMatching([](const auto& event) {
return event.handledByInputMethod();
});
m_keyEventQueue.prepend(WTFMove(event));
}
send(Messages::WebPage::CancelComposition(compositionString));
}
void WebPageProxy::deleteSurrounding(int64_t offset, unsigned characterCount)
{
if (!hasRunningProcess())
return;
send(Messages::WebPage::DeleteSurrounding(offset, characterCount));
}
#endif // PLATFORM(GTK) || PLATFORM(WPE)
void WebPageProxy::setScrollPinningBehavior(ScrollPinningBehavior pinning)
{
if (m_scrollPinningBehavior == pinning)
return;
m_scrollPinningBehavior = pinning;
if (hasRunningProcess())
send(Messages::WebPage::SetScrollPinningBehavior(pinning));
}
void WebPageProxy::setOverlayScrollbarStyle(std::optional<WebCore::ScrollbarOverlayStyle> scrollbarStyle)
{
if (!m_scrollbarOverlayStyle && !scrollbarStyle)
return;
if ((m_scrollbarOverlayStyle && scrollbarStyle) && m_scrollbarOverlayStyle.value() == scrollbarStyle.value())
return;
m_scrollbarOverlayStyle = scrollbarStyle;
std::optional<uint32_t> scrollbarStyleForMessage;
if (scrollbarStyle)
scrollbarStyleForMessage = static_cast<ScrollbarOverlayStyle>(scrollbarStyle.value());
if (hasRunningProcess())
send(Messages::WebPage::SetScrollbarOverlayStyle(scrollbarStyleForMessage), m_webPageID);
}
#if ENABLE(WEB_CRYPTO)
void WebPageProxy::wrapCryptoKey(const Vector<uint8_t>& key, CompletionHandler<void(bool, Vector<uint8_t>&&)>&& completionHandler)
{
PageClientProtector protector(pageClient());
Vector<uint8_t> masterKey;
if (auto keyData = m_navigationClient->webCryptoMasterKey(*this))
masterKey = Vector(keyData->dataReference());
Vector<uint8_t> wrappedKey;
bool succeeded = wrapSerializedCryptoKey(masterKey, key, wrappedKey);
completionHandler(succeeded, WTFMove(wrappedKey));
}
void WebPageProxy::unwrapCryptoKey(const Vector<uint8_t>& wrappedKey, CompletionHandler<void(bool, Vector<uint8_t>&&)>&& completionHandler)
{
PageClientProtector protector(pageClient());
Vector<uint8_t> masterKey;
if (auto keyData = m_navigationClient->webCryptoMasterKey(*this))
masterKey = Vector(keyData->dataReference());
Vector<uint8_t> key;
bool succeeded = unwrapSerializedCryptoKey(masterKey, wrappedKey, key);
completionHandler(succeeded, WTFMove(key));
}
#endif
void WebPageProxy::signedPublicKeyAndChallengeString(unsigned keySizeIndex, const String& challengeString, const URL& url, CompletionHandler<void(String)>&& completionHandler)
{
PageClientProtector protector(pageClient());
if (auto apiString = m_navigationClient->signedPublicKeyAndChallengeString(*this, keySizeIndex, API::String::create(challengeString), url))
return completionHandler(apiString->string());
completionHandler({ });
}
void WebPageProxy::addMIMETypeWithCustomContentProvider(const String& mimeType)
{
send(Messages::WebPage::AddMIMETypeWithCustomContentProvider(mimeType));
}
void WebPageProxy::changeFontAttributes(WebCore::FontAttributeChanges&& changes)
{
if (!hasRunningProcess())
return;
send(Messages::WebPage::ChangeFontAttributes(WTFMove(changes)));
}
void WebPageProxy::changeFont(WebCore::FontChanges&& changes)
{
if (!hasRunningProcess())
return;
send(Messages::WebPage::ChangeFont(WTFMove(changes)));
}
// FIXME: Move these functions to WebPageProxyCocoa.mm.
#if PLATFORM(COCOA)
void WebPageProxy::setTextAsync(const String& text)
{
if (hasRunningProcess())
send(Messages::WebPage::SetTextAsync(text));
}
void WebPageProxy::insertTextAsync(const String& text, const EditingRange& replacementRange, InsertTextOptions&& options)
{
if (!hasRunningProcess())
return;
send(Messages::WebPage::InsertTextAsync(text, replacementRange, WTFMove(options)));
}
void WebPageProxy::hasMarkedText(CompletionHandler<void(bool)>&& callback)
{
if (!hasRunningProcess()) {
callback(false);
return;
}
sendWithAsyncReply(Messages::WebPage::HasMarkedText(), WTFMove(callback));
}
void WebPageProxy::getMarkedRangeAsync(CompletionHandler<void(const EditingRange&)>&& callbackFunction)
{
if (!hasRunningProcess()) {
callbackFunction(EditingRange());
return;
}
sendWithAsyncReply(Messages::WebPage::GetMarkedRangeAsync(), WTFMove(callbackFunction));
}
void WebPageProxy::getSelectedRangeAsync(CompletionHandler<void(const EditingRange&)>&& callbackFunction)
{
if (!hasRunningProcess()) {
callbackFunction(EditingRange());
return;
}
sendWithAsyncReply(Messages::WebPage::GetSelectedRangeAsync(), WTFMove(callbackFunction));
}
void WebPageProxy::characterIndexForPointAsync(const WebCore::IntPoint& point, CompletionHandler<void(uint64_t)>&& callbackFunction)
{
sendWithAsyncReply(Messages::WebPage::CharacterIndexForPointAsync(point), WTFMove(callbackFunction));
}
void WebPageProxy::firstRectForCharacterRangeAsync(const EditingRange& range, CompletionHandler<void(const WebCore::IntRect&, const EditingRange&)>&& callbackFunction)
{
if (!hasRunningProcess())
return callbackFunction({ }, { });
sendWithAsyncReply(Messages::WebPage::FirstRectForCharacterRangeAsync(range), WTFMove(callbackFunction));
}
void WebPageProxy::setCompositionAsync(const String& text, const Vector<CompositionUnderline>& underlines, const Vector<CompositionHighlight>& highlights, const EditingRange& selectionRange, const EditingRange& replacementRange)
{
if (!hasRunningProcess()) {
// If this fails, we should call -discardMarkedText on input context to notify the input method.
// This will happen naturally later, as part of reloading the page.
return;
}
send(Messages::WebPage::SetCompositionAsync(text, underlines, highlights, selectionRange, replacementRange));
}
void WebPageProxy::confirmCompositionAsync()
{
if (!hasRunningProcess())
return;
send(Messages::WebPage::ConfirmCompositionAsync());
}
void WebPageProxy::setScrollPerformanceDataCollectionEnabled(bool enabled)
{
if (enabled == m_scrollPerformanceDataCollectionEnabled)
return;
m_scrollPerformanceDataCollectionEnabled = enabled;
if (m_scrollPerformanceDataCollectionEnabled && !m_scrollingPerformanceData)
m_scrollingPerformanceData = makeUnique<RemoteLayerTreeScrollingPerformanceData>(downcast<RemoteLayerTreeDrawingAreaProxy>(*m_drawingArea));
else if (!m_scrollPerformanceDataCollectionEnabled)
m_scrollingPerformanceData = nullptr;
}
#endif
void WebPageProxy::takeSnapshot(IntRect rect, IntSize bitmapSize, SnapshotOptions options, CompletionHandler<void(const ShareableBitmap::Handle&)>&& callback)
{
sendWithAsyncReply(Messages::WebPage::TakeSnapshot(rect, bitmapSize, options), WTFMove(callback));
}
void WebPageProxy::navigationGestureDidBegin()
{
PageClientProtector protector(pageClient());
m_isShowingNavigationGestureSnapshot = true;
pageClient().navigationGestureDidBegin();
m_navigationClient->didBeginNavigationGesture(*this);
}
void WebPageProxy::navigationGestureWillEnd(bool willNavigate, WebBackForwardListItem& item)
{
PageClientProtector protector(pageClient());
if (willNavigate) {
m_isLayerTreeFrozenDueToSwipeAnimation = true;
send(Messages::WebPage::FreezeLayerTreeDueToSwipeAnimation());
}
pageClient().navigationGestureWillEnd(willNavigate, item);
m_navigationClient->willEndNavigationGesture(*this, willNavigate, item);
}
void WebPageProxy::navigationGestureDidEnd(bool willNavigate, WebBackForwardListItem& item)
{
PageClientProtector protector(pageClient());
pageClient().navigationGestureDidEnd(willNavigate, item);
m_navigationClient->didEndNavigationGesture(*this, willNavigate, item);
if (m_isLayerTreeFrozenDueToSwipeAnimation) {
m_isLayerTreeFrozenDueToSwipeAnimation = false;
send(Messages::WebPage::UnfreezeLayerTreeDueToSwipeAnimation());
if (m_provisionalPage)
m_provisionalPage->unfreezeLayerTreeDueToSwipeAnimation();
}
}
void WebPageProxy::navigationGestureDidEnd()
{
PageClientProtector protector(pageClient());
pageClient().navigationGestureDidEnd();
}
void WebPageProxy::willRecordNavigationSnapshot(WebBackForwardListItem& item)
{
PageClientProtector protector(pageClient());
pageClient().willRecordNavigationSnapshot(item);
}
void WebPageProxy::navigationGestureSnapshotWasRemoved()
{
m_isShowingNavigationGestureSnapshot = false;
// The ViewGestureController may call this method on a WebPageProxy whose view has been destroyed. In such case,
// we need to return early as the pageClient will not be valid below.
if (m_isClosed)
return;
pageClient().didRemoveNavigationGestureSnapshot();
m_navigationClient->didRemoveNavigationGestureSnapshot(*this);
}
void WebPageProxy::isPlayingMediaDidChange(MediaProducerMediaStateFlags newState)
{
#if PLATFORM(IOS_FAMILY)
if (!m_process->throttler().shouldBeRunnable())
return;
#endif
if (!m_isClosed)
updatePlayingMediaDidChange(newState, CanDelayNotification::Yes);
}
void WebPageProxy::updatePlayingMediaDidChange(MediaProducerMediaStateFlags newState, CanDelayNotification canDelayNotification)
{
#if ENABLE(MEDIA_STREAM)
auto updateMediaCaptureStateImmediatelyIfNeeded = [&] {
if (canDelayNotification == CanDelayNotification::No && m_updateReportedMediaCaptureStateTimer.isActive()) {
m_updateReportedMediaCaptureStateTimer.stop();
updateReportedMediaCaptureState();
}
};
#endif
if (newState == m_mediaState) {
#if ENABLE(MEDIA_STREAM)
updateMediaCaptureStateImmediatelyIfNeeded();
#endif
return;
}
#if PLATFORM(MACCATALYST)
// When the page starts playing media for the first time, make sure we register with
// the EndowmentStateTracker to get notifications when the application is no longer
// user-facing, so that we can appropriately suspend all media playback.
if (!m_isListeningForUserFacingStateChangeNotification) {
EndowmentStateTracker::singleton().addClient(*this);
m_isListeningForUserFacingStateChangeNotification = true;
}
#endif
#if ENABLE(MEDIA_STREAM)
WebCore::MediaProducerMediaStateFlags oldMediaCaptureState = m_mediaState & WebCore::MediaProducer::MediaCaptureMask;
WebCore::MediaProducerMediaStateFlags newMediaCaptureState = newState & WebCore::MediaProducer::MediaCaptureMask;
#endif
MediaProducerMediaStateFlags playingMediaMask { MediaProducerMediaState::IsPlayingAudio, MediaProducerMediaState::IsPlayingVideo };
MediaProducerMediaStateFlags oldState = m_mediaState;
bool playingAudioChanges = (oldState.contains(MediaProducerMediaState::IsPlayingAudio)) != (newState.contains(MediaProducerMediaState::IsPlayingAudio));
if (playingAudioChanges)
pageClient().isPlayingAudioWillChange();
m_mediaState = newState;
if (playingAudioChanges)
pageClient().isPlayingAudioDidChange();
#if ENABLE(MEDIA_STREAM)
if (oldMediaCaptureState != newMediaCaptureState) {
updateReportedMediaCaptureState();
ASSERT(m_userMediaPermissionRequestManager);
if (m_userMediaPermissionRequestManager)
m_userMediaPermissionRequestManager->captureStateChanged(oldMediaCaptureState, newMediaCaptureState);
}
updateMediaCaptureStateImmediatelyIfNeeded();
#endif
activityStateDidChange({ ActivityState::IsAudible, ActivityState::IsCapturingMedia });
playingMediaMask.add(WebCore::MediaProducer::MediaCaptureMask);
if ((oldState & playingMediaMask) != (m_mediaState & playingMediaMask))
m_uiClient->isPlayingMediaDidChange(*this);
if ((oldState.containsAny(MediaProducerMediaState::HasAudioOrVideo)) != (m_mediaState.containsAny(MediaProducerMediaState::HasAudioOrVideo)))
videoControlsManagerDidChange();
m_process->updateAudibleMediaAssertions();
}
void WebPageProxy::updateReportedMediaCaptureState()
{
auto activeCaptureState = m_mediaState & MediaProducer::MediaCaptureMask;
if (m_reportedMediaCaptureState == activeCaptureState)
return;
bool haveReportedCapture = m_reportedMediaCaptureState.containsAny(MediaProducer::MediaCaptureMask);
bool willReportCapture = !activeCaptureState.isEmpty();
if (haveReportedCapture && !willReportCapture && m_updateReportedMediaCaptureStateTimer.isActive())
return;
if (!haveReportedCapture && willReportCapture)
m_updateReportedMediaCaptureStateTimer.startOneShot(m_mediaCaptureReportingDelay);
WEBPAGEPROXY_RELEASE_LOG(WebRTC, "updateReportedMediaCaptureState: from %d to %d", m_reportedMediaCaptureState.toRaw(), activeCaptureState.toRaw());
bool microphoneCaptureChanged = (m_reportedMediaCaptureState & MediaProducer::AudioCaptureMask) != (activeCaptureState & MediaProducer::AudioCaptureMask);
bool cameraCaptureChanged = (m_reportedMediaCaptureState & MediaProducer::VideoCaptureMask) != (activeCaptureState & MediaProducer::VideoCaptureMask);
if (microphoneCaptureChanged)
pageClient().microphoneCaptureWillChange();
if (cameraCaptureChanged)
pageClient().cameraCaptureWillChange();
m_reportedMediaCaptureState = activeCaptureState;
m_uiClient->mediaCaptureStateDidChange(m_mediaState);
if (microphoneCaptureChanged)
pageClient().microphoneCaptureChanged();
if (cameraCaptureChanged)
pageClient().cameraCaptureChanged();
}
void WebPageProxy::videoControlsManagerDidChange()
{
pageClient().videoControlsManagerDidChange();
}
bool WebPageProxy::hasActiveVideoForControlsManager() const
{
#if ENABLE(VIDEO_PRESENTATION_MODE)
return m_playbackSessionManager && m_playbackSessionManager->controlsManagerInterface();
#else
return false;
#endif
}
void WebPageProxy::requestControlledElementID() const
{
#if ENABLE(VIDEO_PRESENTATION_MODE)
if (m_playbackSessionManager)
m_playbackSessionManager->requestControlledElementID();
#endif
}
void WebPageProxy::handleControlledElementIDResponse(const String& identifier) const
{
#if PLATFORM(MAC)
pageClient().handleControlledElementIDResponse(identifier);
#endif
}
bool WebPageProxy::isPlayingVideoInEnhancedFullscreen() const
{
#if ENABLE(VIDEO_PRESENTATION_MODE)
return m_videoFullscreenManager && m_videoFullscreenManager->isPlayingVideoInEnhancedFullscreen();
#else
return false;
#endif
}
void WebPageProxy::handleAutoplayEvent(WebCore::AutoplayEvent event, OptionSet<AutoplayEventFlags> flags)
{
m_uiClient->handleAutoplayEvent(*this, event, flags);
}
#if PLATFORM(MAC)
void WebPageProxy::performImmediateActionHitTestAtLocation(FloatPoint point)
{
send(Messages::WebPage::PerformImmediateActionHitTestAtLocation(point));
}
void WebPageProxy::immediateActionDidUpdate()
{
send(Messages::WebPage::ImmediateActionDidUpdate());
}
void WebPageProxy::immediateActionDidCancel()
{
send(Messages::WebPage::ImmediateActionDidCancel());
}
void WebPageProxy::immediateActionDidComplete()
{
send(Messages::WebPage::ImmediateActionDidComplete());
}
void WebPageProxy::didPerformImmediateActionHitTest(const WebHitTestResultData& result, bool contentPreventsDefault, const UserData& userData)
{
pageClient().didPerformImmediateActionHitTest(result, contentPreventsDefault, m_process->transformHandlesToObjects(userData.object()).get());
}
NSObject *WebPageProxy::immediateActionAnimationControllerForHitTestResult(RefPtr<API::HitTestResult> hitTestResult, uint64_t type, RefPtr<API::Object> userData)
{
return pageClient().immediateActionAnimationControllerForHitTestResult(hitTestResult, type, userData);
}
void WebPageProxy::handleAcceptedCandidate(WebCore::TextCheckingResult acceptedCandidate)
{
send(Messages::WebPage::HandleAcceptedCandidate(acceptedCandidate));
}
void WebPageProxy::didHandleAcceptedCandidate()
{
pageClient().didHandleAcceptedCandidate();
}
void WebPageProxy::setUseSystemAppearance(bool useSystemAppearance)
{
if (useSystemAppearance == m_useSystemAppearance)
return;
m_useSystemAppearance = useSystemAppearance;
if (!hasRunningProcess())
return;
send(Messages::WebPage::SetUseSystemAppearance(useSystemAppearance));
}
void WebPageProxy::setHeaderBannerHeightForTesting(int height)
{
send(Messages::WebPage::SetHeaderBannerHeightForTesting(height));
}
void WebPageProxy::setFooterBannerHeightForTesting(int height)
{
send(Messages::WebPage::SetFooterBannerHeightForTesting(height));
}
void WebPageProxy::didEndMagnificationGesture()
{
send(Messages::WebPage::DidEndMagnificationGesture());
}
#endif
void WebPageProxy::installActivityStateChangeCompletionHandler(CompletionHandler<void()>&& completionHandler)
{
if (!hasRunningProcess()) {
completionHandler();
return;
}
m_nextActivityStateChangeCallbacks.append(WTFMove(completionHandler));
}
void WebPageProxy::imageOrMediaDocumentSizeChanged(const WebCore::IntSize& newSize)
{
m_uiClient->imageOrMediaDocumentSizeChanged(newSize);
}
void WebPageProxy::setShouldDispatchFakeMouseMoveEvents(bool shouldDispatchFakeMouseMoveEvents)
{
send(Messages::WebPage::SetShouldDispatchFakeMouseMoveEvents(shouldDispatchFakeMouseMoveEvents));
}
void WebPageProxy::handleAutoFillButtonClick(const UserData& userData)
{
m_uiClient->didClickAutoFillButton(*this, m_process->transformHandlesToObjects(userData.object()).get());
}
void WebPageProxy::didResignInputElementStrongPasswordAppearance(const UserData& userData)
{
m_uiClient->didResignInputElementStrongPasswordAppearance(*this, m_process->transformHandlesToObjects(userData.object()).get());
}
#if ENABLE(WIRELESS_PLAYBACK_TARGET) && !PLATFORM(IOS_FAMILY)
void WebPageProxy::addPlaybackTargetPickerClient(PlaybackTargetClientContextIdentifier contextId)
{
pageClient().mediaSessionManager().addPlaybackTargetPickerClient(*this, contextId);
}
void WebPageProxy::removePlaybackTargetPickerClient(PlaybackTargetClientContextIdentifier contextId)
{
pageClient().mediaSessionManager().removePlaybackTargetPickerClient(*this, contextId);
}
void WebPageProxy::showPlaybackTargetPicker(PlaybackTargetClientContextIdentifier contextId, const WebCore::FloatRect& rect, bool hasVideo)
{
pageClient().mediaSessionManager().showPlaybackTargetPicker(*this, contextId, pageClient().rootViewToScreen(IntRect(rect)), hasVideo, useDarkAppearance());
}
void WebPageProxy::playbackTargetPickerClientStateDidChange(PlaybackTargetClientContextIdentifier contextId, WebCore::MediaProducerMediaStateFlags state)
{
pageClient().mediaSessionManager().clientStateDidChange(*this, contextId, state);
}
void WebPageProxy::setMockMediaPlaybackTargetPickerEnabled(bool enabled)
{
pageClient().mediaSessionManager().setMockMediaPlaybackTargetPickerEnabled(enabled);
}
void WebPageProxy::setMockMediaPlaybackTargetPickerState(const String& name, WebCore::MediaPlaybackTargetContext::MockState state)
{
pageClient().mediaSessionManager().setMockMediaPlaybackTargetPickerState(name, state);
}
void WebPageProxy::mockMediaPlaybackTargetPickerDismissPopup()
{
pageClient().mediaSessionManager().mockMediaPlaybackTargetPickerDismissPopup();
}
void WebPageProxy::setPlaybackTarget(PlaybackTargetClientContextIdentifier contextId, Ref<MediaPlaybackTarget>&& target)
{
if (!hasRunningProcess())
return;
auto context = target->targetContext();
ASSERT(context.type() != MediaPlaybackTargetContext::Type::SerializedAVOutputContext);
if (preferences().useGPUProcessForMediaEnabled())
context.serializeOutputContext();
send(Messages::WebPage::PlaybackTargetSelected(contextId, context));
}
void WebPageProxy::externalOutputDeviceAvailableDidChange(PlaybackTargetClientContextIdentifier contextId, bool available)
{
if (!hasRunningProcess())
return;
send(Messages::WebPage::PlaybackTargetAvailabilityDidChange(contextId, available));
}
void WebPageProxy::setShouldPlayToPlaybackTarget(PlaybackTargetClientContextIdentifier contextId, bool shouldPlay)
{
if (!hasRunningProcess())
return;
send(Messages::WebPage::SetShouldPlayToPlaybackTarget(contextId, shouldPlay));
}
void WebPageProxy::playbackTargetPickerWasDismissed(PlaybackTargetClientContextIdentifier contextId)
{
if (!hasRunningProcess())
return;
send(Messages::WebPage::PlaybackTargetPickerWasDismissed(contextId));
}
#endif
void WebPageProxy::didExceedInactiveMemoryLimitWhileActive()
{
WEBPAGEPROXY_RELEASE_LOG_ERROR(PerformanceLogging, "didExceedInactiveMemoryLimitWhileActive");
m_uiClient->didExceedBackgroundResourceLimitWhileInForeground(*this, kWKResourceLimitMemory);
}
void WebPageProxy::didExceedBackgroundCPULimitWhileInForeground()
{
WEBPAGEPROXY_RELEASE_LOG_ERROR(PerformanceLogging, "didExceedBackgroundCPULimitWhileInForeground");
m_uiClient->didExceedBackgroundResourceLimitWhileInForeground(*this, kWKResourceLimitCPU);
}
void WebPageProxy::didChangeBackgroundColor()
{
pageClient().didChangeBackgroundColor();
}
void WebPageProxy::clearWheelEventTestMonitor()
{
if (!hasRunningProcess())
return;
send(Messages::WebPage::ClearWheelEventTestMonitor());
}
void WebPageProxy::callAfterNextPresentationUpdate(WTF::Function<void (CallbackBase::Error)>&& callback)
{
if (!hasRunningProcess() || !m_drawingArea) {
callback(CallbackBase::Error::OwnerWasInvalidated);
return;
}
m_drawingArea->dispatchAfterEnsuringDrawing(WTFMove(callback));
}
void WebPageProxy::setShouldScaleViewToFitDocument(bool shouldScaleViewToFitDocument)
{
if (m_shouldScaleViewToFitDocument == shouldScaleViewToFitDocument)
return;
m_shouldScaleViewToFitDocument = shouldScaleViewToFitDocument;
if (!hasRunningProcess())
return;
send(Messages::WebPage::SetShouldScaleViewToFitDocument(shouldScaleViewToFitDocument));
}
void WebPageProxy::didRestoreScrollPosition()
{
pageClient().didRestoreScrollPosition();
}
void WebPageProxy::getLoadDecisionForIcon(const WebCore::LinkIcon& icon, CallbackID loadIdentifier)
{
m_iconLoadingClient->getLoadDecisionForIcon(icon, [this, protectedThis = Ref { *this }, loadIdentifier] (CompletionHandler<void(API::Data*)>&& callback) {
if (!hasRunningProcess()) {
if (callback)
callback(nullptr);
return;
}
if (!callback) {
sendWithAsyncReply(Messages::WebPage::DidGetLoadDecisionForIcon(false, loadIdentifier), [](auto) { });
return;
}
sendWithAsyncReply(Messages::WebPage::DidGetLoadDecisionForIcon(true, loadIdentifier), [callback = WTFMove(callback)](const IPC::SharedBufferCopy& iconData) mutable {
callback(API::Data::create(iconData.data(), iconData.size()).ptr());
});
});
}
WebCore::UserInterfaceLayoutDirection WebPageProxy::userInterfaceLayoutDirection()
{
return pageClient().userInterfaceLayoutDirection();
}
void WebPageProxy::setUserInterfaceLayoutDirection(WebCore::UserInterfaceLayoutDirection userInterfaceLayoutDirection)
{
if (!hasRunningProcess())
return;
send(Messages::WebPage::SetUserInterfaceLayoutDirection(static_cast<uint32_t>(userInterfaceLayoutDirection)));
}
void WebPageProxy::hideValidationMessage()
{
#if PLATFORM(COCOA) || PLATFORM(GTK)
m_validationBubble = nullptr;
#endif
}
// FIXME: Consolidate with dismissContentRelativeChildWindows
void WebPageProxy::closeOverlayedViews()
{
hideValidationMessage();
#if ENABLE(DATALIST_ELEMENT)
endDataListSuggestions();
#endif
#if ENABLE(INPUT_TYPE_COLOR)
endColorPicker();
#endif
#if ENABLE(DATE_AND_TIME_INPUT_TYPES)
endDateTimePicker();
#endif
}
#if ENABLE(POINTER_LOCK)
void WebPageProxy::requestPointerLock()
{
ASSERT(!m_isPointerLockPending);
ASSERT(!m_isPointerLocked);
m_isPointerLockPending = true;
if (!isViewVisible() || !(m_activityState & ActivityState::IsFocused)) {
didDenyPointerLock();
return;
}
m_uiClient->requestPointerLock(this);
}
void WebPageProxy::didAllowPointerLock()
{
ASSERT(m_isPointerLockPending && !m_isPointerLocked);
m_isPointerLocked = true;
m_isPointerLockPending = false;
#if PLATFORM(MAC)
CGDisplayHideCursor(CGMainDisplayID());
CGAssociateMouseAndMouseCursorPosition(false);
#endif
send(Messages::WebPage::DidAcquirePointerLock());
}
void WebPageProxy::didDenyPointerLock()
{
ASSERT(m_isPointerLockPending && !m_isPointerLocked);
m_isPointerLockPending = false;
send(Messages::WebPage::DidNotAcquirePointerLock());
}
void WebPageProxy::requestPointerUnlock()
{
if (m_isPointerLocked) {
#if PLATFORM(MAC)
CGAssociateMouseAndMouseCursorPosition(true);
CGDisplayShowCursor(CGMainDisplayID());
#endif
m_uiClient->didLosePointerLock(this);
send(Messages::WebPage::DidLosePointerLock());
}
if (m_isPointerLockPending) {
m_uiClient->didLosePointerLock(this);
send(Messages::WebPage::DidNotAcquirePointerLock());
}
m_isPointerLocked = false;
m_isPointerLockPending = false;
}
#endif
void WebPageProxy::setURLSchemeHandlerForScheme(Ref<WebURLSchemeHandler>&& handler, const String& scheme)
{
auto canonicalizedScheme = WTF::URLParser::maybeCanonicalizeScheme(scheme);
ASSERT(canonicalizedScheme);
ASSERT(!WTF::URLParser::isSpecialScheme(canonicalizedScheme.value()));
auto schemeResult = m_urlSchemeHandlersByScheme.add(canonicalizedScheme.value(), handler.get());
ASSERT_UNUSED(schemeResult, schemeResult.isNewEntry);
auto handlerIdentifier = handler->identifier();
auto handlerIdentifierResult = m_urlSchemeHandlersByIdentifier.add(handlerIdentifier, WTFMove(handler));
ASSERT_UNUSED(handlerIdentifierResult, handlerIdentifierResult.isNewEntry);
send(Messages::WebPage::RegisterURLSchemeHandler(handlerIdentifier, canonicalizedScheme.value()));
}
WebURLSchemeHandler* WebPageProxy::urlSchemeHandlerForScheme(const String& scheme)
{
return scheme.isNull() ? nullptr : m_urlSchemeHandlersByScheme.get(scheme);
}
void WebPageProxy::startURLSchemeTask(URLSchemeTaskParameters&& parameters)
{
startURLSchemeTaskShared(m_process.copyRef(), m_webPageID, WTFMove(parameters));
}
void WebPageProxy::startURLSchemeTaskShared(Ref<WebProcessProxy>&& process, PageIdentifier webPageID, URLSchemeTaskParameters&& parameters)
{
MESSAGE_CHECK(m_process, decltype(m_urlSchemeHandlersByIdentifier)::isValidKey(parameters.handlerIdentifier));
auto iterator = m_urlSchemeHandlersByIdentifier.find(parameters.handlerIdentifier);
MESSAGE_CHECK(process, iterator != m_urlSchemeHandlersByIdentifier.end());
iterator->value->startTask(*this, process, webPageID, WTFMove(parameters), nullptr);
}
void WebPageProxy::stopURLSchemeTask(WebURLSchemeHandlerIdentifier handlerIdentifier, WebCore::ResourceLoaderIdentifier taskIdentifier)
{
MESSAGE_CHECK(m_process, decltype(m_urlSchemeHandlersByIdentifier)::isValidKey(handlerIdentifier));
auto iterator = m_urlSchemeHandlersByIdentifier.find(handlerIdentifier);
MESSAGE_CHECK(m_process, iterator != m_urlSchemeHandlersByIdentifier.end());
iterator->value->stopTask(*this, taskIdentifier);
}
void WebPageProxy::loadSynchronousURLSchemeTask(URLSchemeTaskParameters&& parameters, Messages::WebPageProxy::LoadSynchronousURLSchemeTask::DelayedReply&& reply)
{
MESSAGE_CHECK(m_process, decltype(m_urlSchemeHandlersByIdentifier)::isValidKey(parameters.handlerIdentifier));
auto iterator = m_urlSchemeHandlersByIdentifier.find(parameters.handlerIdentifier);
MESSAGE_CHECK(m_process, iterator != m_urlSchemeHandlersByIdentifier.end());
iterator->value->startTask(*this, m_process, m_webPageID, WTFMove(parameters), WTFMove(reply));
}
#if ENABLE(INTELLIGENT_TRACKING_PREVENTION)
void WebPageProxy::requestStorageAccessConfirm(const RegistrableDomain& subFrameDomain, const RegistrableDomain& topFrameDomain, FrameIdentifier frameID, CompletionHandler<void(bool)>&& completionHandler)
{
m_uiClient->requestStorageAccessConfirm(*this, m_process->webFrame(frameID), subFrameDomain, topFrameDomain, WTFMove(completionHandler));
}
void WebPageProxy::didCommitCrossSiteLoadWithDataTransferFromPrevalentResource()
{
if (!hasRunningProcess())
return;
send(Messages::WebPage::WasLoadedWithDataTransferFromPrevalentResource());
}
#endif
bool WebPageProxy::useDarkAppearance() const
{
return pageClient().effectiveAppearanceIsDark();
}
bool WebPageProxy::useElevatedUserInterfaceLevel() const
{
return pageClient().effectiveUserInterfaceLevelIsElevated();
}
void WebPageProxy::effectiveAppearanceDidChange()
{
if (!hasRunningProcess())
return;
send(Messages::WebPage::EffectiveAppearanceDidChange(useDarkAppearance(), useElevatedUserInterfaceLevel()));
}
#if HAVE(TOUCH_BAR)
void WebPageProxy::touchBarMenuDataChanged(const TouchBarMenuData& touchBarMenuData)
{
m_touchBarMenuData = touchBarMenuData;
}
void WebPageProxy::touchBarMenuItemDataAdded(const TouchBarMenuItemData& touchBarMenuItemData)
{
m_touchBarMenuData.addMenuItem(touchBarMenuItemData);
}
void WebPageProxy::touchBarMenuItemDataRemoved(const TouchBarMenuItemData& touchBarMenuItemData)
{
m_touchBarMenuData.removeMenuItem(touchBarMenuItemData);
}
#endif
#if HAVE(PASTEBOARD_DATA_OWNER)
DataOwnerType WebPageProxy::dataOwnerForPasteboard(PasteboardAccessIntent intent) const
{
return pageClient().dataOwnerForPasteboard(intent);
}
#endif
#if ENABLE(ATTACHMENT_ELEMENT)
void WebPageProxy::writePromisedAttachmentToPasteboard(WebCore::PromisedAttachmentInfo&& info)
{
pageClient().writePromisedAttachmentToPasteboard(WTFMove(info));
}
RefPtr<API::Attachment> WebPageProxy::attachmentForIdentifier(const String& identifier) const
{
if (identifier.isEmpty())
return nullptr;
return m_attachmentIdentifierToAttachmentMap.get(identifier);
}
void WebPageProxy::insertAttachment(Ref<API::Attachment>&& attachment, CompletionHandler<void()>&& callback)
{
auto attachmentIdentifier = attachment->identifier();
sendWithAsyncReply(Messages::WebPage::InsertAttachment(attachmentIdentifier, attachment->fileSizeForDisplay(), attachment->fileName(), attachment->contentType()), WTFMove(callback));
m_attachmentIdentifierToAttachmentMap.set(attachmentIdentifier, WTFMove(attachment));
}
void WebPageProxy::updateAttachmentAttributes(const API::Attachment& attachment, CompletionHandler<void()>&& callback)
{
sendWithAsyncReply(Messages::WebPage::UpdateAttachmentAttributes(attachment.identifier(), attachment.fileSizeForDisplay(), attachment.contentType(), attachment.fileName(), IPC::SharedBufferCopy(attachment.enclosingImageData())), WTFMove(callback));
}
#if HAVE(QUICKLOOK_THUMBNAILING)
void WebPageProxy::updateAttachmentIcon(const String& identifier, const RefPtr<ShareableBitmap>& bitmap)
{
if (!hasRunningProcess())
return;
ShareableBitmap::Handle handle;
if (bitmap)
bitmap->createHandle(handle);
send(Messages::WebPage::UpdateAttachmentIcon(identifier, handle));
}
#endif
void WebPageProxy::registerAttachmentIdentifierFromData(const String& identifier, const String& contentType, const String& preferredFileName, const IPC::SharedBufferCopy& data)
{
MESSAGE_CHECK(m_process, m_preferences->attachmentElementEnabled());
MESSAGE_CHECK(m_process, IdentifierToAttachmentMap::isValidKey(identifier));
if (attachmentForIdentifier(identifier))
return;
auto attachment = ensureAttachment(identifier);
attachment->setContentType(contentType);
m_attachmentIdentifierToAttachmentMap.set(identifier, attachment.copyRef());
platformRegisterAttachment(WTFMove(attachment), preferredFileName, data);
}
void WebPageProxy::registerAttachmentIdentifierFromFilePath(const String& identifier, const String& contentType, const String& filePath)
{
MESSAGE_CHECK(m_process, m_preferences->attachmentElementEnabled());
MESSAGE_CHECK(m_process, IdentifierToAttachmentMap::isValidKey(identifier));
if (attachmentForIdentifier(identifier))
return;
auto attachment = ensureAttachment(identifier);
attachment->setContentType(contentType);
attachment->setFilePath(filePath);
m_attachmentIdentifierToAttachmentMap.set(identifier, attachment.copyRef());
platformRegisterAttachment(WTFMove(attachment), filePath);
#if HAVE(QUICKLOOK_THUMBNAILING)
requestThumbnailWithPath(identifier, filePath);
#endif
}
void WebPageProxy::registerAttachmentIdentifier(const String& identifier)
{
MESSAGE_CHECK(m_process, m_preferences->attachmentElementEnabled());
MESSAGE_CHECK(m_process, IdentifierToAttachmentMap::isValidKey(identifier));
if (!attachmentForIdentifier(identifier))
m_attachmentIdentifierToAttachmentMap.set(identifier, ensureAttachment(identifier));
}
void WebPageProxy::registerAttachmentsFromSerializedData(Vector<WebCore::SerializedAttachmentData>&& data)
{
MESSAGE_CHECK(m_process, m_preferences->attachmentElementEnabled());
for (auto& serializedData : data) {
auto identifier = WTFMove(serializedData.identifier);
if (!attachmentForIdentifier(identifier))
ensureAttachment(identifier)->updateFromSerializedRepresentation(WTFMove(serializedData.data), WTFMove(serializedData.mimeType));
}
}
void WebPageProxy::cloneAttachmentData(const String& fromIdentifier, const String& toIdentifier)
{
MESSAGE_CHECK(m_process, m_preferences->attachmentElementEnabled());
MESSAGE_CHECK(m_process, IdentifierToAttachmentMap::isValidKey(fromIdentifier));
MESSAGE_CHECK(m_process, IdentifierToAttachmentMap::isValidKey(toIdentifier));
auto newAttachment = ensureAttachment(toIdentifier);
auto existingAttachment = attachmentForIdentifier(fromIdentifier);
if (!existingAttachment) {
ASSERT_NOT_REACHED();
return;
}
newAttachment->setContentType(existingAttachment->contentType());
newAttachment->setFilePath(existingAttachment->filePath());
platformCloneAttachment(existingAttachment.releaseNonNull(), WTFMove(newAttachment));
}
void WebPageProxy::invalidateAllAttachments()
{
for (auto& attachment : m_attachmentIdentifierToAttachmentMap.values()) {
if (attachment->insertionState() == API::Attachment::InsertionState::Inserted)
didRemoveAttachment(attachment.get());
attachment->invalidate();
}
m_attachmentIdentifierToAttachmentMap.clear();
}
void WebPageProxy::serializedAttachmentDataForIdentifiers(const Vector<String>& identifiers, CompletionHandler<void(Vector<WebCore::SerializedAttachmentData>&&)>&& completionHandler)
{
Vector<WebCore::SerializedAttachmentData> serializedData;
MESSAGE_CHECK_COMPLETION(m_process, m_preferences->attachmentElementEnabled(), completionHandler(WTFMove(serializedData)));
for (const auto& identifier : identifiers) {
auto attachment = attachmentForIdentifier(identifier);
if (!attachment)
continue;
auto data = attachment->createSerializedRepresentation();
if (!data)
continue;
serializedData.append({ identifier, attachment->mimeType(), data.releaseNonNull() });
}
completionHandler(WTFMove(serializedData));
}
void WebPageProxy::didInvalidateDataForAttachment(API::Attachment& attachment)
{
pageClient().didInvalidateDataForAttachment(attachment);
}
WebPageProxy::ShouldUpdateAttachmentAttributes WebPageProxy::willUpdateAttachmentAttributes(const API::Attachment& attachment)
{
return ShouldUpdateAttachmentAttributes::Yes;
}
#if !PLATFORM(COCOA)
void WebPageProxy::platformRegisterAttachment(Ref<API::Attachment>&&, const String&, const IPC::SharedBufferCopy&)
{
}
void WebPageProxy::platformRegisterAttachment(Ref<API::Attachment>&&, const String&)
{
}
void WebPageProxy::platformCloneAttachment(Ref<API::Attachment>&&, Ref<API::Attachment>&&)
{
}
#endif
void WebPageProxy::didInsertAttachmentWithIdentifier(const String& identifier, const String& source, bool hasEnclosingImage)
{
MESSAGE_CHECK(m_process, m_preferences->attachmentElementEnabled());
MESSAGE_CHECK(m_process, IdentifierToAttachmentMap::isValidKey(identifier));
auto attachment = ensureAttachment(identifier);
attachment->setHasEnclosingImage(hasEnclosingImage);
attachment->setInsertionState(API::Attachment::InsertionState::Inserted);
pageClient().didInsertAttachment(attachment.get(), source);
if (!attachment->isEmpty() && hasEnclosingImage)
updateAttachmentAttributes(attachment.get(), [] { });
}
void WebPageProxy::didRemoveAttachmentWithIdentifier(const String& identifier)
{
MESSAGE_CHECK(m_process, m_preferences->attachmentElementEnabled());
MESSAGE_CHECK(m_process, IdentifierToAttachmentMap::isValidKey(identifier));
if (auto attachment = attachmentForIdentifier(identifier))
didRemoveAttachment(*attachment);
}
void WebPageProxy::didRemoveAttachment(API::Attachment& attachment)
{
attachment.setInsertionState(API::Attachment::InsertionState::NotInserted);
pageClient().didRemoveAttachment(attachment);
}
Ref<API::Attachment> WebPageProxy::ensureAttachment(const String& identifier)
{
if (auto existingAttachment = attachmentForIdentifier(identifier))
return *existingAttachment;
auto attachment = API::Attachment::create(identifier, *this);
m_attachmentIdentifierToAttachmentMap.set(identifier, attachment.copyRef());
return attachment;
}
#endif // ENABLE(ATTACHMENT_ELEMENT)
#if ENABLE(APPLICATION_MANIFEST)
void WebPageProxy::getApplicationManifest(CompletionHandler<void(const std::optional<WebCore::ApplicationManifest>&)>&& callback)
{
sendWithAsyncReply(Messages::WebPage::GetApplicationManifest(), WTFMove(callback));
}
#endif
#if ENABLE(APP_HIGHLIGHTS)
void WebPageProxy::storeAppHighlight(const WebCore::AppHighlight& highlight)
{
MESSAGE_CHECK(m_process, !highlight.highlight->isEmpty());
pageClient().storeAppHighlight(highlight);
}
#endif
namespace {
enum class CompletionCondition {
Cancellation,
Error,
Success,
Timeout,
};
struct MessageType {
CompletionCondition condition;
Seconds seconds;
String message;
};
}
void WebPageProxy::reportPageLoadResult(const ResourceError& error)
{
static const NeverDestroyed<Vector<MessageType>> messages(std::initializer_list<MessageType> {
{ CompletionCondition::Cancellation, 2_s, DiagnosticLoggingKeys::canceledLessThan2SecondsKey() },
{ CompletionCondition::Cancellation, 5_s, DiagnosticLoggingKeys::canceledLessThan5SecondsKey() },
{ CompletionCondition::Cancellation, 20_s, DiagnosticLoggingKeys::canceledLessThan20SecondsKey() },
{ CompletionCondition::Cancellation, Seconds::infinity(), DiagnosticLoggingKeys::canceledMoreThan20SecondsKey() },
{ CompletionCondition::Error, 2_s, DiagnosticLoggingKeys::failedLessThan2SecondsKey() },
{ CompletionCondition::Error, 5_s, DiagnosticLoggingKeys::failedLessThan5SecondsKey() },
{ CompletionCondition::Error, 20_s, DiagnosticLoggingKeys::failedLessThan20SecondsKey() },
{ CompletionCondition::Error, Seconds::infinity(), DiagnosticLoggingKeys::failedMoreThan20SecondsKey() },
{ CompletionCondition::Success, 2_s, DiagnosticLoggingKeys::succeededLessThan2SecondsKey() },
{ CompletionCondition::Success, 5_s, DiagnosticLoggingKeys::succeededLessThan5SecondsKey() },
{ CompletionCondition::Success, 20_s, DiagnosticLoggingKeys::succeededLessThan20SecondsKey() },
{ CompletionCondition::Success, Seconds::infinity(), DiagnosticLoggingKeys::succeededMoreThan20SecondsKey() },
{ CompletionCondition::Timeout, Seconds::infinity(), DiagnosticLoggingKeys::timedOutKey() }
});
if (!m_pageLoadStart)
return;
auto pageLoadTime = MonotonicTime::now() - *m_pageLoadStart;
m_pageLoadStart = std::nullopt;
CompletionCondition condition { CompletionCondition::Success };
if (error.isCancellation())
condition = CompletionCondition::Cancellation;
else if (error.isTimeout())
condition = CompletionCondition::Timeout;
else if (!error.isNull() || error.errorCode())
condition = CompletionCondition::Error;
for (auto& messageItem : messages.get()) {
if (condition == messageItem.condition && pageLoadTime < messageItem.seconds) {
logDiagnosticMessage(DiagnosticLoggingKeys::telemetryPageLoadKey(), messageItem.message, ShouldSample::No);
logDiagnosticMessage(DiagnosticLoggingKeys::telemetryPageLoadKey(), DiagnosticLoggingKeys::occurredKey(), ShouldSample::No);
break;
}
}
}
void WebPageProxy::setDefersLoadingForTesting(bool defersLoading)
{
send(Messages::WebPage::SetDefersLoading(defersLoading));
}
void WebPageProxy::getIsViewVisible(bool& result)
{
result = isViewVisible();
}
void WebPageProxy::updateCurrentModifierState()
{
#if PLATFORM(COCOA)
auto modifiers = PlatformKeyboardEvent::currentStateOfModifierKeys();
send(Messages::WebPage::UpdateCurrentModifierState(modifiers));
#endif
}
bool WebPageProxy::checkURLReceivedFromCurrentOrPreviousWebProcess(WebProcessProxy& process, const String& urlString)
{
return checkURLReceivedFromCurrentOrPreviousWebProcess(process, URL(URL(), urlString));
}
bool WebPageProxy::checkURLReceivedFromCurrentOrPreviousWebProcess(WebProcessProxy& process, const URL& url)
{
if (!url.isLocalFile())
return true;
if (m_mayHaveUniversalFileReadSandboxExtension)
return true;
String path = url.fileSystemPath();
auto startsWithURLPath = [&path](const String& visitedPath) {
return path.startsWith(visitedPath);
};
auto localPathsEnd = m_previouslyVisitedPaths.end();
if (std::find_if(m_previouslyVisitedPaths.begin(), localPathsEnd, startsWithURLPath) != localPathsEnd)
return true;
return process.checkURLReceivedFromWebProcess(url);
}
void WebPageProxy::addPreviouslyVisitedPath(const String& path)
{
m_previouslyVisitedPaths.add(path);
}
void WebPageProxy::willAcquireUniversalFileReadSandboxExtension(WebProcessProxy& process)
{
m_mayHaveUniversalFileReadSandboxExtension = true;
process.willAcquireUniversalFileReadSandboxExtension();
}
void WebPageProxy::simulateDeviceOrientationChange(double alpha, double beta, double gamma)
{
send(Messages::WebPage::SimulateDeviceOrientationChange(alpha, beta, gamma));
}
#if ENABLE(DATA_DETECTION)
void WebPageProxy::detectDataInAllFrames(OptionSet<WebCore::DataDetectorType> types, CompletionHandler<void(const DataDetectionResult&)>&& completionHandler)
{
if (!hasRunningProcess()) {
completionHandler({ });
return;
}
sendWithAsyncReply(Messages::WebPage::DetectDataInAllFrames(types), WTFMove(completionHandler));
}
void WebPageProxy::removeDataDetectedLinks(CompletionHandler<void(const DataDetectionResult&)>&& completionHandler)
{
if (!hasRunningProcess()) {
completionHandler({ });
return;
}
sendWithAsyncReply(Messages::WebPage::RemoveDataDetectedLinks(), WTFMove(completionHandler));
}
#endif
#if USE(SYSTEM_PREVIEW)
void WebPageProxy::systemPreviewActionTriggered(const WebCore::SystemPreviewInfo& previewInfo, const String& message)
{
send(Messages::WebPage::SystemPreviewActionTriggered(previewInfo, message));
}
#endif
void WebPageProxy::dumpPrivateClickMeasurement(CompletionHandler<void(const String&)>&& completionHandler)
{
websiteDataStore().networkProcess().sendWithAsyncReply(Messages::NetworkProcess::DumpPrivateClickMeasurement(m_websiteDataStore->sessionID()), WTFMove(completionHandler));
}
void WebPageProxy::clearPrivateClickMeasurement(CompletionHandler<void()>&& completionHandler)
{
websiteDataStore().networkProcess().sendWithAsyncReply(Messages::NetworkProcess::ClearPrivateClickMeasurement(m_websiteDataStore->sessionID()), WTFMove(completionHandler));
}
void WebPageProxy::setPrivateClickMeasurementOverrideTimerForTesting(bool value, CompletionHandler<void()>&& completionHandler)
{
websiteDataStore().networkProcess().sendWithAsyncReply(Messages::NetworkProcess::SetPrivateClickMeasurementOverrideTimerForTesting(m_websiteDataStore->sessionID(), value), WTFMove(completionHandler));
}
void WebPageProxy::markAttributedPrivateClickMeasurementsAsExpiredForTesting(CompletionHandler<void()>&& completionHandler)
{
websiteDataStore().networkProcess().sendWithAsyncReply(Messages::NetworkProcess::MarkAttributedPrivateClickMeasurementsAsExpiredForTesting(m_websiteDataStore->sessionID()), WTFMove(completionHandler));
}
void WebPageProxy::setPrivateClickMeasurementEphemeralMeasurementForTesting(bool value, CompletionHandler<void()>&& completionHandler)
{
websiteDataStore().networkProcess().sendWithAsyncReply(Messages::NetworkProcess::SetPrivateClickMeasurementEphemeralMeasurementForTesting(m_websiteDataStore->sessionID(), value), WTFMove(completionHandler));
}
void WebPageProxy::simulatePrivateClickMeasurementSessionRestart(CompletionHandler<void()>&& completionHandler)
{
websiteDataStore().networkProcess().sendWithAsyncReply(Messages::NetworkProcess::SimulatePrivateClickMeasurementSessionRestart(m_websiteDataStore->sessionID()), WTFMove(completionHandler));
}
void WebPageProxy::setPrivateClickMeasurementTokenPublicKeyURLForTesting(const URL& url, CompletionHandler<void()>&& completionHandler)
{
websiteDataStore().networkProcess().sendWithAsyncReply(Messages::NetworkProcess::SetPrivateClickMeasurementTokenPublicKeyURLForTesting(m_websiteDataStore->sessionID(), url), WTFMove(completionHandler));
}
void WebPageProxy::setPrivateClickMeasurementTokenSignatureURLForTesting(const URL& url, CompletionHandler<void()>&& completionHandler)
{
websiteDataStore().networkProcess().sendWithAsyncReply(Messages::NetworkProcess::SetPrivateClickMeasurementTokenSignatureURLForTesting(m_websiteDataStore->sessionID(), url), WTFMove(completionHandler));
}
void WebPageProxy::setPrivateClickMeasurementAttributionReportURLsForTesting(const URL& sourceURL, const URL& destinationURL, CompletionHandler<void()>&& completionHandler)
{
websiteDataStore().networkProcess().sendWithAsyncReply(Messages::NetworkProcess::SetPrivateClickMeasurementAttributionReportURLsForTesting(m_websiteDataStore->sessionID(), sourceURL, destinationURL), WTFMove(completionHandler));
}
void WebPageProxy::markPrivateClickMeasurementsAsExpiredForTesting(CompletionHandler<void()>&& completionHandler)
{
websiteDataStore().networkProcess().sendWithAsyncReply(Messages::NetworkProcess::MarkPrivateClickMeasurementsAsExpiredForTesting(m_websiteDataStore->sessionID()), WTFMove(completionHandler));
}
void WebPageProxy::setPCMFraudPreventionValuesForTesting(const String& unlinkableToken, const String& secretToken, const String& signature, const String& keyID, CompletionHandler<void()>&& completionHandler)
{
websiteDataStore().networkProcess().sendWithAsyncReply(Messages::NetworkProcess::SetPCMFraudPreventionValuesForTesting(m_websiteDataStore->sessionID(), unlinkableToken, secretToken, signature, keyID), WTFMove(completionHandler));
}
void WebPageProxy::setPrivateClickMeasurementAppBundleIDForTesting(const String& appBundleIDForTesting, CompletionHandler<void()>&& completionHandler)
{
websiteDataStore().networkProcess().sendWithAsyncReply(Messages::NetworkProcess::SetPrivateClickMeasurementAppBundleIDForTesting(m_websiteDataStore->sessionID(), appBundleIDForTesting), WTFMove(completionHandler));
}
#if ENABLE(SPEECH_SYNTHESIS)
void WebPageProxy::resetSpeechSynthesizer()
{
if (!m_speechSynthesisData)
return;
auto& synthesisData = speechSynthesisData();
synthesisData.speakingFinishedCompletionHandler = nullptr;
synthesisData.speakingStartedCompletionHandler = nullptr;
synthesisData.speakingPausedCompletionHandler = nullptr;
synthesisData.speakingResumedCompletionHandler = nullptr;
if (synthesisData.synthesizer)
synthesisData.synthesizer->resetState();
}
WebPageProxy::SpeechSynthesisData& WebPageProxy::speechSynthesisData()
{
if (!m_speechSynthesisData)
m_speechSynthesisData = SpeechSynthesisData { makeUnique<PlatformSpeechSynthesizer>(this), nullptr, nullptr, nullptr, nullptr, nullptr };
return *m_speechSynthesisData;
}
void WebPageProxy::speechSynthesisVoiceList(CompletionHandler<void(Vector<WebSpeechSynthesisVoice>&&)>&& completionHandler)
{
auto& voiceList = speechSynthesisData().synthesizer->voiceList();
Vector<WebSpeechSynthesisVoice> result;
result.reserveInitialCapacity(voiceList.size());
for (auto& voice : voiceList)
result.uncheckedAppend(WebSpeechSynthesisVoice { voice->voiceURI(), voice->name(), voice->lang(), voice->localService(), voice->isDefault() });
completionHandler(WTFMove(result));
}
void WebPageProxy::speechSynthesisSetFinishedCallback(CompletionHandler<void()>&& completionHandler)
{
speechSynthesisData().speakingFinishedCompletionHandler = WTFMove(completionHandler);
}
void WebPageProxy::speechSynthesisSpeak(const String& text, const String& lang, float volume, float rate, float pitch, MonotonicTime startTime, const String& voiceURI, const String& voiceName, const String& voiceLang, bool localService, bool defaultVoice, CompletionHandler<void()>&& completionHandler)
{
auto voice = WebCore::PlatformSpeechSynthesisVoice::create(voiceURI, voiceName, voiceLang, localService, defaultVoice);
auto utterance = WebCore::PlatformSpeechSynthesisUtterance::create(*this);
utterance->setText(text);
utterance->setLang(lang);
utterance->setVolume(volume);
utterance->setRate(rate);
utterance->setPitch(pitch);
utterance->setVoice(&voice.get());
speechSynthesisData().speakingStartedCompletionHandler = WTFMove(completionHandler);
speechSynthesisData().utterance = WTFMove(utterance);
speechSynthesisData().synthesizer->speak(m_speechSynthesisData->utterance.get());
}
void WebPageProxy::speechSynthesisCancel()
{
speechSynthesisData().synthesizer->cancel();
}
void WebPageProxy::speechSynthesisResetState()
{
speechSynthesisData().synthesizer->resetState();
}
void WebPageProxy::speechSynthesisPause(CompletionHandler<void()>&& completionHandler)
{
speechSynthesisData().speakingPausedCompletionHandler = WTFMove(completionHandler);
speechSynthesisData().synthesizer->pause();
}
void WebPageProxy::speechSynthesisResume(CompletionHandler<void()>&& completionHandler)
{
speechSynthesisData().speakingResumedCompletionHandler = WTFMove(completionHandler);
speechSynthesisData().synthesizer->resume();
}
#endif // ENABLE(SPEECH_SYNTHESIS)
#if !PLATFORM(IOS_FAMILY)
WebContentMode WebPageProxy::effectiveContentModeAfterAdjustingPolicies(API::WebsitePolicies&, const WebCore::ResourceRequest&)
{
return WebContentMode::Recommended;
}
#endif // !PLATFORM(IOS_FAMILY)
void WebPageProxy::addObserver(WebViewDidMoveToWindowObserver& observer)
{
auto result = m_webViewDidMoveToWindowObservers.add(&observer, observer);
ASSERT_UNUSED(result, result.isNewEntry);
}
void WebPageProxy::removeObserver(WebViewDidMoveToWindowObserver& observer)
{
auto result = m_webViewDidMoveToWindowObservers.remove(&observer);
ASSERT_UNUSED(result, result);
}
void WebPageProxy::webViewDidMoveToWindow()
{
auto observersCopy = m_webViewDidMoveToWindowObservers;
for (const auto& observer : observersCopy) {
if (!observer.value)
continue;
observer.value->webViewDidMoveToWindow();
}
auto newWindowKind = pageClient().windowKind();
if (m_windowKind != newWindowKind) {
m_windowKind = newWindowKind;
if (m_drawingArea)
m_drawingArea->windowKindDidChange();
}
}
void WebPageProxy::setCanShowPlaceholder(const WebCore::ElementContext& context, bool canShowPlaceholder)
{
if (hasRunningProcess())
send(Messages::WebPage::SetCanShowPlaceholder(context, canShowPlaceholder));
}
Logger& WebPageProxy::logger()
{
if (!m_logger) {
m_logger = Logger::create(this);
// FIXME: Does this really need to be disabled in ephemeral sessions?
m_logger->setEnabled(this, !sessionID().isEphemeral());
}
return *m_logger;
}
void WebPageProxy::configureLoggingChannel(const String& channelName, WTFLogChannelState state, WTFLogLevel level)
{
#if !RELEASE_LOG_DISABLED
auto* channel = getLogChannel(channelName);
if (!channel)
return;
channel->state = state;
channel->level = level;
#else
UNUSED_PARAM(channelName);
UNUSED_PARAM(state);
UNUSED_PARAM(level);
#endif
}
#if HAVE(APP_SSO)
void WebPageProxy::decidePolicyForSOAuthorizationLoad(const String& extension, CompletionHandler<void(SOAuthorizationLoadPolicy)>&& completionHandler)
{
m_navigationClient->decidePolicyForSOAuthorizationLoad(*this, SOAuthorizationLoadPolicy::Allow, extension, WTFMove(completionHandler));
}
#endif
#if ENABLE(WEB_AUTHN)
void WebPageProxy::setMockWebAuthenticationConfiguration(MockWebAuthenticationConfiguration&& configuration)
{
m_websiteDataStore->setMockWebAuthenticationConfiguration(WTFMove(configuration));
}
#endif
void WebPageProxy::startTextManipulations(const Vector<WebCore::TextManipulationController::ExclusionRule>& exclusionRules,
TextManipulationItemCallback&& callback, WTF::CompletionHandler<void()>&& completionHandler)
{
if (!hasRunningProcess()) {
completionHandler();
return;
}
m_textManipulationItemCallback = WTFMove(callback);
sendWithAsyncReply(Messages::WebPage::StartTextManipulations(exclusionRules), WTFMove(completionHandler));
}
void WebPageProxy::didFindTextManipulationItems(const Vector<WebCore::TextManipulationController::ManipulationItem>& items)
{
if (!m_textManipulationItemCallback)
return;
m_textManipulationItemCallback(items);
}
void WebPageProxy::completeTextManipulation(const Vector<WebCore::TextManipulationController::ManipulationItem>& items,
WTF::Function<void(bool allFailed, const Vector<WebCore::TextManipulationController::ManipulationFailure>&)>&& completionHandler)
{
if (!hasRunningProcess()) {
completionHandler(true, { });
return;
}
sendWithAsyncReply(Messages::WebPage::CompleteTextManipulation(items), WTFMove(completionHandler));
}
void WebPageProxy::setCORSDisablingPatterns(Vector<String>&& patterns)
{
m_corsDisablingPatterns = WTFMove(patterns);
send(Messages::WebPage::UpdateCORSDisablingPatterns(m_corsDisablingPatterns));
}
void WebPageProxy::setOverriddenMediaType(const String& mediaType)
{
m_overriddenMediaType = mediaType;
send(Messages::WebPage::SetOverriddenMediaType(mediaType));
}
void WebPageProxy::setIsTakingSnapshotsForApplicationSuspension(bool isTakingSnapshotsForApplicationSuspension)
{
send(Messages::WebPage::SetIsTakingSnapshotsForApplicationSuspension(isTakingSnapshotsForApplicationSuspension));
}
void WebPageProxy::setNeedsDOMWindowResizeEvent()
{
send(Messages::WebPage::SetNeedsDOMWindowResizeEvent());
}
void WebPageProxy::loadServiceWorker(const URL& url, CompletionHandler<void(bool success)>&& completionHandler)
{
#if ENABLE(SERVICE_WORKER)
if (m_isClosed)
return completionHandler(false);
WEBPAGEPROXY_RELEASE_LOG(Loading, "loadServiceWorker:");
if (m_serviceWorkerLaunchCompletionHandler)
return completionHandler(false);
m_isServiceWorkerPage = true;
m_serviceWorkerLaunchCompletionHandler = WTFMove(completionHandler);
CString html = makeString("<script>navigator.serviceWorker.register('", url.string().utf8().data(), "');</script>").utf8();
loadData({ reinterpret_cast<const uint8_t*>(html.data()), html.length() }, "text/html"_s, "UTF-8"_s, url.protocolHostAndPort());
#else
UNUSED_PARAM(url);
completionHandler(false);
#endif
}
#if !PLATFORM(IOS_FAMILY)
bool WebPageProxy::shouldForceForegroundPriorityForClientNavigation() const
{
return false;
}
#endif
void WebPageProxy::getProcessDisplayName(CompletionHandler<void(String&&)>&& completionHandler)
{
sendWithAsyncReply(Messages::WebPage::GetProcessDisplayName(), WTFMove(completionHandler));
}
void WebPageProxy::setOrientationForMediaCapture(uint64_t orientation)
{
#if ENABLE(MEDIA_STREAM)
#if PLATFORM(COCOA)
if (auto* proxy = m_process->userMediaCaptureManagerProxy())
proxy->setOrientation(orientation);
auto* gpuProcess = m_process->processPool().gpuProcess();
if (gpuProcess && preferences().captureVideoInGPUProcessEnabled())
gpuProcess->setOrientationForMediaCapture(orientation);
#elif USE(GSTREAMER)
send(Messages::WebPage::SetOrientationForMediaCapture(orientation));
#endif
#endif
}
#if ENABLE(MEDIA_STREAM) && USE(GSTREAMER)
void WebPageProxy::setMockCameraIsInterrupted(bool isInterrupted)
{
send(Messages::WebPage::SetMockCameraIsInterrupted(isInterrupted));
}
#endif
#if ENABLE(INTELLIGENT_TRACKING_PREVENTION)
void WebPageProxy::getLoadedSubresourceDomains(CompletionHandler<void(Vector<RegistrableDomain>&&)>&& completionHandler)
{
sendWithAsyncReply(Messages::WebPage::GetLoadedSubresourceDomains(), WTFMove(completionHandler));
}
void WebPageProxy::clearLoadedSubresourceDomains()
{
send(Messages::WebPage::ClearLoadedSubresourceDomains());
}
#endif
#if ENABLE(GPU_PROCESS)
void WebPageProxy::gpuProcessExited(GPUProcessTerminationReason)
{
#if HAVE(VISIBILITY_PROPAGATION_VIEW)
m_contextIDForVisibilityPropagationInGPUProcess = 0;
#endif
pageClient().gpuProcessDidExit();
#if ENABLE(MEDIA_STREAM)
bool activeAudioCapture = isCapturingAudio() && preferences().captureAudioInGPUProcessEnabled();
bool activeVideoCapture = isCapturingVideo() && preferences().captureVideoInGPUProcessEnabled();
bool activeDisplayCapture = false;
if (activeAudioCapture || activeVideoCapture) {
auto& gpuProcess = process().processPool().ensureGPUProcess();
gpuProcess.updateCaptureAccess(activeAudioCapture, activeVideoCapture, activeDisplayCapture, m_process->coreProcessIdentifier(), [] { });
}
#endif
}
#endif
#if ENABLE(CONTEXT_MENUS) && !PLATFORM(MAC)
void WebPageProxy::platformDidSelectItemFromActiveContextMenu(const WebContextMenuItemData&)
{
}
#endif
#if !PLATFORM(COCOA)
void WebPageProxy::willPerformPasteCommand(DOMPasteAccessCategory)
{
}
#endif
void WebPageProxy::dispatchActivityStateUpdateForTesting()
{
RunLoop::current().dispatch([protectedThis = Ref { *this }] {
protectedThis->dispatchActivityStateChange();
});
}
void WebPageProxy::isLayerTreeFrozen(CompletionHandler<void(bool)>&& completionHandler)
{
sendWithAsyncReply(Messages::WebPage::IsLayerTreeFrozen(), WTFMove(completionHandler));
}
void WebPageProxy::requestSpeechRecognitionPermission(WebCore::SpeechRecognitionRequest& request, CompletionHandler<void(std::optional<SpeechRecognitionError>&&)>&& completionHandler)
{
if (!m_speechRecognitionPermissionManager)
m_speechRecognitionPermissionManager = makeUnique<SpeechRecognitionPermissionManager>(*this);
m_speechRecognitionPermissionManager->request(request, WTFMove(completionHandler));
}
void WebPageProxy::requestSpeechRecognitionPermissionByDefaultAction(const WebCore::SecurityOriginData& origin, CompletionHandler<void(bool)>&& completionHandler)
{
if (!m_speechRecognitionPermissionManager) {
completionHandler(false);
return;
}
m_speechRecognitionPermissionManager->decideByDefaultAction(origin, WTFMove(completionHandler));
}
void WebPageProxy::requestUserMediaPermissionForSpeechRecognition(FrameIdentifier frameIdentifier, const WebCore::SecurityOrigin& requestingOrigin, const WebCore::SecurityOrigin& topOrigin, CompletionHandler<void(bool)>&& completionHandler)
{
#if ENABLE(MEDIA_STREAM)
auto captureDevice = SpeechRecognitionCaptureSource::findCaptureDevice();
if (!captureDevice) {
completionHandler(false);
return;
}
userMediaPermissionRequestManager().checkUserMediaPermissionForSpeechRecognition(frameIdentifier, requestingOrigin, topOrigin, *captureDevice, WTFMove(completionHandler));
#else
completionHandler(false);
#endif
}
void WebPageProxy::requestMediaKeySystemPermissionByDefaultAction(const WebCore::SecurityOriginData& origin, CompletionHandler<void(bool)>&& completionHandler)
{
completionHandler(true);
}
#if ENABLE(MEDIA_STREAM)
WebCore::CaptureSourceOrError WebPageProxy::createRealtimeMediaSourceForSpeechRecognition()
{
auto captureDevice = SpeechRecognitionCaptureSource::findCaptureDevice();
if (!captureDevice)
return CaptureSourceOrError { "No device is available for capture" };
if (preferences().captureAudioInGPUProcessEnabled())
return CaptureSourceOrError { SpeechRecognitionRemoteRealtimeMediaSource::create(m_process->ensureSpeechRecognitionRemoteRealtimeMediaSourceManager(), *captureDevice) };
#if PLATFORM(IOS_FAMILY)
return CaptureSourceOrError { SpeechRecognitionRemoteRealtimeMediaSource::create(m_process->ensureSpeechRecognitionRemoteRealtimeMediaSourceManager(), *captureDevice) };
#else
return SpeechRecognitionCaptureSource::createRealtimeMediaSource(*captureDevice);
#endif
}
#endif
#if HAVE(SCREEN_CAPTURE_KIT)
void WebPageProxy::setIndexOfGetDisplayMediaDeviceSelectedForTesting(std::optional<unsigned> index)
{
DisplayCaptureSessionManager::singleton().setIndexOfDeviceSelectedForTesting(index);
}
#endif
#if ENABLE(ARKIT_INLINE_PREVIEW)
void WebPageProxy::modelElementGetCamera(ModelIdentifier modelIdentifier, CompletionHandler<void(Expected<WebCore::HTMLModelElementCamera, ResourceError>)>&& completionHandler)
{
modelElementController()->getCameraForModelElement(modelIdentifier, WTFMove(completionHandler));
}
void WebPageProxy::modelElementSetCamera(ModelIdentifier modelIdentifier, WebCore::HTMLModelElementCamera camera, CompletionHandler<void(bool)>&& completionHandler)
{
modelElementController()->setCameraForModelElement(modelIdentifier, camera, WTFMove(completionHandler));
}
void WebPageProxy::modelElementIsPlayingAnimation(ModelIdentifier modelIdentifier, CompletionHandler<void(Expected<bool, ResourceError>)>&& completionHandler)
{
modelElementController()->isPlayingAnimationForModelElement(modelIdentifier, WTFMove(completionHandler));
}
void WebPageProxy::modelElementSetAnimationIsPlaying(ModelIdentifier modelIdentifier, bool isPlaying, CompletionHandler<void(bool)>&& completionHandler)
{
modelElementController()->setAnimationIsPlayingForModelElement(modelIdentifier, isPlaying, WTFMove(completionHandler));
}
void WebPageProxy::modelElementIsLoopingAnimation(ModelIdentifier modelIdentifier, CompletionHandler<void(Expected<bool, ResourceError>)>&& completionHandler)
{
modelElementController()->isLoopingAnimationForModelElement(modelIdentifier, WTFMove(completionHandler));
}
void WebPageProxy::modelElementSetIsLoopingAnimation(ModelIdentifier modelIdentifier, bool isLooping, CompletionHandler<void(bool)>&& completionHandler)
{
modelElementController()->setIsLoopingAnimationForModelElement(modelIdentifier, isLooping, WTFMove(completionHandler));
}
void WebPageProxy::modelElementAnimationDuration(ModelIdentifier modelIdentifier, CompletionHandler<void(Expected<Seconds, WebCore::ResourceError>)>&& completionHandler)
{
modelElementController()->animationDurationForModelElement(modelIdentifier, WTFMove(completionHandler));
}
void WebPageProxy::modelElementAnimationCurrentTime(ModelIdentifier modelIdentifier, CompletionHandler<void(Expected<Seconds, WebCore::ResourceError>)>&& completionHandler)
{
modelElementController()->animationCurrentTimeForModelElement(modelIdentifier, WTFMove(completionHandler));
}
void WebPageProxy::modelElementSetAnimationCurrentTime(ModelIdentifier modelIdentifier, Seconds currentTime, CompletionHandler<void(bool)>&& completionHandler)
{
modelElementController()->setAnimationCurrentTimeForModelElement(modelIdentifier, currentTime, WTFMove(completionHandler));
}
void WebPageProxy::modelElementHasAudio(ModelIdentifier modelIdentifier, CompletionHandler<void(Expected<bool, ResourceError>)>&& completionHandler)
{
modelElementController()->hasAudioForModelElement(modelIdentifier, WTFMove(completionHandler));
}
void WebPageProxy::modelElementIsMuted(ModelIdentifier modelIdentifier, CompletionHandler<void(Expected<bool, ResourceError>)>&& completionHandler)
{
modelElementController()->isMutedForModelElement(modelIdentifier, WTFMove(completionHandler));
}
void WebPageProxy::modelElementSetIsMuted(ModelIdentifier modelIdentifier, bool isMuted, CompletionHandler<void(bool)>&& completionHandler)
{
modelElementController()->setIsMutedForModelElement(modelIdentifier, isMuted, WTFMove(completionHandler));
}
#endif
#if ENABLE(ARKIT_INLINE_PREVIEW_IOS)
void WebPageProxy::takeModelElementFullscreen(ModelIdentifier modelIdentifier)
{
modelElementController()->takeModelElementFullscreen(modelIdentifier);
}
#endif
#if ENABLE(ARKIT_INLINE_PREVIEW_MAC)
void WebPageProxy::modelElementDidCreatePreview(const URL& url, const String& uuid, const FloatSize& size, CompletionHandler<void(Expected<std::pair<String, uint32_t>, ResourceError>)>&& completionHandler)
{
modelElementController()->modelElementDidCreatePreview(url, uuid, size, WTFMove(completionHandler));
}
void WebPageProxy::handleMouseDownForModelElement(const String& uuid, const WebCore::LayoutPoint& locationInPageCoordinates, MonotonicTime timestamp)
{
modelElementController()->handleMouseDownForModelElement(uuid, locationInPageCoordinates, timestamp);
}
void WebPageProxy::handleMouseMoveForModelElement(const String& uuid, const WebCore::LayoutPoint& locationInPageCoordinates, MonotonicTime timestamp)
{
modelElementController()->handleMouseMoveForModelElement(uuid, locationInPageCoordinates, timestamp);
}
void WebPageProxy::handleMouseUpForModelElement(const String& uuid, const WebCore::LayoutPoint& locationInPageCoordinates, MonotonicTime timestamp)
{
modelElementController()->handleMouseUpForModelElement(uuid, locationInPageCoordinates, timestamp);
}
#endif
#if !PLATFORM(COCOA)
Vector<SandboxExtension::Handle> WebPageProxy::createNetworkExtensionsSandboxExtensions(WebProcessProxy& process)
{
return { };
}
void WebPageProxy::classifyModalContainerControls(Vector<String>&&, CompletionHandler<void(Vector<ModalContainerControlType>&&)>&& completion)
{
completion({ });
}
#endif
#if ENABLE(MEDIA_SESSION_COORDINATOR)
void WebPageProxy::createMediaSessionCoordinator(Ref<MediaSessionCoordinatorProxyPrivate>&& privateCoordinator, CompletionHandler<void(bool)>&& completionHandler)
{
sendWithAsyncReply(Messages::WebPage::CreateMediaSessionCoordinator(privateCoordinator->identifier()), [weakThis = WeakPtr { *this }, privateCoordinator = WTFMove(privateCoordinator), completionHandler = WTFMove(completionHandler)](bool success) mutable {
if (!weakThis || !success) {
completionHandler(false);
return;
}
weakThis->m_mediaSessionCoordinatorProxy = RemoteMediaSessionCoordinatorProxy::create(*weakThis, WTFMove(privateCoordinator));
completionHandler(true);
});
}
#endif
void WebPageProxy::requestScrollToRect(const FloatRect& targetRect, const FloatPoint& origin)
{
pageClient().requestScrollToRect(targetRect, origin);
}
void WebPageProxy::scrollToRect(const FloatRect& targetRect, const FloatPoint& origin)
{
send(Messages::WebPage::ScrollToRect(targetRect, origin));
}
bool WebPageProxy::shouldEnableCaptivePortalMode() const
{
return m_configuration->captivePortalModeEnabled();
}
#if PLATFORM(COCOA)
void WebPageProxy::appPrivacyReportTestingData(CompletionHandler<void(const AppPrivacyReportTestingData&)>&& completionHandler)
{
websiteDataStore().networkProcess().sendWithAsyncReply(Messages::NetworkProcess::AppPrivacyReportTestingData(m_websiteDataStore->sessionID()), WTFMove(completionHandler));
}
void WebPageProxy::clearAppPrivacyReportTestingData(CompletionHandler<void()>&& completionHandler)
{
websiteDataStore().networkProcess().sendWithAsyncReply(Messages::NetworkProcess::ClearAppPrivacyReportTestingData(m_websiteDataStore->sessionID()), WTFMove(completionHandler));
}
#endif
void WebPageProxy::requestCookieConsent(CompletionHandler<void(CookieConsentDecisionResult)>&& completion)
{
m_uiClient->requestCookieConsent(WTFMove(completion));
}
void WebPageProxy::decidePolicyForModalContainer(OptionSet<ModalContainerControlType> types, CompletionHandler<void(ModalContainerDecision)>&& completion)
{
m_uiClient->decidePolicyForModalContainer(types, WTFMove(completion));
}
} // namespace WebKit
#undef WEBPAGEPROXY_RELEASE_LOG
#undef WEBPAGEPROXY_RELEASE_LOG_ERROR
#undef MESSAGE_CHECK_COMPLETION
#undef MESSAGE_CHECK_URL
#undef MESSAGE_CHECK