blob: 274bb31344ef65dfe909692f17483894e70752a6 [file] [log] [blame]
/*
* Copyright (C) 2010-2022 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 "WebFoundTextRange.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 "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
#if HAVE(SC_CONTENT_SHARING_SESSION)
#import <WebCore/ScreenCaptureKitSharingSessionManager.h>
#endif
#if USE(QUICK_LOOK)
#include <WebCore/PreviewConverter.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)
#if ENABLE(VIDEO_PRESENTATION_MODE)
, m_fullscreenVideoTextRecognitionTimer(RunLoop::main(), this, &WebPageProxy::fullscreenVideoTextRecognitionTimerFired)
#endif
{
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::gpuProcessIdentifier() const
{
if (m_isClosed)
return 0;
#if ENABLE(GPU_PROCESS)
if (auto* gpuProcess = process().processPool().gpuProcess())
return gpuProcess->processIdentifier();
#endif
return 0;
}
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_shouldReloadDueToCrashWhenVisible = false;
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();
ASSERT(m_process->websiteDataStore());
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 { 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, std::nullopt));
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("/"_s, *(process.connection()->getAuditToken()))) {
createdExtension = true;
sandboxExtensionHandle = WTFMove(*handle);
}
} else
#endif
{
if (auto handle = SandboxExtension::createHandle("/"_s, SandboxExtension::Type::ReadOnly)) {
createdExtension = true;
sandboxExtensionHandle = WTFMove(*handle);
}
}
if (createdExtension) {
willAcquireUniversalFileReadSandboxExtension(process);
return;
}
#if PLATFORM(COCOA)
if (!linkedOnOrAfterSDKWithBehavior(SDKAlignedBehavior::NoUnconditionalUniversalSandboxExtension))
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, predictedUserAgentForRequest(loadParameters.request));
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 { 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 { 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 { 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 }, 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
}
enum class NavigationDirection { Backward, Forward };
static WebBackForwardListItem* itemSkippingBackForwardItemsAddedByJSWithoutUserGesture(const WebBackForwardList& backForwardList, NavigationDirection direction)
{
auto delta = direction == NavigationDirection::Backward ? -1 : 1;
int itemIndex = delta;
auto* item = backForwardList.itemAtIndex(itemIndex);
if (!item)
return nullptr;
#if PLATFORM(COCOA)
if (!linkedOnOrAfterSDKWithBehavior(SDKAlignedBehavior::UIBackForwardSkipsHistoryItemsWithoutUserGesture))
return item;
#endif
auto* originalItem = item;
while (item->wasCreatedByJSWithoutUserInteraction()) {
itemIndex += delta;
item = backForwardList.itemAtIndex(itemIndex);
if (!item)
return originalItem;
RELEASE_LOG(Loading, "UI Navigation is skipping a WebBackForwardListItem because it was added by JavaScript without user interaction");
}
return item;
}
RefPtr<API::Navigation> WebPageProxy::goForward()
{
WEBPAGEPROXY_RELEASE_LOG(Loading, "goForward:");
auto* forwardItem = itemSkippingBackForwardItemsAddedByJSWithoutUserGesture(m_backForwardList, NavigationDirection::Forward);
if (!forwardItem)
return nullptr;
return goToBackForwardItem(*forwardItem, FrameLoadType::Forward);
}
RefPtr<API::Navigation> WebPageProxy::goBack()
{
WEBPAGEPROXY_RELEASE_LOG(Loading, "goBack:");
auto* backItem = itemSkippingBackForwardItemsAddedByJSWithoutUserGesture(m_backForwardList, NavigationDirection::Backward);
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 { 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, std::nullopt));
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 (m_preferences->pdfJSViewerEnabled() && MIMETypeRegistry::isPDFMIMEType(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;
// We need to do this here instead of inside dispatchActivityStateChange() or viewIsBecomingVisible() because these don't run when the view doesn't
// have a running WebProcess. For the same reason, we need to rely on PageClient::isViewVisible() instead of WebPageProxy::isViewVisible().
if (m_potentiallyChangedActivityStateFlags & ActivityState::IsVisible && m_shouldReloadDueToCrashWhenVisible && pageClient().isViewVisible()) {
RunLoop::main().dispatch([this, weakThis = WeakPtr { *this }] {
if (weakThis && std::exchange(m_shouldReloadDueToCrashWhenVisible, false)) {
WEBPAGEPROXY_RELEASE_LOG(ViewState, "activityStateDidChange: view is becoming visible after a crash, attempt a reload");
tryReloadAfterProcessTermination();
}
});
}
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);
}
if (m_potentiallyChangedActivityStateFlags & ActivityState::IsConnectedToHardwareConsole)
isConnectedToHardwareConsoleDidChange();
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"_s, DOMPasteAccessCategory::General },
{ "PasteAndMatchStyle"_s, DOMPasteAccessCategory::General },
{ "PasteAsQuotation"_s, DOMPasteAccessCategory::General },
{ "PasteAsPlainText"_s, DOMPasteAccessCategory::General },
{ "PasteFont"_s, 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::isConnectedToHardwareConsoleDidChange()
{
SetForScope<bool> isProcessing(m_isProcessingIsConnectedToHardwareConsoleDidChangeNotification, true);
if (m_process->isConnectedToHardwareConsole()) {
if (!m_captureWasMutedWhenHardwareConsoleDisconnected)
setMediaStreamCaptureMuted(false);
m_captureWasMutedWhenHardwareConsoleDisconnected = false;
return;
}
m_captureWasMutedWhenHardwareConsoleDisconnected = m_mutedState.containsAny(WebCore::MediaProducer::MediaStreamCaptureIsMuted);
setMediaStreamCaptureMuted(true);
}
bool WebPageProxy::isAllowedToChangeMuteState() const
{
return m_isProcessingIsConnectedToHardwareConsoleDidChangeNotification || m_process->isConnectedToHardwareConsole();
}
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 = linkedOnOrAfterSDKWithBehavior(SDKAlignedBehavior::BlanksViewOnJSPrompt);
#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, IntPoint&& dragImageHotspot)
{
RefPtr<ShareableBitmap> dragImage = !dragImageHandle.isNull() ? ShareableBitmap::create(dragImageHandle) : nullptr;
pageClient().startDrag(WTFMove(selectionData), dragOperationMask, WTFMove(dragImage), WTFMove(dragImageHotspot));
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 }, process().auditToken(), SandboxExtension::MachBootstrapOptions::EnableMachBootstrap);
#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();