blob: f68850e2120e0b155809a277d9f44382d075fdb3 [file] [log] [blame]
/*
* Copyright (C) 2006-2020 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 "AlternativeTextClient.h"
#include "AnimationFrameRate.h"
#include "AppHighlightStorage.h"
#include "ApplicationCacheStorage.h"
#include "AttachmentElementClient.h"
#include "AuthenticatorCoordinator.h"
#include "AuthenticatorCoordinatorClient.h"
#include "BackForwardCache.h"
#include "BackForwardClient.h"
#include "BackForwardController.h"
#include "BroadcastChannelRegistry.h"
#include "CacheStorageProvider.h"
#include "CachedImage.h"
#include "CachedResourceLoader.h"
#include "Chrome.h"
#include "ChromeClient.h"
#include "CommonAtomStrings.h"
#include "ConstantPropertyMap.h"
#include "ContextMenuClient.h"
#include "ContextMenuController.h"
#include "CookieJar.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 "DocumentInlines.h"
#include "DocumentLoader.h"
#include "DocumentMarkerController.h"
#include "DocumentTimelinesController.h"
#include "DragController.h"
#include "Editing.h"
#include "Editor.h"
#include "EditorClient.h"
#include "EmptyClients.h"
#include "Event.h"
#include "EventHandler.h"
#include "EventLoop.h"
#include "EventNames.h"
#include "ExtensionStyleSheets.h"
#include "FocusController.h"
#include "FontCache.h"
#include "FrameLoader.h"
#include "FrameLoaderClient.h"
#include "FrameSelection.h"
#include "FrameTree.h"
#include "FrameView.h"
#include "FullscreenManager.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 "LayoutDisallowedScope.h"
#include "LegacySchemeRegistry.h"
#include "LibWebRTCProvider.h"
#include "LoaderStrategy.h"
#include "LogInitialization.h"
#include "Logging.h"
#include "LowPowerModeNotifier.h"
#include "MediaCanStartListener.h"
#include "MediaRecorderProvider.h"
#include "ModelPlayerProvider.h"
#include "Navigator.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 "PermissionController.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 "ProgressTracker.h"
#include "Range.h"
#include "RenderDescendantIterator.h"
#include "RenderImage.h"
#include "RenderLayerCompositor.h"
#include "RenderTheme.h"
#include "RenderView.h"
#include "RenderWidget.h"
#include "RenderingUpdateScheduler.h"
#include "ResizeObserver.h"
#include "ResourceUsageOverlay.h"
#include "RuntimeEnabledFeatures.h"
#include "SVGDocumentExtensions.h"
#include "SVGImage.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 "TextIterator.h"
#include "TextRecognitionResult.h"
#include "TextResourceDecoder.h"
#include "UserContentProvider.h"
#include "UserContentURLPattern.h"
#include "UserInputBridge.h"
#include "UserScript.h"
#include "UserStyleSheet.h"
#include "ValidationMessageClient.h"
#include "VisitedLinkState.h"
#include "VisitedLinkStore.h"
#include "VoidCallback.h"
#include "WebCoreJSClientData.h"
#include "WheelEventDeltaFilter.h"
#include "WheelEventTestMonitor.h"
#include "Widget.h"
#include "WorkerOrWorkletScriptController.h"
#include <wtf/FileSystem.h>
#include <wtf/RefCountedLeakCounter.h>
#include <wtf/StdLibExtras.h>
#include <wtf/SystemTracing.h>
#include <wtf/text/Base64.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(WEBGL)
#include "WebGLStateTracker.h"
#endif
#if ENABLE(LAYOUT_FORMATTING_CONTEXT)
#include "DisplayView.h"
#endif
#if ENABLE(MEDIA_SESSION_COORDINATOR)
#include "MediaSessionCoordinator.h"
#include "NavigatorMediaSession.h"
#endif
namespace WebCore {
static HashSet<Page*>& allPages()
{
static NeverDestroyed<HashSet<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(const Function<void(Page&)>& function)
{
for (auto* page : allPages())
function(*page);
}
void Page::updateValidationBubbleStateIfNeeded()
{
if (auto* client = validationMessageClient())
client->updateValidationBubbleStateIfNeeded();
}
static void networkStateChanged(bool isOnLine)
{
Vector<Ref<Frame>> frames;
// Get all the frames of all the pages in all the page groups
for (auto* page : allPages()) {
for (Frame* frame = &page->mainFrame(); frame; frame = frame->tree().traverseNext())
frames.append(*frame);
InspectorInstrumentation::networkStateChanged(*page);
}
auto& eventName = isOnLine ? eventNames().onlineEvent : eventNames().offlineEvent;
for (auto& frame : frames) {
if (!frame->document())
continue;
frame->document()->dispatchWindowEvent(Event::create(eventName, Event::CanBubble::No, Event::IsCancelable::No));
}
}
static constexpr OptionSet<ActivityState::Flag> pageInitialActivityState()
{
return { ActivityState::IsVisible, ActivityState::IsInWindow };
}
Page::Page(PageConfiguration&& pageConfiguration)
: m_chrome(makeUnique<Chrome>(*this, *pageConfiguration.chromeClient))
, m_dragCaretController(makeUnique<DragCaretController>())
#if ENABLE(DRAG_SUPPORT)
, m_dragController(makeUnique<DragController>(*this, WTFMove(pageConfiguration.dragClient)))
#endif
, m_focusController(makeUnique<FocusController>(*this, pageInitialActivityState()))
#if ENABLE(CONTEXT_MENUS)
, m_contextMenuController(makeUnique<ContextMenuController>(*this, *pageConfiguration.contextMenuClient))
#endif
, m_userInputBridge(makeUnique<UserInputBridge>(*this))
, m_inspectorController(makeUnique<InspectorController>(*this, pageConfiguration.inspectorClient))
, m_pointerCaptureController(makeUnique<PointerCaptureController>(*this))
#if ENABLE(POINTER_LOCK)
, m_pointerLockController(makeUnique<PointerLockController>(*this))
#endif
, m_settings(Settings::create(this))
, m_progress(makeUnique<ProgressTracker>(*this, WTFMove(pageConfiguration.progressTrackerClient)))
, m_backForwardController(makeUnique<BackForwardController>(*this, WTFMove(pageConfiguration.backForwardClient)))
, m_mainFrame(Frame::create(this, nullptr, WTFMove(pageConfiguration.loaderClientForMainFrame)))
, m_editorClient(WTFMove(pageConfiguration.editorClient))
, m_validationMessageClient(WTFMove(pageConfiguration.validationMessageClient))
, m_diagnosticLoggingClient(WTFMove(pageConfiguration.diagnosticLoggingClient))
, m_performanceLoggingClient(WTFMove(pageConfiguration.performanceLoggingClient))
#if ENABLE(WEBGL)
, m_webGLStateTracker(WTFMove(pageConfiguration.webGLStateTracker))
#endif
#if ENABLE(SPEECH_SYNTHESIS)
, m_speechSynthesisClient(WTFMove(pageConfiguration.speechSynthesisClient))
#endif
, m_speechRecognitionProvider((WTFMove(pageConfiguration.speechRecognitionProvider)))
, m_mediaRecorderProvider((WTFMove(pageConfiguration.mediaRecorderProvider)))
, m_libWebRTCProvider(WTFMove(pageConfiguration.libWebRTCProvider))
, m_domTimerAlignmentInterval(DOMTimer::defaultAlignmentInterval())
, m_domTimerAlignmentIntervalIncreaseTimer(*this, &Page::domTimerAlignmentIntervalIncreaseTimerFired)
, m_activityState(pageInitialActivityState())
, m_alternativeTextClient(WTFMove(pageConfiguration.alternativeTextClient))
, m_consoleClient(makeUnique<PageConsoleClient>(*this))
#if ENABLE(REMOTE_INSPECTOR)
, m_inspectorDebuggable(makeUnique<PageDebuggable>(*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_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 : makeUnique<PerformanceMonitor>(*this))
, m_lowPowerModeNotifier(makeUnique<LowPowerModeNotifier>([this](bool isLowPowerModeEnabled) { handleLowModePowerChange(isLowPowerModeEnabled); }))
, m_performanceLogging(makeUnique<PerformanceLogging>(*this))
#if PLATFORM(MAC) && (ENABLE(SERVICE_CONTROLS) || ENABLE(TELEPHONE_NUMBER_DETECTION))
, m_servicesOverlayController(makeUnique<ServicesOverlayController>(*this))
#endif
, m_recentWheelEventDeltaFilter(WheelEventDeltaFilter::create())
, m_pageOverlayController(makeUnique<PageOverlayController>(*this))
#if ENABLE(APPLE_PAY)
, m_paymentCoordinator(makeUnique<PaymentCoordinator>(*pageConfiguration.paymentCoordinatorClient))
#endif
#if ENABLE(WEB_AUTHN)
, m_authenticatorCoordinator(makeUniqueRef<AuthenticatorCoordinator>(WTFMove(pageConfiguration.authenticatorCoordinatorClient)))
#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_allowedNetworkHosts(WTFMove(pageConfiguration.allowedNetworkHosts))
, m_loadsSubresources(pageConfiguration.loadsSubresources)
, m_shouldRelaxThirdPartyCookieBlocking(pageConfiguration.shouldRelaxThirdPartyCookieBlocking)
, m_httpsUpgradeEnabled(pageConfiguration.httpsUpgradeEnabled)
, m_permissionController(WTFMove(pageConfiguration.permissionController))
, m_storageProvider(WTFMove(pageConfiguration.storageProvider))
, m_modelPlayerProvider(WTFMove(pageConfiguration.modelPlayerProvider))
#if ENABLE(ATTACHMENT_ELEMENT)
, m_attachmentElementClient(WTFMove(pageConfiguration.attachmentElementClient))
#endif
, m_contentSecurityPolicyModeForExtension(WTFMove(pageConfiguration.contentSecurityPolicyModeForExtension))
{
updateTimerThrottlingState();
m_pluginInfoProvider->addPage(*this);
m_userContentProvider->addPage(*this);
m_visitedLinkStore->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
#if ENABLE(REMOTE_INSPECTOR)
if (m_inspectorController->inspectorClient() && m_inspectorController->inspectorClient()->allowRemoteInspectionToPageDirectly())
m_inspectorDebuggable->init();
#endif
#if PLATFORM(COCOA)
platformInitialize();
#endif
settingsDidChange();
if (!pageConfiguration.userScriptsShouldWaitUntilNotification)
m_hasBeenNotifiedToInjectUserScripts = true;
if (m_lowPowerModeNotifier->isLowPowerModeEnabled())
m_throttlingReasons.add(ThrottlingReason::LowPowerMode);
}
Page::~Page()
{
ASSERT(!m_nestedRunLoopCount);
ASSERT(!m_unnestCallback);
m_validationMessageClient = nullptr;
m_diagnosticLoggingClient = nullptr;
m_performanceLoggingClient = nullptr;
m_mainFrame->setView(nullptr);
setGroupName(String());
allPages().remove(this);
if (!isUtilityPage()) {
--gNonUtilityPageCount;
MemoryPressureHandler::setPageCount(gNonUtilityPageCount);
}
m_settings->pageDestroyed();
m_inspectorController->inspectedPageDestroyed();
forEachFrame([] (Frame& frame) {
frame.willDetachPage();
frame.detachFromPage();
});
if (m_scrollingCoordinator)
m_scrollingCoordinator->pageDestroyed();
backForward().close();
if (!isUtilityPage())
BackForwardCache::singleton().removeAllItemsForPage(*this);
#ifndef NDEBUG
pageCounter.decrement();
#endif
m_pluginInfoProvider->removePage(*this);
m_userContentProvider->removePage(*this);
m_visitedLinkStore->removePage(*this);
}
void Page::firstTimeInitialization()
{
platformStrategies()->loaderStrategy()->addOnlineStateChangeListener(&networkStateChanged);
FontCache::registerFontCacheInvalidationCallback([] {
forEachPage([](auto& page) {
page.setNeedsRecalcStyleInAllFrames();
});
});
}
void Page::clearPreviousItemFromAllPages(HistoryItem* item)
{
for (auto* page : allPages()) {
auto& controller = page->mainFrame().loader().history();
if (item == controller.previousItem()) {
controller.clearPreviousItem();
return;
}
}
}
uint64_t Page::renderTreeSize() const
{
uint64_t total = 0;
forEachDocument([&] (Document& document) {
if (auto* renderView = document.renderView())
total += renderView->rendererCount();
});
return total;
}
OptionSet<DisabledAdaptations> Page::disabledAdaptations() const
{
if (mainFrame().document())
return mainFrame().document()->disabledAdaptations();
return { };
}
ViewportArguments Page::viewportArguments() const
{
return mainFrame().document() ? mainFrame().document()->viewportArguments() : ViewportArguments();
}
void Page::setOverrideViewportArguments(const std::optional<ViewportArguments>& viewportArguments)
{
if (viewportArguments == m_overrideViewportArguments)
return;
m_overrideViewportArguments = viewportArguments;
if (auto* document = mainFrame().document())
document->updateViewportArguments();
}
ScrollingCoordinator* Page::scrollingCoordinator()
{
if (!m_scrollingCoordinator && m_settings->scrollingCoordinatorEnabled()) {
m_scrollingCoordinator = chrome().client().createScrollingCoordinator(*this);
if (!m_scrollingCoordinator)
m_scrollingCoordinator = ScrollingCoordinator::create(this);
m_scrollingCoordinator->windowScreenDidChange(m_displayID, m_displayNominalFramesPerSecond);
}
return m_scrollingCoordinator.get();
}
String Page::scrollingStateTreeAsText()
{
if (Document* document = m_mainFrame->document()) {
document->updateLayout();
#if ENABLE(IOS_TOUCH_EVENTS)
document->updateTouchEventRegions();
#endif
}
if (ScrollingCoordinator* scrollingCoordinator = this->scrollingCoordinator())
return scrollingCoordinator->scrollingStateTreeAsText();
return String();
}
String Page::synchronousScrollingReasonsAsText()
{
if (Document* document = m_mainFrame->document())
document->updateLayout();
if (ScrollingCoordinator* scrollingCoordinator = this->scrollingCoordinator())
return scrollingCoordinator->synchronousScrollingReasonsAsText();
return String();
}
Ref<DOMRectList> Page::nonFastScrollableRectsForTesting()
{
if (Document* document = m_mainFrame->document()) {
document->updateLayout();
#if ENABLE(IOS_TOUCH_EVENTS)
document->updateTouchEventRegions();
#endif
}
Vector<IntRect> rects;
if (ScrollingCoordinator* 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)
{
if (Document* document = m_mainFrame->document()) {
document->updateLayout();
#if ENABLE(IOS_TOUCH_EVENTS)
document->updateTouchEventRegions();
#endif
}
Vector<IntRect> rects;
if (ScrollingCoordinator* 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()
{
if (Document* document = m_mainFrame->document()) {
document->updateLayout();
#if ENABLE(IOS_TOUCH_EVENTS)
document->updateTouchEventRegions();
#endif
}
Vector<IntRect> rects;
if (auto* 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 USE(LIBWEBRTC)
m_libWebRTCProvider->setH265Support(settings().webRTCH265CodecEnabled());
m_libWebRTCProvider->setVP9Support(settings().webRTCVP9Profile0CodecEnabled(), settings().webRTCVP9Profile2CodecEnabled());
#endif
}
std::optional<AXTreeData> Page::accessibilityTreeData() const
{
auto* document = mainFrame().document();
if (!document)
return std::nullopt;
if (auto* cache = document->existingAXObjectCache())
return { cache->treeData() };
return std::nullopt;
}
void Page::progressEstimateChanged(Frame& frameWithProgressUpdate) const
{
if (auto* document = frameWithProgressUpdate.document()) {
if (auto* axObjectCache = document->existingAXObjectCache())
axObjectCache->updateLoadingProgress(progress().estimatedProgress());
}
}
void Page::progressFinished(Frame& frameWithCompletedProgress) const
{
if (auto* document = frameWithCompletedProgress.document()) {
if (auto* axObjectCache = document->existingAXObjectCache())
axObjectCache->loadingFinished();
}
}
bool Page::openedByDOM() const
{
return m_openedByDOM;
}
void Page::setOpenedByDOM()
{
m_openedByDOM = true;
}
void Page::goToItem(HistoryItem& item, FrameLoadType type, ShouldTreatAsContinuingLoad shouldTreatAsContinuingLoad)
{
// 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<HistoryItem> protector(item);
auto& frameLoader = m_mainFrame->loader();
if (frameLoader.history().shouldStopLoadingForHistoryItem(item))
m_mainFrame->loader().stopAllLoadersAndCheckCompleteness();
m_mainFrame->loader().history().goToItem(item, type, shouldTreatAsContinuingLoad);
}
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();
}
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 (auto* styleResolver = document.styleScope().resolverIfExists())
styleResolver->invalidateMatchedDeclarationsCache();
document.scheduleFullStyleRebuild();
document.styleScope().didChangeStyleSheetEnvironment();
document.scheduleRenderingUpdate(RenderingUpdateStep::MediaQueryEvaluation);
});
}
void Page::updateStyleForAllPagesAfterGlobalChangeInEnvironment()
{
for (auto* page : allPages())
page->updateStyleAfterChangeInEnvironment();
}
void Page::setNeedsRecalcStyleInAllFrames()
{
// FIXME: Figure out what this function is actually trying to add in different call sites.
forEachDocument([] (Document& document) {
document.styleScope().didChangeStyleSheetEnvironment();
});
}
void Page::refreshPlugins(bool reload)
{
HashSet<PluginInfoProvider*> pluginInfoProviders;
for (auto* page : allPages())
pluginInfoProviders.add(&page->pluginInfoProvider());
for (auto& pluginInfoProvider : pluginInfoProviders)
pluginInfoProvider->refresh(reload);
}
PluginData& Page::pluginData()
{
if (!m_pluginData)
m_pluginData = PluginData::create(*this);
return *m_pluginData;
}
void Page::clearPluginData()
{
m_pluginData = nullptr;
}
bool Page::showAllPlugins() const
{
if (m_showAllPlugins)
return true;
if (Document* document = mainFrame().document())
return document->securityOrigin().isLocal();
return false;
}
inline std::optional<std::pair<MediaCanStartListener&, Document&>> Page::takeAnyMediaCanStartListener()
{
for (Frame* frame = &mainFrame(); frame; frame = frame->tree().traverseNext()) {
if (!frame->document())
continue;
if (MediaCanStartListener* listener = frame->document()->takeAnyMediaCanStartListener())
return { { *listener, *frame->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;
listener->first.mediaCanStart(listener->second);
}
}
static Frame* incrementFrame(Frame* curr, bool forward, CanWrap canWrap, DidWrap* didWrap = nullptr)
{
return forward
? curr->tree().traverseNext(canWrap, didWrap)
: curr->tree().traversePrevious(canWrap, didWrap);
}
bool Page::findString(const String& target, FindOptions options, DidWrap* didWrap)
{
if (target.isEmpty())
return false;
CanWrap canWrap = options.contains(WrapAround) ? CanWrap::Yes : CanWrap::No;
CheckedRef focusController { *m_focusController };
RefPtr frame = &focusController->focusedOrMainFrame();
RefPtr startFrame = frame;
do {
if (frame->editor().findString(target, (options - WrapAround) | StartInSelection)) {
if (frame != startFrame)
startFrame->selection().clear();
focusController->setFocusedFrame(frame.get());
return true;
}
frame = incrementFrame(frame.get(), !options.contains(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 && !startFrame->selection().isNone()) {
if (didWrap)
*didWrap = DidWrap::Yes;
bool found = startFrame->editor().findString(target, options | WrapAround | StartInSelection);
focusController->setFocusedFrame(frame.get());
return found;
}
return false;
}
auto Page::findTextMatches(const String& target, FindOptions options, unsigned limit, bool markMatches) -> MatchingRanges
{
MatchingRanges result;
Frame* frame = &mainFrame();
Frame* frameWithSelection = nullptr;
do {
frame->editor().countMatchesForText(target, { }, options, limit ? (limit - result.ranges.size()) : 0, markMatches, &result.ranges);
if (frame->selection().isRange())
frameWithSelection = frame;
frame = incrementFrame(frame, 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(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(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(WrapAround) ? CanWrap::Yes : CanWrap::No;
Frame* frame = referenceRange ? referenceRange->start.document().frame() : &mainFrame();
Frame* startFrame = frame;
do {
if (auto resultRange = frame->editor().rangeOfString(target, frame == startFrame ? referenceRange : std::nullopt, options - WrapAround))
return resultRange;
frame = incrementFrame(frame, !options.contains(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->editor().rangeOfString(target, *referenceRange, options | WrapAround | 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;
Frame* frame = &mainFrame();
do {
if (shouldMarkMatches == MarkMatches)
frame->editor().setMarkedTextMatchesAreHighlighted(shouldHighlightMatches == HighlightMatches);
matchCount += frame->editor().countMatchesForText(target, std::nullopt, options, maxMatchCount ? (maxMatchCount - matchCount) : 0, shouldMarkMatches == MarkMatches, nullptr);
frame = incrementFrame(frame, 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<Frame>, unsigned> frameToTraversalIndexMap;
unsigned currentFrameTraversalIndex = 0;
for (Frame* frame = &page.mainFrame(); frame; frame = frame->tree().traverseNext())
frameToTraversalIndexMap.set(frame, 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::sort(containerNodesInOrderOfReplacement.begin(), containerNodesInOrderOfReplacement.end(), [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->selection().setSelectedRange(range, Affinity::Downstream, FrameSelection::ShouldCloseTyping::Yes);
frame->editor().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.
Vector<FindReplacementRange> replacementRanges;
replacementRanges.reserveInitialCapacity(rangesToReplace.size());
for (auto& range : rangesToReplace) {
RefPtr highestRoot = highestEditableRoot(makeDeprecatedLegacyPosition(range.start));
if (!highestRoot || highestRoot != highestEditableRoot(makeDeprecatedLegacyPosition(range.end)) || !highestRoot->document().frame())
continue;
auto scope = makeRangeSelectingNodeContents(*highestRoot);
replacementRanges.uncheckedAppend({ WTFMove(highestRoot), characterRange(scope, range) });
}
replaceRanges(*this, replacementRanges, replacementText);
return rangesToReplace.size();
}
uint32_t Page::replaceSelectionWithText(const String& replacementText)
{
Ref frame = CheckedRef(focusController())->focusedOrMainFrame();
auto selection = frame->selection().selection();
if (!selection.isContentEditable())
return 0;
auto editAction = selection.isRange() ? EditAction::InsertReplacement : EditAction::Insert;
frame->editor().replaceSelectionWithText(replacementText, Editor::SelectReplacement::Yes, Editor::SmartReplace::No, editAction);
return 1;
}
void Page::unmarkAllTextMatches()
{
forEachDocument([] (Document& document) {
document.markers().removeMarkers(DocumentMarker::TextMatch);
});
}
#if ENABLE(EDITABLE_REGION)
void Page::setEditableRegionEnabled(bool enabled)
{
if (m_isEditableRegionEnabled == enabled)
return;
m_isEditableRegionEnabled = enabled;
RefPtr frameView = mainFrame().view();
if (!frameView)
return;
if (auto* 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 frameView = mainFrame().view();
if (!frameView)
return { };
RefPtr document = mainFrame().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 (is<HTMLTextFormControlElement>(node)) {
if (downcast<HTMLTextFormControlElement>(node).isInnerTextElementEditable())
return &downcast<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 (auto* editableElement = rootEditableElement(node)) {
ASSERT(searchRectInRootViewCoordinates.inclusivelyIntersects(editableElement->boundingBoxInRootViewCoordinates()));
rootEditableElements.add(*editableElement);
}
}
// 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.
if (RefPtr focusedElement = CheckedRef(focusController())->focusedOrMainFrame().document()->focusedElement()) {
if (searchRectInRootViewCoordinates.inclusivelyIntersects(focusedElement->boundingBoxInRootViewCoordinates())) {
if (auto* editableElement = rootEditableElement(*focusedElement))
rootEditableElements.add(*editableElement);
}
}
return WTF::map(rootEditableElements, [](const auto& element) { return element.copyRef(); });
}
#if ENABLE(INTERACTION_REGIONS_IN_EVENT_REGION)
bool Page::shouldBuildInteractionRegions() const
{
return m_settings->interactionRegionsEnabled();
}
#endif
const VisibleSelection& Page::selection() const
{
return CheckedRef(focusController())->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 (Frame* frame = &mainFrame(); frame; frame = frame->tree().traverseNext())
frame->loader().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 FormData* formData) const
{
unsigned imageOrMediaFilesCount = formData ? formData->imageOrMediaFilesCount() : 0;
if (!imageOrMediaFilesCount)
return;
auto message = makeString(imageOrMediaFilesCount, imageOrMediaFilesCount == 1 ? " media file has been submitted" : " media files have been submitted");
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;
mainFrame().deviceOrPageScaleFactorChanged();
}
void Page::setPageScaleFactor(float scale, const IntPoint& origin, bool inStableState)
{
LOG(Viewports, "Page::setPageScaleFactor %.2f - inStableState %d", scale, inStableState);
Document* document = mainFrame().document();
RefPtr<FrameView> view = document->view();
if (scale == m_pageScaleFactor) {
if (view && view->scrollPosition() != origin && !delegatesScaling())
document->updateLayoutIgnorePendingStylesheets();
} else {
m_pageScaleFactor = scale;
if (view && !delegatesScaling()) {
view->setNeedsLayoutAfterViewConfigurationChange();
view->setNeedsCompositingGeometryUpdate();
document->resolveStyle(Document::ResolveStyleType::Rebuild);
// Transform change on RenderView doesn't trigger repaint on non-composited contents.
mainFrame().view()->invalidateRect(IntRect(LayoutRect::infiniteRect()));
}
mainFrame().deviceOrPageScaleFactorChanged();
if (view && view->fixedElementsLayoutRelativeToFrame())
view->setViewportConstrainedObjectsNeedLayout();
if (view && view->scrollPosition() != origin && !delegatesScaling() && document->renderView() && document->renderView()->needsLayout() && view->didFirstLayout())
view->layoutContext().layout();
}
if (view && view->scrollPosition() != origin) {
if (!view->delegatesScrolling())
view->setScrollPosition(origin);
#if USE(COORDINATED_GRAPHICS)
else
view->requestScrollPositionUpdate(origin);
#endif
}
#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();
mainFrame().deviceOrPageScaleFactorChanged();
BackForwardCache::singleton().markPagesForDeviceOrPageScaleChanged(*this);
pageOverlayController().didChangeDeviceScaleFactor();
}
void Page::screenPropertiesDidChange()
{
#if ENABLE(VIDEO)
auto mode = preferredDynamicRangeMode(mainFrame().view());
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;
if (!m_displayNominalFramesPerSecond) {
// If the caller didn't give us a refresh rate, maybe the relevant DisplayRefreshMonitor can? This happens in WebKitLegacy
// because WebView doesn't have a convenient way to access the display refresh rate.
m_displayNominalFramesPerSecond = DisplayRefreshMonitorManager::sharedManager().nominalFramesPerSecondForDisplay(m_displayID, chrome().client().displayRefreshMonitorFactory());
}
forEachDocument([&] (Document& document) {
document.windowScreenDidChange(displayID);
});
#if ENABLE(VIDEO)
auto mode = preferredDynamicRangeMode(mainFrame().view());
forEachMediaElement([mode] (auto& element) {
element.setPreferredDynamicRangeMode(mode);
});
#endif
if (m_scrollingCoordinator)
m_scrollingCoordinator->windowScreenDidChange(displayID, m_displayNominalFramesPerSecond);
renderingUpdateScheduler().windowScreenDidChange(displayID);
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 (m_performanceMonitor)
m_performanceMonitor->didStartProvisionalLoad();
if (m_settings->resourceLoadSchedulingEnabled())
setLoadSchedulingMode(LoadSchedulingMode::Prioritized);
}
void Page::didCommitLoad()
{
#if ENABLE(EDITABLE_REGION)
m_isEditableRegionEnabled = false;
#endif
#if HAVE(OS_DARK_MODE_SUPPORT)
setUseDarkAppearanceOverride(std::nullopt);
#endif
resetSeenPlugins();
resetSeenMediaEngines();
#if ENABLE(IMAGE_ANALYSIS)
resetTextRecognitionResults();
resetImageAnalysisQueue();
#endif
#if ENABLE(GEOLOCATION)
if (auto* geolocationController = GeolocationController::from(this))
geolocationController->didNavigatePage();
#endif
}
void Page::didFinishLoad()
{
resetRelevantPaintedObjectCounter();
if (m_performanceMonitor)
m_performanceMonitor->didFinishLoad();
setLoadSchedulingMode(LoadSchedulingMode::Direct);
}
bool Page::isOnlyNonUtilityPage() const
{
return !isUtilityPage() && gNonUtilityPageCount == 1;
}
void Page::setLowPowerModeEnabledOverrideForTesting(std::optional<bool> isEnabled)
{
// Remove ThrottlingReason::LowPowerMode so handleLowModePowerChange() can do its work.
m_throttlingReasonsOverridenForTesting.remove(ThrottlingReason::LowPowerMode);
// Use the current low power mode value of the device.
if (!isEnabled) {
handleLowModePowerChange(m_lowPowerModeNotifier->isLowPowerModeEnabled());
return;
}
// Override the value and add ThrottlingReason::LowPowerMode so it override the device state.
handleLowModePowerChange(isEnabled.value());
m_throttlingReasonsOverridenForTesting.add(ThrottlingReason::LowPowerMode);
}
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::setTopContentInset(float contentInset)
{
if (m_topContentInset == contentInset)
return;
m_topContentInset = contentInset;
if (FrameView* view = mainFrame().view())
view->topContentInsetDidChange(m_topContentInset);
}
void Page::setShouldSuppressScrollbarAnimations(bool suppressAnimations)
{
if (suppressAnimations == m_suppressScrollbarAnimations)
return;
lockAllOverlayScrollbarsToHidden(suppressAnimations);
m_suppressScrollbarAnimations = suppressAnimations;
}
void Page::lockAllOverlayScrollbarsToHidden(bool lockOverlayScrollbars)
{
FrameView* view = mainFrame().view();
if (!view)
return;
view->lockOverlayScrollbarStateToHidden(lockOverlayScrollbars);
for (Frame* frame = &mainFrame(); frame; frame = frame->tree().traverseNext()) {
FrameView* frameView = frame->view();
if (!frameView)
continue;
const HashSet<ScrollableArea*>* scrollableAreas = frameView->scrollableAreas();
if (!scrollableAreas)
continue;
for (auto& scrollableArea : *scrollableAreas)
scrollableArea->lockOverlayScrollbarStateToHidden(lockOverlayScrollbars);
}
}
void Page::setVerticalScrollElasticity(ScrollElasticity elasticity)
{
if (m_verticalScrollElasticity == elasticity)
return;
m_verticalScrollElasticity = elasticity;
if (FrameView* view = mainFrame().view())
view->setVerticalScrollElasticity(elasticity);
}
void Page::setHorizontalScrollElasticity(ScrollElasticity elasticity)
{
if (m_horizontalScrollElasticity == elasticity)
return;
m_horizontalScrollElasticity = elasticity;
if (FrameView* view = mainFrame().view())
view->setHorizontalScrollElasticity(elasticity);
}
void Page::setPagination(const Pagination& pagination)
{
if (m_pagination == pagination)
return;
m_pagination = pagination;
setNeedsRecalcStyleInAllFrames();
}
void Page::setPaginationLineGridEnabled(bool enabled)
{
if (m_paginationLineGridEnabled == enabled)
return;
m_paginationLineGridEnabled = enabled;
setNeedsRecalcStyleInAllFrames();
}
unsigned Page::pageCount() const
{
if (m_pagination.mode == Pagination::Unpaginated)
return 0;
if (Document* document = mainFrame().document())
document->updateLayoutIgnorePendingStylesheets();
RenderView* contentRenderer = mainFrame().contentRenderer();
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 (Frame* frame = &mainFrame(); frame; frame = frame->tree().traverseNext()) {
if (FrameView* frameView = frame->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()
{
if (FrameView* view = m_mainFrame->view())
view->updateLayoutAndStyleIfNeededRecursive();
}
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())
return;
renderingUpdateScheduler().scheduleRenderingUpdate();
}
void Page::didScheduleRenderingUpdate()
{
#if ENABLE(ASYNC_SCROLLING)
if (auto* 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()");
renderingUpdateScheduler().triggerRenderingUpdateForTesting();
}
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();
m_renderingUpdateRemainingSteps.last().remove(updateRenderingSteps);
return;
}
m_lastRenderingUpdateTimestamp = MonotonicTime::now();
bool isSVGImagePage = chrome().client().isSVGImageChromeClient();
if (!isSVGImagePage)
tracePoint(RenderingUpdateStart);
layoutIfNeeded();
#if ENABLE(ASYNC_SCROLLING)
if (auto* scrollingCoordinator = this->scrollingCoordinator())
scrollingCoordinator->willStartRenderingUpdate();
#endif
// Timestamps should not change while serving the rendering update steps.
Vector<WeakPtr<Document>> initialDocuments;
forEachDocument([&initialDocuments] (Document& document) {
document.domWindow()->freezeNowTimestamp();
initialDocuments.append(document);
});
auto runProcessingStep = [&](RenderingUpdateStep step, const Function<void(Document&)>& perDocumentFunction) {
m_renderingUpdateRemainingSteps.last().remove(step);
forEachDocument(perDocumentFunction);
};
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();
});
runProcessingStep(RenderingUpdateStep::Animations, [] (Document& document) {
document.updateAnimationsAndSendEvents();
});
// FIXME: Run the fullscreen steps.
m_renderingUpdateRemainingSteps.last().remove(RenderingUpdateStep::Fullscreen);
runProcessingStep(RenderingUpdateStep::VideoFrameCallbacks, [] (Document& document) {
document.serviceRequestVideoFrameCallbacks();
});
runProcessingStep(RenderingUpdateStep::AnimationFrameCallbacks, [] (Document& document) {
document.serviceRequestAnimationFrameCallbacks();
});
layoutIfNeeded();
runProcessingStep(RenderingUpdateStep::IntersectionObservations, [] (Document& document) {
document.updateIntersectionObservations();
});
runProcessingStep(RenderingUpdateStep::ResizeObservations, [&] (Document& document) {
document.updateResizeObservations(*this);
});
runProcessingStep(RenderingUpdateStep::Images, [] (Document& document) {
for (auto& image : document.cachedResourceLoader().allCachedSVGImages()) {
if (auto* page = image->internalPage())
page->isolatedUpdateRendering();
}
});
for (auto& document : initialDocuments) {
if (document && document->domWindow())
document->domWindow()->unfreezeNowTimestamp();
}
m_renderingUpdateRemainingSteps.last().remove(RenderingUpdateStep::WheelEventMonitorCallbacks);
if (UNLIKELY(isMonitoringWheelEvents()))
wheelEventTestMonitor()->checkShouldFireCallbacks();
if (m_isTrackingRenderingUpdates)
++m_renderingUpdateCount;
layoutIfNeeded();
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.
#if ENABLE(LAYOUT_FORMATTING_CONTEXT)
if (RuntimeEnabledFeatures::sharedFeatures().layoutFormattingContextEnabled()) {
forEachDocument([] (Document& document) {
if (auto* frameView = document.view())
frameView->displayView().prepareForDisplay();
});
}
#endif
auto runProcessingStep = [&](RenderingUpdateStep step, const Function<void(Document&)>& perDocumentFunction) {
m_renderingUpdateRemainingSteps.last().remove(step);
forEachDocument(perDocumentFunction);
};
runProcessingStep(RenderingUpdateStep::CursorUpdate, [] (Document& document) {
if (auto* frame = document.frame())
frame->eventHandler().updateCursorIfNeeded();
});
forEachDocument([] (Document& document) {
document.enqueuePaintTimingEntryIfNeeded();
});
forEachDocument([] (Document& document) {
document.selection().updateAppearanceAfterLayout();
});
forEachDocument([] (Document& document) {
document.updateHighlightPositions();
});
#if ENABLE(APP_HIGHLIGHTS)
forEachDocument([] (Document& document) {
auto appHighlightStorage = document.appHighlightStorageIfExists();
if (!appHighlightStorage)
return;
if (appHighlightStorage->hasUnrestoredHighlights() && MonotonicTime::now() - appHighlightStorage->lastRangeSearchTime() > 1_s) {
appHighlightStorage->resetLastRangeSearchTime();
document.eventLoop().queueTask(TaskSource::InternalAsyncTask, [weakDocument = WeakPtr { document }] {
RefPtr document { weakDocument.get() };
if (!document)
return;
if (auto* appHighlightStorage = document->appHighlightStorageIfExists())
appHighlightStorage->restoreUnrestoredAppHighlights();
});
}
});
#endif
#if ENABLE(VIDEO)
forEachDocument([] (Document& document) {
document.updateTextTrackRepresentationImageIfNeeded();
});
#endif
#if ENABLE(IMAGE_ANALYSIS)
updateElementsWithTextRecognitionResults();
#endif
prioritizeVisibleResources();
m_renderingUpdateRemainingSteps.last().remove(RenderingUpdateStep::EventRegionUpdate);
#if ENABLE(IOS_TOUCH_EVENTS)
// updateTouchEventRegions() needs to be called only on the top document.
if (RefPtr<Document> document = mainFrame().document())
document->updateTouchEventRegions();
#endif
forEachDocument([] (Document& document) {
document.updateEventRegions();
});
DebugPageOverlays::doAfterUpdateRendering(*this);
m_renderingUpdateRemainingSteps.last().remove(RenderingUpdateStep::PrepareCanvasesForDisplay);
forEachDocument([] (Document& document) {
document.prepareCanvasesForDisplayIfNeeded();
});
ASSERT(!mainFrame().view() || !mainFrame().view()->needsLayout());
#if ASSERT_ENABLED
for (Frame* child = mainFrame().tree().firstRenderedChild(); child; child = child->tree().traverseNextRendered()) {
auto* frameView = child->view();
ASSERT(!frameView || !frameView->needsLayout());
}
#endif
if (auto* view = mainFrame().view())
view->notifyAllFramesThatContentAreaWillPaint();
if (!m_sampledPageTopColor) {
m_sampledPageTopColor = PageColorSampler::sampleTop(*this);
if (m_sampledPageTopColor)
chrome().client().sampledPageTopColorChanged();
}
}
void Page::finalizeRenderingUpdate(OptionSet<FinalizeRenderingUpdateFlags> flags)
{
LOG(EventLoop, "Page %p finalizeRenderingUpdate()", this);
auto* view = mainFrame().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 (auto* scrollingCoordinator = this->scrollingCoordinator()) {
scrollingCoordinator->commitTreeStateIfNeeded();
if (flags.contains(FinalizeRenderingUpdateFlags::ApplyScrollingTreeLayerPositions))
scrollingCoordinator->applyScrollingTreeLayerPositions();
scrollingCoordinator->didCompleteRenderingUpdate();
}
#endif
ASSERT(m_renderingUpdateRemainingSteps.last().isEmpty());
renderingUpdateCompleted();
}
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 = { };
}
}
void Page::willStartPlatformRenderingUpdate()
{
// 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.
m_inspectorController->willComposite(mainFrame());
if (m_scrollingCoordinator)
m_scrollingCoordinator->willStartPlatformRenderingUpdate();
}
void Page::didCompletePlatformRenderingUpdate()
{
if (m_scrollingCoordinator)
m_scrollingCoordinator->didCompletePlatformRenderingUpdate();
m_inspectorController->didComposite(mainFrame());
}
void Page::prioritizeVisibleResources()
{
if (loadSchedulingMode() == LoadSchedulingMode::Direct)
return;
if (!mainFrame().document())
return;
Vector<CachedResource*> toPrioritize;
forEachDocument([&] (Document& document) {
toPrioritize.appendVector(document.cachedResourceLoader().visibleResourcesToPrioritize());
});
auto computeSchedulingMode = [&] {
auto& document = *mainFrame().document();
// Parsing generates resource loads.
if (document.parsing())
return LoadSchedulingMode::Prioritized;
// Async script execution may generate more resource loads that benefit from prioritization.
if (document.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);
}
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(DocumentTimelinesController&)
{
renderingUpdateScheduler().adjustRenderingUpdateFrequency();
}
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 DocumentTimelinesController::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 (auto* 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);
renderingUpdateScheduler().adjustRenderingUpdateFrequency();
}
void Page::handleLowModePowerChange(bool isLowPowerModeEnabled)
{
if (!canUpdateThrottlingReason(ThrottlingReason::LowPowerMode))
return;
if (isLowPowerModeEnabled == m_throttlingReasons.contains(ThrottlingReason::LowPowerMode))
return;
m_throttlingReasons.set(ThrottlingReason::LowPowerMode, isLowPowerModeEnabled);
renderingUpdateScheduler().adjustRenderingUpdateFrequency();
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;
if (auto styleSheetAsUTF8 = base64Decode(PAL::decodeURLEscapeSequences(StringView(url.string()).substring(35)), Base64DecodeOptions::IgnoreSpacesAndNewLines))
m_userStyleSheet = String::fromUTF8(styleSheetAsUTF8->data(), styleSheetAsUTF8->size());
}
forEachDocument([] (Document& document) {
document.extensionStyleSheets().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.
auto data = SharedBuffer::createWithContentsOfFile(m_userStyleSheetPath);
if (!data)
return m_userStyleSheet;
m_userStyleSheet = TextResourceDecoder::create(cssContentTypeAtom())->decodeAndFlush(data->data(), data->size());
return m_userStyleSheet;
}
void Page::userAgentChanged()
{
forEachDocument([] (Document& document) {
if (auto* window = document.domWindow()) {
if (auto* navigator = window->optionalNavigator())
navigator->userAgentChanged();
}
});
}
void Page::invalidateStylesForAllLinks()
{
forEachDocument([] (Document& document) {
document.visitedLinkState().invalidateStyleForAllLinks();
});
}
void Page::invalidateStylesForLink(SharedStringHash linkHash)
{
forEachDocument([&] (Document& document) {
document.visitedLinkState().invalidateStyleForLink(linkHash);
});
}
void Page::invalidateInjectedStyleSheetCacheInAllFrames()
{
forEachDocument([] (Document& document) {
document.extensionStyleSheets().invalidateInjectedStyleSheetCache();
});
}
void Page::setDebugger(JSC::Debugger* debugger)
{
if (m_debugger == debugger)
return;
m_debugger = debugger;
for (Frame* frame = &m_mainFrame.get(); frame; frame = frame->tree().traverseNext())
frame->windowProxy().attachDebugger(m_debugger);
}
StorageNamespace* Page::sessionStorage(bool optionalCreate)
{
if (!m_sessionStorage && optionalCreate) {
ASSERT(m_settings->sessionStorageQuota() != StorageMap::noQuota);
m_sessionStorage = m_storageNamespaceProvider->createSessionStorageNamespace(*this, m_settings->sessionStorageQuota());
}
return m_sessionStorage.get();
}
void Page::setSessionStorage(RefPtr<StorageNamespace>&& newStorage)
{
m_sessionStorage = WTFMove(newStorage);
}
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::setMemoryCacheClientCallsEnabled(bool enabled)
{
if (m_areMemoryCacheClientCallsEnabled == enabled)
return;
m_areMemoryCacheClientCallsEnabled = enabled;
if (!enabled)
return;
for (RefPtr<Frame> frame = &mainFrame(); frame; frame = frame->tree().traverseNext())
frame->loader().tellClientAboutPastMemoryCacheLoads();
}
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:
m_domTimerAlignmentInterval = isLowPowerModeEnabled() ? DOMTimer::defaultAlignmentIntervalInLowPowerMode() : 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 alinment 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::dnsPrefetchingStateChanged()
{
forEachDocument([] (Document& document) {
document.initDNSPrefetch();
});
}
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::playbackControlsMediaEngineChanged()
{
chrome().client().playbackControlsMediaEngineChanged();
}
#endif
void Page::setMuted(MediaProducerMutedStateFlags muted)
{
m_mutedState = muted;
forEachDocument([] (Document& document) {
document.pageMutedStateDidChange();
});
}
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 (auto* platformMediaSessionManager = PlatformMediaSessionManager::sharedManagerIfExists())
return !platformMediaSessionManager->hasNoSession();
#endif
return false;
}
bool Page::mediaPlaybackIsPaused()
{
#if ENABLE(VIDEO)
if (auto* platformMediaSessionManager = PlatformMediaSessionManager::sharedManagerIfExists())
return platformMediaSessionManager->mediaPlaybackIsPaused(mediaSessionGroupIdentifier());
#endif
return false;
}
void Page::pauseAllMediaPlayback()
{
#if ENABLE(VIDEO)
if (auto* platformMediaSessionManager = PlatformMediaSessionManager::sharedManagerIfExists())
platformMediaSessionManager->pauseAllMediaPlaybackForGroup(mediaSessionGroupIdentifier());
#endif
}
void Page::suspendAllMediaPlayback()
{
#if ENABLE(VIDEO)
ASSERT(!m_mediaPlaybackIsSuspended);
if (m_mediaPlaybackIsSuspended)
return;
if (auto* platformMediaSessionManager = PlatformMediaSessionManager::sharedManagerIfExists())
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
}
MediaSessionGroupIdentifier Page::mediaSessionGroupIdentifier() const
{
if (!m_mediaSessionGroupIdentifier) {
if (auto identifier = m_mainFrame->loader().pageID())
m_mediaSessionGroupIdentifier = makeObjectIdentifier<MediaSessionGroupIdentifierType>(identifier->toUInt64());
}
return m_mediaSessionGroupIdentifier;
}
void Page::resumeAllMediaPlayback()
{
#if ENABLE(VIDEO)
ASSERT(m_mediaPlaybackIsSuspended);
if (!m_mediaPlaybackIsSuspended)
return;
m_mediaPlaybackIsSuspended = false;
if (auto* platformMediaSessionManager = PlatformMediaSessionManager::sharedManagerIfExists())
platformMediaSessionManager->resumeAllMediaPlaybackForGroup(mediaSessionGroupIdentifier());
#endif
}
void Page::suspendAllMediaBuffering()
{
#if ENABLE(VIDEO)
ASSERT(!m_mediaBufferingIsSuspended);
if (m_mediaBufferingIsSuspended)
return;
m_mediaBufferingIsSuspended = true;
if (auto* platformMediaSessionManager = PlatformMediaSessionManager::sharedManagerIfExists())
platformMediaSessionManager->suspendAllMediaBufferingForGroup(mediaSessionGroupIdentifier());
#endif
}
void Page::resumeAllMediaBuffering()
{
#if ENABLE(VIDEO)
if (!m_mediaBufferingIsSuspended)
return;
m_mediaBufferingIsSuspended = false;
if (auto* platformMediaSessionManager = PlatformMediaSessionManager::sharedManagerIfExists())
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.
if (FrameView* view = mainFrame().view())
view->resumeVisibleImageAnimationsIncludingSubframes();
}
void Page::setActivityState(OptionSet<ActivityState::Flag> activityState)
{
auto changed = m_activityState ^ activityState;
if (!changed)
return;
auto oldActivityState = m_activityState;
bool wasVisibleAndActive = isVisibleAndActive();
m_activityState = activityState;
CheckedRef(*m_focusController)->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));
if (changed & ActivityState::WindowIsActive) {
if (auto* view = m_mainFrame->view())
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 (m_performanceMonitor)
m_performanceMonitor->activityStateChanged(oldActivityState, activityState);
}
void Page::stopKeyboardScrollAnimation()
{
for (auto* frame = &mainFrame(); frame; frame = frame->tree().traverseNext()) {
auto* frameView = frame->view();
if (!frameView)
continue;
frameView->stopKeyboardScrollAnimation();
auto scrollableAreas = frameView->scrollableAreas();
if (!scrollableAreas)
continue;
for (auto& scrollableArea : *scrollableAreas) {
// First call stopAsyncAnimatedScroll() to prepare for the keyboard scroller running on the scrolling thread.
scrollableArea->stopAsyncAnimatedScroll();
scrollableArea->stopKeyboardScrollAnimation();
}
}
}
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
if (FrameView* view = mainFrame().view())
view->show();
if (m_settings->hiddenPageCSSAnimationSuspensionEnabled()) {
forEachDocument([] (Document& document) {
if (auto* timelines = document.timelinesController())
timelines->resumeAnimations();
});
}
forEachDocument([] (Document& document) {
if (document.svgExtensions())
document.accessSVGExtensions().unpauseAnimations();
});
resumeAnimatingImages();
if (m_navigationToLogWhenVisible) {
logNavigation(m_navigationToLogWhenVisible.value());
m_navigationToLogWhenVisible = std::nullopt;
}
}
if (!isVisible) {
if (m_settings->hiddenPageCSSAnimationSuspensionEnabled()) {
forEachDocument([] (Document& document) {
if (auto* timelines = document.timelinesController())
timelines->suspendAnimations();
});
}
forEachDocument([] (Document& document) {
if (document.svgExtensions())
document.accessSVGExtensions().pauseAnimations();
});
#if PLATFORM(IOS_FAMILY)
forEachDocument([] (Document& document) {
document.suspendDeviceMotionAndOrientationUpdates();
});
#endif
suspendScriptedAnimations();
if (FrameView* view = mainFrame().view())
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;
FrameView* frameView = mainFrame().view();
if (!frameView)
return;
RenderView* renderView = frameView->renderView();
if (!renderView)
return;
frameView->setNeedsLayoutAfterViewConfigurationChange();
frameView->setNeedsCompositingGeometryUpdate();
}
void Page::setFooterHeight(int footerHeight)
{
if (footerHeight == m_footerHeight)
return;
m_footerHeight = footerHeight;
FrameView* frameView = mainFrame().view();
if (!frameView)
return;
RenderView* renderView = frameView->renderView();
if (!renderView)
return;
frameView->setNeedsLayoutAfterViewConfigurationChange();
frameView->setNeedsCompositingGeometryUpdate();
}
void Page::incrementNestedRunLoopCount()
{
m_nestedRunLoopCount++;
}
void Page::decrementNestedRunLoopCount()
{
ASSERT(m_nestedRunLoopCount);
if (m_nestedRunLoopCount <= 0)
return;
m_nestedRunLoopCount--;
if (!m_nestedRunLoopCount && m_unnestCallback) {
callOnMainThread([this] {
if (insideNestedRunLoop())
return;
// This callback may destruct the Page.
if (m_unnestCallback) {
auto callback = WTFMove(m_unnestCallback);
callback();
}
});
}
}
void Page::whenUnnested(Function<void()>&& callback)
{
ASSERT(!m_unnestCallback);
m_unnestCallback = WTFMove(callback);
}
#if ENABLE(REMOTE_INSPECTOR)
bool Page::remoteInspectionAllowed() const
{
return m_inspectorDebuggable->remoteDebuggingAllowed();
}
void Page::setRemoteInspectionAllowed(bool allowed)
{
m_inspectorDebuggable->setRemoteDebuggingAllowed(allowed);
}
String Page::remoteInspectionNameOverride() const
{
return m_inspectorDebuggable->nameOverride();
}
void Page::setRemoteInspectionNameOverride(const String& name)
{
m_inspectorDebuggable->setNameOverride(name);
}
void Page::remoteInspectorInformationDidChange() const
{
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
{
auto* document = mainFrame().document();
if (!document)
return { };
return document->themeColor();
}
Color Page::pageExtendedBackgroundColor() const
{
FrameView* frameView = mainFrame().view();
if (!frameView)
return Color();
RenderView* renderView = frameView->renderView();
if (!renderView)
return Color();
return renderView->compositor().rootExtendedBackgroundColor();
}
Color Page::sampledPageTopColor() const
{
return valueOrDefault(m_sampledPageTopColor);
}
void Page::setUnderPageBackgroundColorOverride(Color&& underPageBackgroundColorOverride)
{
if (underPageBackgroundColorOverride == m_underPageBackgroundColorOverride)
return;
m_underPageBackgroundColorOverride = WTFMove(underPageBackgroundColorOverride);
scheduleRenderingUpdate({ });
#if HAVE(RUBBER_BANDING)
if (RefPtr frameView = mainFrame().view()) {
if (auto* 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(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_topRelevantPaintedRegion = Region();
m_bottomRelevantPaintedRegion = Region();
m_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(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_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_topRelevantPaintedRegion.unite(topIntersection);
IntRect bottomIntersection = snappedPaintRect;
bottomIntersection.intersect(snappedIntRect(bottomRelevantRect));
m_bottomRelevantPaintedRegion.unite(bottomIntersection);
} else if (topRelevantRect.intersects(snappedPaintRect))
m_topRelevantPaintedRegion.unite(snappedPaintRect);
else
m_bottomRelevantPaintedRegion.unite(snappedPaintRect);
float topPaintedArea = m_topRelevantPaintedRegion.totalArea();
float bottomPaintedArea = m_bottomRelevantPaintedRegion.totalArea();
float viewArea = relevantRect.width() * relevantRect.height();
float ratioThatIsPaintedOnTop = topPaintedArea / viewArea;
float ratioThatIsPaintedOnBottom = bottomPaintedArea / viewArea;
float ratioOfViewThatIsUnpainted = m_relevantUnpaintedRegion.totalArea() / viewArea;
if (ratioThatIsPaintedOnTop > (gMinimumPaintedAreaRatio / 2) && ratioThatIsPaintedOnBottom > (gMinimumPaintedAreaRatio / 2)
&& ratioOfViewThatIsUnpainted < gMaximumUnpaintedAreaRatio) {
m_isCountingRelevantRepaintedObjects = false;
resetRelevantPaintedObjectCounter();
if (Frame* frame = &mainFrame())
frame->loader().didReachLayoutMilestone(DidHitRelevantRepaintedObjectsAreaThreshold);
}
}
void Page::addRelevantUnpaintedObject(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_relevantUnpaintedRegion.unite(snappedIntRect(objectPaintRect));
}
void Page::suspendActiveDOMObjectsAndAnimations()
{
for (Frame* frame = &mainFrame(); frame; frame = frame->tree().traverseNext())
frame->suspendActiveDOMObjectsAndAnimations();
}
void Page::resumeActiveDOMObjectsAndAnimations()
{
for (Frame* frame = &mainFrame(); frame; frame = frame->tree().traverseNext())
frame->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 (auto* 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);
}
PluginInfoProvider& Page::pluginInfoProvider()
{
return m_pluginInfoProvider;
}
UserContentProvider& Page::userContentProvider()
{
return m_userContentProvider;
}
void Page::notifyToInjectUserScripts()
{
m_hasBeenNotifiedToInjectUserScripts = true;
forEachFrame([] (Frame& frame) {
frame.injectUserScriptsAwaitingNotification();
});
}
void Page::setUserContentProvider(Ref<UserContentProvider>&& userContentProvider)
{
m_userContentProvider->removePage(*this);
m_userContentProvider = WTFMove(userContentProvider);
m_userContentProvider->addPage(*this);
invalidateInjectedStyleSheetCacheInAllFrames();
}
VisitedLinkStore& Page::visitedLinkStore()
{
return m_visitedLinkStore;
}
void Page::setVisitedLinkStore(Ref<VisitedLinkStore>&& visitedLinkStore)
{
m_visitedLinkStore->removePage(*this);
m_visitedLinkStore = WTFMove(visitedLinkStore);
m_visitedLinkStore->addPage(*this);
invalidateStylesForAllLinks();
}
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 && m_sessionStorage)
m_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 (m_scrollingCoordinator)
m_scrollingCoordinator->stopMonitoringWheelEvents();
m_wheelEventTestMonitor = nullptr;
}
bool Page::isMonitoringWheelEvents() const
{
return !!m_wheelEventTestMonitor;
}
void Page::startMonitoringWheelEvents(bool clearLatchingState)
{
ensureWheelEventTestMonitor().clearAllTestDeferrals();
#if ENABLE(WHEEL_EVENT_LATCHING)
if (clearLatchingState)
scrollLatchingController().clear();
#endif
if (auto* frameView = mainFrame().view()) {
if (m_scrollingCoordinator) {
m_scrollingCoordinator->startMonitoringWheelEvents(clearLatchingState);
m_scrollingCoordinator->updateIsMonitoringWheelEventsForFrameView(*frameView);
}
}
}
WheelEventTestMonitor& Page::ensureWheelEventTestMonitor()
{
if (!m_wheelEventTestMonitor)
m_wheelEventTestMonitor = adoptRef(new WheelEventTestMonitor(*this));
return *m_wheelEventTestMonitor;
}
#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 = &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 = makeUnique<ResourceUsageOverlay>(*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.styleScope().evaluateMediaQueriesForAccessibilitySettingsChange();
document.updateElementsAffectedByMediaQueries();
document.scheduleRenderingUpdate(RenderingUpdateStep::MediaQueryEvaluation);
});
}
void Page::appearanceDidChange()
{
forEachDocument([] (auto& document) {
document.styleScope().didChangeStyleSheetEnvironment();
document.styleScope().evaluateMediaQueriesForAppearanceChange();
document.updateElementsAffectedByMediaQueries();
document.scheduleRenderingUpdate(RenderingUpdateStep::MediaQueryEvaluation);
document.invalidateScrollbars();
});
}
void Page::setUnobscuredSafeAreaInsets(const FloatBoxExtent& insets)
{
if (m_unobscuredSafeAreaInsets == insets)
return;
m_unobscuredSafeAreaInsets = insets;
forEachDocument([&] (Document& document) {
document.constantProperties().didChangeSafeAreaInsets();
});
}
void Page::setUseSystemAppearance(bool value)
{
if (m_useSystemAppearance == value)
return;
m_useSystemAppearance = value;
appearanceDidChange();
forEachDocument([&] (Document& document) {
// System apperance change may affect stylesheet parsing. We need to reparse.
document.extensionStyleSheets().clearPageUserSheet();
document.extensionStyleSheets().invalidateInjectedStyleSheetCache();
});
}
void Page::effectiveAppearanceDidChange(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, useDarkAppearance);
appearanceDidChange();
#else
UNUSED_PARAM(useDarkAppearance);
if (m_useElevatedUserInterfaceLevel == useElevatedUserInterfaceLevel)
return;
m_useElevatedUserInterfaceLevel = useElevatedUserInterfaceLevel;
appearanceDidChange();
#endif
}
bool Page::useDarkAppearance() const
{
#if ENABLE(DARK_MODE_CSS)
FrameView* view = mainFrame().view();
if (!view || !equalLettersIgnoringASCIICase(view->mediaType(), "screen"_s))
return false;
if (m_useDarkAppearanceOverride)
return m_useDarkAppearanceOverride.value();
return m_useDarkAppearance;
#else
return false;
#endif
}
void Page::setUseDarkAppearanceOverride(std::optional<bool> valueOverride)
{
#if HAVE(OS_DARK_MODE_SUPPORT)
if (valueOverride == m_useDarkAppearanceOverride)
return;
m_useDarkAppearanceOverride = valueOverride;
appearanceDidChange();
#else
UNUSED_PARAM(valueOverride);
#endif
}
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);
});
}
void Page::setFullscreenControlsHidden(bool hidden)
{
#if ENABLE(FULLSCREEN_API)
forEachDocument([&] (Document& document) {
document.fullscreenManager().setFullscreenControlsHidden(hidden);
});
#else
UNUSED_PARAM(hidden);
#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
}
void Page::didChangeMainDocument()
{
#if ENABLE(WEB_RTC)
m_rtcController.reset(m_shouldEnableICECandidateFilteringByDefault);
#endif
m_pointerCaptureController->reset();
if (m_sampledPageTopColor) {
m_sampledPageTopColor = std::nullopt;
chrome().client().sampledPageTopColorChanged();
}
}
RenderingUpdateScheduler& Page::renderingUpdateScheduler()
{
if (!m_renderingUpdateScheduler)
m_renderingUpdateScheduler = RenderingUpdateScheduler::create(*this);
return *m_renderingUpdateScheduler;
}
void Page::forEachDocumentFromMainFrame(const Frame& mainFrame, const Function<void(Document&)>& functor)
{
Vector<Ref<Document>> documents;
for (auto* frame = &mainFrame; frame; frame = frame->tree().traverseNext()) {
auto* document = frame->document();
if (!document)
continue;
documents.append(*document);
}
for (auto& document : documents)
functor(document);
}
void Page::forEachDocument(const Function<void(Document&)>& functor) const
{
forEachDocumentFromMainFrame(mainFrame(), functor);
}
void Page::forEachMediaElement(const Function<void(HTMLMediaElement&)>& functor)
{
#if ENABLE(VIDEO)
forEachDocument([&] (Document& document) {
document.forEachMediaElement(functor);
});
#else
UNUSED_PARAM(functor);
#endif
}
void Page::forEachFrame(const Function<void(Frame&)>& functor)
{
Vector<Ref<Frame>> frames;
for (auto* frame = &mainFrame(); frame; frame = frame->tree().traverseNext())
frames.append(*frame);
for (auto& frame : frames)
functor(frame);
}
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());
}
void Page::applicationWillResignActive()
{
#if ENABLE(VIDEO)
forEachMediaElement([] (HTMLMediaElement& element) {
element.applicationWillResignActive();
});
#endif
}
void Page::applicationDidEnterBackground()
{
m_libWebRTCProvider->setActive(false);
}
void Page::applicationWillEnterForeground()
{
m_libWebRTCProvider->setActive(true);
}
void Page::applicationDidBecomeActive()
{
#if ENABLE(VIDEO)
forEachMediaElement([] (HTMLMediaElement& element) {
element.applicationDidBecomeActive();
});
#endif
}
#if ENABLE(WHEEL_EVENT_LATCHING)
ScrollLatchingController& Page::scrollLatchingController()
{
if (!m_scrollLatchingController)
m_scrollLatchingController = makeUnique<ScrollLatchingController>();
return *m_scrollLatchingController;
}
ScrollLatchingController* Page::scrollLatchingControllerIfExists()
{
return m_scrollLatchingController.get();
}
#endif // ENABLE(WHEEL_EVENT_LATCHING)
enum class DispatchedOnDocumentEventLoop : bool { No, Yes };
static void dispatchPrintEvent(Frame& mainFrame, const AtomString& eventType, DispatchedOnDocumentEventLoop dispatchedOnDocumentEventLoop)
{
Vector<Ref<Frame>> frames;
for (auto* frame = &mainFrame; frame; frame = frame->tree().traverseNext())
frames.append(*frame);
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->document());
};
if (dispatchedOnDocumentEventLoop == DispatchedOnDocumentEventLoop::No)
return dispatchEvent();
if (auto* document = frame->document())
document->eventLoop().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(std::unique_ptr<PaymentCoordinator>&& paymentCoordinator)
{
m_paymentCoordinator = WTFMove(paymentCoordinator);
}
#endif
#if ENABLE(APPLE_PAY_AMS_UI)
bool Page::startApplePayAMSUISession(Document& document, ApplePayAMSUIPaymentHandler& paymentHandler, const ApplePayAMSUIRequest& request)
{
if (hasActiveApplePayAMSUISession())
return false;
m_activeApplePayAMSUIPaymentHandler = &paymentHandler;
chrome().client().startApplePayAMSUISession(document.url(), request, [weakThis = WeakPtr { *this }, paymentHandlerPtr = &paymentHandler] (std::optional<bool>&& result) {
auto strongThis = weakThis.get();
if (!strongThis)
return;
if (paymentHandlerPtr != strongThis->m_activeApplePayAMSUIPaymentHandler)
return;
if (auto activePaymentHandler = std::exchange(strongThis->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 ENABLE(MEDIA_SESSION_COORDINATOR)
void Page::setMediaSessionCoordinator(Ref<MediaSessionCoordinatorPrivate>&& mediaSessionCoordinator)
{
m_mediaSessionCoordinator = WTFMove(mediaSessionCoordinator);
auto* window = mainFrame().window();
if (auto* navigator = window ? window->optionalNavigator() : nullptr)
NavigatorMediaSession::mediaSession(*navigator).coordinator().setMediaSessionCoordinatorPrivate(*m_mediaSessionCoordinator);
}
void Page::invalidateMediaSessionCoordinator()
{
m_mediaSessionCoordinator = nullptr;
auto* window = mainFrame().window();
if (!window)
return;
auto* 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)
if (channel == &LogWebRTC && m_mainFrame->document() && !sessionID().isEphemeral())
libWebRTCProvider().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.document().eventLoop().queueTask(TaskSource::Networking, [element = Ref { element }]() {
RefPtr frame = element->document().frame();
if (!frame)
return;
frame->editor().revealSelectionIfNeededAfterLoadingImageForElement(element);
if (element->document().frame() != frame)
return;
if (auto* page = frame->page()) {
#if ENABLE(IMAGE_ANALYSIS)
if (auto* queue = page->imageAnalysisQueueIfExists())
queue->enqueueIfNeeded(element);
#endif
page->chrome().client().didFinishLoadingImageForElement(element);
}
});
}
#if ENABLE(TEXT_AUTOSIZING)
void Page::recomputeTextAutoSizingInAllFrames()
{
ASSERT(settings().textAutosizingEnabled() && settings().textAutosizingUsesIdempotentMode());
forEachDocument([] (Document& document) {
if (auto* 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 (auto* element = renderer.element()) {
if (auto adjustment = Style::Adjuster::adjustmentForTextAutosizing(renderer.style(), *element)) {
auto newStyle = RenderStyle::clone(renderer.style());
Style::Adjuster::adjustForTextAutosizing(newStyle, *element, adjustment);
renderer.setStyle(WTFMove(newStyle));
}
}
}
}
});
}
#endif
bool Page::acceleratedFiltersEnabled() const
{
#if USE(CORE_IMAGE)
return settings().acceleratedFiltersEnabled();
#else
return false;
#endif
}
bool Page::shouldDisableCorsForRequestTo(const URL& url) const
{
return WTF::anyOf(m_corsDisablingPatterns, [&] (const auto& pattern) {
return pattern.matches(url);
});
}
void Page::revealCurrentSelection()
{
CheckedRef(focusController())->focusedOrMainFrame().selection().revealSelection(SelectionRevealMode::Reveal, ScrollAlignment::alignCenterIfNeeded);
}
void Page::injectUserStyleSheet(UserStyleSheet& userStyleSheet)
{
#if ENABLE(APP_BOUND_DOMAINS)
if (m_mainFrame->loader().client().shouldEnableInAppBrowserPrivacyProtections()) {
if (auto* document = m_mainFrame->document())
document->addConsoleMessage(MessageSource::Security, MessageLevel::Warning, "Ignoring user style sheet for non-app bound domain."_s);
return;
}
m_mainFrame->loader().client().notifyPageOfAppBoundBehavior();
#endif
// We need to wait until we're no longer displaying the initial empty document before we can inject the stylesheets.
if (m_mainFrame->loader().stateMachine().isDisplayingInitialEmptyDocument()) {
m_userStyleSheetsPendingInjection.append(userStyleSheet);
return;
}
if (userStyleSheet.injectedFrames() == UserContentInjectedFrames::InjectInTopFrameOnly) {
if (auto* document = m_mainFrame->document())
document->extensionStyleSheets().injectPageSpecificUserStyleSheet(userStyleSheet);
} else {
forEachDocument([&] (Document& document) {
document.extensionStyleSheets().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) {
if (auto* document = m_mainFrame->document())
document->extensionStyleSheets().removePageSpecificUserStyleSheet(userStyleSheet);
} else {
forEachDocument([&] (Document& document) {
document.extensionStyleSheets().removePageSpecificUserStyleSheet(userStyleSheet);
});
}
}
void Page::mainFrameDidChangeToNonInitialEmptyDocument()
{
ASSERT(!m_mainFrame->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::FlushAutofocusCandidates: ts << "FlushAutofocusCandidates"; break;
case RenderingUpdateStep::Resize: ts << "Resize"; break;
case RenderingUpdateStep::Scroll: ts << "Scroll"; break;
case RenderingUpdateStep::MediaQueryEvaluation: ts << "MediaQueryEvaluation"; break;
case RenderingUpdateStep::Animations: ts << "Animations"; break;
case RenderingUpdateStep::Fullscreen: ts << "Fullscreen"; break;
case RenderingUpdateStep::AnimationFrameCallbacks: ts << "AnimationFrameCallbacks"; break;
case RenderingUpdateStep::IntersectionObservations: ts << "IntersectionObservations"; break;
case RenderingUpdateStep::ResizeObservations: ts << "ResizeObservations"; break;
case RenderingUpdateStep::Images: ts << "Images"; break;
case RenderingUpdateStep::WheelEventMonitorCallbacks: ts << "WheelEventMonitorCallbacks"; break;
case RenderingUpdateStep::CursorUpdate: ts << "CursorUpdate"; break;
case RenderingUpdateStep::EventRegionUpdate: ts << "EventRegionUpdate"; break;
case RenderingUpdateStep::LayerFlush: ts << "LayerFlush"; break;
#if ENABLE(ASYNC_SCROLLING)
case RenderingUpdateStep::ScrollingTreeUpdate: ts << "ScrollingTreeUpdate"; break;
#endif
case RenderingUpdateStep::VideoFrameCallbacks: ts << "VideoFrameCallbacks"; break;
case RenderingUpdateStep::PrepareCanvasesForDisplay: ts << "PrepareCanvasesForDisplay"; break;
}
return ts;
}
ImageOverlayController& Page::imageOverlayController()
{
if (!m_imageOverlayController)
m_imageOverlayController = makeUnique<ImageOverlayController>(*this);
return *m_imageOverlayController;
}
#if ENABLE(IMAGE_ANALYSIS)
ImageAnalysisQueue& Page::imageAnalysisQueue()
{
if (!m_imageAnalysisQueue)
m_imageAnalysisQueue = makeUnique<ImageAnalysisQueue>(*this);
return *m_imageAnalysisQueue;
}
void Page::resetImageAnalysisQueue()
{
if (auto 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->document().eventLoop().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)
#if ENABLE(SERVICE_WORKER)
JSC::JSGlobalObject* Page::serviceWorkerGlobalObject(DOMWrapperWorld& world)
{
if (!m_serviceWorkerGlobalScope)
return nullptr;
auto scriptController = m_serviceWorkerGlobalScope->script();
if (!scriptController)
return nullptr;
// FIXME: We currently do not support non-normal worlds in service workers.
RELEASE_ASSERT(&static_cast<JSVMClientData*>(m_serviceWorkerGlobalScope->vm().clientData)->normalWorld() == &world);
return scriptController->globalScopeWrapper();
}
void Page::setServiceWorkerGlobalScope(ServiceWorkerGlobalScope& serviceWorkerGlobalScope)
{
ASSERT(isMainThread());
ASSERT(m_isServiceWorkerPage);
m_serviceWorkerGlobalScope = serviceWorkerGlobalScope;
}
#endif
PermissionController& Page::permissionController()
{
return m_permissionController.get();
}
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)
{
mainFrame().loader().initForSynthesizedDocument({ });
auto document = Document::createNonRenderedPlaceholder(mainFrame(), scriptURL);
document->createDOMWindow();
document->storageBlockingStateDidChange();
auto origin = topOrigin.securityOrigin();
auto originAsURL = origin->toURL();
document->setSiteForCookies(originAsURL);
document->setFirstPartyForCookies(originAsURL);
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);
mainFrame().setDocument(WTFMove(document));
}
void Page::forceRepaintAllFrames()
{
for (Frame* frame = &mainFrame(); frame; frame = frame->tree().traverseNext()) {
FrameView* frameView = frame->view();
if (!frameView || !frameView->renderView())
continue;
frameView->renderView()->repaintViewAndCompositedLayers();
}
}
} // namespace WebCore