blob: 6a427efc6932d5dd73942e2ed9e65d4ae55a46e1 [file] [log] [blame]
/*
* Copyright (C) 2006-2025 Apple Inc. All rights reserved.
* Copyright (C) 2008 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "config.h"
#include "Page.h"
#include "ActivityStateChangeObserver.h"
#include "AdvancedPrivacyProtections.h"
#include "AlternativeTextClient.h"
#include "AnchorPositionEvaluator.h"
#include "AnimationFrameRate.h"
#include "AnimationTimelinesController.h"
#include "AppHighlightStorage.h"
#include "ApplicationCacheStorage.h"
#include "ArchiveResource.h"
#include "AsyncNodeDeletionQueueInlines.h"
#include "AttachmentElementClient.h"
#include "AuthenticatorCoordinator.h"
#include "AuthenticatorCoordinatorClient.h"
#include "BackForwardCache.h"
#include "BackForwardClient.h"
#include "BackForwardController.h"
#include "BadgeClient.h"
#include "BoundaryPointInlines.h"
#include "BroadcastChannelRegistry.h"
#include "CacheStorageProvider.h"
#include "CachedImage.h"
#include "CachedResourceLoader.h"
#include "Chrome.h"
#include "ChromeClient.h"
#include "CommonAtomStrings.h"
#include "CommonVM.h"
#include "ConstantPropertyMap.h"
#include "ContainerNodeInlines.h"
#include "ContextMenuClient.h"
#include "ContextMenuController.h"
#include "CookieJar.h"
#include "CredentialRequestCoordinator.h"
#include "CryptoClient.h"
#include "DOMRect.h"
#include "DOMRectList.h"
#include "DatabaseProvider.h"
#include "DebugOverlayRegions.h"
#include "DebugPageOverlays.h"
#include "DiagnosticLoggingClient.h"
#include "DiagnosticLoggingKeys.h"
#include "DisplayRefreshMonitorManager.h"
#include "DocumentFullscreen.h"
#include "DocumentInlines.h"
#include "DocumentLoader.h"
#include "DocumentMarkerController.h"
#include "DocumentSyncData.h"
#include "DragController.h"
#include "Editing.h"
#include "Editor.h"
#include "EditorClient.h"
#include "ElementTargetingController.h"
#include "EmptyClients.h"
#include "Event.h"
#include "EventHandler.h"
#include "EventLoop.h"
#include "EventNames.h"
#include "ExtensionStyleSheets.h"
#include "FilterRenderingMode.h"
#include "FixedContainerEdges.h"
#include "FocusController.h"
#include "FontCache.h"
#include "FragmentDirectiveGenerator.h"
#include "FrameLoader.h"
#include "FrameSelection.h"
#include "FrameTree.h"
#include "GeolocationController.h"
#include "HTMLElement.h"
#include "HTMLImageElement.h"
#include "HTMLMediaElement.h"
#include "HTMLTextAreaElement.h"
#include "HTMLTextFormControlElement.h"
#include "HistoryController.h"
#include "HistoryItem.h"
#include "IDBConnectionToServer.h"
#include "ImageAnalysisQueue.h"
#include "ImageOverlay.h"
#include "ImageOverlayController.h"
#include "InspectorClient.h"
#include "InspectorController.h"
#include "InspectorInstrumentation.h"
#include "IntelligenceTextEffectsSupport.h"
#include "LayoutDisallowedScope.h"
#include "LegacySchemeRegistry.h"
#include "LoaderStrategy.h"
#include "LocalFrameLoaderClient.h"
#include "LocalFrameView.h"
#include "LogInitialization.h"
#include "Logging.h"
#include "LoginStatus.h"
#include "LowPowerModeNotifier.h"
#include "MediaCanStartListener.h"
#include "MemoryCache.h"
#include "ModelPlayerProvider.h"
#include "NavigationScheduler.h"
#include "Navigator.h"
#include "NavigatorGamepad.h"
#include "OpportunisticTaskScheduler.h"
#include "PageColorSampler.h"
#include "PageConfiguration.h"
#include "PageConsoleClient.h"
#include "PageDebuggable.h"
#include "PageGroup.h"
#include "PageOverlayController.h"
#include "PaymentCoordinator.h"
#include "PerformanceLogging.h"
#include "PerformanceLoggingClient.h"
#include "PerformanceMonitor.h"
#include "PlatformMediaSessionManager.h"
#include "PlatformScreen.h"
#include "PlatformStrategies.h"
#include "PluginData.h"
#include "PluginInfoProvider.h"
#include "PluginViewBase.h"
#include "PointerCaptureController.h"
#include "PointerLockController.h"
#include "ProcessSyncClient.h"
#include "ProcessSyncData.h"
#include "ProgressTracker.h"
#include "RTCController.h"
#include "Range.h"
#include "RemoteFrame.h"
#include "RenderDescendantIterator.h"
#include "RenderElementInlines.h"
#include "RenderImage.h"
#include "RenderLayerCompositor.h"
#include "RenderObjectInlines.h"
#include "RenderTheme.h"
#include "RenderView.h"
#include "RenderWidget.h"
#include "RenderingUpdateScheduler.h"
#include "ResizeObserver.h"
#include "ResourceLoadObserver.h"
#include "ResourceUsageOverlay.h"
#include "SVGDocumentExtensions.h"
#include "SVGImage.h"
#include "ScreenOrientationManager.h"
#include "ScriptController.h"
#include "ScriptDisallowedScope.h"
#include "ScriptRunner.h"
#include "ScriptedAnimationController.h"
#include "ScrollLatchingController.h"
#include "ScrollingCoordinator.h"
#include "ServiceWorkerGlobalScope.h"
#include "Settings.h"
#include "SharedBuffer.h"
#include "SocketProvider.h"
#include "SpeechRecognitionProvider.h"
#include "SpeechSynthesisClient.h"
#include "StorageArea.h"
#include "StorageNamespace.h"
#include "StorageNamespaceProvider.h"
#include "StorageProvider.h"
#include "StyleAdjuster.h"
#include "StyleResolver.h"
#include "StyleScope.h"
#include "SubframeLoader.h"
#include "SubresourceLoader.h"
#include "TextExtraction.h"
#include "TextIterator.h"
#include "TextRecognitionResult.h"
#include "TextResourceDecoder.h"
#include "ThermalMitigationNotifier.h"
#include "UserContentProvider.h"
#include "UserContentURLPattern.h"
#include "UserMediaController.h"
#include "UserScript.h"
#include "UserStyleSheet.h"
#include "ValidationMessageClient.h"
#include "VisibilityState.h"
#include "VisitedLinkState.h"
#include "VisitedLinkStore.h"
#include "VoidCallback.h"
#include "WebCoreJSClientData.h"
#include "WebRTCProvider.h"
#include "WheelEventDeltaFilter.h"
#include "WheelEventTestMonitor.h"
#include "Widget.h"
#include "WindowEventLoop.h"
#include "WindowFeatures.h"
#include "WorkerOrWorkletScriptController.h"
#include <JavaScriptCore/VM.h>
#include <ranges>
#include <wtf/FileSystem.h>
#include <wtf/RefCountedLeakCounter.h>
#include <wtf/StdLibExtras.h>
#include <wtf/SystemTracing.h>
#include <wtf/TZoneMallocInlines.h>
#include <wtf/text/Base64.h>
#include <wtf/text/MakeString.h>
#include <wtf/text/StringHash.h>
#include <wtf/text/TextStream.h>
#if ENABLE(APPLE_PAY_AMS_UI)
#include "ApplePayAMSUIPaymentHandler.h"
#endif
#if ENABLE(WIRELESS_PLAYBACK_TARGET)
#include "HTMLVideoElement.h"
#include "MediaPlaybackTarget.h"
#endif
#if PLATFORM(MAC)
#include "ServicesOverlayController.h"
#endif
#if ENABLE(MEDIA_SESSION_COORDINATOR)
#include "MediaSessionCoordinator.h"
#include "NavigatorMediaSession.h"
#endif
#if USE(ATSPI)
#include "AccessibilityRootAtspi.h"
#endif
#if ENABLE(WRITING_TOOLS)
#include "WritingToolsController.h"
#endif
#if ENABLE(WEBXR)
#include "NavigatorWebXR.h"
#include "WebXRSession.h"
#include "WebXRSystem.h"
#endif
#if PLATFORM(VISION) && ENABLE(GAMEPAD)
#include "GamepadManager.h"
#endif
namespace WebCore {
WTF_MAKE_TZONE_ALLOCATED_IMPL(Page);
static UncheckedKeyHashSet<WeakRef<Page>>& allPages()
{
static NeverDestroyed<UncheckedKeyHashSet<WeakRef<Page>>> set;
return set;
}
static unsigned gNonUtilityPageCount { 0 };
static inline bool isUtilityPageChromeClient(ChromeClient& chromeClient)
{
return chromeClient.isEmptyChromeClient() || chromeClient.isSVGImageChromeClient();
}
unsigned Page::nonUtilityPageCount()
{
return gNonUtilityPageCount;
}
DEFINE_DEBUG_ONLY_GLOBAL(WTF::RefCountedLeakCounter, pageCounter, ("Page"));
void Page::forEachPage(NOESCAPE const Function<void(Page&)>& function)
{
for (auto& page : allPages())
function(Ref { page.get() });
}
void Page::updateValidationBubbleStateIfNeeded()
{
if (auto* client = validationMessageClient())
client->updateValidationBubbleStateIfNeeded();
}
void Page::scheduleValidationMessageUpdate(ValidatedFormListedElement& element, HTMLElement& anchor)
{
m_validationMessageUpdates.append({ element, anchor });
}
void Page::updateValidationMessages()
{
for (auto& item : std::exchange(m_validationMessageUpdates, { })) {
if (RefPtr anchor = item.second.get())
Ref { item.first }->updateVisibleValidationMessage(*anchor);
}
}
static void networkStateChanged(bool isOnLine)
{
Vector<Ref<LocalFrame>> frames;
// Get all the frames of all the pages in all the page groups
for (auto& page : allPages()) {
for (RefPtr frame = page->mainFrame(); frame; frame = frame->tree().traverseNext()) {
if (RefPtr localFrame = dynamicDowncast<LocalFrame>(*frame))
frames.append(*localFrame);
}
}
auto& eventName = isOnLine ? eventNames().onlineEvent : eventNames().offlineEvent;
for (auto& frame : frames) {
if (RefPtr document = frame->document())
document->dispatchWindowEvent(Event::create(eventName, Event::CanBubble::No, Event::IsCancelable::No));
}
}
static constexpr OptionSet<ActivityState> pageInitialActivityState()
{
return { ActivityState::IsVisible, ActivityState::IsInWindow };
}
// FIXME: workaround for GCC bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=115135
#if COMPILER(GCC) && CPU(ARM64)
#define GCC_MAYBE_NO_INLINE NEVER_INLINE
#else
#define GCC_MAYBE_NO_INLINE
#endif
GCC_MAYBE_NO_INLINE static Ref<Frame> createMainFrame(Page& page, PageConfiguration::MainFrameCreationParameters&& clientCreator, RefPtr<Frame> mainFrameOpener, FrameIdentifier identifier, Ref<FrameTreeSyncData>&& frameTreeSyncData)
{
page.relaxAdoptionRequirement();
return switchOn(WTFMove(clientCreator), [&] (PageConfiguration::LocalMainFrameCreationParameters&& creationParameters) -> Ref<Frame> {
return LocalFrame::createMainFrame(page, WTFMove(creationParameters.clientCreator), identifier, creationParameters.effectiveSandboxFlags, mainFrameOpener.get(), WTFMove(frameTreeSyncData));
}, [&] (CompletionHandler<UniqueRef<RemoteFrameClient>(RemoteFrame&)>&& remoteFrameClientCreator) -> Ref<Frame> {
return RemoteFrame::createMainFrame(page, WTFMove(remoteFrameClientCreator), identifier, mainFrameOpener.get(), WTFMove(frameTreeSyncData));
});
}
Ref<Page> Page::create(PageConfiguration&& pageConfiguration)
{
return adoptRef(*new Page(WTFMove(pageConfiguration)));
}
struct Page::Internals {
WTF_MAKE_STRUCT_FAST_ALLOCATED;
Region topRelevantPaintedRegion;
Region bottomRelevantPaintedRegion;
Region relevantUnpaintedRegion;
};
Page::Page(PageConfiguration&& pageConfiguration)
: m_internals(makeUniqueRef<Internals>())
, m_identifier(pageConfiguration.identifier)
, m_chrome(makeUniqueRef<Chrome>(*this, WTFMove(pageConfiguration.chromeClient)))
, m_dragCaretController(makeUniqueRef<DragCaretController>())
#if ENABLE(DRAG_SUPPORT)
, m_dragController(makeUniqueRef<DragController>(*this, WTFMove(pageConfiguration.dragClient)))
#endif
, m_focusController(makeUnique<FocusController>(*this, pageInitialActivityState()))
#if ENABLE(CONTEXT_MENUS)
, m_contextMenuController(makeUniqueRef<ContextMenuController>(*this, WTFMove(pageConfiguration.contextMenuClient)))
#endif
, m_inspectorController(makeUniqueRefWithoutRefCountedCheck<InspectorController>(*this, WTFMove(pageConfiguration.inspectorClient)))
, m_pointerCaptureController(makeUniqueRef<PointerCaptureController>(*this))
#if ENABLE(POINTER_LOCK)
, m_pointerLockController(makeUniqueRef<PointerLockController>(*this))
#endif
, m_elementTargetingController(makeUniqueRef<ElementTargetingController>(*this))
, m_settings(Settings::create(this))
, m_cryptoClient(WTFMove(pageConfiguration.cryptoClient))
, m_progress(makeUniqueRef<ProgressTracker>(*this, WTFMove(pageConfiguration.progressTrackerClient)))
, m_processSyncClient(WTFMove(pageConfiguration.processSyncClient))
, m_backForwardController(makeUniqueRef<BackForwardController>(*this, WTFMove(pageConfiguration.backForwardClient)))
, m_editorClient(WTFMove(pageConfiguration.editorClient))
, m_mainFrame(createMainFrame(*this, WTFMove(pageConfiguration.mainFrameCreationParameters), WTFMove(pageConfiguration.mainFrameOpener), pageConfiguration.mainFrameIdentifier, FrameTreeSyncData::create()))
, m_validationMessageClient(WTFMove(pageConfiguration.validationMessageClient))
, m_diagnosticLoggingClient(WTFMove(pageConfiguration.diagnosticLoggingClient))
, m_performanceLoggingClient(WTFMove(pageConfiguration.performanceLoggingClient))
#if ENABLE(SPEECH_SYNTHESIS)
, m_speechSynthesisClient(WTFMove(pageConfiguration.speechSynthesisClient))
#endif
, m_speechRecognitionProvider((WTFMove(pageConfiguration.speechRecognitionProvider)))
, m_webRTCProvider(WTFMove(pageConfiguration.webRTCProvider))
, m_rtcController(RTCController::create())
#if PLATFORM(IOS_FAMILY)
, m_canShowWhileLocked(pageConfiguration.canShowWhileLocked)
#endif
, m_domTimerAlignmentInterval(DOMTimer::defaultAlignmentInterval())
, m_domTimerAlignmentIntervalIncreaseTimer(*this, &Page::domTimerAlignmentIntervalIncreaseTimerFired)
, m_activityState(pageInitialActivityState())
, m_alternativeTextClient(WTFMove(pageConfiguration.alternativeTextClient))
, m_consoleClient(makeUniqueRef<PageConsoleClient>(*this))
#if ENABLE(REMOTE_INSPECTOR)
, m_inspectorDebuggable(PageDebuggable::create(*this))
#endif
, m_socketProvider(WTFMove(pageConfiguration.socketProvider))
, m_cookieJar(WTFMove(pageConfiguration.cookieJar))
, m_applicationCacheStorage(WTFMove(pageConfiguration.applicationCacheStorage))
, m_cacheStorageProvider(WTFMove(pageConfiguration.cacheStorageProvider))
, m_databaseProvider(*WTFMove(pageConfiguration.databaseProvider))
, m_pluginInfoProvider(*WTFMove(pageConfiguration.pluginInfoProvider))
, m_storageNamespaceProvider(*WTFMove(pageConfiguration.storageNamespaceProvider))
, m_userContentProvider(WTFMove(pageConfiguration.userContentProvider))
, m_screenOrientationManager(WTFMove(pageConfiguration.screenOrientationManager))
, m_visitedLinkStore(*WTFMove(pageConfiguration.visitedLinkStore))
, m_broadcastChannelRegistry(WTFMove(pageConfiguration.broadcastChannelRegistry))
, m_sessionID(pageConfiguration.sessionID)
#if ENABLE(VIDEO)
, m_playbackControlsManagerUpdateTimer(*this, &Page::playbackControlsManagerUpdateTimerFired)
#endif
, m_isUtilityPage(isUtilityPageChromeClient(chrome().client()))
, m_performanceMonitor(isUtilityPage() ? nullptr : makeUniqueWithoutRefCountedCheck<PerformanceMonitor>(*this))
, m_lowPowerModeNotifier(makeUnique<LowPowerModeNotifier>([this](bool isLowPowerModeEnabled) { handleLowPowerModeChange(isLowPowerModeEnabled); }))
, m_thermalMitigationNotifier(makeUnique<ThermalMitigationNotifier>([this](bool thermalMitigationEnabled) { handleThermalMitigationChange(thermalMitigationEnabled); }))
, m_performanceLogging(makeUnique<PerformanceLogging>(*this))
#if PLATFORM(MAC) && (ENABLE(SERVICE_CONTROLS) || ENABLE(TELEPHONE_NUMBER_DETECTION))
, m_servicesOverlayController(makeUniqueRefWithoutRefCountedCheck<ServicesOverlayController>(*this))
#endif
, m_recentWheelEventDeltaFilter(WheelEventDeltaFilter::create())
, m_pageOverlayController(makeUnique<PageOverlayController>(*this))
#if ENABLE(APPLE_PAY)
, m_paymentCoordinator(PaymentCoordinator::create(WTFMove(pageConfiguration.paymentCoordinatorClient)))
#endif
#if ENABLE(WEB_AUTHN)
, m_authenticatorCoordinator(makeUniqueRef<AuthenticatorCoordinator>(WTFMove(pageConfiguration.authenticatorCoordinatorClient)))
#endif
#if HAVE(DIGITAL_CREDENTIALS_UI)
, m_credentialRequestCoordinator(CredentialRequestCoordinator::create(WTFMove(pageConfiguration.credentialRequestCoordinatorClient), *this))
#endif
#if ENABLE(APPLICATION_MANIFEST)
, m_applicationManifest(pageConfiguration.applicationManifest)
#endif
#if ENABLE(DEVICE_ORIENTATION) && PLATFORM(IOS_FAMILY)
, m_deviceOrientationUpdateProvider(WTFMove(pageConfiguration.deviceOrientationUpdateProvider))
#endif
, m_corsDisablingPatterns(WTFMove(pageConfiguration.corsDisablingPatterns))
, m_maskedURLSchemes(WTFMove(pageConfiguration.maskedURLSchemes))
, m_allowedNetworkHosts(WTFMove(pageConfiguration.allowedNetworkHosts))
, m_loadsSubresources(pageConfiguration.loadsSubresources)
, m_shouldRelaxThirdPartyCookieBlocking(pageConfiguration.shouldRelaxThirdPartyCookieBlocking)
, m_fixedContainerEdgesAndElements(std::make_pair(makeUniqueRef<FixedContainerEdges>(), WeakElementEdges { }))
, m_httpsUpgradeEnabled(pageConfiguration.httpsUpgradeEnabled)
, m_portsForUpgradingInsecureSchemeForTesting(WTFMove(pageConfiguration.portsForUpgradingInsecureSchemeForTesting))
, m_storageProvider(WTFMove(pageConfiguration.storageProvider))
, m_modelPlayerProvider(WTFMove(pageConfiguration.modelPlayerProvider))
#if ENABLE(ATTACHMENT_ELEMENT)
, m_attachmentElementClient(WTFMove(pageConfiguration.attachmentElementClient))
#endif
, m_opportunisticTaskScheduler(OpportunisticTaskScheduler::create(*this))
, m_contentSecurityPolicyModeForExtension(WTFMove(pageConfiguration.contentSecurityPolicyModeForExtension))
, m_badgeClient(WTFMove(pageConfiguration.badgeClient))
, m_historyItemClient(WTFMove(pageConfiguration.historyItemClient))
#if PLATFORM(VISION) && ENABLE(GAMEPAD)
, m_gamepadAccessRequiresExplicitConsent(pageConfiguration.gamepadAccessRequiresExplicitConsent)
#endif
#if ENABLE(WRITING_TOOLS)
, m_writingToolsController(makeUniqueRef<WritingToolsController>(*this))
#endif
, m_activeNowPlayingSessionUpdateTimer(*this, &Page::updateActiveNowPlayingSessionNow)
, m_topDocumentSyncData(DocumentSyncData::create())
#if HAVE(AUDIT_TOKEN)
, m_presentingApplicationAuditToken(WTFMove(pageConfiguration.presentingApplicationAuditToken))
#endif
#if PLATFORM(COCOA)
, m_presentingApplicationBundleIdentifier(WTFMove(pageConfiguration.presentingApplicationBundleIdentifier))
#endif
{
updateTimerThrottlingState();
protectedPluginInfoProvider()->addPage(*this);
protectedUserContentProvider()->addPage(*this);
protectedVisitedLinkStore()->addPage(*this);
static bool firstTimeInitializationRan = false;
if (!firstTimeInitializationRan) {
firstTimeInitialization();
firstTimeInitializationRan = true;
}
ASSERT(!allPages().contains(*this));
allPages().add(*this);
if (!isUtilityPage()) {
++gNonUtilityPageCount;
MemoryPressureHandler::setPageCount(gNonUtilityPageCount);
}
#ifndef NDEBUG
pageCounter.increment();
#endif
protectedStorageNamespaceProvider()->setSessionStorageQuota(m_settings->sessionStorageQuota());
#if ENABLE(REMOTE_INSPECTOR)
if (m_inspectorController->inspectorClient() && m_inspectorController->inspectorClient()->allowRemoteInspectionToPageDirectly())
m_inspectorDebuggable->init();
#endif
#if PLATFORM(COCOA)
platformInitialize();
#endif
#if PLATFORM(VISION) && ENABLE(GAMEPAD)
initializeGamepadAccessForPageLoad();
#endif
settingsDidChange();
if (m_lowPowerModeNotifier->isLowPowerModeEnabled())
m_throttlingReasons.add(ThrottlingReason::LowPowerMode);
if (m_thermalMitigationNotifier->thermalMitigationEnabled()) {
m_throttlingReasons.add(ThrottlingReason::ThermalMitigation);
m_throttlingReasons.set(ThrottlingReason::AggressiveThermalMitigation, settings().respondToThermalPressureAggressively());
}
}
Page::~Page()
{
m_validationMessageClient = nullptr;
m_diagnosticLoggingClient = nullptr;
m_performanceLoggingClient = nullptr;
protectedMainFrame()->disconnectView();
setGroupName(String());
allPages().remove(*this);
if (!isUtilityPage()) {
--gNonUtilityPageCount;
MemoryPressureHandler::setPageCount(gNonUtilityPageCount);
}
m_inspectorController->inspectedPageDestroyed();
#if ENABLE(REMOTE_INSPECTOR)
m_inspectorDebuggable->detachFromPage();
#endif
forEachLocalFrame([] (LocalFrame& frame) {
frame.willDetachPage();
frame.detachFromPage();
});
ASSERT(m_rootFrames.isEmpty());
if (RefPtr scrollingCoordinator = m_scrollingCoordinator)
scrollingCoordinator->pageDestroyed();
#if ENABLE(RESOURCE_USAGE)
if (RefPtr resourceUsageOverlay = m_resourceUsageOverlay)
resourceUsageOverlay->detachFromPage();
#endif
checkedBackForward()->close();
if (!isUtilityPage())
BackForwardCache::singleton().removeAllItemsForPage(*this);
#ifndef NDEBUG
pageCounter.decrement();
#endif
protectedPluginInfoProvider()->removePage(*this);
protectedUserContentProvider()->removePage(*this);
protectedVisitedLinkStore()->removePage(*this);
}
CheckedRef<BackForwardController> Page::checkedBackForward()
{
return m_backForwardController.get();
}
void Page::firstTimeInitialization()
{
platformStrategies()->loaderStrategy()->addOnlineStateChangeListener(&networkStateChanged);
FontCache::registerFontCacheInvalidationCallback([] {
updateStyleForAllPagesAfterGlobalChangeInEnvironment();
});
}
void Page::clearPreviousItemFromAllPages(BackForwardItemIdentifier itemID)
{
for (auto& page : allPages()) {
RefPtr localMainFrame = page->localMainFrame();
if (!localMainFrame)
return;
Ref controller = localMainFrame->loader().history();
if (controller->previousItem() && controller->previousItem()->itemID() == itemID) {
controller->clearPreviousItem();
return;
}
}
}
uint64_t Page::renderTreeSize() const
{
uint64_t total = 0;
forEachDocument([&] (Document& document) {
if (CheckedPtr renderView = document.renderView())
total += renderView->rendererCount();
});
return total;
}
void Page::destroyRenderTrees()
{
// When closing or entering back/forward cache, tear down the render tree before setting the in-cache flag.
// This maintains the invariant that render trees are never present in the back/forward cache or outliving the page.
// Note that destruction happens bottom-up so that the main frame's tree dies last.
for (RefPtr frame = m_mainFrame->tree().traversePrevious(CanWrap::Yes); frame; frame = frame->tree().traversePrevious(CanWrap::No)) {
RefPtr localFrame = dynamicDowncast<LocalFrame>(*frame);
if (!localFrame)
continue;
if (!localFrame->document())
continue;
Ref document = *localFrame->document();
if (document->hasLivingRenderTree())
document->destroyRenderTree();
}
}
OptionSet<DisabledAdaptations> Page::disabledAdaptations() const
{
if (RefPtr localTopDocument = this->localTopDocument())
return localTopDocument->disabledAdaptations();
return { };
}
static RefPtr<Document> viewportDocumentForFrame(const Frame& frame)
{
RefPtr localFrame = dynamicDowncast<LocalFrame>(frame);
if (!localFrame)
return nullptr;
RefPtr document = localFrame->document();
if (!document)
return nullptr;
RefPtr page = localFrame->page();
if (!page)
return nullptr;
if (RefPtr fullscreenDocument = page->outermostFullscreenDocument())
return fullscreenDocument;
return document;
}
ViewportArguments Page::viewportArguments() const
{
if (RefPtr document = viewportDocumentForFrame(protectedMainFrame()))
return document->viewportArguments();
return ViewportArguments();
}
void Page::setOverrideViewportArguments(const std::optional<ViewportArguments>& viewportArguments)
{
std::optional<ViewportArguments> oldArguments = m_overrideViewportArguments ? std::optional(*m_overrideViewportArguments) : std::nullopt;
if (oldArguments == viewportArguments)
return;
m_overrideViewportArguments = viewportArguments ? makeUnique<ViewportArguments>(*viewportArguments) : nullptr;
if (RefPtr localTopDocument = this->localTopDocument())
localTopDocument->updateViewportArguments();
}
ScrollingCoordinator* Page::scrollingCoordinator()
{
if (!m_scrollingCoordinator && m_settings->scrollingCoordinatorEnabled()) {
m_scrollingCoordinator = chrome().client().createScrollingCoordinator(*this);
if (!m_scrollingCoordinator)
m_scrollingCoordinator = ScrollingCoordinator::create(this);
protectedScrollingCoordinator()->windowScreenDidChange(m_displayID, m_displayNominalFramesPerSecond);
}
return m_scrollingCoordinator.get();
}
RefPtr<ScrollingCoordinator> Page::protectedScrollingCoordinator()
{
return scrollingCoordinator();
}
String Page::scrollingStateTreeAsText()
{
RefPtr localMainFrame = dynamicDowncast<LocalFrame>(m_mainFrame.get());
if (RefPtr document = localMainFrame ? localMainFrame->document() : nullptr) {
if (RefPtr frameView = document->view())
frameView->updateLayoutAndStyleIfNeededRecursive(LayoutOptions::UpdateCompositingLayers);
#if ENABLE(IOS_TOUCH_EVENTS)
document->updateTouchEventRegions();
#endif
}
if (RefPtr scrollingCoordinator = this->scrollingCoordinator())
return scrollingCoordinator->scrollingStateTreeAsText();
return String();
}
String Page::synchronousScrollingReasonsAsText()
{
RefPtr localMainFrame = dynamicDowncast<LocalFrame>(m_mainFrame.get());
if (RefPtr document = localMainFrame ? localMainFrame->document() : nullptr)
document->updateLayout();
if (RefPtr scrollingCoordinator = this->scrollingCoordinator())
return scrollingCoordinator->synchronousScrollingReasonsAsText();
return { };
}
Ref<DOMRectList> Page::nonFastScrollableRectsForTesting()
{
RefPtr localMainFrame = dynamicDowncast<LocalFrame>(m_mainFrame.get());
if (RefPtr document = localMainFrame ? localMainFrame->document() : nullptr) {
document->updateLayout();
#if ENABLE(IOS_TOUCH_EVENTS)
document->updateTouchEventRegions();
#endif
}
Vector<IntRect> rects;
if (RefPtr scrollingCoordinator = this->scrollingCoordinator()) {
const EventTrackingRegions& eventTrackingRegions = scrollingCoordinator->absoluteEventTrackingRegions();
for (const auto& synchronousEventRegion : eventTrackingRegions.eventSpecificSynchronousDispatchRegions)
rects.appendVector(synchronousEventRegion.value.rects());
}
Vector<FloatQuad> quads(rects.size());
for (size_t i = 0; i < rects.size(); ++i)
quads[i] = FloatRect(rects[i]);
return DOMRectList::create(quads);
}
Ref<DOMRectList> Page::touchEventRectsForEventForTesting(EventTrackingRegions::EventType eventType)
{
RefPtr localMainFrame = dynamicDowncast<LocalFrame>(m_mainFrame.get());
if (RefPtr document = localMainFrame ? localMainFrame->document() : nullptr) {
document->updateLayout();
#if ENABLE(IOS_TOUCH_EVENTS)
document->updateTouchEventRegions();
#endif
}
Vector<IntRect> rects;
if (RefPtr scrollingCoordinator = this->scrollingCoordinator()) {
const EventTrackingRegions& eventTrackingRegions = scrollingCoordinator->absoluteEventTrackingRegions();
const auto& region = eventTrackingRegions.eventSpecificSynchronousDispatchRegions.get(eventType);
rects.appendVector(region.rects());
}
Vector<FloatQuad> quads(rects.size());
for (size_t i = 0; i < rects.size(); ++i)
quads[i] = FloatRect(rects[i]);
return DOMRectList::create(quads);
}
Ref<DOMRectList> Page::passiveTouchEventListenerRectsForTesting()
{
RefPtr localMainFrame = dynamicDowncast<LocalFrame>(m_mainFrame.get());
if (RefPtr document = localMainFrame ? localMainFrame->document() : nullptr) {
document->updateLayout();
#if ENABLE(IOS_TOUCH_EVENTS)
document->updateTouchEventRegions();
#endif
}
Vector<IntRect> rects;
if (RefPtr scrollingCoordinator = this->scrollingCoordinator())
rects.appendVector(scrollingCoordinator->absoluteEventTrackingRegions().asynchronousDispatchRegion.rects());
Vector<FloatQuad> quads(rects.size());
for (size_t i = 0; i < rects.size(); ++i)
quads[i] = FloatRect(rects[i]);
return DOMRectList::create(quads);
}
void Page::settingsDidChange()
{
#if ENABLE(WEB_RTC)
m_webRTCProvider->setH265Support(settings().webRTCH265CodecEnabled());
m_webRTCProvider->setVP9Support(settings().webRTCVP9Profile0CodecEnabled(), settings().webRTCVP9Profile2CodecEnabled());
m_webRTCProvider->setAV1Support(settings().webRTCAV1CodecEnabled());
m_webRTCProvider->setPortAllocatorRange(settings().webRTCUDPPortRange());
#endif
}
std::optional<AXTreeData> Page::accessibilityTreeData() const
{
RefPtr localTopDocument = this->localTopDocument();
if (!localTopDocument)
return std::nullopt;
if (CheckedPtr cache = localTopDocument->existingAXObjectCache())
return { cache->treeData() };
return std::nullopt;
}
void Page::progressEstimateChanged(LocalFrame& frameWithProgressUpdate) const
{
if (RefPtr document = frameWithProgressUpdate.document()) {
if (CheckedPtr axObjectCache = document->existingAXObjectCache())
axObjectCache->updateLoadingProgress(progress().estimatedProgress());
}
}
void Page::progressFinished(LocalFrame& frameWithCompletedProgress) const
{
if (RefPtr document = frameWithCompletedProgress.document()) {
if (CheckedPtr axObjectCache = document->existingAXObjectCache())
axObjectCache->loadingFinished();
}
}
void Page::setMainFrame(Ref<Frame>&& frame)
{
m_mainFrame = WTFMove(frame);
RefPtr<Document> document;
if (RefPtr localFrame = dynamicDowncast<LocalFrame>(m_mainFrame.get()))
document = localFrame->document();
m_topDocumentSyncData = document ? document->syncData() : DocumentSyncData::create();
}
void Page::setMainFrameURLAndOrigin(const URL& url, RefPtr<SecurityOrigin>&& origin)
{
// This URL and SecurityOrigin is relevant to this Page only if it is not
// directly hosting the local main frame.
RefPtr localFrame = dynamicDowncast<LocalFrame>(m_mainFrame.get());
if (!localFrame) {
m_topDocumentSyncData->documentURL = url;
if (!origin)
origin = SecurityOrigin::create(url);
m_topDocumentSyncData->documentSecurityOrigin = WTFMove(origin);
return;
}
if (!settings().siteIsolationEnabled())
return;
// If this page is hosting the local main frame, make sure the url and origin
// match what we expect, then broadcast them out to other processes.
RELEASE_ASSERT(url == m_topDocumentSyncData->documentURL);
if (!origin)
RELEASE_ASSERT(!m_topDocumentSyncData->documentSecurityOrigin);
processSyncClient().broadcastTopDocumentSyncDataToOtherProcesses(m_topDocumentSyncData.get());
}
void Page::setIsClosing()
{
m_topDocumentSyncData->isClosing = true;
if (settings().siteIsolationEnabled())
processSyncClient().broadcastIsClosingToOtherProcesses(true);
}
bool Page::isClosing() const
{
return m_topDocumentSyncData->isClosing;
}
#if ENABLE(DOM_AUDIO_SESSION)
void Page::setAudioSessionType(DOMAudioSessionType audioSessionType)
{
m_topDocumentSyncData->audioSessionType = audioSessionType;
if (settings().siteIsolationEnabled())
processSyncClient().broadcastAudioSessionTypeToOtherProcesses(audioSessionType);
}
DOMAudioSessionType Page::audioSessionType() const
{
return m_topDocumentSyncData->audioSessionType;
}
#endif
void Page::setUserDidInteractWithPage(bool didInteract)
{
if (m_topDocumentSyncData->userDidInteractWithPage == didInteract)
return;
m_topDocumentSyncData->userDidInteractWithPage = didInteract;
if (settings().siteIsolationEnabled())
processSyncClient().broadcastUserDidInteractWithPageToOtherProcesses(didInteract);
}
bool Page::userDidInteractWithPage() const
{
return m_topDocumentSyncData->userDidInteractWithPage;
}
void Page::setAutofocusProcessed()
{
if (m_topDocumentSyncData->isAutofocusProcessed)
return;
m_topDocumentSyncData->isAutofocusProcessed = true;
if (settings().siteIsolationEnabled())
processSyncClient().broadcastIsAutofocusProcessedToOtherProcesses(true);
}
bool Page::autofocusProcessed() const
{
return m_topDocumentSyncData->isAutofocusProcessed;
}
bool Page::topDocumentHasDocumentClass(DocumentClass documentClass) const
{
return m_topDocumentSyncData->documentClasses.contains(documentClass);
}
bool Page::hasInjectedUserScript()
{
return m_topDocumentSyncData->hasInjectedUserScript;
}
void Page::setHasInjectedUserScript()
{
if (m_topDocumentSyncData->hasInjectedUserScript)
return;
m_topDocumentSyncData->hasInjectedUserScript = true;
if (settings().siteIsolationEnabled())
processSyncClient().broadcastHasInjectedUserScriptToOtherProcesses(true);
}
void Page::updateProcessSyncData(const ProcessSyncData& data)
{
switch (data.type) {
case ProcessSyncDataType::DocumentClasses:
case ProcessSyncDataType::DocumentSecurityOrigin:
case ProcessSyncDataType::DocumentURL:
case ProcessSyncDataType::HasInjectedUserScript:
case ProcessSyncDataType::IsAutofocusProcessed:
case ProcessSyncDataType::IsClosing:
case ProcessSyncDataType::UserDidInteractWithPage:
#if ENABLE(DOM_AUDIO_SESSION)
case ProcessSyncDataType::AudioSessionType:
#endif
protectedTopDocumentSyncData()->update(data);
break;
case ProcessSyncDataType::FrameCanCreatePaymentSession:
case ProcessSyncDataType::FrameDocumentSecurityOrigin:
ASSERT_NOT_REACHED();
}
}
void Page::updateTopDocumentSyncData(Ref<DocumentSyncData>&& data)
{
if (RefPtr localFrame = dynamicDowncast<LocalFrame>(m_mainFrame.get())) {
// Prefer the main LocalFrame document's data, but if the main LocalFrame
// has no document, accept the remote pushed data.
if (localFrame->document())
return;
}
m_topDocumentSyncData = WTFMove(data);
}
void Page::setMainFrameURLFragment(String&& fragment)
{
if (!fragment.isEmpty())
m_mainFrameURLFragment = WTFMove(fragment);
}
const URL& Page::mainFrameURL() const
{
return m_topDocumentSyncData->documentURL;
}
SecurityOrigin& Page::mainFrameOrigin() const
{
if (!m_topDocumentSyncData->documentSecurityOrigin)
return SecurityOrigin::opaqueOrigin();
return *m_topDocumentSyncData->documentSecurityOrigin;
}
bool Page::openedByDOM() const
{
return m_openedByDOM;
}
void Page::setOpenedByDOM()
{
m_openedByDOM = true;
}
void Page::goToItem(LocalFrame& frame, HistoryItem& item, FrameLoadType type, ShouldTreatAsContinuingLoad shouldTreatAsContinuingLoad, ProcessSwapDisposition processSwapDisposition)
{
// stopAllLoaders may end up running onload handlers, which could cause further history traversals that may lead to the passed in HistoryItem
// being deref()-ed. Make sure we can still use it with HistoryController::goToItem later.
Ref protectedItem { item };
if (frame.loader().protectedHistory()->shouldStopLoadingForHistoryItem(item))
frame.protectedLoader()->stopAllLoadersAndCheckCompleteness();
frame.loader().protectedHistory()->goToItem(item, type, shouldTreatAsContinuingLoad, processSwapDisposition);
}
void Page::goToItemForNavigationAPI(LocalFrame& frame, HistoryItem& item, FrameLoadType type, LocalFrame& triggeringFrame, NavigationAPIMethodTracker* tracker)
{
if (frame.loader().protectedHistory()->shouldStopLoadingForHistoryItem(item))
frame.protectedLoader()->stopAllLoadersAndCheckCompleteness();
frame.loader().protectedHistory()->goToItemForNavigationAPI(item, type, triggeringFrame, tracker);
}
void Page::setGroupName(const String& name)
{
if (m_group && !m_group->name().isEmpty()) {
ASSERT(m_group != m_singlePageGroup.get());
ASSERT(!m_singlePageGroup);
m_group->removePage(*this);
}
if (name.isEmpty())
m_group = m_singlePageGroup.get();
else {
m_singlePageGroup = nullptr;
m_group = PageGroup::pageGroup(name);
m_group->addPage(*this);
}
}
const String& Page::groupName() const
{
return m_group ? m_group->name() : nullAtom().string();
}
Ref<BroadcastChannelRegistry> Page::protectedBroadcastChannelRegistry() const
{
return m_broadcastChannelRegistry;
}
void Page::setBroadcastChannelRegistry(Ref<BroadcastChannelRegistry>&& broadcastChannelRegistry)
{
m_broadcastChannelRegistry = WTFMove(broadcastChannelRegistry);
}
void Page::initGroup()
{
ASSERT(!m_singlePageGroup);
ASSERT(!m_group);
m_singlePageGroup = makeUnique<PageGroup>(*this);
m_group = m_singlePageGroup.get();
}
void Page::updateStyleAfterChangeInEnvironment()
{
forEachDocument([] (Document& document) {
if (RefPtr styleResolver = document.styleScope().resolverIfExists())
styleResolver->invalidateMatchedDeclarationsCache();
document.scheduleFullStyleRebuild();
document.checkedStyleScope()->didChangeStyleSheetEnvironment();
document.updateElementsAffectedByMediaQueries();
document.scheduleRenderingUpdate(RenderingUpdateStep::MediaQueryEvaluation);
});
}
void Page::updateStyleForAllPagesAfterGlobalChangeInEnvironment()
{
for (auto& page : allPages())
Ref { page.get() }->updateStyleAfterChangeInEnvironment();
}
void Page::setNeedsRecalcStyleInAllFrames()
{
// FIXME: Figure out what this function is actually trying to add in different call sites.
forEachDocument([] (Document& document) {
document.checkedStyleScope()->didChangeStyleSheetEnvironment();
});
}
void Page::refreshPlugins(bool reload)
{
WeakHashSet<PluginInfoProvider> pluginInfoProviders;
for (auto& page : allPages())
pluginInfoProviders.add(Ref { page.get() }->protectedPluginInfoProvider());
for (Ref pluginInfoProvider : pluginInfoProviders)
pluginInfoProvider->refresh(reload);
}
PluginData& Page::pluginData()
{
if (!m_pluginData)
m_pluginData = PluginData::create(*this);
return *m_pluginData;
}
Ref<PluginData> Page::protectedPluginData()
{
return pluginData();
}
void Page::clearPluginData()
{
m_pluginData = nullptr;
}
bool Page::showAllPlugins() const
{
if (m_showAllPlugins)
return true;
return mainFrameOrigin().isLocal();
}
inline std::optional<std::pair<WeakRef<MediaCanStartListener>, WeakRef<Document, WeakPtrImplWithEventTargetData>>> Page::takeAnyMediaCanStartListener()
{
for (RefPtr frame = &mainFrame(); frame; frame = frame->tree().traverseNext()) {
RefPtr localFrame = dynamicDowncast<LocalFrame>(*frame);
if (!localFrame)
continue;
RefPtr document = localFrame->document();
if (!document)
continue;
if (RefPtr listener = document->takeAnyMediaCanStartListener())
return { { *listener, *document } };
}
return std::nullopt;
}
void Page::setCanStartMedia(bool canStartMedia)
{
if (m_canStartMedia == canStartMedia)
return;
m_canStartMedia = canStartMedia;
while (m_canStartMedia) {
auto listener = takeAnyMediaCanStartListener();
if (!listener)
break;
Ref { listener->first.get() }->mediaCanStart(Ref { listener->second.get() });
}
}
Ref<Frame> Page::protectedMainFrame() const
{
return m_mainFrame;
}
static Frame* incrementFrame(Frame* current, bool forward, CanWrap canWrap, DidWrap* didWrap = nullptr)
{
return forward
? current->tree().traverseNext(canWrap, didWrap)
: current->tree().traversePrevious(canWrap, didWrap);
}
std::optional<FrameIdentifier> Page::findString(const String& target, FindOptions options, DidWrap* didWrap)
{
if (target.isEmpty())
return std::nullopt;
CanWrap canWrap = options.contains(FindOption::WrapAround) ? CanWrap::Yes : CanWrap::No;
CheckedRef focusController { *m_focusController };
RefPtr frame = focusController->focusedFrame() ? focusController->focusedFrame() : m_mainFrame.ptr();
RefPtr startFrame = frame;
RefPtr focusedLocalFrame = dynamicDowncast<LocalFrame>(frame);
do {
RefPtr localFrame = dynamicDowncast<LocalFrame>(frame.get());
if (!localFrame) {
frame = incrementFrame(frame.get(), !options.contains(FindOption::Backwards), canWrap, didWrap);
continue;
}
if (localFrame->protectedEditor()->findString(target, (options - FindOption::WrapAround) | FindOption::StartInSelection)) {
if (!options.contains(FindOption::DoNotSetSelection)) {
if (focusedLocalFrame && localFrame != focusedLocalFrame)
focusedLocalFrame->checkedSelection()->clear();
focusController->setFocusedFrame(localFrame.get());
}
return localFrame->frameID();
}
frame = incrementFrame(frame.get(), !options.contains(FindOption::Backwards), canWrap, didWrap);
} while (frame && frame != startFrame);
// Search contents of startFrame, on the other side of the selection that we did earlier.
// We cheat a bit and just research with wrap on
if (canWrap == CanWrap::Yes && focusedLocalFrame && !focusedLocalFrame->selection().isNone()) {
if (didWrap)
*didWrap = DidWrap::Yes;
bool found = focusedLocalFrame->protectedEditor()->findString(target, options | FindOption::WrapAround | FindOption::StartInSelection);
if (!options.contains(FindOption::DoNotSetSelection))
focusController->setFocusedFrame(frame.get());
return found ? std::make_optional(focusedLocalFrame->frameID()) : std::nullopt;
}
return std::nullopt;
}
#if ENABLE(IMAGE_ANALYSIS)
void Page::analyzeImagesForFindInPage(Function<void()>&& callback)
{
if (settings().imageAnalysisDuringFindInPageEnabled()) {
Ref imageAnalysisQueue = this->imageAnalysisQueue();
imageAnalysisQueue->setDidBecomeEmptyCallback(WTFMove(callback));
RefPtr localMainFrame = dynamicDowncast<LocalFrame>(m_mainFrame.get());
if (RefPtr mainDocument = localMainFrame ? localMainFrame->document() : nullptr)
imageAnalysisQueue->enqueueAllImagesIfNeeded(*mainDocument, { }, { });
}
}
#endif
auto Page::findTextMatches(const String& target, FindOptions options, unsigned limit, bool markMatches) -> MatchingRanges
{
MatchingRanges result;
RefPtr frame { &mainFrame() };
RefPtr<LocalFrame> frameWithSelection;
do {
RefPtr localFrame = dynamicDowncast<LocalFrame>(frame.get());
if (!localFrame) {
frame = incrementFrame(frame.get(), true, CanWrap::No);
continue;
}
localFrame->protectedEditor()->countMatchesForText(target, { }, options, limit ? (limit - result.ranges.size()) : 0, markMatches, &result.ranges);
if (localFrame->selection().isRange())
frameWithSelection = localFrame;
frame = incrementFrame(frame.get(), true, CanWrap::No);
} while (frame);
if (result.ranges.isEmpty())
return result;
if (frameWithSelection) {
result.indexForSelection = NoMatchAfterUserSelection;
auto selectedRange = *frameWithSelection->selection().selection().firstRange();
if (options.contains(FindOption::Backwards)) {
for (size_t i = result.ranges.size(); i > 0; --i) {
// FIXME: Seems like this should be is_gteq to correctly handle the same string found twice in a row.
if (is_gt(treeOrder<ComposedTree>(selectedRange.start, result.ranges[i - 1].end))) {
result.indexForSelection = i - 1;
break;
}
}
} else {
for (size_t i = 0, size = result.ranges.size(); i < size; ++i) {
// FIXME: Seems like this should be is_lteq to correctly handle the same string found twice in a row.
if (is_lt(treeOrder<ComposedTree>(selectedRange.end, result.ranges[i].start))) {
result.indexForSelection = i;
break;
}
}
}
} else {
if (options.contains(FindOption::Backwards))
result.indexForSelection = result.ranges.size() - 1;
else
result.indexForSelection = 0;
}
return result;
}
std::optional<SimpleRange> Page::rangeOfString(const String& target, const std::optional<SimpleRange>& referenceRange, FindOptions options)
{
if (target.isEmpty())
return std::nullopt;
if (referenceRange && referenceRange->start.document().page() != this)
return std::nullopt;
CanWrap canWrap = options.contains(FindOption::WrapAround) ? CanWrap::Yes : CanWrap::No;
RefPtr frame = referenceRange ? referenceRange->start.document().frame() : &mainFrame();
RefPtr startFrame = dynamicDowncast<LocalFrame>(frame.get());
do {
RefPtr localFrame = dynamicDowncast<LocalFrame>(frame.get());
if (!localFrame) {
frame = incrementFrame(frame.get(), !options.contains(FindOption::Backwards), canWrap);
continue;
}
if (auto resultRange = localFrame->protectedEditor()->rangeOfString(target, localFrame.get() == startFrame.get() ? referenceRange : std::nullopt, options - FindOption::WrapAround))
return resultRange;
frame = incrementFrame(localFrame.get(), !options.contains(FindOption::Backwards), canWrap);
} while (frame && frame != startFrame);
// Search contents of startFrame, on the other side of the reference range that we did earlier.
// We cheat a bit and just search again with wrap on.
if (canWrap == CanWrap::Yes && referenceRange) {
if (auto resultRange = startFrame->protectedEditor()->rangeOfString(target, *referenceRange, options | FindOption::WrapAround | FindOption::StartInSelection))
return resultRange;
}
return std::nullopt;
}
unsigned Page::findMatchesForText(const String& target, FindOptions options, unsigned maxMatchCount, ShouldHighlightMatches shouldHighlightMatches, ShouldMarkMatches shouldMarkMatches)
{
if (target.isEmpty())
return 0;
unsigned matchCount = 0;
RefPtr frame = &mainFrame();
do {
RefPtr localFrame = dynamicDowncast<LocalFrame>(frame.get());
if (!localFrame) {
frame = incrementFrame(frame.get(), true, CanWrap::No);
continue;
}
if (shouldMarkMatches == MarkMatches)
localFrame->protectedEditor()->setMarkedTextMatchesAreHighlighted(shouldHighlightMatches == HighlightMatches);
matchCount += localFrame->protectedEditor()->countMatchesForText(target, std::nullopt, options, maxMatchCount ? (maxMatchCount - matchCount) : 0, shouldMarkMatches == MarkMatches, nullptr);
frame = incrementFrame(frame.get(), true, CanWrap::No);
} while (frame);
return matchCount;
}
unsigned Page::markAllMatchesForText(const String& target, FindOptions options, bool shouldHighlight, unsigned maxMatchCount)
{
return findMatchesForText(target, options, maxMatchCount, shouldHighlight ? HighlightMatches : DoNotHighlightMatches, MarkMatches);
}
unsigned Page::countFindMatches(const String& target, FindOptions options, unsigned maxMatchCount)
{
return findMatchesForText(target, options, maxMatchCount, DoNotHighlightMatches, DoNotMarkMatches);
}
struct FindReplacementRange {
RefPtr<ContainerNode> root;
CharacterRange range;
};
static void replaceRanges(Page& page, const Vector<FindReplacementRange>& ranges, const String& replacementText)
{
HashMap<RefPtr<ContainerNode>, Vector<FindReplacementRange>> rangesByContainerNode;
for (auto& range : ranges) {
auto& rangeList = rangesByContainerNode.ensure(range.root, [] {
return Vector<FindReplacementRange> { };
}).iterator->value;
// Ensure that ranges are sorted by their end offsets, per editing container.
auto endOffsetForRange = range.range.location + range.range.length;
auto insertionIndex = rangeList.size();
for (auto iterator = rangeList.rbegin(); iterator != rangeList.rend(); ++iterator) {
auto endOffsetBeforeInsertionIndex = iterator->range.location + iterator->range.length;
if (endOffsetForRange >= endOffsetBeforeInsertionIndex)
break;
insertionIndex--;
}
rangeList.insert(insertionIndex, range);
}
HashMap<RefPtr<LocalFrame>, unsigned> frameToTraversalIndexMap;
unsigned currentFrameTraversalIndex = 0;
for (RefPtr frame = page.mainFrame(); frame; frame = frame->tree().traverseNext()) {
if (RefPtr localFrame = dynamicDowncast<LocalFrame>(*frame))
frameToTraversalIndexMap.set(WTFMove(localFrame), currentFrameTraversalIndex++);
}
// Likewise, iterate backwards (in document and frame order) through editing containers that contain text matches,
// so that we're consistent with our backwards iteration behavior per editing container when replacing text.
auto containerNodesInOrderOfReplacement = copyToVector(rangesByContainerNode.keys());
std::ranges::sort(containerNodesInOrderOfReplacement, [frameToTraversalIndexMap](auto& firstNode, auto& secondNode) {
if (firstNode == secondNode)
return false;
RefPtr firstFrame = firstNode->document().frame();
if (!firstFrame)
return true;
RefPtr secondFrame = secondNode->document().frame();
if (!secondFrame)
return false;
if (firstFrame == secondFrame) {
// Must not use Node::compareDocumentPosition here because some editing roots are inside shadow roots.
return is_gt(treeOrder<ComposedTree>(*firstNode, *secondNode));
}
return frameToTraversalIndexMap.get(firstFrame) > frameToTraversalIndexMap.get(secondFrame);
});
for (auto& container : containerNodesInOrderOfReplacement) {
RefPtr frame = container->document().frame();
if (!frame)
continue;
// Iterate backwards through ranges when replacing text, such that earlier text replacements don't clobber replacement ranges later on.
auto& ranges = rangesByContainerNode.find(container)->value;
for (auto iterator = ranges.rbegin(); iterator != ranges.rend(); ++iterator) {
auto range = resolveCharacterRange(makeRangeSelectingNodeContents(*container), iterator->range);
if (range.collapsed())
continue;
frame->checkedSelection()->setSelectedRange(range, Affinity::Downstream, FrameSelection::ShouldCloseTyping::Yes);
frame->protectedEditor()->replaceSelectionWithText(replacementText, Editor::SelectReplacement::Yes, Editor::SmartReplace::No, EditAction::InsertReplacement);
}
}
}
uint32_t Page::replaceRangesWithText(const Vector<SimpleRange>& rangesToReplace, const String& replacementText, bool /*selectionOnly*/)
{
// FIXME: In the future, we should respect the `selectionOnly` flag by checking whether each range being replaced is contained within its frame's selection.
auto replacementRanges = WTF::compactMap(rangesToReplace, [&](auto& range) -> std::optional<FindReplacementRange> {
RefPtr highestRoot = highestEditableRoot(makeDeprecatedLegacyPosition(range.start));
if (!highestRoot || highestRoot != highestEditableRoot(makeDeprecatedLegacyPosition(range.end)) || !highestRoot->document().frame())
return std::nullopt;
auto scope = makeRangeSelectingNodeContents(*highestRoot);
return FindReplacementRange { WTFMove(highestRoot), characterRange(scope, range) };
});
replaceRanges(*this, replacementRanges, replacementText);
return rangesToReplace.size();
}
uint32_t Page::replaceSelectionWithText(const String& replacementText)
{
RefPtr frame = checkedFocusController()->focusedOrMainFrame();
if (!frame)
return 0;
auto selection = frame->selection().selection();
if (!selection.isContentEditable())
return 0;
auto editAction = selection.isRange() ? EditAction::InsertReplacement : EditAction::Insert;
frame->protectedEditor()->replaceSelectionWithText(replacementText, Editor::SelectReplacement::Yes, Editor::SmartReplace::No, editAction);
return 1;
}
void Page::unmarkAllTextMatches()
{
forEachDocument([] (Document& document) {
if (CheckedPtr markers = document.markersIfExists())
markers->removeMarkers(DocumentMarkerType::TextMatch);
});
}
#if ENABLE(EDITABLE_REGION)
void Page::setEditableRegionEnabled(bool enabled)
{
if (m_isEditableRegionEnabled == enabled)
return;
m_isEditableRegionEnabled = enabled;
RefPtr localMainFrame = this->localMainFrame();
RefPtr frameView = localMainFrame ? localMainFrame->view() : nullptr;
if (!frameView)
return;
if (CheckedPtr renderView = frameView->renderView())
renderView->compositor().invalidateEventRegionForAllLayers();
}
#endif
#if ENABLE(EDITABLE_REGION)
bool Page::shouldBuildEditableRegion() const
{
return m_isEditableRegionEnabled || OptionSet<DebugOverlayRegions>::fromRaw(m_settings->visibleDebugOverlayRegions()).contains(DebugOverlayRegions::EditableElementRegion);
}
#endif
Vector<Ref<Element>> Page::editableElementsInRect(const FloatRect& searchRectInRootViewCoordinates) const
{
RefPtr localMainFrame = this->localMainFrame();
RefPtr frameView = localMainFrame ? localMainFrame->view() : nullptr;
if (!frameView)
return { };
RefPtr document = localMainFrame->document();
if (!document)
return { };
constexpr OptionSet<HitTestRequest::Type> hitType { HitTestRequest::Type::ReadOnly, HitTestRequest::Type::Active, HitTestRequest::Type::CollectMultipleElements, HitTestRequest::Type::DisallowUserAgentShadowContent, HitTestRequest::Type::AllowVisibleChildFrameContentOnly };
LayoutRect searchRectInMainFrameCoordinates = frameView->rootViewToContents(roundedIntRect(searchRectInRootViewCoordinates));
HitTestResult hitTestResult { searchRectInMainFrameCoordinates };
if (!document->hitTest(hitType, hitTestResult))
return { };
auto rootEditableElement = [](Node& node) -> Element* {
if (RefPtr element = dynamicDowncast<HTMLTextFormControlElement>(node)) {
if (element->isInnerTextElementEditable())
return &uncheckedDowncast<Element>(node);
} else if (is<Element>(node) && node.hasEditableStyle())
return node.rootEditableElement();
return nullptr;
};
ListHashSet<Ref<Element>> rootEditableElements;
auto& nodeSet = hitTestResult.listBasedTestResult();
for (auto& node : nodeSet) {
if (RefPtr editableElement = rootEditableElement(node)) {
ASSERT(searchRectInRootViewCoordinates.inclusivelyIntersects(editableElement->boundingBoxInRootViewCoordinates()));
rootEditableElements.add(editableElement.releaseNonNull());
}
}
// Fix up for a now empty focused inline element, e.g. <span contenteditable='true'>Hello</span> became
// <span contenteditable='true'></span>. Hit testing will likely not find this element because the engine
// tries to avoid creating line boxes, which are things it hit tests, for them to reduce memory. If the
// focused element is inside the search rect it's the most likely target for future editing operations,
// even if it's empty. So, we special case it here.
RefPtr focusedOrMainFrame = checkedFocusController()->focusedOrMainFrame();
if (RefPtr focusedElement = focusedOrMainFrame ? focusedOrMainFrame->document()->focusedElement() : nullptr) {
if (searchRectInRootViewCoordinates.inclusivelyIntersects(focusedElement->boundingBoxInRootViewCoordinates())) {
if (RefPtr editableElement = rootEditableElement(*focusedElement))
rootEditableElements.add(editableElement.releaseNonNull());
}
}
return WTF::map(rootEditableElements, [](const auto& element) { return element.copyRef(); });
}
CheckedRef<FocusController> Page::checkedFocusController() const
{
return *m_focusController;
}
#if ENABLE(INTERACTION_REGIONS_IN_EVENT_REGION)
bool Page::shouldBuildInteractionRegions() const
{
return m_settings->interactionRegionsEnabled();
}
void Page::setInteractionRegionsEnabled(bool enable)
{
bool needsUpdate = enable && !shouldBuildInteractionRegions();
m_settings->setInteractionRegionsEnabled(enable);
if (needsUpdate) {
if (RefPtr localMainFrame = this->localMainFrame())
localMainFrame->invalidateContentEventRegionsIfNeeded(LocalFrame::InvalidateContentEventRegionsReason::Layout);
}
}
#endif // ENABLE(INTERACTION_REGIONS_IN_EVENT_REGION)
const VisibleSelection& Page::selection() const
{
RefPtr focusedOrMainFrame = checkedFocusController()->focusedOrMainFrame();
if (!focusedOrMainFrame)
return VisibleSelection::emptySelection();
return focusedOrMainFrame->selection().selection();
}
void Page::setDefersLoading(bool defers)
{
if (!m_settings->loadDeferringEnabled())
return;
if (m_settings->wantsBalancedSetDefersLoadingBehavior()) {
ASSERT(defers || m_defersLoadingCallCount);
if (defers && ++m_defersLoadingCallCount > 1)
return;
if (!defers && --m_defersLoadingCallCount)
return;
} else {
ASSERT(!m_defersLoadingCallCount);
if (defers == m_defersLoading)
return;
}
m_defersLoading = defers;
for (RefPtr frame = &mainFrame(); frame; frame = frame->tree().traverseNext()) {
if (RefPtr localFrame = dynamicDowncast<LocalFrame>(*frame))
localFrame->protectedLoader()->setDefersLoading(defers);
}
}
void Page::clearUndoRedoOperations()
{
m_editorClient->clearUndoRedoOperations();
}
bool Page::inLowQualityImageInterpolationMode() const
{
return m_inLowQualityInterpolationMode;
}
void Page::setInLowQualityImageInterpolationMode(bool mode)
{
m_inLowQualityInterpolationMode = mode;
}
DiagnosticLoggingClient& Page::diagnosticLoggingClient() const
{
if (!settings().diagnosticLoggingEnabled() || !m_diagnosticLoggingClient)
return emptyDiagnosticLoggingClient();
return *m_diagnosticLoggingClient;
}
void Page::logMediaDiagnosticMessage(const RefPtr<FormData>& formData) const
{
unsigned imageOrMediaFilesCount = formData ? formData->imageOrMediaFilesCount() : 0;
if (!imageOrMediaFilesCount)
return;
auto message = makeString(imageOrMediaFilesCount, imageOrMediaFilesCount == 1 ? " media file has been submitted"_s : " media files have been submitted"_s);
diagnosticLoggingClient().logDiagnosticMessageWithDomain(message, DiagnosticLoggingDomain::Media);
}
void Page::setMediaVolume(float volume)
{
if (!(volume >= 0 && volume <= 1))
return;
if (m_mediaVolume == volume)
return;
m_mediaVolume = volume;
#if ENABLE(VIDEO)
forEachMediaElement([] (HTMLMediaElement& element) {
element.mediaVolumeDidChange();
});
#endif
}
void Page::setZoomedOutPageScaleFactor(float scale)
{
if (m_zoomedOutPageScaleFactor == scale)
return;
m_zoomedOutPageScaleFactor = scale;
if (RefPtr localMainFrame = this->localMainFrame())
localMainFrame->deviceOrPageScaleFactorChanged();
}
void Page::setPageScaleFactor(float scale, const IntPoint& origin, bool inStableState)
{
LOG_WITH_STREAM(Viewports, stream << "Page " << this << " setPageScaleFactor " << scale << " at " << origin << " - stable " << inStableState);
RefPtr mainDocument = localTopDocument();
RefPtr mainFrameView = mainDocument ? mainDocument->view() : nullptr;
if (scale == m_pageScaleFactor) {
if (mainFrameView && mainFrameView->scrollPosition() != origin && !delegatesScaling())
mainDocument->updateLayoutIgnorePendingStylesheets({ WebCore::LayoutOptions::UpdateCompositingLayers });
} else {
m_pageScaleFactor = scale;
for (auto& rootFrame : m_rootFrames) {
ASSERT(rootFrame->isRootFrame());
RefPtr view = rootFrame->view();
if (!view)
continue;
if (!delegatesScaling()) {
view->setNeedsLayoutAfterViewConfigurationChange();
view->setNeedsCompositingGeometryUpdate();
view->setDescendantsNeedUpdateBackingAndHierarchyTraversal();
if (RefPtr doc = rootFrame->document())
doc->resolveStyle(Document::ResolveStyleType::Rebuild);
// Transform change on RenderView doesn't trigger repaint on non-composited contents.
view->invalidateRect(IntRect(LayoutRect::infiniteRect()));
}
rootFrame->deviceOrPageScaleFactorChanged();
if (view->fixedElementsLayoutRelativeToFrame())
view->setViewportConstrainedObjectsNeedLayout();
}
if (mainFrameView && mainFrameView->scrollPosition() != origin && !delegatesScaling() && mainDocument->renderView() && mainDocument->renderView()->needsLayout() && mainFrameView->didFirstLayout()) {
mainFrameView->layoutContext().layout();
mainFrameView->layoutContext().updateCompositingLayersAfterLayoutIfNeeded();
}
}
if (mainFrameView && mainFrameView->scrollPosition() != origin) {
if (mainFrameView->delegatedScrollingMode() != DelegatedScrollingMode::DelegatedToNativeScrollView)
mainFrameView->setScrollPosition(origin);
}
#if ENABLE(VIDEO)
if (inStableState) {
forEachMediaElement([] (HTMLMediaElement& element) {
element.pageScaleFactorChanged();
});
}
#else
UNUSED_PARAM(inStableState);
#endif
}
void Page::setDelegatesScaling(bool delegatesScaling)
{
m_delegatesScaling = delegatesScaling;
}
void Page::setViewScaleFactor(float scale)
{
if (m_viewScaleFactor == scale)
return;
m_viewScaleFactor = scale;
BackForwardCache::singleton().markPagesForDeviceOrPageScaleChanged(*this);
}
void Page::setDeviceScaleFactor(float scaleFactor)
{
ASSERT(scaleFactor > 0);
if (scaleFactor <= 0)
return;
if (m_deviceScaleFactor == scaleFactor)
return;
m_deviceScaleFactor = scaleFactor;
setNeedsRecalcStyleInAllFrames();
if (RefPtr localMainFrame = this->localMainFrame())
localMainFrame->deviceOrPageScaleFactorChanged();
BackForwardCache::singleton().markPagesForDeviceOrPageScaleChanged(*this);
pageOverlayController().didChangeDeviceScaleFactor();
}
void Page::screenPropertiesDidChange()
{
#if ENABLE(VIDEO)
auto mode = preferredDynamicRangeMode(protectedMainFrame()->protectedVirtualView().get());
forEachMediaElement([mode] (auto& element) {
element.setPreferredDynamicRangeMode(mode);
});
#endif
setNeedsRecalcStyleInAllFrames();
}
void Page::windowScreenDidChange(PlatformDisplayID displayID, std::optional<FramesPerSecond> nominalFramesPerSecond)
{
if (displayID == m_displayID && nominalFramesPerSecond == m_displayNominalFramesPerSecond)
return;
m_displayID = displayID;
m_displayNominalFramesPerSecond = nominalFramesPerSecond;
forEachDocument([&] (Document& document) {
document.windowScreenDidChange(displayID);
});
#if ENABLE(VIDEO)
auto mode = preferredDynamicRangeMode(protectedMainFrame()->protectedVirtualView().get());
forEachMediaElement([mode] (auto& element) {
element.setPreferredDynamicRangeMode(mode);
});
#endif
if (RefPtr scrollingCoordinator = m_scrollingCoordinator)
scrollingCoordinator->windowScreenDidChange(displayID, m_displayNominalFramesPerSecond);
if (CheckedPtr scheduler = existingRenderingUpdateScheduler())
scheduler->windowScreenDidChange(displayID);
chrome().client().renderingUpdateFramesPerSecondChanged();
setNeedsRecalcStyleInAllFrames();
}
void Page::setInitialScaleIgnoringContentSize(float scale)
{
m_initialScaleIgnoringContentSize = scale;
}
void Page::setUserInterfaceLayoutDirection(UserInterfaceLayoutDirection userInterfaceLayoutDirection)
{
if (m_userInterfaceLayoutDirection == userInterfaceLayoutDirection)
return;
m_userInterfaceLayoutDirection = userInterfaceLayoutDirection;
#if ENABLE(VIDEO)
forEachMediaElement([] (HTMLMediaElement& element) {
element.userInterfaceLayoutDirectionChanged();
});
#endif
}
#if ENABLE(VIDEO)
void Page::updateMediaElementRateChangeRestrictions()
{
// FIXME: This used to call this on all media elements, seemingly by accident. But was there some advantage to that for elements in the back/forward cache?
forEachMediaElement([] (HTMLMediaElement& element) {
element.updateRateChangeRestrictions();
});
}
#endif
void Page::didStartProvisionalLoad()
{
if (RefPtr performanceMonitor = m_performanceMonitor.get())
performanceMonitor->didStartProvisionalLoad();
if (m_settings->resourceLoadSchedulingEnabled())
setLoadSchedulingMode(LoadSchedulingMode::Prioritized);
}
void Page::didCommitLoad()
{
#if ENABLE(EDITABLE_REGION)
m_isEditableRegionEnabled = false;
#endif
m_hasEverSetVisibilityAdjustment = false;
m_mainFrameURLFragment = { };
#if PLATFORM(VISION) && ENABLE(GAMEPAD)
initializeGamepadAccessForPageLoad();
#endif
resetSeenPlugins();
resetSeenMediaEngines();
#if ENABLE(IMAGE_ANALYSIS)
resetTextRecognitionResults();
resetImageAnalysisQueue();
#endif
#if ENABLE(GEOLOCATION)
if (auto* geolocationController = GeolocationController::from(this))
geolocationController->didNavigatePage();
#endif
m_fixedContainerEdgesAndElements = std::make_pair(makeUniqueRef<FixedContainerEdges>(), WeakElementEdges { });
m_elementTargetingController->reset();
m_reportedScriptsWithTelemetry.clear();
m_isWaitingForLoadToFinish = true;
}
void Page::didFinishLoad()
{
resetRelevantPaintedObjectCounter();
if (RefPtr performanceMonitor = m_performanceMonitor.get())
performanceMonitor->didFinishLoad();
setLoadSchedulingMode(LoadSchedulingMode::Direct);
m_isWaitingForLoadToFinish = false;
}
bool Page::isOnlyNonUtilityPage() const
{
return !isUtilityPage() && gNonUtilityPageCount == 1;
}
void Page::setLowPowerModeEnabledOverrideForTesting(std::optional<bool> isEnabled)
{
// Remove ThrottlingReason::LowPowerMode so handleLowPowerModeChange() can do its work.
m_throttlingReasonsOverridenForTesting.remove(ThrottlingReason::LowPowerMode);
// Use the current low power mode value of the device.
if (!isEnabled) {
handleLowPowerModeChange(m_lowPowerModeNotifier->isLowPowerModeEnabled());
return;
}
// Override the value and add ThrottlingReason::LowPowerMode so it override the device state.
handleLowPowerModeChange(isEnabled.value());
m_throttlingReasonsOverridenForTesting.add(ThrottlingReason::LowPowerMode);
}
void Page::setAggressiveThermalMitigationEnabledForTesting(std::optional<bool> isEnabled)
{
m_throttlingReasonsOverridenForTesting.remove(ThrottlingReason::AggressiveThermalMitigation);
if (!isEnabled.has_value()) {
handleThermalMitigationChange(m_thermalMitigationNotifier->thermalMitigationEnabled());
return;
}
handleThermalMitigationChange(isEnabled.value());
m_throttlingReasonsOverridenForTesting.add(ThrottlingReason::AggressiveThermalMitigation);
}
void Page::setOutsideViewportThrottlingEnabledForTesting(bool isEnabled)
{
if (!isEnabled)
m_throttlingReasonsOverridenForTesting.add(ThrottlingReason::OutsideViewport);
else
m_throttlingReasonsOverridenForTesting.remove(ThrottlingReason::OutsideViewport);
m_throttlingReasons.remove(ThrottlingReason::OutsideViewport);
}
void Page::setObscuredContentInsets(const FloatBoxExtent& obscuredContentInsets)
{
if (m_obscuredContentInsets == obscuredContentInsets)
return;
m_obscuredContentInsets = obscuredContentInsets;
RefPtr localMainFrame = this->localMainFrame();
if (RefPtr view = localMainFrame ? localMainFrame->view() : nullptr)
view->obscuredContentInsetsDidChange(obscuredContentInsets);
}
void Page::setShouldSuppressScrollbarAnimations(bool suppressAnimations)
{
if (suppressAnimations == m_suppressScrollbarAnimations)
return;
lockAllOverlayScrollbarsToHidden(suppressAnimations);
m_suppressScrollbarAnimations = suppressAnimations;
}
void Page::lockAllOverlayScrollbarsToHidden(bool lockOverlayScrollbars)
{
RefPtr view = protectedMainFrame()->virtualView();
if (!view)
return;
view->lockOverlayScrollbarStateToHidden(lockOverlayScrollbars);
for (RefPtr frame = &mainFrame(); frame; frame = frame->tree().traverseNext()) {
RefPtr localFrame = dynamicDowncast<LocalFrame>(*frame);
if (!localFrame)
continue;
RefPtr frameView = localFrame->view();
if (!frameView)
continue;
auto scrollableAreas = frameView->scrollableAreas();
if (!scrollableAreas)
continue;
for (CheckedRef area : *scrollableAreas)
area->lockOverlayScrollbarStateToHidden(lockOverlayScrollbars);
}
}
PageGroup& Page::group()
{
if (!m_group)
initGroup();
return *m_group;
}
void Page::setVerticalScrollElasticity(ScrollElasticity elasticity)
{
if (m_verticalScrollElasticity == elasticity)
return;
m_verticalScrollElasticity = elasticity;
if (RefPtr view = protectedMainFrame()->virtualView())
view->setVerticalScrollElasticity(elasticity);
}
void Page::setHorizontalScrollElasticity(ScrollElasticity elasticity)
{
if (m_horizontalScrollElasticity == elasticity)
return;
m_horizontalScrollElasticity = elasticity;
RefPtr localMainFrame = this->localMainFrame();
if (RefPtr view = localMainFrame ? localMainFrame->view() : nullptr)
view->setHorizontalScrollElasticity(elasticity);
}
void Page::setPagination(const Pagination& pagination)
{
if (m_pagination == pagination)
return;
m_pagination = pagination;
setNeedsRecalcStyleInAllFrames();
}
unsigned Page::pageCount() const
{
if (m_pagination.mode == Pagination::Mode::Unpaginated)
return 0;
if (RefPtr localTopDocument = this->localTopDocument())
localTopDocument->updateLayoutIgnorePendingStylesheets();
return pageCountAssumingLayoutIsUpToDate();
}
unsigned Page::pageCountAssumingLayoutIsUpToDate() const
{
if (m_pagination.mode == Pagination::Mode::Unpaginated)
return 0;
RefPtr localMainFrame = this->localMainFrame();
ASSERT(!localMainFrame || !localMainFrame->view() || !localMainFrame->view()->needsLayout());
CheckedPtr contentRenderer = localMainFrame ? localMainFrame->contentRenderer() : nullptr;
return contentRenderer ? contentRenderer->pageCount() : 0;
}
void Page::setIsInWindow(bool isInWindow)
{
setActivityState(isInWindow ? m_activityState | ActivityState::IsInWindow : m_activityState - ActivityState::IsInWindow);
}
void Page::setIsInWindowInternal(bool isInWindow)
{
for (RefPtr frame = &mainFrame(); frame; frame = frame->tree().traverseNext()) {
RefPtr localFrame = dynamicDowncast<LocalFrame>(*frame);
if (!localFrame)
continue;
if (RefPtr frameView = localFrame->view())
frameView->setIsInWindow(isInWindow);
}
if (isInWindow)
resumeAnimatingImages();
}
void Page::addActivityStateChangeObserver(ActivityStateChangeObserver& observer)
{
m_activityStateChangeObservers.add(observer);
}
void Page::removeActivityStateChangeObserver(ActivityStateChangeObserver& observer)
{
m_activityStateChangeObservers.remove(observer);
}
void Page::layoutIfNeeded(OptionSet<LayoutOptions> layoutOptions)
{
for (auto& rootFrame : m_rootFrames) {
ASSERT(rootFrame->isRootFrame());
RefPtr view = rootFrame->view();
if (!view)
continue;
view->updateLayoutAndStyleIfNeededRecursive(layoutOptions);
}
}
void Page::scheduleRenderingUpdate(OptionSet<RenderingUpdateStep> requestedSteps)
{
LOG_WITH_STREAM(EventLoop, stream << "Page " << this << " scheduleTimedRenderingUpdate() - requestedSteps " << requestedSteps << " remaining steps " << m_renderingUpdateRemainingSteps);
if (m_renderingUpdateRemainingSteps.isEmpty()) {
scheduleRenderingUpdateInternal();
return;
}
computeUnfulfilledRenderingSteps(requestedSteps);
}
void Page::scheduleRenderingUpdateInternal()
{
if (!chrome().client().scheduleRenderingUpdate())
renderingUpdateScheduler().scheduleRenderingUpdate();
m_renderingUpdateIsScheduled = true;
}
std::optional<MonotonicTime> Page::nextRenderingUpdateTimestamp() const
{
if (!m_lastRenderingUpdateTimestamp)
return std::nullopt;
if (!m_renderingUpdateIsScheduled)
return std::nullopt;
auto interval = preferredRenderingUpdateInterval();
auto now = MonotonicTime::now();
ASSERT(now > m_lastRenderingUpdateTimestamp);
return m_lastRenderingUpdateTimestamp + std::floor((now + interval - m_lastRenderingUpdateTimestamp) / interval) * interval;
}
void Page::didScheduleRenderingUpdate()
{
#if ENABLE(ASYNC_SCROLLING)
if (RefPtr scrollingCoordinator = this->scrollingCoordinator())
scrollingCoordinator->didScheduleRenderingUpdate();
#endif
}
void Page::computeUnfulfilledRenderingSteps(OptionSet<RenderingUpdateStep> requestedSteps)
{
// m_renderingUpdateRemainingSteps only has more than one entry for the re-entrant rendering update triggered by testing.
// For scheduling, we only care about the value of the first entry.
auto remainingSteps = m_renderingUpdateRemainingSteps[0];
auto stepsForNextUpdate = requestedSteps - remainingSteps;
m_unfulfilledRequestedSteps.add(stepsForNextUpdate);
}
void Page::triggerRenderingUpdateForTesting()
{
LOG_WITH_STREAM(EventLoop, stream << "Page " << this << " triggerRenderingUpdateForTesting()");
chrome().client().triggerRenderingUpdate();
}
void Page::startTrackingRenderingUpdates()
{
m_isTrackingRenderingUpdates = true;
m_renderingUpdateCount = 0;
}
unsigned Page::renderingUpdateCount() const
{
return m_renderingUpdateCount;
}
// https://html.spec.whatwg.org/multipage/webappapis.html#update-the-rendering
void Page::updateRendering()
{
LOG(EventLoop, "Page %p updateRendering() - re-entering %d", this, !m_renderingUpdateRemainingSteps.isEmpty());
if (m_renderingUpdateRemainingSteps.isEmpty())
m_unfulfilledRequestedSteps = { };
m_renderingUpdateRemainingSteps.append(allRenderingUpdateSteps);
// This function is not reentrant, e.g. a rAF callback may trigger a forces repaint in testing.
// This is why we track m_renderingUpdateRemainingSteps as a stack.
if (m_renderingUpdateRemainingSteps.size() > 1) {
layoutIfNeeded(LayoutOptions::UpdateCompositingLayers);
m_renderingUpdateRemainingSteps.last().remove(updateRenderingSteps);
return;
}
m_lastRenderingUpdateTimestamp = MonotonicTime::now();
m_renderingUpdateIsScheduled = false;
bool isSVGImagePage = chrome().client().isSVGImageChromeClient();
if (!isSVGImagePage)
tracePoint(RenderingUpdateStart);
layoutIfNeeded();
auto runProcessingStep = [&](RenderingUpdateStep step, NOESCAPE const Function<void(Document&)>& perDocumentFunction) {
m_renderingUpdateRemainingSteps.last().remove(step);
forEachRenderableDocument(perDocumentFunction);
};
runProcessingStep(RenderingUpdateStep::RestoreScrollPositionAndViewState, [] (Document& document) {
if (RefPtr frame = document.frame())
frame->protectedLoader()->restoreScrollPositionAndViewStateNowIfNeeded();
});
#if ENABLE(ASYNC_SCROLLING)
if (RefPtr scrollingCoordinator = this->scrollingCoordinator())
scrollingCoordinator->willStartRenderingUpdate();
#endif
// Timestamps should not change while serving the rendering update steps.
Vector<WeakPtr<Document, WeakPtrImplWithEventTargetData>> initialDocuments;
forEachDocument([&initialDocuments] (Document& document) {
document.protectedWindow()->freezeNowTimestamp();
initialDocuments.append(document);
});
runProcessingStep(RenderingUpdateStep::Reveal, [] (Document& document) {
// FIXME: Bug 278193 - Hidden docs should already be excluded.
if (document.visibilityState() != VisibilityState::Hidden)
document.reveal();
});
runProcessingStep(RenderingUpdateStep::FlushAutofocusCandidates, [] (Document& document) {
if (document.isTopDocument())
document.flushAutofocusCandidates();
});
runProcessingStep(RenderingUpdateStep::Resize, [] (Document& document) {
document.runResizeSteps();
});
runProcessingStep(RenderingUpdateStep::Scroll, [] (Document& document) {
document.runScrollSteps();
});
runProcessingStep(RenderingUpdateStep::MediaQueryEvaluation, [] (Document& document) {
document.evaluateMediaQueriesAndReportChanges();
});
// FIXME: This suppression shouldn't be needed.
SUPPRESS_UNCOUNTED_LAMBDA_CAPTURE runProcessingStep(RenderingUpdateStep::AdjustVisibility, [&] (auto& document) {
m_elementTargetingController->adjustVisibilityInRepeatedlyTargetedRegions(document);
});
runProcessingStep(RenderingUpdateStep::Animations, [] (Document& document) {
document.updateAnimationsAndSendEvents();
});
#if ENABLE(FULLSCREEN_API)
runProcessingStep(RenderingUpdateStep::Fullscreen, [] (Document& document) {
document.protectedFullscreen()->dispatchPendingEvents();
});
#else
m_renderingUpdateRemainingSteps.last().remove(RenderingUpdateStep::Fullscreen);
#endif
runProcessingStep(RenderingUpdateStep::VideoFrameCallbacks, [] (Document& document) {
document.serviceRequestVideoFrameCallbacks();
});
runProcessingStep(RenderingUpdateStep::AnimationFrameCallbacks, [] (Document& document) {
document.serviceRequestAnimationFrameCallbacks();
});
runProcessingStep(RenderingUpdateStep::CaretAnimation, [] (Document& document) {
document.serviceCaretAnimation();
});
layoutIfNeeded();
// FIXME: This suppression shouldn't be needed.
SUPPRESS_UNCOUNTED_LAMBDA_CAPTURE runProcessingStep(RenderingUpdateStep::ResizeObservations, [&] (Document& document) {
document.updateResizeObservations(*this);
});
// https://drafts.csswg.org/scroll-animations-1/#event-loop
forEachDocument([] (Document& document) {
document.updateStaleScrollTimelines();
});
runProcessingStep(RenderingUpdateStep::FocusFixup, [&] (Document& document) {
if (RefPtr focusedElement = document.focusedElement()) {
if (!focusedElement->isFocusable())
document.setFocusedElement(nullptr);
}
});
runProcessingStep(RenderingUpdateStep::UpdateContentRelevancy, [] (Document& document) {
document.updateRelevancyOfContentVisibilityElements();
});
runProcessingStep(RenderingUpdateStep::PerformPendingViewTransitions, [] (Document& document) {
document.performPendingViewTransitions();
});
runProcessingStep(RenderingUpdateStep::IntersectionObservations, [] (Document& document) {
document.updateIntersectionObservations();
});
runProcessingStep(RenderingUpdateStep::Images, [] (Document& document) {
for (auto& image : document.protectedCachedResourceLoader()->allCachedSVGImages()) {
if (RefPtr page = image->internalPage())
page->isolatedUpdateRendering();
}
});
runProcessingStep(RenderingUpdateStep::UpdateValidationMessagePositions, [] (Document& document) {
document.adjustValidationMessagePositions();
});
runProcessingStep(RenderingUpdateStep::SnapshottedScrollOffsets, [&] (Document& document) {
Style::AnchorPositionEvaluator::updateSnapshottedScrollOffsets(document);
});
for (auto& document : initialDocuments) {
if (document && document->domWindow())
document->protectedWindow()->unfreezeNowTimestamp();
}
m_renderingUpdateRemainingSteps.last().remove(RenderingUpdateStep::WheelEventMonitorCallbacks);
if (isMonitoringWheelEvents()) [[unlikely]]
wheelEventTestMonitor()->checkShouldFireCallbacks();
if (m_isTrackingRenderingUpdates)
++m_renderingUpdateCount;
layoutIfNeeded(LayoutOptions::UpdateCompositingLayers);
doAfterUpdateRendering();
if (!isSVGImagePage)
tracePoint(RenderingUpdateEnd);
}
void Page::isolatedUpdateRendering()
{
LOG(EventLoop, "Page %p isolatedUpdateRendering()", this);
updateRendering();
renderingUpdateCompleted();
}
void Page::doAfterUpdateRendering()
{
// Code here should do once-per-frame work that needs to be done before painting, and requires
// layout to be up-to-date. It should not run script, trigger layout, or dirty layout.
auto runProcessingStep = [&](RenderingUpdateStep step, NOESCAPE const Function<void(Document&)>& perDocumentFunction) {
m_renderingUpdateRemainingSteps.last().remove(step);
forEachRenderableDocument(perDocumentFunction);
};
runProcessingStep(RenderingUpdateStep::CursorUpdate, [] (Document& document) {
if (RefPtr frame = document.frame())
frame->checkedEventHandler()->updateCursorIfNeeded();
});
forEachRenderableDocument([] (Document& document) {
document.enqueuePaintTimingEntryIfNeeded();
});
forEachRenderableDocument([] (Document& document) {
document.checkedSelection()->updateAppearanceAfterUpdatingRendering();
});
forEachRenderableDocument([] (Document& document) {
document.updateHighlightPositions();
});
#if ENABLE(APP_HIGHLIGHTS)
forEachRenderableDocument([timestamp = m_lastRenderingUpdateTimestamp] (Document& document) {
document.restoreUnrestoredAppHighlights(timestamp);
});
#endif
#if ENABLE(VIDEO)
forEachRenderableDocument([] (Document& document) {
document.updateTextTrackRepresentationImageIfNeeded();
});
#endif
#if ENABLE(IMAGE_ANALYSIS)
updateElementsWithTextRecognitionResults();
#endif
updateValidationMessages();
prioritizeVisibleResources();
m_renderingUpdateRemainingSteps.last().remove(RenderingUpdateStep::EventRegionUpdate);
RefPtr localMainFrame = this->localMainFrame();
#if ENABLE(IOS_TOUCH_EVENTS)
// updateTouchEventRegions() needs to be called only on the top document.
if (RefPtr document = localMainFrame ? localMainFrame->document() : nullptr)
document->updateTouchEventRegions();
#endif
forEachDocument([] (Document& document) {
document.updateEventRegions();
});
#if ENABLE(ACCESSIBILITY_ISOLATED_TREE)
m_renderingUpdateRemainingSteps.last().remove(RenderingUpdateStep::AccessibilityRegionUpdate);
if (shouldUpdateAccessibilityRegions()) {
m_lastAccessibilityObjectRegionsUpdate = m_lastRenderingUpdateTimestamp;
forEachRenderableDocument([] (Document& document) {
document.updateAccessibilityObjectRegions();
});
}
#endif
DebugPageOverlays::doAfterUpdateRendering(*this);
m_renderingUpdateRemainingSteps.last().remove(RenderingUpdateStep::PrepareCanvasesForDisplayOrFlush);
forEachRenderableDocument([] (Document& document) {
document.prepareCanvasesForDisplayOrFlushIfNeeded();
});
if (localMainFrame) {
ASSERT(!localMainFrame->view() || !localMainFrame->view()->needsLayout());
#if ASSERT_ENABLED
for (auto* child = localMainFrame->tree().firstRenderedChild(); child; child = child->tree().traverseNextRendered()) {
auto* localFrame = dynamicDowncast<LocalFrame>(child);
auto* frameView = localFrame->view();
ASSERT(!frameView || !frameView->needsLayout());
}
#endif
if (RefPtr view = localMainFrame->view())
view->notifyAllFramesThatContentAreaWillPaint();
}
computeSampledPageTopColorIfNecessary();
}
void Page::finalizeRenderingUpdate(OptionSet<FinalizeRenderingUpdateFlags> flags)
{
for (auto& rootFrame : m_rootFrames)
finalizeRenderingUpdateForRootFrame(Ref { rootFrame.get() }, flags);
ASSERT(m_renderingUpdateRemainingSteps.last().isEmpty());
renderingUpdateCompleted();
}
void Page::finalizeRenderingUpdateForRootFrame(LocalFrame& rootFrame, OptionSet<FinalizeRenderingUpdateFlags> flags)
{
LOG(EventLoop, "Page %p finalizeRenderingUpdate()", this);
ASSERT(rootFrame.isRootFrame());
RefPtr view = rootFrame.view();
if (!view)
return;
if (flags.contains(FinalizeRenderingUpdateFlags::InvalidateImagesWithAsyncDecodes))
view->invalidateImagesWithAsyncDecodes();
m_renderingUpdateRemainingSteps.last().remove(RenderingUpdateStep::LayerFlush);
view->flushCompositingStateIncludingSubframes();
#if ENABLE(ASYNC_SCROLLING)
m_renderingUpdateRemainingSteps.last().remove(RenderingUpdateStep::ScrollingTreeUpdate);
if (RefPtr scrollingCoordinator = this->scrollingCoordinator()) {
scrollingCoordinator->commitTreeStateIfNeeded();
if (flags.contains(FinalizeRenderingUpdateFlags::ApplyScrollingTreeLayerPositions))
scrollingCoordinator->applyScrollingTreeLayerPositions();
scrollingCoordinator->didCompleteRenderingUpdate();
}
#endif
}
void Page::renderingUpdateCompleted()
{
m_renderingUpdateRemainingSteps.removeLast();
LOG_WITH_STREAM(EventLoop, stream << "Page " << this << " renderingUpdateCompleted() - steps " << m_renderingUpdateRemainingSteps << " unfulfilled steps " << m_unfulfilledRequestedSteps);
if (m_unfulfilledRequestedSteps) {
scheduleRenderingUpdateInternal();
m_unfulfilledRequestedSteps = { };
}
if (!isUtilityPage()) {
auto nextRenderingUpdate = m_lastRenderingUpdateTimestamp + preferredRenderingUpdateInterval();
protectedOpportunisticTaskScheduler()->rescheduleIfNeeded(nextRenderingUpdate);
}
}
Ref<OpportunisticTaskScheduler> Page::protectedOpportunisticTaskScheduler() const
{
return m_opportunisticTaskScheduler;
}
void Page::willStartRenderingUpdateDisplay()
{
LOG_WITH_STREAM(EventLoop, stream << "Page " << this << " willStartRenderingUpdateDisplay()");
// Inspector's use of "composite" is rather innacurate. On Apple platforms, the "composite" step happens
// in another process; these hooks wrap the non-WebKit CA commit time which is mostly painting-related.
if (RefPtr localMainFrame = this->localMainFrame())
m_inspectorController->willComposite(*localMainFrame);
if (RefPtr scrollingCoordinator = m_scrollingCoordinator)
scrollingCoordinator->willStartPlatformRenderingUpdate();
}
void Page::didCompleteRenderingUpdateDisplay()
{
LOG_WITH_STREAM(EventLoop, stream << "Page " << this << " didCompleteRenderingUpdateDisplay()");
if (RefPtr scrollingCoordinator = m_scrollingCoordinator)
scrollingCoordinator->didCompletePlatformRenderingUpdate();
if (RefPtr localMainFrame = this->localMainFrame())
m_inspectorController->didComposite(*localMainFrame);
}
void Page::didCompleteRenderingFrame()
{
LOG_WITH_STREAM(EventLoop, stream << "Page " << this << " didCompleteRenderingFrame()");
// FIXME: This is where we'd call requestPostAnimationFrame callbacks: webkit.org/b/249798.
// FIXME: Run WindowEventLoop tasks from here: webkit.org/b/249684.
InspectorInstrumentation::didCompleteRenderingFrame(m_mainFrame);
}
void Page::didUpdateRendering()
{
LOG_WITH_STREAM(EventLoop, stream << "Page " << this << " didUpdateRendering()");
forEachDocument([&] (Document& document) {
document.flushDeferredRenderingIsSuppressedForViewTransitionChanges();
});
}
void Page::prioritizeVisibleResources()
{
if (loadSchedulingMode() == LoadSchedulingMode::Direct)
return;
RefPtr localTopDocument = this->localTopDocument();
if (!localTopDocument)
return;
Vector<CachedResourceHandle<CachedResource>> toPrioritize;
forEachRenderableDocument([&] (Document& document) {
toPrioritize.appendVector(document.protectedCachedResourceLoader()->visibleResourcesToPrioritize());
});
auto computeSchedulingMode = [&] {
// Parsing generates resource loads.
if (localTopDocument->parsing())
return LoadSchedulingMode::Prioritized;
// Async script execution may generate more resource loads that benefit from prioritization.
if (CheckedPtr scriptRunner = localTopDocument->scriptRunnerIfExists(); scriptRunner && scriptRunner->hasPendingScripts())
return LoadSchedulingMode::Prioritized;
// We still haven't finished loading the visible resources.
if (!toPrioritize.isEmpty())
return LoadSchedulingMode::Prioritized;
return LoadSchedulingMode::Direct;
};
setLoadSchedulingMode(computeSchedulingMode());
if (toPrioritize.isEmpty())
return;
auto resourceLoaders = toPrioritize.map([](auto& resource) {
return resource->loader();
});
platformStrategies()->loaderStrategy()->prioritizeResourceLoads(resourceLoaders);
}
void Page::setLoadSchedulingMode(LoadSchedulingMode mode)
{
if (m_loadSchedulingMode == mode)
return;
m_loadSchedulingMode = mode;
platformStrategies()->loaderStrategy()->setResourceLoadSchedulingMode(*this, m_loadSchedulingMode);
}
#if ENABLE(ACCESSIBILITY_ISOLATED_TREE)
bool Page::shouldUpdateAccessibilityRegions() const
{
static constexpr Seconds updateInterval { 750_ms };
if (!AXObjectCache::accessibilityEnabled() || !AXObjectCache::isIsolatedTreeEnabled())
return false;
ASSERT(m_lastRenderingUpdateTimestamp >= m_lastAccessibilityObjectRegionsUpdate);
if ((m_lastRenderingUpdateTimestamp - m_lastAccessibilityObjectRegionsUpdate) < updateInterval) {
// We've already updated accessibility object rects recently, so skip this update and schedule another for later.
RefPtr<Document> protectedMainDocument;
if (RefPtr localMainFrame = this->localMainFrame())
protectedMainDocument = localMainFrame ? localMainFrame->document() : nullptr;
else if (RefPtr remoteFrame = dynamicDowncast<RemoteFrame>(mainFrame())) {
if (auto* owner = remoteFrame->ownerElement())
protectedMainDocument = &(owner->document());
}
// If accessibility is enabled and we have a main document, that document should have an AX object cache.
ASSERT(!protectedMainDocument || protectedMainDocument->existingAXObjectCache());
if (CheckedPtr topAxObjectCache = protectedMainDocument ? protectedMainDocument->existingAXObjectCache() : nullptr)
topAxObjectCache->scheduleObjectRegionsUpdate();
return false;
}
return true;
}
#endif
#if ENABLE(ACCESSIBILITY_ANIMATION_CONTROL)
void Page::setImageAnimationEnabled(bool enabled)
{
if (!settings().imageAnimationControlEnabled())
return;
// This method overrides any individually set animation play-states (so we need to do work even if `enabled` is
// already equal to `m_imageAnimationEnabled` because there may be individually playing or paused images).
m_imageAnimationEnabled = enabled;
updatePlayStateForAllAnimations();
chrome().client().isAnyAnimationAllowedToPlayDidChange(enabled);
}
#endif // ENABLE(ACCESSIBILITY_ANIMATION_CONTROL)
#if ENABLE(ACCESSIBILITY_NON_BLINKING_CURSOR)
void Page::setPrefersNonBlinkingCursor(bool enabled)
{
m_prefersNonBlinkingCursor = enabled;
}
#endif
void Page::suspendScriptedAnimations()
{
m_scriptedAnimationsSuspended = true;
forEachDocument([] (Document& document) {
document.suspendScriptedAnimationControllerCallbacks();
});
}
void Page::resumeScriptedAnimations()
{
m_scriptedAnimationsSuspended = false;
forEachDocument([] (Document& document) {
document.resumeScriptedAnimationControllerCallbacks();
});
}
void Page::timelineControllerMaximumAnimationFrameRateDidChange(AnimationTimelinesController&)
{
if (CheckedPtr scheduler = existingRenderingUpdateScheduler())
scheduler->adjustRenderingUpdateFrequency();
chrome().client().renderingUpdateFramesPerSecondChanged();
}
std::optional<FramesPerSecond> Page::preferredRenderingUpdateFramesPerSecond(OptionSet<PreferredRenderingUpdateOption> flags) const
{
// Unless the call site specifies an explicit set of options, this method will account for both
// throttling reasons and the frame rate of animations to determine its return value. The only
// place where we specify an explicit set of options is AnimationTimelinesController::updateAnimationsAndSendEvents()
// where we need to identify what the update frame rate would be _not_ accounting for animations.
auto throttlingReasons = m_throttlingReasons;
if (!flags.contains(PreferredRenderingUpdateOption::IncludeThrottlingReasons))
throttlingReasons = { };
auto frameRate = preferredFramesPerSecond(throttlingReasons, m_displayNominalFramesPerSecond, settings().preferPageRenderingUpdatesNear60FPSEnabled());
if (!flags.contains(PreferredRenderingUpdateOption::IncludeAnimationsFrameRate))
return frameRate;
// If we're throttled, we do not account for the frame rate set on animations and simply use the throttled frame rate.
auto unthrottledDefaultFrameRate = preferredRenderingUpdateFramesPerSecond({ });
auto isThrottled = frameRate && unthrottledDefaultFrameRate && *frameRate < *unthrottledDefaultFrameRate;
if (isThrottled)
return frameRate;
forEachDocument([&] (Document& document) {
if (CheckedPtr timelinesController = document.timelinesController()) {
if (auto timelinePreferredFrameRate = timelinesController->maximumAnimationFrameRate()) {
if (!frameRate || *frameRate < *timelinePreferredFrameRate)
frameRate = *timelinePreferredFrameRate;
}
}
});
return frameRate;
}
Seconds Page::preferredRenderingUpdateInterval() const
{
return preferredFrameInterval(m_throttlingReasons, m_displayNominalFramesPerSecond, settings().preferPageRenderingUpdatesNear60FPSEnabled());
}
void Page::setIsVisuallyIdleInternal(bool isVisuallyIdle)
{
if (isVisuallyIdle == m_throttlingReasons.contains(ThrottlingReason::VisuallyIdle))
return;
m_throttlingReasons.set(ThrottlingReason::VisuallyIdle, isVisuallyIdle);
if (CheckedPtr scheduler = existingRenderingUpdateScheduler())
scheduler->adjustRenderingUpdateFrequency();
chrome().client().renderingUpdateFramesPerSecondChanged();
}
void Page::handleLowPowerModeChange(bool isLowPowerModeEnabled)
{
if (!canUpdateThrottlingReason(ThrottlingReason::LowPowerMode))
return;
if (isLowPowerModeEnabled == m_throttlingReasons.contains(ThrottlingReason::LowPowerMode))
return;
m_throttlingReasons.set(ThrottlingReason::LowPowerMode, isLowPowerModeEnabled);
if (CheckedPtr scheduler = existingRenderingUpdateScheduler())
scheduler->adjustRenderingUpdateFrequency();
chrome().client().renderingUpdateFramesPerSecondChanged();
updateDOMTimerAlignmentInterval();
}
void Page::handleThermalMitigationChange(bool thermalMitigationEnabled)
{
if (!canUpdateThrottlingReason(ThrottlingReason::ThermalMitigation))
return;
if (thermalMitigationEnabled == m_throttlingReasons.contains(ThrottlingReason::ThermalMitigation))
return;
m_throttlingReasons.set(ThrottlingReason::ThermalMitigation, thermalMitigationEnabled);
if (settings().respondToThermalPressureAggressively()) {
m_throttlingReasons.set(ThrottlingReason::AggressiveThermalMitigation, thermalMitigationEnabled);
if (CheckedPtr scheduler = existingRenderingUpdateScheduler())
scheduler->adjustRenderingUpdateFrequency();
chrome().client().renderingUpdateFramesPerSecondChanged();
}
RELEASE_LOG(PerformanceLogging, "%p - Page::handleThermalMitigationChange: thermal mitigation %d, aggressive thermal mitigation %d", this, isThermalMitigationEnabled(), isAggressiveThermalMitigationEnabled());
updateDOMTimerAlignmentInterval();
}
void Page::userStyleSheetLocationChanged()
{
// FIXME: Eventually we will move to a model of just being handed the sheet
// text instead of loading the URL ourselves.
URL url = m_settings->userStyleSheetLocation();
// Allow any local file URL scheme to be loaded.
if (LegacySchemeRegistry::shouldTreatURLSchemeAsLocal(url.protocol()))
m_userStyleSheetPath = url.fileSystemPath();
else
m_userStyleSheetPath = String();
m_didLoadUserStyleSheet = false;
m_userStyleSheet = String();
m_userStyleSheetModificationTime = std::nullopt;
// Data URLs with base64-encoded UTF-8 style sheets are common. We can process them
// synchronously and avoid using a loader.
if (url.protocolIsData() && url.string().startsWith("data:text/css;charset=utf-8;base64,"_s)) {
m_didLoadUserStyleSheet = true;
String styleSheetAsBase64 = base64DecodeToString(PAL::decodeURLEscapeSequences(StringView(url.string()).substring(35)), { Base64DecodeOption::ValidatePadding, Base64DecodeOption::IgnoreWhitespace });
if (!styleSheetAsBase64.isNull())
m_userStyleSheet = styleSheetAsBase64;
}
forEachDocument([] (Document& document) {
document.checkedExtensionStyleSheets()->updatePageUserSheet();
});
}
const String& Page::userStyleSheet() const
{
if (m_userStyleSheetPath.isEmpty())
return m_userStyleSheet;
auto modificationTime = FileSystem::fileModificationTime(m_userStyleSheetPath);
if (!modificationTime) {
// The stylesheet either doesn't exist, was just deleted, or is
// otherwise unreadable. If we've read the stylesheet before, we should
// throw away that data now as it no longer represents what's on disk.
m_userStyleSheet = String();
return m_userStyleSheet;
}
// If the stylesheet hasn't changed since the last time we read it, we can
// just return the old data.
if (m_didLoadUserStyleSheet && (m_userStyleSheetModificationTime && modificationTime.value() <= m_userStyleSheetModificationTime.value()))
return m_userStyleSheet;
m_didLoadUserStyleSheet = true;
m_userStyleSheet = String();
m_userStyleSheetModificationTime = modificationTime;
// FIXME: It would be better to load this asynchronously to avoid blocking
// the process, but we will first need to create an asynchronous loading
// mechanism that is not tied to a particular Frame. We will also have to
// determine what our behavior should be before the stylesheet is loaded
// and what should happen when it finishes loading, especially with respect
// to when the load event fires, when Document::close is called, and when
// layout/paint are allowed to happen.
RefPtr data = SharedBuffer::createWithContentsOfFile(m_userStyleSheetPath);
if (!data)
return m_userStyleSheet;
m_userStyleSheet = TextResourceDecoder::create(cssContentTypeAtom())->decodeAndFlush(data->span());
return m_userStyleSheet;
}
void Page::userAgentChanged()
{
forEachDocument([] (Document& document) {
if (RefPtr window = document.domWindow()) {
if (RefPtr navigator = window->optionalNavigator())
navigator->userAgentChanged();
}
});
}
void Page::invalidateStylesForAllLinks()
{
forEachDocument([] (Document& document) {
if (CheckedPtr visitedLinkState = document.visitedLinkStateIfExists())
visitedLinkState->invalidateStyleForAllLinks();
});
}
void Page::invalidateStylesForLink(SharedStringHash linkHash)
{
forEachDocument([&] (Document& document) {
if (CheckedPtr visitedLinkState = document.visitedLinkStateIfExists())
visitedLinkState->invalidateStyleForLink(linkHash);
});
}
void Page::invalidateInjectedStyleSheetCacheInAllFrames()
{
forEachDocument([] (Document& document) {
if (CheckedPtr extensionStyleSheets = document.extensionStyleSheetsIfExists())
extensionStyleSheets->invalidateInjectedStyleSheetCache();
});
}
void Page::setDebugger(JSC::Debugger* debugger)
{
if (m_debugger == debugger)
return;
m_debugger = debugger;
for (RefPtr frame = &m_mainFrame.get(); frame; frame = frame->tree().traverseNext())
frame->protectedWindowProxy()->attachDebugger(m_debugger);
}
bool Page::hasCustomHTMLTokenizerTimeDelay() const
{
return m_settings->maxParseDuration() != -1;
}
double Page::customHTMLTokenizerTimeDelay() const
{
ASSERT(m_settings->maxParseDuration() != -1);
return m_settings->maxParseDuration();
}
void Page::setCORSDisablingPatterns(Vector<UserContentURLPattern>&& patterns)
{
m_corsDisablingPatterns = WTFMove(patterns);
}
void Page::addCORSDisablingPatternForTesting(UserContentURLPattern&& pattern)
{
m_corsDisablingPatterns.append(WTFMove(pattern));
}
void Page::setMemoryCacheClientCallsEnabled(bool enabled)
{
if (m_areMemoryCacheClientCallsEnabled == enabled)
return;
m_areMemoryCacheClientCallsEnabled = enabled;
if (!enabled || !m_hasPendingMemoryCacheLoadNotifications)
return;
for (RefPtr frame = &mainFrame(); frame; frame = frame->tree().traverseNext()) {
if (RefPtr localFrame = dynamicDowncast<LocalFrame>(*frame))
localFrame->protectedLoader()->tellClientAboutPastMemoryCacheLoads();
}
m_hasPendingMemoryCacheLoadNotifications = false;
}
void Page::setEditable(bool isEditable)
{
m_isEditable = isEditable;
if (CheckedPtr cache = axObjectCache()) {
forEachDocument([&] (Document& document) {
cache->handlePageEditibilityChanged(document);
});
}
}
void Page::hiddenPageDOMTimerThrottlingStateChanged()
{
// Disable & reengage to ensure state is updated.
setTimerThrottlingState(TimerThrottlingState::Disabled);
updateTimerThrottlingState();
}
void Page::updateTimerThrottlingState()
{
// Timer throttling disabled if page is visually active, or disabled by setting.
if (!m_settings->hiddenPageDOMTimerThrottlingEnabled() || !(m_activityState & ActivityState::IsVisuallyIdle)) {
setTimerThrottlingState(TimerThrottlingState::Disabled);
return;
}
// If the page is visible (but idle), there is any activity (loading, media playing, etc), or per setting,
// we allow timer throttling, but not increasing timer throttling.
if (!m_settings->hiddenPageDOMTimerThrottlingAutoIncreases()
|| m_activityState.containsAny({ActivityState::IsVisible, ActivityState::IsAudible, ActivityState::IsLoading, ActivityState::IsCapturingMedia })) {
setTimerThrottlingState(TimerThrottlingState::Enabled);
return;
}
// If we get here increasing timer throttling is enabled.
setTimerThrottlingState(TimerThrottlingState::EnabledIncreasing);
}
void Page::setTimerThrottlingState(TimerThrottlingState state)
{
if (state == m_timerThrottlingState)
return;
m_timerThrottlingState = state;
m_timerThrottlingStateLastChangedTime = MonotonicTime::now();
updateDOMTimerAlignmentInterval();
// When throttling is disabled, release all throttled timers.
if (state == TimerThrottlingState::Disabled) {
forEachDocument([] (Document& document) {
document.didChangeTimerAlignmentInterval();
});
}
}
void Page::setDOMTimerAlignmentIntervalIncreaseLimit(Seconds limit)
{
m_domTimerAlignmentIntervalIncreaseLimit = limit;
// If (m_domTimerAlignmentIntervalIncreaseLimit < m_domTimerAlignmentInterval) then we need
// to update m_domTimerAlignmentInterval, if greater then need to restart the increase timer.
if (m_timerThrottlingState == TimerThrottlingState::EnabledIncreasing)
updateDOMTimerAlignmentInterval();
}
void Page::updateDOMTimerAlignmentInterval()
{
bool needsIncreaseTimer = false;
switch (m_timerThrottlingState) {
case TimerThrottlingState::Disabled: {
bool isInLowPowerOrThermallyMitigatedMode = isLowPowerModeEnabled() || isThermalMitigationEnabled();
m_domTimerAlignmentInterval = isInLowPowerOrThermallyMitigatedMode ? DOMTimer::defaultAlignmentIntervalInLowPowerOrThermallyMitigatedMode() : DOMTimer::defaultAlignmentInterval();
break;
}
case TimerThrottlingState::Enabled:
m_domTimerAlignmentInterval = DOMTimer::hiddenPageAlignmentInterval();
break;
case TimerThrottlingState::EnabledIncreasing:
// For pages in prerender state maximum throttling kicks in immediately.
if (m_isPrerender)
m_domTimerAlignmentInterval = m_domTimerAlignmentIntervalIncreaseLimit;
else {
ASSERT(!!m_timerThrottlingStateLastChangedTime);
m_domTimerAlignmentInterval = MonotonicTime::now() - m_timerThrottlingStateLastChangedTime;
// If we're below the limit, set the timer. If above, clamp to limit.
if (m_domTimerAlignmentInterval < m_domTimerAlignmentIntervalIncreaseLimit)
needsIncreaseTimer = true;
else
m_domTimerAlignmentInterval = m_domTimerAlignmentIntervalIncreaseLimit;
}
// Alignment interval should not be less than DOMTimer::hiddenPageAlignmentInterval().
m_domTimerAlignmentInterval = std::max(m_domTimerAlignmentInterval, DOMTimer::hiddenPageAlignmentInterval());
}
// If throttling is enabled, auto-increasing of throttling is enabled, and the auto-increase
// limit has not yet been reached, and then arm the timer to consider an increase. Time to wait
// between increases is equal to the current throttle time. Since alignment interval increases
// exponentially, time between steps is exponential too.
if (!needsIncreaseTimer)
m_domTimerAlignmentIntervalIncreaseTimer.stop();
else if (!m_domTimerAlignmentIntervalIncreaseTimer.isActive())
m_domTimerAlignmentIntervalIncreaseTimer.startOneShot(m_domTimerAlignmentInterval);
}
void Page::domTimerAlignmentIntervalIncreaseTimerFired()
{
ASSERT(m_settings->hiddenPageDOMTimerThrottlingAutoIncreases());
ASSERT(m_timerThrottlingState == TimerThrottlingState::EnabledIncreasing);
ASSERT(m_domTimerAlignmentInterval < m_domTimerAlignmentIntervalIncreaseLimit);
// Alignment interval is increased to equal the time the page has been throttled, to a limit.
updateDOMTimerAlignmentInterval();
}
void Page::storageBlockingStateChanged()
{
forEachDocument([] (Document& document) {
document.storageBlockingStateDidChange();
});
}
void Page::updateIsPlayingMedia()
{
MediaProducerMediaStateFlags state;
forEachDocument([&](auto& document) {
state.add(document.mediaState());
});
if (state == m_mediaState)
return;
m_mediaState = state;
chrome().client().isPlayingMediaDidChange(state);
}
void Page::schedulePlaybackControlsManagerUpdate()
{
#if ENABLE(VIDEO)
if (!m_playbackControlsManagerUpdateTimer.isActive())
m_playbackControlsManagerUpdateTimer.startOneShot(0_s);
#endif
}
#if ENABLE(VIDEO)
void Page::playbackControlsManagerUpdateTimerFired()
{
if (auto bestMediaElement = HTMLMediaElement::bestMediaElementForRemoteControls(MediaElementSession::PlaybackControlsPurpose::ControlsManager))
chrome().client().setUpPlaybackControlsManager(*bestMediaElement);
else
chrome().client().clearPlaybackControlsManager();
}
void Page::mediaEngineChanged(HTMLMediaElement& mediaElement)
{
chrome().client().mediaEngineChanged(mediaElement);
}
#endif
void Page::setMuted(MediaProducerMutedStateFlags mutedState)
{
#if ENABLE(MEDIA_STREAM) && ENABLE(MEDIA_SESSION)
bool cameraCaptureStateDidChange = mutedState.contains(MediaProducerMutedState::VideoCaptureIsMuted) != m_mutedState.contains(MediaProducerMutedState::VideoCaptureIsMuted);
bool microphoneCaptureStateDidChange = mutedState.contains(MediaProducerMutedState::AudioCaptureIsMuted) != m_mutedState.contains(MediaProducerMutedState::AudioCaptureIsMuted);
bool screenshareCaptureStateDidChange = (mutedState.contains(MediaProducerMutedState::ScreenCaptureIsMuted) || mutedState.contains(MediaProducerMutedState::WindowCaptureIsMuted)) != (m_mutedState.contains(MediaProducerMutedState::ScreenCaptureIsMuted) || m_mutedState.contains(MediaProducerMutedState::WindowCaptureIsMuted));
#endif
m_mutedState = mutedState;
forEachDocument([&] (Document& document) {
#if ENABLE(MEDIA_STREAM) && ENABLE(MEDIA_SESSION)
if (cameraCaptureStateDidChange)
document.cameraCaptureStateDidChange();
if (microphoneCaptureStateDidChange)
document.microphoneCaptureStateDidChange();
if (screenshareCaptureStateDidChange)
document.screenshareCaptureStateDidChange();
#endif
document.pageMutedStateDidChange();
});
}
void Page::setShouldSuppressHDR(bool shouldSuppressHDR)
{
if (m_shouldSuppressHDR == shouldSuppressHDR)
return;
m_shouldSuppressHDR = shouldSuppressHDR;
forEachDocument([](auto& document) {
document.shouldSuppressHDRDidChange();
});
}
#if ENABLE(MEDIA_STREAM)
static inline MediaProducerMutedStateFlags toMediaProducerMutedStateFlags(MediaProducerMediaCaptureKind kind)
{
switch (kind) {
case MediaProducerMediaCaptureKind::Microphone:
return MediaProducerMutedState::AudioCaptureIsMuted;
case MediaProducerMediaCaptureKind::Camera:
return MediaProducerMutedState::VideoCaptureIsMuted;
case MediaProducerMediaCaptureKind::Display:
return { MediaProducerMutedState::ScreenCaptureIsMuted, MediaProducerMutedState::WindowCaptureIsMuted };
case MediaProducerMediaCaptureKind::SystemAudio:
case MediaProducerMediaCaptureKind::EveryKind:
ASSERT_NOT_REACHED();
}
return { };
}
static inline MediaProducerMutedStateFlags computeCaptureMutedState(MediaProducerMutedStateFlags currentState, bool isActive, MediaProducerMediaCaptureKind kind)
{
auto flagsToUpdate = toMediaProducerMutedStateFlags(kind);
return isActive ? (currentState - flagsToUpdate) : (currentState | flagsToUpdate);
}
void Page::updateCaptureState(bool isActive, MediaProducerMediaCaptureKind kind)
{
m_mutedState = computeCaptureMutedState(m_mutedState, isActive, kind);
forEachDocument([&] (Document& document) {
document.pageMutedStateDidChange();
});
}
void Page::voiceActivityDetected()
{
if (auto* controller = UserMediaController::from(this))
controller->voiceActivityDetected();
}
#endif
void Page::stopMediaCapture(MediaProducerMediaCaptureKind kind)
{
UNUSED_PARAM(kind);
#if ENABLE(MEDIA_STREAM)
forEachDocument([kind] (Document& document) {
document.stopMediaCapture(kind);
});
#endif
}
bool Page::mediaPlaybackExists()
{
#if ENABLE(VIDEO)
if (RefPtr platformMediaSessionManager = PlatformMediaSessionManager::singletonIfExists())
return !platformMediaSessionManager->hasNoSession();
#endif
return false;
}
bool Page::mediaPlaybackIsPaused()
{
#if ENABLE(VIDEO)
if (RefPtr platformMediaSessionManager = PlatformMediaSessionManager::singletonIfExists())
return platformMediaSessionManager->mediaPlaybackIsPaused(mediaSessionGroupIdentifier());
#endif
return false;
}
void Page::pauseAllMediaPlayback()
{
#if ENABLE(VIDEO)
if (RefPtr platformMediaSessionManager = PlatformMediaSessionManager::singletonIfExists())
platformMediaSessionManager->pauseAllMediaPlaybackForGroup(mediaSessionGroupIdentifier());
#endif
}
void Page::suspendAllMediaPlayback()
{
#if ENABLE(VIDEO)
ASSERT(!m_mediaPlaybackIsSuspended);
if (m_mediaPlaybackIsSuspended)
return;
if (RefPtr platformMediaSessionManager = PlatformMediaSessionManager::singletonIfExists())
platformMediaSessionManager->suspendAllMediaPlaybackForGroup(mediaSessionGroupIdentifier());
// FIXME: We cannot set m_mediaPlaybackIsSuspended before, see https://bugs.webkit.org/show_bug.cgi?id=192829#c7.
m_mediaPlaybackIsSuspended = true;
#endif
}
std::optional<MediaSessionGroupIdentifier> Page::mediaSessionGroupIdentifier() const
{
if (!m_mediaSessionGroupIdentifier) {
if (auto identifier = this->identifier())
m_mediaSessionGroupIdentifier = ObjectIdentifier<MediaSessionGroupIdentifierType>(identifier->toUInt64());
}
return m_mediaSessionGroupIdentifier;
}
void Page::resumeAllMediaPlayback()
{
#if ENABLE(VIDEO)
ASSERT(m_mediaPlaybackIsSuspended);
if (!m_mediaPlaybackIsSuspended)
return;
m_mediaPlaybackIsSuspended = false;
if (RefPtr platformMediaSessionManager = PlatformMediaSessionManager::singletonIfExists())
platformMediaSessionManager->resumeAllMediaPlaybackForGroup(mediaSessionGroupIdentifier());
#endif
}
void Page::suspendAllMediaBuffering()
{
#if ENABLE(VIDEO)
ASSERT(!m_mediaBufferingIsSuspended);
if (m_mediaBufferingIsSuspended)
return;
m_mediaBufferingIsSuspended = true;
if (RefPtr platformMediaSessionManager = PlatformMediaSessionManager::singletonIfExists())
platformMediaSessionManager->suspendAllMediaBufferingForGroup(mediaSessionGroupIdentifier());
#endif
}
void Page::resumeAllMediaBuffering()
{
#if ENABLE(VIDEO)
if (!m_mediaBufferingIsSuspended)
return;
m_mediaBufferingIsSuspended = false;
if (RefPtr platformMediaSessionManager = PlatformMediaSessionManager::singletonIfExists())
platformMediaSessionManager->resumeAllMediaBufferingForGroup(mediaSessionGroupIdentifier());
#endif
}
unsigned Page::subframeCount() const
{
return mainFrame().tree().descendantCount();
}
void Page::resumeAnimatingImages()
{
// Drawing models which cache painted content while out-of-window (WebKit2's composited drawing areas, etc.)
// require that we repaint animated images to kickstart the animation loop.
RefPtr localMainFrame = this->localMainFrame();
if (RefPtr view = localMainFrame ? localMainFrame->view() : nullptr)
view->resumeVisibleImageAnimationsIncludingSubframes();
}
void Page::setActivityState(OptionSet<ActivityState> activityState)
{
auto changed = m_activityState ^ activityState;
if (!changed)
return;
auto oldActivityState = m_activityState;
bool wasVisibleAndActive = isVisibleAndActive();
m_activityState = activityState;
checkedFocusController()->setActivityState(activityState);
if (changed & ActivityState::IsVisible)
setIsVisibleInternal(activityState.contains(ActivityState::IsVisible));
if (changed & ActivityState::IsInWindow)
setIsInWindowInternal(activityState.contains(ActivityState::IsInWindow));
if (changed & ActivityState::IsVisuallyIdle)
setIsVisuallyIdleInternal(activityState.contains(ActivityState::IsVisuallyIdle));
WeakPtr localMainFrame = dynamicDowncast<LocalFrame>(m_mainFrame.get());
if (changed & ActivityState::WindowIsActive) {
if (RefPtr view = localMainFrame ? localMainFrame->view() : nullptr)
view->updateTiledBackingAdaptiveSizing();
}
if (changed.containsAny({ActivityState::IsVisible, ActivityState::IsVisuallyIdle, ActivityState::IsAudible, ActivityState::IsLoading, ActivityState::IsCapturingMedia }))
updateTimerThrottlingState();
for (auto& observer : m_activityStateChangeObservers)
observer.activityStateDidChange(oldActivityState, m_activityState);
if (wasVisibleAndActive != isVisibleAndActive()) {
PlatformMediaSessionManager::updateNowPlayingInfoIfNecessary();
stopKeyboardScrollAnimation();
}
if (RefPtr document = localMainFrame ? localMainFrame->document() : nullptr) {
if (CheckedPtr cache = document->existingAXObjectCache())
cache->onPageActivityStateChange(m_activityState);
}
if (RefPtr performanceMonitor = m_performanceMonitor.get())
performanceMonitor->activityStateChanged(oldActivityState, activityState);
}
void Page::stopKeyboardScrollAnimation()
{
for (RefPtr frame = &mainFrame(); frame; frame = frame->tree().traverseNext()) {
RefPtr localFrame = dynamicDowncast<LocalFrame>(*frame);
if (!localFrame)
continue;
RefPtr frameView = localFrame->view();
if (!frameView)
continue;
frameView->stopKeyboardScrollAnimation();
auto scrollableAreas = frameView->scrollableAreas();
if (!scrollableAreas)
continue;
for (CheckedRef area : *scrollableAreas) {
// First call stopAsyncAnimatedScroll() to prepare for the keyboard scroller running on the scrolling thread.
area->stopAsyncAnimatedScroll();
area->stopKeyboardScrollAnimation();
}
}
}
Ref<DocumentSyncData> Page::protectedTopDocumentSyncData() const
{
return m_topDocumentSyncData;
}
bool Page::isVisibleAndActive() const
{
return m_activityState.contains(ActivityState::IsVisible) && m_activityState.contains(ActivityState::WindowIsActive);
}
bool Page::isWindowActive() const
{
return m_activityState.contains(ActivityState::WindowIsActive);
}
void Page::setIsVisible(bool isVisible)
{
auto state = m_activityState;
if (isVisible) {
state.remove(ActivityState::IsVisuallyIdle);
state.add({ ActivityState::IsVisible, ActivityState::IsVisibleOrOccluded });
} else {
state.add(ActivityState::IsVisuallyIdle);
state.remove({ ActivityState::IsVisible, ActivityState::IsVisibleOrOccluded });
}
setActivityState(state);
}
void Page::setIsVisibleInternal(bool isVisible)
{
// FIXME: The visibility state should be stored on the top-level document.
// https://bugs.webkit.org/show_bug.cgi?id=116769
if (isVisible) {
m_isPrerender = false;
resumeScriptedAnimations();
#if PLATFORM(IOS_FAMILY)
forEachDocument([] (Document& document) {
document.resumeDeviceMotionAndOrientationUpdates();
});
#endif
RefPtr localMainFrame = this->localMainFrame();
if (RefPtr view = localMainFrame ? localMainFrame->view() : nullptr)
view->show();
if (m_settings->hiddenPageCSSAnimationSuspensionEnabled()) {
forEachDocument([] (Document& document) {
if (CheckedPtr timelines = document.timelinesController())
timelines->resumeAnimations();
});
}
forEachDocument([] (Document& document) {
if (CheckedPtr svgExtensions = document.svgExtensionsIfExists())
svgExtensions->unpauseAnimations();
});
resumeAnimatingImages();
if (m_navigationToLogWhenVisible) {
logNavigation(m_navigationToLogWhenVisible.value());
m_navigationToLogWhenVisible = std::nullopt;
}
}
if (!isVisible) {
if (m_settings->hiddenPageCSSAnimationSuspensionEnabled()) {
forEachDocument([] (Document& document) {
if (CheckedPtr timelines = document.timelinesController())
timelines->suspendAnimations();
});
}
forEachDocument([] (Document& document) {
if (CheckedPtr svgExtensions = document.svgExtensionsIfExists())
svgExtensions->pauseAnimations();
});
#if PLATFORM(IOS_FAMILY)
forEachDocument([] (Document& document) {
document.suspendDeviceMotionAndOrientationUpdates();
});
#endif
suspendScriptedAnimations();
RefPtr localMainFrame = this->localMainFrame();
if (RefPtr view = localMainFrame ? localMainFrame->view() : nullptr)
view->hide();
}
forEachDocument([] (Document& document) {
document.visibilityStateChanged();
});
}
void Page::setIsPrerender()
{
m_isPrerender = true;
updateDOMTimerAlignmentInterval();
}
VisibilityState Page::visibilityState() const
{
if (isVisible())
return VisibilityState::Visible;
return VisibilityState::Hidden;
}
void Page::setHeaderHeight(int headerHeight)
{
if (headerHeight == m_headerHeight)
return;
m_headerHeight = headerHeight;
RefPtr localMainFrame = this->localMainFrame();
RefPtr frameView = localMainFrame ? localMainFrame->view() : nullptr;
if (!frameView)
return;
if (!frameView->renderView())
return;
frameView->updateScrollbars(frameView->scrollPosition());
frameView->setNeedsLayoutAfterViewConfigurationChange();
frameView->setNeedsCompositingGeometryUpdate();
}
void Page::setFooterHeight(int footerHeight)
{
if (footerHeight == m_footerHeight)
return;
m_footerHeight = footerHeight;
RefPtr localMainFrame = this->localMainFrame();
RefPtr frameView = localMainFrame ? localMainFrame->view() : nullptr;
if (!frameView)
return;
if (!frameView->renderView())
return;
frameView->updateScrollbars(frameView->scrollPosition());
frameView->setNeedsLayoutAfterViewConfigurationChange();
frameView->setNeedsCompositingGeometryUpdate();
}
void Page::setCurrentKeyboardScrollingAnimator(KeyboardScrollingAnimator* animator)
{
m_currentKeyboardScrollingAnimator = animator;
}
bool Page::shouldApplyScreenFingerprintingProtections(Document& document) const
{
if (advancedPrivacyProtections().contains(AdvancedPrivacyProtections::FingerprintingProtections))
return true;
if (advancedPrivacyProtections().contains(AdvancedPrivacyProtections::ScriptTelemetry))
return document.requiresScriptExecutionTelemetry(ScriptTelemetryCategory::ScreenOrViewport);
return false;
}
OptionSet<AdvancedPrivacyProtections> Page::advancedPrivacyProtections() const
{
return protectedMainFrame()->advancedPrivacyProtections();
}
#if ENABLE(REMOTE_INSPECTOR)
bool Page::inspectable() const
{
return m_inspectorDebuggable->inspectable();
}
void Page::setInspectable(bool inspectable)
{
m_inspectorDebuggable->setInspectable(inspectable);
}
String Page::remoteInspectionNameOverride() const
{
return m_inspectorDebuggable->nameOverride();
}
void Page::setRemoteInspectionNameOverride(const String& name)
{
m_inspectorDebuggable->setNameOverride(name);
}
void Page::remoteInspectorInformationDidChange()
{
m_inspectorDebuggable->update();
}
#endif
void Page::addLayoutMilestones(OptionSet<LayoutMilestone> milestones)
{
// In the future, we may want a function that replaces m_layoutMilestones instead of just adding to it.
m_requestedLayoutMilestones.add(milestones);
}
void Page::removeLayoutMilestones(OptionSet<LayoutMilestone> milestones)
{
m_requestedLayoutMilestones.remove(milestones);
}
Color Page::themeColor() const
{
if (RefPtr localTopDocument = this->localTopDocument())
return localTopDocument->themeColor();
return { };
}
#if ENABLE(WEB_PAGE_SPATIAL_BACKDROP)
std::optional<SpatialBackdropSource> Page::spatialBackdropSource() const
{
RefPtr localMainFrame = this->localMainFrame();
RefPtr document = localMainFrame ? localMainFrame->document() : nullptr;
if (!document)
return std::nullopt;
return document->spatialBackdropSource();
}
#endif
Color Page::pageExtendedBackgroundColor() const
{
RefPtr localMainFrame = this->localMainFrame();
RefPtr frameView = localMainFrame ? localMainFrame->view() : nullptr;
if (!frameView)
return Color();
CheckedPtr renderView = frameView->renderView();
if (!renderView)
return Color();
return renderView->compositor().rootExtendedBackgroundColor();
}
Color Page::sampledPageTopColor() const
{
return valueOrDefault(m_sampledPageTopColor);
}
void Page::computeSampledPageTopColorIfNecessary()
{
if (m_sampledPageTopColor)
return;
m_sampledPageTopColor = PageColorSampler::sampleTop(*this);
if (m_sampledPageTopColor)
chrome().client().sampledPageTopColorChanged();
}
void Page::clearSampledPageTopColor()
{
if (std::exchange(m_sampledPageTopColor, std::nullopt))
chrome().client().sampledPageTopColorChanged();
}
#if HAVE(APP_ACCENT_COLORS) && PLATFORM(MAC)
void Page::setAppUsesCustomAccentColor(bool appUsesCustomAccentColor)
{
m_appUsesCustomAccentColor = appUsesCustomAccentColor;
}
bool Page::appUsesCustomAccentColor() const
{
return m_appUsesCustomAccentColor;
}
#endif
void Page::setUnderPageBackgroundColorOverride(Color&& underPageBackgroundColorOverride)
{
if (underPageBackgroundColorOverride == m_underPageBackgroundColorOverride)
return;
m_underPageBackgroundColorOverride = WTFMove(underPageBackgroundColorOverride);
scheduleRenderingUpdate({ });
#if HAVE(RUBBER_BANDING)
RefPtr localMainFrame = this->localMainFrame();
if (RefPtr frameView = localMainFrame ? localMainFrame->view() : nullptr) {
if (CheckedPtr renderView = frameView->renderView()) {
if (renderView->usesCompositing())
renderView->compositor().updateLayerForOverhangAreasBackgroundColor();
}
}
#endif // HAVE(RUBBER_BANDING)
}
// These are magical constants that might be tweaked over time.
static const double gMinimumPaintedAreaRatio = 0.1;
static const double gMaximumUnpaintedAreaRatio = 0.04;
bool Page::isCountingRelevantRepaintedObjects() const
{
return m_isCountingRelevantRepaintedObjects && m_requestedLayoutMilestones.contains(LayoutMilestone::DidHitRelevantRepaintedObjectsAreaThreshold);
}
void Page::startCountingRelevantRepaintedObjects()
{
// Reset everything in case we didn't hit the threshold last time.
resetRelevantPaintedObjectCounter();
m_isCountingRelevantRepaintedObjects = true;
}
void Page::resetRelevantPaintedObjectCounter()
{
m_isCountingRelevantRepaintedObjects = false;
m_relevantUnpaintedRenderObjects.clear();
m_internals->topRelevantPaintedRegion = Region();
m_internals->bottomRelevantPaintedRegion = Region();
m_internals->relevantUnpaintedRegion = Region();
}
static LayoutRect relevantViewRect(RenderView* view)
{
LayoutRect viewRect = view->viewRect();
float relevantViewRectWidth = 980;
#if PLATFORM(WATCHOS)
// FIXME(186051): Consider limiting the relevant rect width to the view width everywhere.
relevantViewRectWidth = std::min<float>(viewRect.width().toFloat(), relevantViewRectWidth);
#endif
// DidHitRelevantRepaintedObjectsAreaThreshold is a LayoutMilestone intended to indicate that
// a certain relevant amount of content has been drawn to the screen. This is the rect that
// has been determined to be relevant in the context of this goal. We may choose to tweak
// the rect over time, much like we may choose to tweak gMinimumPaintedAreaRatio and
// gMaximumUnpaintedAreaRatio. But this seems to work well right now.
LayoutRect relevantViewRect { 0, 0, LayoutUnit(relevantViewRectWidth), 1300 };
// If the viewRect is wider than the relevantViewRect, center the relevantViewRect.
if (viewRect.width() > relevantViewRect.width())
relevantViewRect.setX((viewRect.width() - relevantViewRect.width()) / 2);
return relevantViewRect;
}
void Page::addRelevantRepaintedObject(const RenderObject& object, const LayoutRect& objectPaintRect)
{
if (!isCountingRelevantRepaintedObjects())
return;
// Objects inside sub-frames are not considered to be relevant.
if (&object.frame() != &mainFrame())
return;
LayoutRect relevantRect = relevantViewRect(&object.view());
// The objects are only relevant if they are being painted within the viewRect().
if (!objectPaintRect.intersects(snappedIntRect(relevantRect)))
return;
IntRect snappedPaintRect = snappedIntRect(objectPaintRect);
// If this object was previously counted as an unpainted object, remove it from that HashSet
// and corresponding Region. FIXME: This doesn't do the right thing if the objects overlap.
if (m_relevantUnpaintedRenderObjects.remove(object))
m_internals->relevantUnpaintedRegion.subtract(snappedPaintRect);
// Split the relevantRect into a top half and a bottom half. Making sure we have coverage in
// both halves helps to prevent cases where we have a fully loaded menu bar or masthead with
// no content beneath that.
LayoutRect topRelevantRect = relevantRect;
topRelevantRect.contract(LayoutSize(0_lu, relevantRect.height() / 2));
LayoutRect bottomRelevantRect = topRelevantRect;
bottomRelevantRect.setY(relevantRect.height() / 2);
// If the rect straddles both Regions, split it appropriately.
if (topRelevantRect.intersects(snappedPaintRect) && bottomRelevantRect.intersects(snappedPaintRect)) {
IntRect topIntersection = snappedPaintRect;
topIntersection.intersect(snappedIntRect(topRelevantRect));
m_internals->topRelevantPaintedRegion.unite(topIntersection);
IntRect bottomIntersection = snappedPaintRect;
bottomIntersection.intersect(snappedIntRect(bottomRelevantRect));
m_internals->bottomRelevantPaintedRegion.unite(bottomIntersection);
} else if (topRelevantRect.intersects(snappedPaintRect))
m_internals->topRelevantPaintedRegion.unite(snappedPaintRect);
else
m_internals->bottomRelevantPaintedRegion.unite(snappedPaintRect);
float topPaintedArea = m_internals->topRelevantPaintedRegion.totalArea();
float bottomPaintedArea = m_internals->bottomRelevantPaintedRegion.totalArea();
float viewArea = relevantRect.width() * relevantRect.height();
float ratioThatIsPaintedOnTop = topPaintedArea / viewArea;
float ratioThatIsPaintedOnBottom = bottomPaintedArea / viewArea;
float ratioOfViewThatIsUnpainted = m_internals->relevantUnpaintedRegion.totalArea() / viewArea;
if (ratioThatIsPaintedOnTop > (gMinimumPaintedAreaRatio / 2) && ratioThatIsPaintedOnBottom > (gMinimumPaintedAreaRatio / 2)
&& ratioOfViewThatIsUnpainted < gMaximumUnpaintedAreaRatio) {
m_isCountingRelevantRepaintedObjects = false;
resetRelevantPaintedObjectCounter();
if (RefPtr frame = dynamicDowncast<LocalFrame>(mainFrame()))
frame->protectedLoader()->didReachLayoutMilestone(LayoutMilestone::DidHitRelevantRepaintedObjectsAreaThreshold);
}
}
void Page::addRelevantUnpaintedObject(const RenderObject& object, const LayoutRect& objectPaintRect)
{
if (!isCountingRelevantRepaintedObjects())
return;
// The objects are only relevant if they are being painted within the relevantViewRect().
if (!objectPaintRect.intersects(snappedIntRect(relevantViewRect(&object.view()))))
return;
m_relevantUnpaintedRenderObjects.add(object);
m_internals->relevantUnpaintedRegion.unite(snappedIntRect(objectPaintRect));
}
void Page::suspendActiveDOMObjectsAndAnimations()
{
for (RefPtr frame = &mainFrame(); frame; frame = frame->tree().traverseNext()) {
if (RefPtr localFrame = dynamicDowncast<LocalFrame>(*frame))
localFrame->suspendActiveDOMObjectsAndAnimations();
}
}
void Page::resumeActiveDOMObjectsAndAnimations()
{
for (RefPtr frame = &mainFrame(); frame; frame = frame->tree().traverseNext()) {
if (RefPtr localFrame = dynamicDowncast<LocalFrame>(*frame))
localFrame->resumeActiveDOMObjectsAndAnimations();
}
resumeAnimatingImages();
}
bool Page::hasSeenAnyPlugin() const
{
return !m_seenPlugins.isEmpty();
}
bool Page::hasSeenPlugin(const String& serviceType) const
{
return m_seenPlugins.contains(serviceType);
}
void Page::sawPlugin(const String& serviceType)
{
m_seenPlugins.add(serviceType);
}
void Page::resetSeenPlugins()
{
m_seenPlugins.clear();
}
bool Page::hasSeenAnyMediaEngine() const
{
return !m_seenMediaEngines.isEmpty();
}
bool Page::hasSeenMediaEngine(const String& engineDescription) const
{
return m_seenMediaEngines.contains(engineDescription);
}
void Page::sawMediaEngine(const String& engineDescription)
{
m_seenMediaEngines.add(engineDescription);
}
void Page::resetSeenMediaEngines()
{
m_seenMediaEngines.clear();
}
void Page::hiddenPageCSSAnimationSuspensionStateChanged()
{
if (!isVisible()) {
forEachDocument([&] (Document& document) {
if (CheckedPtr timelines = document.timelinesController()) {
if (m_settings->hiddenPageCSSAnimationSuspensionEnabled())
timelines->suspendAnimations();
else
timelines->resumeAnimations();
}
});
}
}
#if ENABLE(VIDEO)
void Page::captionPreferencesChanged()
{
forEachDocument([] (Document& document) {
document.captionPreferencesChanged();
});
}
#endif
void Page::forbidPrompts()
{
++m_forbidPromptsDepth;
}
void Page::allowPrompts()
{
ASSERT(m_forbidPromptsDepth);
--m_forbidPromptsDepth;
}
bool Page::arePromptsAllowed()
{
return !m_forbidPromptsDepth;
}
void Page::forbidSynchronousLoads()
{
++m_forbidSynchronousLoadsDepth;
}
void Page::allowSynchronousLoads()
{
ASSERT(m_forbidSynchronousLoadsDepth);
--m_forbidSynchronousLoadsDepth;
}
bool Page::areSynchronousLoadsAllowed()
{
return !m_forbidSynchronousLoadsDepth;
}
void Page::logNavigation(const Navigation& navigation)
{
String navigationDescription;
switch (navigation.type) {
case FrameLoadType::Standard:
navigationDescription = "standard"_s;
break;
case FrameLoadType::Back:
navigationDescription = "back"_s;
break;
case FrameLoadType::Forward:
navigationDescription = "forward"_s;
break;
case FrameLoadType::IndexedBackForward:
navigationDescription = "indexedBackForward"_s;
break;
case FrameLoadType::Reload:
navigationDescription = "reload"_s;
break;
case FrameLoadType::Same:
navigationDescription = "same"_s;
break;
case FrameLoadType::ReloadFromOrigin:
navigationDescription = "reloadFromOrigin"_s;
break;
case FrameLoadType::ReloadExpiredOnly:
navigationDescription = "reloadRevalidatingExpired"_s;
break;
case FrameLoadType::Replace:
case FrameLoadType::RedirectWithLockedBackForwardList:
// Not logging those for now.
return;
}
diagnosticLoggingClient().logDiagnosticMessage(DiagnosticLoggingKeys::navigationKey(), navigationDescription, ShouldSample::No);
if (!navigation.domain.isEmpty())
diagnosticLoggingClient().logDiagnosticMessageWithEnhancedPrivacy(DiagnosticLoggingKeys::domainVisitedKey(), navigation.domain.string(), ShouldSample::Yes);
}
void Page::mainFrameLoadStarted(const URL& destinationURL, FrameLoadType type)
{
Navigation navigation = { RegistrableDomain { destinationURL }, type };
// To avoid being too verbose, we only log navigations if the page is or becomes visible. This avoids logging non-user observable loads.
if (!isVisible()) {
m_navigationToLogWhenVisible = navigation;
return;
}
m_navigationToLogWhenVisible = std::nullopt;
logNavigation(navigation);
}
Ref<CookieJar> Page::protectedCookieJar() const
{
return m_cookieJar;
}
Ref<StorageNamespaceProvider> Page::protectedStorageNamespaceProvider() const
{
return m_storageNamespaceProvider;
}
PluginInfoProvider& Page::pluginInfoProvider()
{
return m_pluginInfoProvider;
}
Ref<PluginInfoProvider> Page::protectedPluginInfoProvider() const
{
return m_pluginInfoProvider;
}
UserContentProvider& Page::userContentProvider()
{
return m_userContentProvider;
}
Ref<UserContentProvider> Page::protectedUserContentProvider()
{
return m_userContentProvider;
}
void Page::setUserContentProvider(Ref<UserContentProvider>&& userContentProvider)
{
protectedUserContentProvider()->removePage(*this);
m_userContentProvider = WTFMove(userContentProvider);
protectedUserContentProvider()->addPage(*this);
invalidateInjectedStyleSheetCacheInAllFrames();
}
VisitedLinkStore& Page::visitedLinkStore()
{
return m_visitedLinkStore;
}
Ref<VisitedLinkStore> Page::protectedVisitedLinkStore()
{
return m_visitedLinkStore;
}
void Page::setVisitedLinkStore(Ref<VisitedLinkStore>&& visitedLinkStore)
{
protectedVisitedLinkStore()->removePage(*this);
m_visitedLinkStore = WTFMove(visitedLinkStore);
protectedVisitedLinkStore()->addPage(*this);
invalidateStylesForAllLinks();
}
std::optional<uint64_t> Page::noiseInjectionHashSaltForDomain(const RegistrableDomain& domain)
{
if (!m_noiseInjectionHashSalts.isValidKey(domain))
return std::nullopt;
return m_noiseInjectionHashSalts.ensure(domain, [] {
return cryptographicallyRandomNumber<uint64_t>();
}).iterator->value;
}
PAL::SessionID Page::sessionID() const
{
return m_sessionID;
}
// This is only called by WebKitLegacy.
void Page::setSessionID(PAL::SessionID sessionID)
{
ASSERT(sessionID.isValid());
ASSERT(m_sessionID == PAL::SessionID::legacyPrivateSessionID() || m_sessionID == PAL::SessionID::defaultSessionID());
ASSERT(sessionID == PAL::SessionID::legacyPrivateSessionID() || sessionID == PAL::SessionID::defaultSessionID());
if (sessionID != m_sessionID)
m_idbConnectionToServer = nullptr;
if (sessionID != m_sessionID) {
constexpr auto doNotCreate = StorageNamespaceProvider::ShouldCreateNamespace::No;
RefPtr topOrigin = &mainFrameOrigin();
if (RefPtr sessionStorage = topOrigin ? m_storageNamespaceProvider->sessionStorageNamespace(*topOrigin, *this, doNotCreate) : nullptr)
sessionStorage->setSessionIDForTesting(sessionID);
}
bool privateBrowsingStateChanged = (sessionID.isEphemeral() != m_sessionID.isEphemeral());
m_sessionID = sessionID;
if (!privateBrowsingStateChanged)
return;
forEachDocument([&] (Document& document) {
document.privateBrowsingStateDidChange(m_sessionID);
});
}
#if ENABLE(WIRELESS_PLAYBACK_TARGET)
void Page::addPlaybackTargetPickerClient(PlaybackTargetClientContextIdentifier contextId)
{
chrome().client().addPlaybackTargetPickerClient(contextId);
}
void Page::removePlaybackTargetPickerClient(PlaybackTargetClientContextIdentifier contextId)
{
chrome().client().removePlaybackTargetPickerClient(contextId);
}
void Page::showPlaybackTargetPicker(PlaybackTargetClientContextIdentifier contextId, const WebCore::IntPoint& location, bool isVideo, RouteSharingPolicy routeSharingPolicy, const String& routingContextUID)
{
#if PLATFORM(IOS_FAMILY)
// FIXME: refactor iOS implementation.
UNUSED_PARAM(contextId);
UNUSED_PARAM(location);
chrome().client().showPlaybackTargetPicker(isVideo, routeSharingPolicy, routingContextUID);
#else
UNUSED_PARAM(routeSharingPolicy);
UNUSED_PARAM(routingContextUID);
chrome().client().showPlaybackTargetPicker(contextId, location, isVideo);
#endif
}
void Page::playbackTargetPickerClientStateDidChange(PlaybackTargetClientContextIdentifier contextId, MediaProducerMediaStateFlags state)
{
chrome().client().playbackTargetPickerClientStateDidChange(contextId, state);
}
void Page::setMockMediaPlaybackTargetPickerEnabled(bool enabled)
{
chrome().client().setMockMediaPlaybackTargetPickerEnabled(enabled);
}
void Page::setMockMediaPlaybackTargetPickerState(const String& name, MediaPlaybackTargetContext::MockState state)
{
chrome().client().setMockMediaPlaybackTargetPickerState(name, state);
}
void Page::mockMediaPlaybackTargetPickerDismissPopup()
{
chrome().client().mockMediaPlaybackTargetPickerDismissPopup();
}
void Page::setPlaybackTarget(PlaybackTargetClientContextIdentifier contextId, Ref<MediaPlaybackTarget>&& target)
{
forEachDocument([&] (Document& document) {
document.setPlaybackTarget(contextId, target.copyRef());
});
}
void Page::playbackTargetAvailabilityDidChange(PlaybackTargetClientContextIdentifier contextId, bool available)
{
forEachDocument([&] (Document& document) {
document.playbackTargetAvailabilityDidChange(contextId, available);
});
}
void Page::setShouldPlayToPlaybackTarget(PlaybackTargetClientContextIdentifier contextId, bool shouldPlay)
{
forEachDocument([&] (Document& document) {
document.setShouldPlayToPlaybackTarget(contextId, shouldPlay);
});
}
void Page::playbackTargetPickerWasDismissed(PlaybackTargetClientContextIdentifier contextId)
{
forEachDocument([&] (Document& document) {
document.playbackTargetPickerWasDismissed(contextId);
});
}
#endif
RefPtr<WheelEventTestMonitor> Page::wheelEventTestMonitor() const
{
return m_wheelEventTestMonitor;
}
void Page::clearWheelEventTestMonitor()
{
if (RefPtr scrollingCoordinator = m_scrollingCoordinator)
scrollingCoordinator->stopMonitoringWheelEvents();
m_wheelEventTestMonitor = nullptr;
}
bool Page::isMonitoringWheelEvents() const
{
return !!m_wheelEventTestMonitor;
}
void Page::startMonitoringWheelEvents(bool clearLatchingState)
{
ensureProtectedWheelEventTestMonitor()->clearAllTestDeferrals();
#if ENABLE(WHEEL_EVENT_LATCHING)
if (clearLatchingState)
protectedScrollLatchingController()->clear();
#endif
RefPtr localMainFrame = this->localMainFrame();
if (RefPtr frameView = localMainFrame ? localMainFrame->view() : nullptr) {
if (RefPtr scrollingCoordinator = m_scrollingCoordinator) {
scrollingCoordinator->startMonitoringWheelEvents(clearLatchingState);
scrollingCoordinator->updateIsMonitoringWheelEventsForFrameView(*frameView);
}
}
}
WheelEventTestMonitor& Page::ensureWheelEventTestMonitor()
{
if (!m_wheelEventTestMonitor)
m_wheelEventTestMonitor = adoptRef(new WheelEventTestMonitor(*this));
return *m_wheelEventTestMonitor;
}
Ref<WheelEventTestMonitor> Page::ensureProtectedWheelEventTestMonitor()
{
return ensureWheelEventTestMonitor();
}
#if ENABLE(VIDEO)
void Page::setAllowsMediaDocumentInlinePlayback(bool flag)
{
if (m_allowsMediaDocumentInlinePlayback == flag)
return;
m_allowsMediaDocumentInlinePlayback = flag;
forEachMediaElement([] (HTMLMediaElement& element) {
element.allowsMediaDocumentInlinePlaybackChanged();
});
}
#endif
IDBClient::IDBConnectionToServer& Page::idbConnection()
{
if (!m_idbConnectionToServer)
m_idbConnectionToServer = &m_databaseProvider->idbConnectionToServerForSession(m_sessionID);
return *m_idbConnectionToServer;
}
IDBClient::IDBConnectionToServer* Page::optionalIDBConnection()
{
return m_idbConnectionToServer.get();
}
void Page::clearIDBConnection()
{
m_idbConnectionToServer = nullptr;
}
#if ENABLE(RESOURCE_USAGE)
void Page::setResourceUsageOverlayVisible(bool visible)
{
if (!visible) {
m_resourceUsageOverlay = nullptr;
return;
}
if (!m_resourceUsageOverlay && m_settings->acceleratedCompositingEnabled())
m_resourceUsageOverlay = ResourceUsageOverlay::create(*this);
}
#endif
String Page::captionUserPreferencesStyleSheet()
{
return m_captionUserPreferencesStyleSheet;
}
void Page::setCaptionUserPreferencesStyleSheet(const String& styleSheet)
{
if (m_captionUserPreferencesStyleSheet == styleSheet)
return;
m_captionUserPreferencesStyleSheet = styleSheet;
}
void Page::accessibilitySettingsDidChange()
{
forEachDocument([] (auto& document) {
document.checkedStyleScope()->evaluateMediaQueriesForAccessibilitySettingsChange();
document.updateElementsAffectedByMediaQueries();
document.scheduleRenderingUpdate(RenderingUpdateStep::MediaQueryEvaluation);
});
InspectorInstrumentation::accessibilitySettingsDidChange(*this);
}
void Page::appearanceDidChange()
{
forEachDocument([] (auto& document) {
document.checkedStyleScope()->didChangeStyleSheetEnvironment();
document.checkedStyleScope()->evaluateMediaQueriesForAppearanceChange();
document.updateElementsAffectedByMediaQueries();
document.scheduleRenderingUpdate(RenderingUpdateStep::MediaQueryEvaluation);
document.invalidateScrollbars();
});
}
void Page::clearAXObjectCache()
{
m_axObjectCache = nullptr;
}
AXObjectCache* Page::axObjectCache()
{
if (!m_axObjectCache) {
RefPtr localMainFrame = dynamicDowncast<LocalFrame>(m_mainFrame.get());
RefPtr mainFrameDocument = localMainFrame ? localMainFrame->document() : nullptr;
if (mainFrameDocument && !mainFrameDocument->hasLivingRenderTree())
return nullptr;
m_axObjectCache = makeUnique<AXObjectCache>(*this, mainFrameDocument.get());
Document::hasEverCreatedAnAXObjectCache = true;
}
return m_axObjectCache.get();
}
void Page::setUnobscuredSafeAreaInsets(const FloatBoxExtent& insets)
{
if (m_unobscuredSafeAreaInsets == insets)
return;
m_unobscuredSafeAreaInsets = insets;
forEachDocument([&] (Document& document) {
document.constantProperties().didChangeSafeAreaInsets();
});
}
void Page::useSystemAppearanceChanged()
{
appearanceDidChange();
forEachDocument([&](Document& document) {
// System appearance change may affect stylesheet parsing. We need to re-parse.
if (CheckedPtr extensionStyleSheets = document.extensionStyleSheetsIfExists()) {
extensionStyleSheets->clearPageUserSheet();
extensionStyleSheets->invalidateInjectedStyleSheetCache();
}
});
}
void Page::setUseColorAppearance(bool useDarkAppearance, bool useElevatedUserInterfaceLevel)
{
#if ENABLE(DARK_MODE_CSS)
if (m_useDarkAppearance == useDarkAppearance && m_useElevatedUserInterfaceLevel == useElevatedUserInterfaceLevel)
return;
m_useDarkAppearance = useDarkAppearance;
m_useElevatedUserInterfaceLevel = useElevatedUserInterfaceLevel;
InspectorInstrumentation::defaultAppearanceDidChange(*this);
appearanceDidChange();
#else
UNUSED_PARAM(useDarkAppearance);
if (m_useElevatedUserInterfaceLevel == useElevatedUserInterfaceLevel)
return;
m_useElevatedUserInterfaceLevel = useElevatedUserInterfaceLevel;
appearanceDidChange();
#endif
}
bool Page::useDarkAppearance() const
{
#if ENABLE(DARK_MODE_CSS)
RefPtr localMainFrame = this->localMainFrame();
// FIXME: If this page is being printed, this function should return false.
// Currently remote mainFrame() does not have this information.
if (!localMainFrame)
return m_useDarkAppearance;
RefPtr view = localMainFrame->view();
if (!view || view->mediaType() != screenAtom())
return false;
if (m_useDarkAppearanceOverride)
return m_useDarkAppearanceOverride.value();
if (RefPtr documentLoader = localMainFrame->loader().documentLoader()) {
auto colorSchemePreference = documentLoader->colorSchemePreference();
if (colorSchemePreference != ColorSchemePreference::NoPreference)
return colorSchemePreference == ColorSchemePreference::Dark;
}
return m_useDarkAppearance;
#else
return false;
#endif
}
void Page::setUseDarkAppearanceOverride(std::optional<bool> valueOverride)
{
if (valueOverride == m_useDarkAppearanceOverride)
return;
m_useDarkAppearanceOverride = valueOverride;
appearanceDidChange();
}
void Page::setFullscreenInsets(const FloatBoxExtent& insets)
{
if (insets == m_fullscreenInsets)
return;
m_fullscreenInsets = insets;
forEachDocument([] (Document& document) {
document.constantProperties().didChangeFullscreenInsets();
});
}
void Page::setFullscreenAutoHideDuration(Seconds duration)
{
if (duration == m_fullscreenAutoHideDuration)
return;
m_fullscreenAutoHideDuration = duration;
forEachDocument([&] (Document& document) {
document.constantProperties().setFullscreenAutoHideDuration(duration);
});
}
Document* Page::outermostFullscreenDocument() const
{
#if ENABLE(FULLSCREEN_API)
RefPtr localMainFrame = dynamicDowncast<LocalFrame>(m_mainFrame.get());
if (!localMainFrame)
return nullptr;
RefPtr<Document> outermostFullscreenDocument;
RefPtr currentDocument = localMainFrame->document();
while (currentDocument) {
RefPtr fullscreenElement = currentDocument->protectedFullscreen()->fullscreenElement();
if (!fullscreenElement)
break;
outermostFullscreenDocument = currentDocument;
RefPtr fullscreenFrame = dynamicDowncast<HTMLFrameOwnerElement>(fullscreenElement.releaseNonNull());
if (!fullscreenFrame)
break;
currentDocument = fullscreenFrame->contentDocument();
}
return outermostFullscreenDocument.get();
#else
return nullptr;
#endif
}
void Page::disableICECandidateFiltering()
{
m_shouldEnableICECandidateFilteringByDefault = false;
#if ENABLE(WEB_RTC)
m_rtcController->disableICECandidateFilteringForAllOrigins();
#endif
}
void Page::enableICECandidateFiltering()
{
m_shouldEnableICECandidateFilteringByDefault = true;
#if ENABLE(WEB_RTC)
m_rtcController->enableICECandidateFiltering();
#endif
}
RefPtr<LocalFrame> Page::localMainFrame()
{
return dynamicDowncast<LocalFrame>(mainFrame());
}
RefPtr<const LocalFrame> Page::localMainFrame() const
{
return dynamicDowncast<LocalFrame>(mainFrame());
}
RefPtr<Document> Page::localTopDocument()
{
if (RefPtr localMainFrame = this->localMainFrame())
return localMainFrame->document();
return nullptr;
}
RefPtr<Document> Page::localTopDocument() const
{
if (RefPtr localMainFrame = this->localMainFrame())
return localMainFrame->document();
return nullptr;
}
bool Page::hasLocalMainFrame()
{
return dynamicDowncast<LocalFrame>(mainFrame());
}
void Page::didChangeMainDocument(Document* newDocument)
{
m_topDocumentSyncData = newDocument ? newDocument->syncData() : DocumentSyncData::create();
if (settings().siteIsolationEnabled())
processSyncClient().broadcastTopDocumentSyncDataToOtherProcesses(protectedTopDocumentSyncData().get());
#if ENABLE(WEB_RTC)
m_rtcController->reset(m_shouldEnableICECandidateFilteringByDefault);
#endif
m_pointerCaptureController->reset();
clearSampledPageTopColor();
checkedElementTargetingController()->didChangeMainDocument(newDocument);
updateActiveNowPlayingSessionNow();
}
RenderingUpdateScheduler& Page::renderingUpdateScheduler()
{
if (!m_renderingUpdateScheduler)
m_renderingUpdateScheduler = RenderingUpdateScheduler::create(*this);
return *m_renderingUpdateScheduler;
}
RenderingUpdateScheduler* Page::existingRenderingUpdateScheduler()
{
return m_renderingUpdateScheduler.get();
}
void Page::forEachDocumentFromMainFrame(const Frame& mainFrame, NOESCAPE const Function<void(Document&)>& functor)
{
Vector<Ref<Document>, 8> documents;
for (RefPtr frame = &mainFrame; frame; frame = frame->tree().traverseNext()) {
RefPtr localFrame = dynamicDowncast<LocalFrame>(*frame);
if (!localFrame)
continue;
if (RefPtr document = localFrame->document())
documents.append(document.releaseNonNull());
}
for (auto& document : documents)
functor(document);
}
void Page::forEachDocument(NOESCAPE const Function<void(Document&)>& functor) const
{
forEachDocumentFromMainFrame(protectedMainFrame(), functor);
}
bool Page::findMatchingLocalDocument(NOESCAPE const Function<bool(Document&)>& functor) const
{
for (RefPtr frame = &mainFrame(); frame; frame = frame->tree().traverseNext()) {
RefPtr localFrame = dynamicDowncast<LocalFrame>(*frame);
if (!localFrame)
continue;
RefPtr document = localFrame->document();
if (!document)
continue;
if (functor(*document))
return true;
}
return false;
}
void Page::forEachRenderableDocument(NOESCAPE const Function<void(Document&)>& functor) const
{
Vector<Ref<Document>, 8> documents;
for (RefPtr frame = &mainFrame(); frame; frame = frame->tree().traverseNext()) {
RefPtr localFrame = dynamicDowncast<LocalFrame>(*frame);
if (!localFrame)
continue;
RefPtr document = localFrame->document();
if (!document)
continue;
if (document->renderingIsSuppressedForViewTransition())
continue;
if (!document->visualUpdatesAllowed())
continue;
documents.append(document.releaseNonNull());
}
for (auto& document : documents)
functor(document);
}
void Page::forEachMediaElement(NOESCAPE const Function<void(HTMLMediaElement&)>& functor)
{
#if ENABLE(VIDEO)
forEachDocument([&] (Document& document) {
document.forEachMediaElement(functor);
});
#else
UNUSED_PARAM(functor);
#endif
}
void Page::forEachLocalFrame(NOESCAPE const Function<void(LocalFrame&)>& functor)
{
Vector<Ref<LocalFrame>> frames;
for (RefPtr frame = &mainFrame(); frame; frame = frame->tree().traverseNext()) {
if (RefPtr localFrame = dynamicDowncast<LocalFrame>(*frame))
frames.append(localFrame.releaseNonNull());
}
for (auto& frame : frames)
functor(frame);
}
void Page::forEachWindowEventLoop(NOESCAPE const Function<void(WindowEventLoop&)>& functor)
{
UncheckedKeyHashSet<Ref<WindowEventLoop>> windowEventLoops;
RefPtr<WindowEventLoop> lastEventLoop;
for (RefPtr frame = &mainFrame(); frame; frame = frame->tree().traverseNext()) {
RefPtr localFrame = dynamicDowncast<LocalFrame>(*frame);
if (!localFrame)
continue;
RefPtr document = localFrame->document();
if (!document)
continue;
Ref currentEventLoop = document->windowEventLoop();
if (lastEventLoop == currentEventLoop.ptr())
continue; // Common and faster than a hash table lookup
lastEventLoop = currentEventLoop.ptr();
windowEventLoops.add(WTFMove(currentEventLoop));
}
for (auto& eventLoop : windowEventLoops)
functor(eventLoop);
}
bool Page::allowsLoadFromURL(const URL& url, MainFrameMainResource mainFrameMainResource) const
{
if (mainFrameMainResource == MainFrameMainResource::No && !m_loadsSubresources)
return false;
if (!m_allowedNetworkHosts)
return true;
if (!url.protocolIsInHTTPFamily() && !url.protocolIs("ws"_s) && !url.protocolIs("wss"_s))
return true;
return m_allowedNetworkHosts->contains<StringViewHashTranslator>(url.host());
}
bool Page::hasLocalDataForURL(const URL& url)
{
if (url.protocolIsFile())
return true;
RefPtr localMainFrame = this->localMainFrame();
RefPtr documentLoader = localMainFrame ? localMainFrame->loader().documentLoader() : nullptr;
if (documentLoader && documentLoader->subresource(MemoryCache::removeFragmentIdentifierIfNeeded(url)))
return true;
return false;
}
void Page::applicationWillResignActive()
{
#if ENABLE(VIDEO)
forEachMediaElement([] (HTMLMediaElement& element) {
element.applicationWillResignActive();
});
#endif
}
void Page::applicationDidEnterBackground()
{
#if ENABLE(WEBXR)
if (auto session = this->activeImmersiveXRSession())
session->applicationDidEnterBackground();
#endif
}
void Page::applicationWillEnterForeground()
{
#if ENABLE(WEBXR)
if (auto session = this->activeImmersiveXRSession())
session->applicationWillEnterForeground();
#endif
}
void Page::applicationDidBecomeActive()
{
#if ENABLE(VIDEO)
forEachMediaElement([] (HTMLMediaElement& element) {
element.applicationDidBecomeActive();
});
#endif
}
#if ENABLE(WHEEL_EVENT_LATCHING)
ScrollLatchingController& Page::scrollLatchingController()
{
if (!m_scrollLatchingController)
lazyInitialize(m_scrollLatchingController, makeUniqueWithoutRefCountedCheck<ScrollLatchingController>(*this));
return *m_scrollLatchingController;
}
Ref<ScrollLatchingController> Page::protectedScrollLatchingController()
{
return scrollLatchingController();
}
#endif // ENABLE(WHEEL_EVENT_LATCHING)
enum class DispatchedOnDocumentEventLoop : bool { No, Yes };
static void dispatchPrintEvent(Frame& mainFrame, const AtomString& eventType, DispatchedOnDocumentEventLoop dispatchedOnDocumentEventLoop)
{
Vector<Ref<LocalFrame>> frames;
for (RefPtr frame = &mainFrame; frame; frame = frame->tree().traverseNext()) {
RefPtr localFrame = dynamicDowncast<LocalFrame>(frame);
if (!localFrame)
continue;
frames.append(localFrame.releaseNonNull());
}
for (auto& frame : frames) {
if (RefPtr window = frame->window()) {
auto dispatchEvent = [window = WTFMove(window), eventType] {
window->dispatchEvent(Event::create(eventType, Event::CanBubble::No, Event::IsCancelable::No), window->protectedDocument().get());
};
if (dispatchedOnDocumentEventLoop == DispatchedOnDocumentEventLoop::No)
return dispatchEvent();
if (RefPtr document = frame->document())
document->checkedEventLoop()->queueTask(TaskSource::DOMManipulation, WTFMove(dispatchEvent));
}
}
}
void Page::dispatchBeforePrintEvent()
{
dispatchPrintEvent(m_mainFrame, eventNames().beforeprintEvent, DispatchedOnDocumentEventLoop::No);
}
void Page::dispatchAfterPrintEvent()
{
dispatchPrintEvent(m_mainFrame, eventNames().afterprintEvent, DispatchedOnDocumentEventLoop::Yes);
}
#if ENABLE(APPLE_PAY)
void Page::setPaymentCoordinator(Ref<PaymentCoordinator>&& paymentCoordinator)
{
m_paymentCoordinator = WTFMove(paymentCoordinator);
}
#endif
#if ENABLE(APPLE_PAY_AMS_UI)
bool Page::startApplePayAMSUISession(const URL& originatingURL, ApplePayAMSUIPaymentHandler& paymentHandler, const ApplePayAMSUIRequest& request)
{
if (hasActiveApplePayAMSUISession())
return false;
m_activeApplePayAMSUIPaymentHandler = &paymentHandler;
chrome().client().startApplePayAMSUISession(originatingURL, request, [weakThis = WeakPtr { *this }, paymentHandler = Ref { paymentHandler }] (std::optional<bool>&& result) {
RefPtr protectedThis = weakThis.get();
if (!protectedThis)
return;
if (paymentHandler.ptr() != protectedThis->m_activeApplePayAMSUIPaymentHandler)
return;
if (auto activePaymentHandler = std::exchange(protectedThis->m_activeApplePayAMSUIPaymentHandler, nullptr))
activePaymentHandler->finishSession(WTFMove(result));
});
return true;
}
void Page::abortApplePayAMSUISession(ApplePayAMSUIPaymentHandler& paymentHandler)
{
if (&paymentHandler != m_activeApplePayAMSUIPaymentHandler)
return;
chrome().client().abortApplePayAMSUISession();
if (auto activePaymentHandler = std::exchange(m_activeApplePayAMSUIPaymentHandler, nullptr))
activePaymentHandler->finishSession(std::nullopt);
}
#endif // ENABLE(APPLE_PAY_AMS_UI)
#if USE(SYSTEM_PREVIEW)
void Page::beginSystemPreview(const URL& url, const SecurityOriginData& topOrigin, const SystemPreviewInfo& systemPreviewInfo, CompletionHandler<void()>&& completionHandler)
{
chrome().client().beginSystemPreview(url, topOrigin, systemPreviewInfo, WTFMove(completionHandler));
}
#endif
#if ENABLE(MEDIA_SESSION_COORDINATOR)
void Page::setMediaSessionCoordinator(Ref<MediaSessionCoordinatorPrivate>&& mediaSessionCoordinator)
{
m_mediaSessionCoordinator = WTFMove(mediaSessionCoordinator);
RefPtr localMainFrame = this->localMainFrame();
RefPtr window = localMainFrame ? localMainFrame->window() : nullptr;
if (RefPtr navigator = window ? window->optionalNavigator() : nullptr)
NavigatorMediaSession::mediaSession(*navigator).coordinator().setMediaSessionCoordinatorPrivate(*m_mediaSessionCoordinator);
}
void Page::invalidateMediaSessionCoordinator()
{
m_mediaSessionCoordinator = nullptr;
RefPtr localMainFrame = this->localMainFrame();
RefPtr window = localMainFrame ? localMainFrame->window() : nullptr;
if (!window)
return;
RefPtr navigator = window->optionalNavigator();
if (!navigator)
return;
NavigatorMediaSession::mediaSession(*navigator).coordinator().close();
}
#endif
void Page::configureLoggingChannel(const String& channelName, WTFLogChannelState state, WTFLogLevel level)
{
#if !RELEASE_LOG_DISABLED
if (auto* channel = getLogChannel(channelName)) {
channel->state = state;
channel->level = level;
#if USE(LIBWEBRTC)
RefPtr localMainFrame = dynamicDowncast<LocalFrame>(m_mainFrame.get());
if (channel == &LogWebRTC && localMainFrame && localMainFrame->document() && isAlwaysOnLoggingAllowed() && (m_settings->webCodecsVideoEnabled() || m_settings->peerConnectionEnabled()))
webRTCProvider().setLoggingLevel(LogWebRTC.level);
#endif
}
chrome().client().configureLoggingChannel(channelName, state, level);
#else
UNUSED_PARAM(channelName);
UNUSED_PARAM(state);
UNUSED_PARAM(level);
#endif
}
void Page::didFinishLoadingImageForElement(HTMLImageElement& element)
{
element.protectedDocument()->checkedEventLoop()->queueTask(TaskSource::Networking, [element = Ref { element }]() {
RefPtr frame = element->document().frame();
if (!frame)
return;
frame->protectedEditor()->revealSelectionIfNeededAfterLoadingImageForElement(element);
if (element->document().frame() != frame)
return;
if (RefPtr page = frame->page()) {
#if ENABLE(IMAGE_ANALYSIS)
if (RefPtr queue = page->imageAnalysisQueueIfExists())
queue->enqueueIfNeeded(element);
#endif
page->chrome().client().didFinishLoadingImageForElement(element);
}
});
}
void Page::didFinishLoadingImageForSVGImage(SVGImageElement& element)
{
chrome().client().didFinishLoadingImageForSVGImage(element);
}
#if ENABLE(TEXT_AUTOSIZING)
void Page::recomputeTextAutoSizingInAllFrames()
{
ASSERT(settings().textAutosizingEnabled() && settings().textAutosizingUsesIdempotentMode());
forEachDocument([] (Document& document) {
if (CheckedPtr renderView = document.renderView()) {
for (auto& renderer : descendantsOfType<RenderElement>(*renderView)) {
// Use the fact that descendantsOfType() returns parent nodes before child nodes.
// The adjustment is only valid if the parent nodes have already been updated.
if (RefPtr element = renderer.element()) {
if (auto adjustment = Style::Adjuster::adjustmentForTextAutosizing(renderer.style(), *element)) {
auto newStyle = RenderStyle::clone(renderer.style());
Style::Adjuster::adjustForTextAutosizing(newStyle, adjustment);
renderer.setStyle(WTFMove(newStyle));
}
}
}
}
});
}
#endif
OptionSet<FilterRenderingMode> Page::preferredFilterRenderingModes() const
{
OptionSet<FilterRenderingMode> modes = FilterRenderingMode::Software;
#if USE(CORE_IMAGE)
if (settings().acceleratedFiltersEnabled())
modes.add(FilterRenderingMode::Accelerated);
#endif
#if USE(SKIA)
if (settings().acceleratedCompositingEnabled())
modes.add(FilterRenderingMode::Accelerated);
#endif
#if USE(GRAPHICS_CONTEXT_FILTERS)
modes.add(FilterRenderingMode::GraphicsContext);
#endif
return modes;
}
bool Page::shouldDisableCorsForRequestTo(const URL& url) const
{
return std::ranges::any_of(m_corsDisablingPatterns, [&](auto& pattern) {
return pattern.matches(url);
});
}
const URL Page::fragmentDirectiveURLForSelectedText()
{
RefPtr focusedOrMainFrame = checkedFocusController()->focusedOrMainFrame();
if (!focusedOrMainFrame)
return { };
if (auto range = focusedOrMainFrame->selection().selection().range()) {
FragmentDirectiveGenerator fragmentDirectiveGenerator(range.value());
return fragmentDirectiveGenerator.urlWithFragment();
}
return { };
}
void Page::revealCurrentSelection()
{
RefPtr focusedOrMainFrame = checkedFocusController()->focusedOrMainFrame();
if (!focusedOrMainFrame)
return;
focusedOrMainFrame->checkedSelection()->revealSelection(SelectionRevealMode::Reveal, ScrollAlignment::alignCenterIfNeeded);
}
void Page::injectUserStyleSheet(UserStyleSheet& userStyleSheet)
{
#if ENABLE(APP_BOUND_DOMAINS)
if (RefPtr localMainFrame = dynamicDowncast<LocalFrame>(m_mainFrame.get())) {
if (localMainFrame->protectedLoader()->client().shouldEnableInAppBrowserPrivacyProtections()) {
if (RefPtr document = localMainFrame->document())
document->addConsoleMessage(MessageSource::Security, MessageLevel::Warning, "Ignoring user style sheet for non-app bound domain."_s);
return;
}
localMainFrame->protectedLoader()->client().notifyPageOfAppBoundBehavior();
}
#endif
// We need to wait until we're no longer displaying the initial empty document before we can inject the stylesheets.
RefPtr localMainFrame = dynamicDowncast<LocalFrame>(m_mainFrame.get());
if (localMainFrame && localMainFrame->loader().stateMachine().isDisplayingInitialEmptyDocument()) {
m_userStyleSheetsPendingInjection.append(userStyleSheet);
return;
}
if (userStyleSheet.injectedFrames() == UserContentInjectedFrames::InjectInTopFrameOnly) {
if (RefPtr document = localMainFrame ? localMainFrame->document() : nullptr)
document->checkedExtensionStyleSheets()->injectPageSpecificUserStyleSheet(userStyleSheet);
} else {
forEachDocument([&] (Document& document) {
document.checkedExtensionStyleSheets()->injectPageSpecificUserStyleSheet(userStyleSheet);
});
}
}
void Page::removeInjectedUserStyleSheet(UserStyleSheet& userStyleSheet)
{
if (!m_userStyleSheetsPendingInjection.isEmpty()) {
m_userStyleSheetsPendingInjection.removeFirstMatching([userStyleSheet](auto& storedUserStyleSheet) {
return storedUserStyleSheet.url() == userStyleSheet.url();
});
return;
}
if (userStyleSheet.injectedFrames() == UserContentInjectedFrames::InjectInTopFrameOnly) {
RefPtr localMainFrame = dynamicDowncast<LocalFrame>(m_mainFrame.get());
if (RefPtr document = localMainFrame ? localMainFrame->document() : nullptr)
document->checkedExtensionStyleSheets()->removePageSpecificUserStyleSheet(userStyleSheet);
} else {
forEachDocument([&] (Document& document) {
document.checkedExtensionStyleSheets()->removePageSpecificUserStyleSheet(userStyleSheet);
});
}
}
void Page::mainFrameDidChangeToNonInitialEmptyDocument()
{
RefPtr localMainFrame = dynamicDowncast<LocalFrame>(m_mainFrame.get());
ASSERT_UNUSED(localMainFrame, !localMainFrame || !localMainFrame->loader().stateMachine().isDisplayingInitialEmptyDocument());
for (auto& userStyleSheet : m_userStyleSheetsPendingInjection)
injectUserStyleSheet(userStyleSheet);
m_userStyleSheetsPendingInjection.clear();
}
SpeechRecognitionConnection& Page::speechRecognitionConnection()
{
return m_speechRecognitionProvider->speechRecognitionConnection();
}
WTF::TextStream& operator<<(WTF::TextStream& ts, RenderingUpdateStep step)
{
switch (step) {
case RenderingUpdateStep::Reveal: ts << "Reveal"_s; break;
case RenderingUpdateStep::FlushAutofocusCandidates: ts << "FlushAutofocusCandidates"_s; break;
case RenderingUpdateStep::Resize: ts << "Resize"_s; break;
case RenderingUpdateStep::Scroll: ts << "Scroll"_s; break;
case RenderingUpdateStep::MediaQueryEvaluation: ts << "MediaQueryEvaluation"_s; break;
case RenderingUpdateStep::Animations: ts << "Animations"_s; break;
case RenderingUpdateStep::Fullscreen: ts << "Fullscreen"_s; break;
case RenderingUpdateStep::AnimationFrameCallbacks: ts << "AnimationFrameCallbacks"_s; break;
case RenderingUpdateStep::PerformPendingViewTransitions: ts << "PerformPendingViewTransitions"_s; break;
case RenderingUpdateStep::IntersectionObservations: ts << "IntersectionObservations"_s; break;
case RenderingUpdateStep::UpdateContentRelevancy: ts << "UpdateContentRelevancy"_s; break;
case RenderingUpdateStep::ResizeObservations: ts << "ResizeObservations"_s; break;
case RenderingUpdateStep::Images: ts << "Images"_s; break;
case RenderingUpdateStep::WheelEventMonitorCallbacks: ts << "WheelEventMonitorCallbacks"_s; break;
case RenderingUpdateStep::CursorUpdate: ts << "CursorUpdate"_s; break;
case RenderingUpdateStep::EventRegionUpdate: ts << "EventRegionUpdate"_s; break;
case RenderingUpdateStep::LayerFlush: ts << "LayerFlush"_s; break;
#if ENABLE(ASYNC_SCROLLING)
case RenderingUpdateStep::ScrollingTreeUpdate: ts << "ScrollingTreeUpdate"_s; break;
#endif
case RenderingUpdateStep::VideoFrameCallbacks: ts << "VideoFrameCallbacks"_s; break;
case RenderingUpdateStep::PrepareCanvasesForDisplayOrFlush: ts << "PrepareCanvasesForDisplayOrFlush"_s; break;
case RenderingUpdateStep::CaretAnimation: ts << "CaretAnimation"_s; break;
case RenderingUpdateStep::FocusFixup: ts << "FocusFixup"_s; break;
case RenderingUpdateStep::UpdateValidationMessagePositions: ts << "UpdateValidationMessagePositions"_s; break;
#if ENABLE(ACCESSIBILITY_ISOLATED_TREE)
case RenderingUpdateStep::AccessibilityRegionUpdate: ts << "AccessibilityRegionUpdate"_s; break;
#endif
case RenderingUpdateStep::RestoreScrollPositionAndViewState: ts << "RestoreScrollPositionAndViewState"_s; break;
case RenderingUpdateStep::AdjustVisibility: ts << "AdjustVisibility"_s; break;
case RenderingUpdateStep::SnapshottedScrollOffsets: ts << "SnapshottedScrollOffsets"_s; break;
}
return ts;
}
ImageOverlayController& Page::imageOverlayController()
{
if (!m_imageOverlayController)
m_imageOverlayController = makeUnique<ImageOverlayController>(*this);
return *m_imageOverlayController;
}
Page* Page::serviceWorkerPage(ScriptExecutionContextIdentifier serviceWorkerPageIdentifier)
{
RefPtr serviceWorkerPageDocument = Document::allDocumentsMap().get(serviceWorkerPageIdentifier);
return serviceWorkerPageDocument ? serviceWorkerPageDocument->page() : nullptr;
}
#if ENABLE(IMAGE_ANALYSIS)
ImageAnalysisQueue& Page::imageAnalysisQueue()
{
if (!m_imageAnalysisQueue)
m_imageAnalysisQueue = ImageAnalysisQueue::create(*this);
return *m_imageAnalysisQueue;
}
Ref<ImageAnalysisQueue> Page::protectedImageAnalysisQueue()
{
return imageAnalysisQueue();
}
void Page::resetImageAnalysisQueue()
{
if (RefPtr previousQueue = std::exchange(m_imageAnalysisQueue, { }))
previousQueue->clear();
}
void Page::updateElementsWithTextRecognitionResults()
{
if (m_textRecognitionResults.isEmptyIgnoringNullReferences())
return;
m_textRecognitionResults.removeNullReferences();
Vector<std::pair<Ref<HTMLElement>, TextRecognitionResult>> elementsToUpdate;
for (auto entry : m_textRecognitionResults) {
Ref protectedElement = entry.key;
if (!protectedElement->isConnected())
continue;
auto renderer = protectedElement->renderer();
if (!is<RenderImage>(renderer))
continue;
auto& [result, containerRect] = entry.value;
auto newContainerRect = ImageOverlay::containerRect(protectedElement.get());
if (containerRect == newContainerRect)
continue;
containerRect = newContainerRect;
elementsToUpdate.append({ WTFMove(protectedElement), result });
}
for (auto& [element, result] : elementsToUpdate) {
element->protectedDocument()->checkedEventLoop()->queueTask(TaskSource::InternalAsyncTask, [result = TextRecognitionResult { result }, weakElement = WeakPtr { element }] {
if (RefPtr element = weakElement.get())
ImageOverlay::updateWithTextRecognitionResult(*element, result, ImageOverlay::CacheTextRecognitionResults::No);
});
}
}
bool Page::hasCachedTextRecognitionResult(const HTMLElement& element) const
{
return m_textRecognitionResults.contains(element);
}
std::optional<TextRecognitionResult> Page::cachedTextRecognitionResult(const HTMLElement& element) const
{
auto iterator = m_textRecognitionResults.find(element);
if (iterator == m_textRecognitionResults.end())
return std::nullopt;
return { iterator->value.first };
}
void Page::cacheTextRecognitionResult(const HTMLElement& element, const IntRect& containerRect, const TextRecognitionResult& result)
{
m_textRecognitionResults.set(element, CachedTextRecognitionResult { result, containerRect });
}
void Page::resetTextRecognitionResults()
{
m_textRecognitionResults.clear();
}
void Page::resetTextRecognitionResult(const HTMLElement& element)
{
m_textRecognitionResults.remove(element);
}
#endif // ENABLE(IMAGE_ANALYSIS)
JSC::JSGlobalObject* Page::serviceWorkerGlobalObject(DOMWrapperWorld& world)
{
RefPtr serviceWorkerGlobalScope = m_serviceWorkerGlobalScope.get();
if (!serviceWorkerGlobalScope)
return nullptr;
CheckedPtr scriptController = serviceWorkerGlobalScope->script();
if (!scriptController)
return nullptr;
// FIXME: We currently do not support non-normal worlds in service workers.
RELEASE_ASSERT(&downcast<JSVMClientData>(serviceWorkerGlobalScope->vm().clientData)->normalWorldSingleton() == &world);
return scriptController->globalScopeWrapper();
}
void Page::setServiceWorkerGlobalScope(ServiceWorkerGlobalScope& serviceWorkerGlobalScope)
{
ASSERT(isMainThread());
ASSERT(m_isServiceWorkerPage);
m_serviceWorkerGlobalScope = serviceWorkerGlobalScope;
}
StorageConnection& Page::storageConnection()
{
return m_storageProvider->storageConnection();
}
ModelPlayerProvider& Page::modelPlayerProvider()
{
return m_modelPlayerProvider.get();
}
void Page::setupForRemoteWorker(const URL& scriptURL, const SecurityOriginData& topOrigin, const String& referrerPolicy, OptionSet<AdvancedPrivacyProtections> advancedPrivacyProtections)
{
RefPtr localMainFrame = this->localMainFrame();
if (!localMainFrame)
return;
// FIXME: <rdar://117922051> Investigate if the correct origins are set here with site isolation enabled.
localMainFrame->protectedLoader()->initForSynthesizedDocument({ });
Ref document = Document::createNonRenderedPlaceholder(*localMainFrame, scriptURL);
document->createDOMWindow();
document->storageBlockingStateDidChange();
Ref origin = topOrigin.securityOrigin();
URL originAsURL = origin->toURL();
document->setSiteForCookies(originAsURL);
document->setFirstPartyForCookies(originAsURL);
if (RefPtr documentLoader = localMainFrame->protectedLoader()->documentLoader())
documentLoader->setAdvancedPrivacyProtections(advancedPrivacyProtections);
if (document->settings().storageBlockingPolicy() != StorageBlockingPolicy::BlockThirdParty)
document->setDomainForCachePartition(String { emptyString() });
else
document->setDomainForCachePartition(origin->domainForCachePartition());
if (auto policy = parseReferrerPolicy(referrerPolicy, ReferrerPolicySource::HTTPHeader))
document->setReferrerPolicy(*policy);
localMainFrame->setDocument(WTFMove(document));
}
void Page::forceRepaintAllFrames()
{
for (RefPtr frame = &mainFrame(); frame; frame = frame->tree().traverseNext()) {
RefPtr localFrame = dynamicDowncast<LocalFrame>(*frame);
if (!localFrame)
continue;
RefPtr frameView = localFrame->view();
if (!frameView || !frameView->renderView())
continue;
frameView->checkedRenderView()->repaintViewAndCompositedLayers();
}
}
#if ENABLE(ACCESSIBILITY_ANIMATION_CONTROL)
void Page::updatePlayStateForAllAnimations()
{
RefPtr localMainFrame = this->localMainFrame();
if (RefPtr view = localMainFrame ? localMainFrame->view() : nullptr)
view->updatePlayStateForAllAnimationsIncludingSubframes();
}
void Page::addIndividuallyPlayingAnimationElement(HTMLImageElement& element)
{
ASSERT(element.allowsAnimation());
bool wasEmpty = !m_individuallyPlayingAnimationElements.computeSize();
m_individuallyPlayingAnimationElements.add(element);
// If there were no individually playing animations prior to this addition, then the effective state of isAnyAnimationAllowedToPlay has changed.
if (wasEmpty && !m_imageAnimationEnabled)
chrome().client().isAnyAnimationAllowedToPlayDidChange(true);
}
void Page::removeIndividuallyPlayingAnimationElement(HTMLImageElement& element)
{
m_individuallyPlayingAnimationElements.remove(element);
// If removing this animation caused there to be no remaining individually playing animations,
// then the effective state of isAnyAnimationAllowedToPlay has changed.
if (!m_individuallyPlayingAnimationElements.computeSize() && !m_imageAnimationEnabled)
chrome().client().isAnyAnimationAllowedToPlayDidChange(false);
}
#endif // ENABLE(ACCESSIBILITY_ANIMATION_CONTROL)
ScreenOrientationManager* Page::screenOrientationManager() const
{
return m_screenOrientationManager.get();
}
URL Page::applyLinkDecorationFiltering(const URL& url, LinkDecorationFilteringTrigger trigger) const
{
return chrome().client().applyLinkDecorationFiltering(url, trigger);
}
String Page::applyLinkDecorationFiltering(const String& urlString, LinkDecorationFilteringTrigger trigger) const
{
if (auto url = URL { urlString }; url.isValid()) {
if (auto sanitizedURL = applyLinkDecorationFiltering(WTFMove(url), trigger); sanitizedURL != url)
return sanitizedURL.string();
}
return urlString;
}
URL Page::allowedQueryParametersForAdvancedPrivacyProtections(const URL& url) const
{
return chrome().client().allowedQueryParametersForAdvancedPrivacyProtections(url);
}
void Page::willBeginScrolling()
{
}
void Page::didFinishScrolling()
{
}
void Page::addRootFrame(LocalFrame& frame)
{
ASSERT(frame.isRootFrame());
ASSERT(!m_rootFrames.contains(frame));
m_rootFrames.add(frame);
chrome().client().rootFrameAdded(frame);
}
void Page::removeRootFrame(LocalFrame& frame)
{
ASSERT(frame.isRootFrame());
ASSERT(m_rootFrames.contains(frame));
m_rootFrames.remove(frame);
chrome().client().rootFrameRemoved(frame);
}
String Page::ensureMediaKeysStorageDirectoryForOrigin(const SecurityOriginData& origin)
{
if (usesEphemeralSession())
return emptyString();
return m_storageProvider->ensureMediaKeysStorageDirectoryForOrigin(origin);
}
void Page::setMediaKeysStorageDirectory(const String& directory)
{
m_storageProvider->setMediaKeysStorageDirectory(directory);
}
void Page::reloadExecutionContextsForOrigin(const ClientOrigin& origin, std::optional<FrameIdentifier> triggeringFrame) const
{
RefPtr localMainFrame = dynamicDowncast<LocalFrame>(m_mainFrame.get());
if (!localMainFrame || localMainFrame->protectedDocument()->topOrigin().data() != origin.topOrigin)
return;
for (RefPtr frame = &m_mainFrame.get(); frame;) {
RefPtr localFrame = dynamicDowncast<LocalFrame>(*frame);
if (!localFrame || frame->frameID() == triggeringFrame) {
frame = frame->tree().traverseNext();
continue;
}
RefPtr document = localFrame->document();
if (!document || document->securityOrigin().data() != origin.clientOrigin) {
frame = frame->tree().traverseNext();
continue;
}
localFrame->protectedNavigationScheduler()->scheduleRefresh(*document);
frame = frame->tree().traverseNextSkippingChildren();
}
}
void Page::opportunisticallyRunIdleCallbacks(MonotonicTime deadline)
{
forEachWindowEventLoop([&](auto& eventLoop) {
eventLoop.opportunisticallyRunIdleCallbacks(deadline);
});
}
void Page::willChangeLocationInCompletelyLoadedSubframe()
{
commonVM().heap.scheduleOpportunisticFullCollection();
}
void Page::performOpportunisticallyScheduledTasks(MonotonicTime deadline)
{
OptionSet<JSC::VM::SchedulerOptions> options;
if (m_opportunisticTaskScheduler->hasImminentlyScheduledWork())
options.add(JSC::VM::SchedulerOptions::HasImminentlyScheduledWork);
commonVM().performOpportunisticallyScheduledTasks(deadline, options);
deleteRemovedNodesAndDetachedRenderers();
}
void Page::deleteRemovedNodesAndDetachedRenderers()
{
RefPtr localMainFrame = dynamicDowncast<LocalFrame>(mainFrame());
if (!localMainFrame)
return;
RefPtr document = localMainFrame->document();
if (!document)
return;
forEachLocalFrame([] (LocalFrame& frame) {
RefPtr document = frame.document();
if (!document)
return;
document->asyncNodeDeletionQueue().deleteNodesNow();
document->view()->layoutContext().deleteDetachedRenderersNow();
});
}
CheckedRef<ProgressTracker> Page::checkedProgress()
{
return m_progress.get();
}
CheckedRef<const ProgressTracker> Page::checkedProgress() const
{
return m_progress.get();
}
CheckedRef<ElementTargetingController> Page::checkedElementTargetingController()
{
return m_elementTargetingController.get();
}
const String& Page::sceneIdentifier() const
{
#if PLATFORM(IOS_FAMILY)
return m_sceneIdentifier;
#else
return emptyString();
#endif
}
#if PLATFORM(IOS_FAMILY)
void Page::setSceneIdentifier(String&& sceneIdentifier)
{
if (m_sceneIdentifier == sceneIdentifier)
return;
m_sceneIdentifier = WTFMove(sceneIdentifier);
forEachDocument([&] (Document& document) {
document.sceneIdentifierDidChange();
});
}
void Page::setObscuredInsets(const FloatBoxExtent& insets)
{
if (m_obscuredInsets == insets)
return;
m_obscuredInsets = insets;
m_chrome->client().setNeedsFixedContainerEdgesUpdate();
}
#endif
void Page::updateFixedContainerEdges(BoxSideSet sides)
{
RefPtr mainFrame = localMainFrame();
if (!mainFrame)
return;
RefPtr frameView = mainFrame->view();
if (!frameView)
return;
auto [edges, elements] = frameView->fixedContainerEdges(sides);
for (auto sideFlag : sides) {
auto side = boxSideFromFlag(sideFlag);
if (edges.hasFixedEdge(side))
continue;
WeakPtr lastElement = m_fixedContainerEdgesAndElements.second.at(side);
if (!lastElement)
continue;
if (!lastElement->renderer())
continue;
elements.setAt(side, WTFMove(lastElement));
edges.colors.setAt(side, m_fixedContainerEdgesAndElements.first->colors.at(side));
}
m_fixedContainerEdgesAndElements = std::make_pair(makeUniqueRef<FixedContainerEdges>(WTFMove(edges)), WTFMove(elements));
}
Color Page::lastTopFixedContainerColor() const
{
return m_fixedContainerEdgesAndElements.first->predominantColor(BoxSide::Top);
}
void Page::setPortsForUpgradingInsecureSchemeForTesting(uint16_t upgradeFromInsecurePort, uint16_t upgradeToSecurePort)
{
m_portsForUpgradingInsecureSchemeForTesting = { upgradeFromInsecurePort, upgradeToSecurePort };
}
std::optional<std::pair<uint16_t, uint16_t>> Page::portsForUpgradingInsecureSchemeForTesting() const
{
return m_portsForUpgradingInsecureSchemeForTesting;
}
#if USE(ATSPI)
AccessibilityRootAtspi* Page::accessibilityRootObject() const
{
return m_accessibilityRootObject.get();
}
void Page::setAccessibilityRootObject(AccessibilityRootAtspi* rootObject)
{
m_accessibilityRootObject = rootObject;
}
#endif // USE(ATSPI)
#if ENABLE(WEBXR)
#if PLATFORM(IOS_FAMILY)
bool Page::hasActiveImmersiveSession() const
{
return !!activeImmersiveXRSession();
}
#endif // PLATFORM(IOS_FAMILY)
RefPtr<WebXRSession> Page::activeImmersiveXRSession() const
{
for (RefPtr frame = &m_mainFrame.get(); frame; frame = frame->tree().traverseNext()) {
RefPtr localFrame = dynamicDowncast<LocalFrame>(*frame);
RefPtr window = localFrame ? localFrame->window() : nullptr;
if (!window)
continue;
RefPtr navigator = window->optionalNavigator();
if (!navigator)
continue;
if (auto xrSystem = NavigatorWebXR::xrIfExists(*navigator))
return xrSystem->activeImmersiveSession();
}
return nullptr;
}
#endif // ENABLE(WEBXR)
#if HAVE(SPATIAL_TRACKING_LABEL)
void Page::setDefaultSpatialTrackingLabel(const String& label)
{
if (m_defaultSpatialTrackingLabel == label)
return;
m_defaultSpatialTrackingLabel = WTFMove(label);
forEachDocument([&] (Document& document) {
document.defaultSpatialTrackingLabelChanged(m_defaultSpatialTrackingLabel);
});
}
#endif
#if ENABLE(GAMEPAD)
void Page::gamepadsRecentlyAccessed()
{
if (MonotonicTime::now() - m_lastAccessNotificationTime < NavigatorGamepad::gamepadsRecentlyAccessedThreshold())
return;
chrome().client().gamepadsRecentlyAccessed();
m_lastAccessNotificationTime = MonotonicTime::now();
}
#if PLATFORM(VISION)
void Page::allowGamepadAccess()
{
if (m_gamepadAccessGranted)
return;
m_gamepadAccessGranted = true;
GamepadManager::singleton().updateQuarantineStatus();
}
void Page::initializeGamepadAccessForPageLoad()
{
m_gamepadAccessGranted = m_gamepadAccessRequiresExplicitConsent == ShouldRequireExplicitConsentForGamepadAccess::No;
}
#endif // PLATFORM(VISION)
#endif // ENABLE(GAMEPAD)
#if ENABLE(WRITING_TOOLS)
void Page::willBeginWritingToolsSession(const std::optional<WritingTools::Session>& session, CompletionHandler<void(const Vector<WritingTools::Context>&)>&& completionHandler)
{
m_writingToolsController->willBeginWritingToolsSession(session, WTFMove(completionHandler));
}
void Page::didBeginWritingToolsSession(const WritingTools::Session& session, const Vector<WritingTools::Context>& contexts)
{
m_writingToolsController->didBeginWritingToolsSession(session, contexts);
}
void Page::proofreadingSessionDidReceiveSuggestions(const WritingTools::Session& session, const Vector<WritingTools::TextSuggestion>& suggestions, const CharacterRange& processedRange, const WritingTools::Context& context, bool finished)
{
m_writingToolsController->proofreadingSessionDidReceiveSuggestions(session, suggestions, processedRange, context, finished);
}
void Page::proofreadingSessionDidUpdateStateForSuggestion(const WritingTools::Session& session, WritingTools::TextSuggestion::State state, const WritingTools::TextSuggestion& suggestion, const WritingTools::Context& context)
{
m_writingToolsController->proofreadingSessionDidUpdateStateForSuggestion(session, state, suggestion, context);
}
void Page::willEndWritingToolsSession(const WritingTools::Session& session, bool accepted)
{
m_writingToolsController->willEndWritingToolsSession(session, accepted);
}
void Page::didEndWritingToolsSession(const WritingTools::Session& session, bool accepted)
{
m_writingToolsController->didEndWritingToolsSession(session, accepted);
}
void Page::compositionSessionDidReceiveTextWithReplacementRange(const WritingTools::Session& session, const AttributedString& attributedText, const CharacterRange& range, const WritingTools::Context& context, bool finished)
{
m_writingToolsController->compositionSessionDidReceiveTextWithReplacementRange(session, attributedText, range, context, finished);
}
void Page::writingToolsSessionDidReceiveAction(const WritingTools::Session& session, WritingTools::Action action)
{
m_writingToolsController->writingToolsSessionDidReceiveAction(session, action);
}
void Page::updateStateForSelectedSuggestionIfNeeded()
{
m_writingToolsController->updateStateForSelectedSuggestionIfNeeded();
}
void Page::respondToUnappliedWritingToolsEditing(EditCommandComposition* command)
{
m_writingToolsController->respondToUnappliedEditing(command);
}
void Page::respondToReappliedWritingToolsEditing(EditCommandComposition* command)
{
m_writingToolsController->respondToReappliedEditing(command);
}
Vector<FloatRect> Page::proofreadingSessionSuggestionTextRectsInRootViewCoordinates(const CharacterRange& enclosingRangeRelativeToSessionRange) const
{
RefPtr localTopDocument = this->localTopDocument();
if (!localTopDocument) {
ASSERT_NOT_REACHED();
return { };
}
auto scope = m_writingToolsController->activeSessionRange();
if (!scope) {
ASSERT_NOT_REACHED();
return { };
}
return IntelligenceTextEffectsSupport::writingToolsTextSuggestionRectsInRootViewCoordinates(*localTopDocument, *scope, enclosingRangeRelativeToSessionRange);
}
void Page::updateTextVisibilityForActiveWritingToolsSession(const CharacterRange& rangeRelativeToSessionRange, bool visible, const WTF::UUID& identifier)
{
RefPtr localTopDocument = this->localTopDocument();
if (!localTopDocument) {
ASSERT_NOT_REACHED();
return;
}
auto scope = m_writingToolsController->activeSessionRange();
if (!scope) {
ASSERT_NOT_REACHED();
return;
}
IntelligenceTextEffectsSupport::updateTextVisibility(*localTopDocument, *scope, rangeRelativeToSessionRange, visible, identifier);
}
RefPtr<TextIndicator> Page::textPreviewDataForActiveWritingToolsSession(const CharacterRange& rangeRelativeToSessionRange)
{
RefPtr localTopDocument = this->localTopDocument();
if (!localTopDocument) {
ASSERT_NOT_REACHED();
return nullptr;
}
auto scope = m_writingToolsController->activeSessionRange();
if (!scope) {
ASSERT_NOT_REACHED();
return nullptr;
}
return IntelligenceTextEffectsSupport::textPreviewDataForRange(*localTopDocument, *scope, rangeRelativeToSessionRange);
}
void Page::decorateTextReplacementsForActiveWritingToolsSession(const CharacterRange& rangeRelativeToSessionRange)
{
RefPtr localTopDocument = this->localTopDocument();
if (!localTopDocument) {
ASSERT_NOT_REACHED();
return;
}
auto scope = m_writingToolsController->activeSessionRange();
if (!scope) {
ASSERT_NOT_REACHED();
return;
}
IntelligenceTextEffectsSupport::decorateWritingToolsTextReplacements(*localTopDocument, *scope, rangeRelativeToSessionRange);
}
void Page::setSelectionForActiveWritingToolsSession(const CharacterRange& rangeRelativeToSessionRange)
{
RefPtr localTopDocument = this->localTopDocument();
if (!localTopDocument) {
ASSERT_NOT_REACHED();
return;
}
auto scope = m_writingToolsController->activeSessionRange();
if (!scope) {
ASSERT_NOT_REACHED();
return;
}
auto resolvedRange = resolveCharacterRange(*scope, rangeRelativeToSessionRange);
auto visibleSelection = VisibleSelection { resolvedRange };
if (visibleSelection.isNoneOrOrphaned())
return;
localTopDocument->selection().setSelection(visibleSelection);
}
std::optional<SimpleRange> Page::contextRangeForActiveWritingToolsSession() const
{
return m_writingToolsController->activeSessionRange();
}
void Page::intelligenceTextAnimationsDidComplete()
{
m_writingToolsController->intelligenceTextAnimationsDidComplete();
}
#endif
void Page::hasActiveNowPlayingSessionChanged()
{
if (!m_activeNowPlayingSessionUpdateTimer.isActive())
m_activeNowPlayingSessionUpdateTimer.startOneShot(0_s);
}
void Page::updateActiveNowPlayingSessionNow()
{
if (m_activeNowPlayingSessionUpdateTimer.isActive())
m_activeNowPlayingSessionUpdateTimer.stop();
RefPtr mediaSessionManager = PlatformMediaSessionManager::singletonIfExists();
if (!mediaSessionManager)
return;
bool hasActiveNowPlayingSession = mediaSessionManager->hasActiveNowPlayingSessionInGroup(mediaSessionGroupIdentifier());
if (hasActiveNowPlayingSession == m_hasActiveNowPlayingSession)
return;
m_hasActiveNowPlayingSession = hasActiveNowPlayingSession;
chrome().client().hasActiveNowPlayingSessionChanged(hasActiveNowPlayingSession);
}
void Page::setLastAuthentication(LoginStatus::AuthenticationType authType)
{
auto loginStatus = LoginStatus::create(RegistrableDomain(mainFrameURL()), emptyString(), LoginStatus::CredentialTokenType::HTTPStateToken, authType, LoginStatus::TimeToLiveAuthentication);
if (loginStatus.hasException())
return;
m_lastAuthentication = loginStatus.releaseReturnValue().moveToUniquePtr();
if (RefPtr document = localMainFrame() ? localMainFrame()->document() : nullptr)
ResourceLoadObserver::shared().logUserInteractionWithReducedTimeResolution(*document);
}
#if ENABLE(FULLSCREEN_API)
bool Page::isDocumentFullscreenEnabled() const
{
return m_settings->fullScreenEnabled() || m_settings->videoFullscreenRequiresElementFullscreen();
}
#endif
void Page::startDeferringResizeEvents()
{
m_shouldDeferResizeEvents = true;
}
void Page::flushDeferredResizeEvents()
{
m_shouldDeferResizeEvents = false;
forEachDocument([&] (Document& document) {
document.flushDeferredResizeEvents();
});
}
void Page::startDeferringScrollEvents()
{
m_shouldDeferScrollEvents = true;
}
void Page::flushDeferredScrollEvents()
{
m_shouldDeferScrollEvents = false;
forEachDocument([&] (Document& document) {
document.flushDeferredScrollEvents();
});
}
bool Page::reportScriptTelemetry(const URL& url, ScriptTelemetryCategory category)
{
return !url.isEmpty() && m_reportedScriptsWithTelemetry.add({ url, category }).isNewEntry;
}
bool Page::requiresScriptTelemetryForURL(const URL& scriptURL) const
{
if (!advancedPrivacyProtections().contains(AdvancedPrivacyProtections::ScriptTelemetry))
return false;
return chrome().client().requiresScriptTelemetryForURL(scriptURL, mainFrameOrigin());
}
void Page::applyWindowFeatures(const WindowFeatures& features)
{
Ref frame = mainFrame();
chrome().setToolbarsVisible(features.toolBarVisible || features.locationBarVisible);
if (!frame->page())
return;
if (features.statusBarVisible)
chrome().setStatusbarVisible(*features.statusBarVisible);
if (!frame->page())
return;
if (features.scrollbarsVisible)
chrome().setScrollbarsVisible(*features.scrollbarsVisible);
if (!frame->page())
return;
if (features.menuBarVisible)
chrome().setMenubarVisible(*features.menuBarVisible);
if (!frame->page())
return;
if (features.resizable)
chrome().setResizable(*features.resizable);
// 'x' and 'y' specify the location of the window, while 'width' and 'height'
// specify the size of the viewport. We can only resize the window, so adjust
// for the difference between the window size and the viewport size.
// FIXME: We should reconcile the initialization of viewport arguments between iOS and non-IOS.
#if !PLATFORM(IOS_FAMILY)
FloatSize viewportSize = chrome().pageRect().size();
FloatRect windowRect = chrome().windowRect();
if (features.x)
windowRect.setX(*features.x);
if (features.y)
windowRect.setY(*features.y);
// Zero width and height mean using default size, not minimum one.
if (features.width && *features.width)
windowRect.setWidth(*features.width + (windowRect.width() - viewportSize.width()));
if (features.height && *features.height)
windowRect.setHeight(*features.height + (windowRect.height() - viewportSize.height()));
#if PLATFORM(GTK)
// Use the size of the previous window if there is no default size.
if (!windowRect.width())
windowRect.setWidth(features.oldWindowRect.width());
if (!windowRect.height())
windowRect.setHeight(features.oldWindowRect.height());
#endif
// Ensure non-NaN values, minimum size as well as being within valid screen area.
FloatRect newWindowRect = LocalDOMWindow::adjustWindowRect(*this, windowRect);
if (!frame->page())
return;
chrome().setWindowRect(newWindowRect);
#else
// On iOS, width and height refer to the viewport dimensions.
ViewportArguments arguments;
// Zero width and height mean using default size, not minimum one.
if (features.width && *features.width)
arguments.width = *features.width;
if (features.height && *features.height)
arguments.height = *features.height;
if (RefPtr localFrame = dynamicDowncast<LocalFrame>(frame))
localFrame->setViewportArguments(arguments);
#endif
}
bool Page::isAlwaysOnLoggingAllowed() const
{
return m_sessionID.isAlwaysOnLoggingAllowed() || settings().allowPrivacySensitiveOperationsInNonPersistentDataStores();
}
Ref<InspectorController> Page::protectedInspectorController()
{
return m_inspectorController.get();
}
#if PLATFORM(MAC) && (ENABLE(SERVICE_CONTROLS) || ENABLE(TELEPHONE_NUMBER_DETECTION))
Ref<ServicesOverlayController> Page::protectedServicesOverlayController()
{
return m_servicesOverlayController.get();
}
#endif
ProcessID Page::presentingApplicationPID() const
{
#if HAVE(AUDIT_TOKEN)
if (m_presentingApplicationAuditToken)
return pidFromAuditToken(*m_presentingApplicationAuditToken);
#endif
return WTF::legacyPresentingApplicationPID();
}
#if HAVE(AUDIT_TOKEN)
const std::optional<audit_token_t>& Page::presentingApplicationAuditToken() const
{
return m_presentingApplicationAuditToken;
}
void Page::setPresentingApplicationAuditToken(std::optional<audit_token_t> presentingApplicationAuditToken)
{
m_presentingApplicationAuditToken = WTFMove(presentingApplicationAuditToken);
#if ENABLE(EXTENSION_CAPABILITIES)
if (settings().mediaCapabilityGrantsEnabled())
return;
#endif
if (RefPtr mediaSessionManager = PlatformMediaSessionManager::singletonIfExists())
mediaSessionManager->updatePresentingApplicationPIDIfNecessary(presentingApplicationPID());
}
#endif
bool Page::requiresUserGestureForAudioPlayback() const
{
auto autoplayPolicy = m_mainFrame->autoplayPolicy();
if (autoplayPolicy != AutoplayPolicy::Default)
return autoplayPolicy == AutoplayPolicy::AllowWithoutSound || autoplayPolicy == AutoplayPolicy::Deny;
return m_settings->requiresUserGestureForAudioPlayback();
}
bool Page::requiresUserGestureForVideoPlayback() const
{
auto autoplayPolicy = m_mainFrame->autoplayPolicy();
if (autoplayPolicy != AutoplayPolicy::Default)
return autoplayPolicy == AutoplayPolicy::Deny;
return m_settings->requiresUserGestureForVideoPlayback();
}
} // namespace WebCore