blob: 507a8a14568d433bb71e0954e99ced83aa199e6a [file] [log] [blame]
/*
* Copyright (C) 1998, 1999 Torben Weis <weis@kde.org>
* 1999 Lars Knoll <knoll@kde.org>
* 1999 Antti Koivisto <koivisto@kde.org>
* 2000 Simon Hausmann <hausmann@kde.org>
* 2000 Stefan Schimanski <1Stein@gmx.de>
* 2001 George Staikos <staikos@kde.org>
* Copyright (C) 2004-2025 Apple Inc. All rights reserved.
* Copyright (C) 2005 Alexey Proskuryakov <ap@nypop.com>
* Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies)
* Copyright (C) 2008 Eric Seidel <eric@webkit.org>
* Copyright (C) 2008 Google Inc.
*
* 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 "LocalFrame.h"
#include "AdjustViewSize.h"
#include "AnimationTimelinesController.h"
#include "ApplyStyleCommand.h"
#include "BackForwardCache.h"
#include "BackForwardController.h"
#include "CSSComputedStyleDeclaration.h"
#include "CSSPropertyNames.h"
#include "CSSValuePool.h"
#include "CachedCSSStyleSheet.h"
#include "CachedResourceLoader.h"
#include "Chrome.h"
#include "ChromeClient.h"
#include "DiagnosticLoggingClient.h"
#include "DiagnosticLoggingKeys.h"
#include "DocumentLoader.h"
#include "DocumentType.h"
#include "Editing.h"
#include "Editor.h"
#include "EditorClient.h"
#include "ElementInlines.h"
#include "Event.h"
#include "EventHandler.h"
#include "EventNames.h"
#include "FloatQuad.h"
#include "FocusController.h"
#include "FrameDestructionObserver.h"
#include "FrameLoader.h"
#include "FrameSelection.h"
#include "GraphicsContext.h"
#include "GraphicsLayer.h"
#include "HTMLAttachmentElement.h"
#include "HTMLFormControlElement.h"
#include "HTMLFormElement.h"
#include "HTMLFrameElementBase.h"
#include "HTMLIFrameElement.h"
#include "HTMLNames.h"
#include "HTMLTableCellElement.h"
#include "HTMLTableRowElement.h"
#include "HitTestResult.h"
#include "ImageBuffer.h"
#include "InspectorInstrumentation.h"
#include "JSDOMWindow.h"
#include "JSNode.h"
#include "JSServiceWorkerGlobalScope.h"
#include "JSWindowProxy.h"
#include "LocalDOMWindow.h"
#include "LocalFrameLoaderClient.h"
#include "LocalFrameView.h"
#include "LocalizedStrings.h"
#include "Logging.h"
#include "Navigator.h"
#include "NodeList.h"
#include "NodeTraversal.h"
#include "Page.h"
#include "PaymentSession.h"
#include "ProcessSyncClient.h"
#include "ProcessWarming.h"
#include "RemoteFrame.h"
#include "RenderLayerCompositor.h"
#include "RenderTableCell.h"
#include "RenderText.h"
#include "RenderTextControl.h"
#include "RenderTheme.h"
#include "RenderView.h"
#include "RenderWidgetInlines.h"
#include "ResourceMonitor.h"
#include "SVGDocument.h"
#include "SVGDocumentExtensions.h"
#include "SVGElementTypeHelpers.h"
#include "ScriptController.h"
#include "ScriptSourceCode.h"
#include "ScrollingCoordinator.h"
#include "ServiceWorkerGlobalScope.h"
#include "Settings.h"
#include "StyleProperties.h"
#include "StyleScope.h"
#include "TextNodeTraversal.h"
#include "TextResourceDecoder.h"
#include "UserContentController.h"
#include "UserContentURLPattern.h"
#include "UserGestureIndicator.h"
#include "UserScript.h"
#include "UserTypingGestureIndicator.h"
#include "VisibleUnits.h"
#include "markup.h"
#include "runtime_root.h"
#include <JavaScriptCore/APICast.h>
#include <JavaScriptCore/RegularExpression.h>
#include <wtf/HexNumber.h>
#include <wtf/RefCountedLeakCounter.h>
#include <wtf/StdLibExtras.h>
#include <wtf/text/MakeString.h>
#include <wtf/text/StringBuilder.h>
#include <wtf/text/TextStream.h>
#if ENABLE(DATA_DETECTION)
#include "DataDetectionResultsStorage.h"
#endif
#if ENABLE(CONTENT_EXTENSIONS) && USE(APPLE_INTERNAL_SDK) && __has_include(<WebKitAdditions/LocalFrameAdditions.h>)
#include <WebKitAdditions/LocalFrameAdditions.h>
#endif
#define FRAME_RELEASE_LOG(channel, fmt, ...) RELEASE_LOG(channel, "%p - Frame::" fmt, this, ##__VA_ARGS__)
#define FRAME_RELEASE_LOG_ERROR(channel, fmt, ...) RELEASE_LOG_ERROR(channel, "%p - Frame::" fmt, this, ##__VA_ARGS__)
namespace WebCore {
using namespace HTMLNames;
#if PLATFORM(IOS_FAMILY)
static const Seconds scrollFrequency { 1000_s / 60. };
#endif
DEFINE_DEBUG_ONLY_GLOBAL(WTF::RefCountedLeakCounter, frameCounter, ("Frame"));
struct OverrideScreenSize {
WTF_DEPRECATED_MAKE_STRUCT_FAST_ALLOCATED(OverrideScreenSize);
FloatSize size;
};
static inline float parentPageZoomFactor(LocalFrame* frame)
{
SUPPRESS_UNCOUNTED_LOCAL auto* parent = dynamicDowncast<LocalFrame>(frame->tree().parent());
if (!parent)
return 1;
return parent->pageZoomFactor();
}
static inline float parentTextZoomFactor(LocalFrame* frame)
{
SUPPRESS_UNCOUNTED_LOCAL auto* parent = dynamicDowncast<LocalFrame>(frame->tree().parent());
if (!parent)
return 1;
return parent->textZoomFactor();
}
static const LocalFrame& rootFrame(const LocalFrame& frame, Frame* parent)
{
SUPPRESS_UNCOUNTED_LOCAL auto* localParent = dynamicDowncast<LocalFrame>(parent);
if (localParent)
return localParent->rootFrame();
ASSERT(is<RemoteFrame>(parent) || frame.isMainFrame());
return frame;
}
LocalFrame::LocalFrame(Page& page, ClientCreator&& clientCreator, FrameIdentifier identifier, SandboxFlags sandboxFlags, std::optional<ScrollbarMode> scrollingMode, HTMLFrameOwnerElement* ownerElement, Frame* parent, Frame* opener, Ref<FrameTreeSyncData>&& frameTreeSyncData, AddToFrameTree addToFrameTree)
: Frame(page, identifier, FrameType::Local, ownerElement, parent, opener, WTFMove(frameTreeSyncData), addToFrameTree)
, m_loader(makeUniqueRefWithoutRefCountedCheck<FrameLoader>(*this, WTFMove(clientCreator)))
, m_script(makeUniqueRef<ScriptController>(*this))
#if PLATFORM(IOS_FAMILY)
, m_viewportArguments(makeUniqueRef<ViewportArguments>())
, m_rangedSelectionBase(makeUniqueRef<VisibleSelection>())
, m_rangedSelectionInitialExtent(makeUniqueRef<VisibleSelection>())
#endif
, m_pageZoomFactor(parentPageZoomFactor(this))
, m_textZoomFactor(parentTextZoomFactor(this))
, m_rootFrame(WebCore::rootFrame(*this, parent))
, m_sandboxFlags(sandboxFlags)
, m_eventHandler(makeUniqueRef<EventHandler>(*this))
{
ProcessWarming::initializeNames();
StaticCSSValuePool::init();
if (RefPtr localMainFrame = this->localMainFrame(); localMainFrame && parent)
localMainFrame->selfOnlyRef();
#ifndef NDEBUG
frameCounter.increment();
#endif
ASSERT(scrollingMode.has_value() ^ !!ownerElement);
m_scrollingMode = scrollingMode ? *scrollingMode : ownerElement->scrollingMode();
// Pause future ActiveDOMObjects if this frame is being created while the page is in a paused state.
if (RefPtr parent = dynamicDowncast<LocalFrame>(tree().parent()); parent && parent->activeDOMObjectsAndAnimationsSuspended())
suspendActiveDOMObjectsAndAnimations();
if (isRootFrame())
page.addRootFrame(*this);
ASSERT(isRootFrameIdentifier(frameID()) == isRootFrame());
}
void LocalFrame::init()
{
loader().init();
}
Ref<LocalFrame> LocalFrame::createMainFrame(Page& page, ClientCreator&& clientCreator, FrameIdentifier identifier, SandboxFlags effectiveSandboxFlags, Frame* opener, Ref<FrameTreeSyncData>&& frameTreeSyncData)
{
return adoptRef(*new LocalFrame(page, WTFMove(clientCreator), identifier, effectiveSandboxFlags, ScrollbarMode::Auto, nullptr, nullptr, opener, WTFMove(frameTreeSyncData)));
}
Ref<LocalFrame> LocalFrame::createSubframe(Page& page, ClientCreator&& clientCreator, FrameIdentifier identifier, SandboxFlags effectiveSandboxFlags, HTMLFrameOwnerElement& ownerElement, Ref<FrameTreeSyncData>&& frameTreeSyncData)
{
return adoptRef(*new LocalFrame(page, WTFMove(clientCreator), identifier, effectiveSandboxFlags, std::nullopt, &ownerElement, ownerElement.document().frame(), nullptr, WTFMove(frameTreeSyncData)));
}
Ref<LocalFrame> LocalFrame::createProvisionalSubframe(Page& page, ClientCreator&& clientCreator, FrameIdentifier identifier, SandboxFlags effectiveSandboxFlags, ScrollbarMode scrollingMode, Frame& parent, Ref<FrameTreeSyncData>&& frameTreeSyncData)
{
return adoptRef(*new LocalFrame(page, WTFMove(clientCreator), identifier, effectiveSandboxFlags, scrollingMode, nullptr, &parent, nullptr, WTFMove(frameTreeSyncData), AddToFrameTree::No));
}
LocalFrame::~LocalFrame()
{
setView(nullptr);
Ref loader = this->loader();
if (!loader->isComplete())
loader->closeURL();
loader->clear(protectedDocument(), false);
checkedScript()->updatePlatformScriptObjects();
// FIXME: We should not be doing all this work inside the destructor
#ifndef NDEBUG
frameCounter.decrement();
#endif
disconnectOwnerElement();
while (auto* destructionObserver = m_destructionObservers.takeAny())
destructionObserver->frameDestroyed();
RefPtr localMainFrame = this->localMainFrame();
if (!isMainFrame() && localMainFrame)
localMainFrame->selfOnlyDeref();
detachFromPage();
}
RefPtr<const LocalFrame> LocalFrame::localMainFrame() const
{
return dynamicDowncast<const LocalFrame>(mainFrame());
}
RefPtr<LocalFrame> LocalFrame::localMainFrame()
{
return dynamicDowncast<LocalFrame>(mainFrame());
}
void LocalFrame::addDestructionObserver(FrameDestructionObserver& observer)
{
m_destructionObservers.add(observer);
}
void LocalFrame::removeDestructionObserver(FrameDestructionObserver& observer)
{
m_destructionObservers.remove(observer);
}
void LocalFrame::setView(RefPtr<LocalFrameView>&& view)
{
// We the custom scroll bars as early as possible to prevent m_doc->detach()
// from messing with the view such that its scroll bars won't be torn down.
// FIXME: We should revisit this.
if (RefPtr view = m_view)
view->prepareForDetach();
// Prepare for destruction now, so any unload event handlers get run and the LocalDOMWindow is
// notified. If we wait until the view is destroyed, then things won't be hooked up enough for
// these calls to work.
if (!view && m_doc && m_doc->backForwardCacheState() != Document::InBackForwardCache)
protectedDocument()->willBeRemovedFromFrame();
if (RefPtr view = m_view)
view->checkedLayoutContext()->unscheduleLayout();
m_eventHandler->clear();
RELEASE_ASSERT(!m_doc || !m_doc->hasLivingRenderTree());
m_view = WTFMove(view);
// Only one form submission is allowed per view of a part.
// Since this part may be getting reused as a result of being
// pulled from the back/forward cache, reset this flag.
loader().resetMultipleFormSubmissionProtection();
}
void LocalFrame::setDocument(RefPtr<Document>&& newDocument)
{
ASSERT(!newDocument || newDocument->frame() == this);
if (m_documentIsBeingReplaced)
return;
m_documentIsBeingReplaced = true;
if (isMainFrame()) {
if (RefPtr page = this->page())
page->didChangeMainDocument(newDocument.get());
loader().client().dispatchDidChangeMainDocument();
}
if (RefPtr previousDocument = m_doc) {
#if ENABLE(ATTACHMENT_ELEMENT)
for (Ref attachment : previousDocument->attachmentElementsByIdentifier().values())
protectedEditor()->didRemoveAttachmentElement(attachment);
#endif
if (previousDocument->backForwardCacheState() != Document::InBackForwardCache)
previousDocument->willBeRemovedFromFrame();
}
m_doc = newDocument.copyRef();
ASSERT(!m_doc || m_doc->window());
ASSERT(!m_doc || m_doc->window()->frame() == this);
// Don't use m_doc because it can be overwritten and we want to guarantee
// that the document is not destroyed during this function call.
if (newDocument)
newDocument->didBecomeCurrentDocumentInFrame();
#if ENABLE(ATTACHMENT_ELEMENT)
if (RefPtr document = m_doc) {
Ref editor = this->editor();
for (Ref attachment : document->attachmentElementsByIdentifier().values())
editor->didInsertAttachmentElement(attachment);
}
#endif
if (RefPtr page = this->page(); page && isMainFrame()) {
if (m_doc && !loader().stateMachine().isDisplayingInitialEmptyDocument())
page->mainFrameDidChangeToNonInitialEmptyDocument();
page->clearAXObjectCache();
}
InspectorInstrumentation::frameDocumentUpdated(*this);
#if ENABLE(WINDOW_PROXY_PROPERTY_ACCESS_NOTIFICATION)
m_accessedWindowProxyPropertiesViaOpener = { };
#endif
m_documentIsBeingReplaced = false;
}
void LocalFrame::frameDetached()
{
loader().frameDetached();
}
bool LocalFrame::preventsParentFromBeingComplete() const
{
return !loader().isComplete() && (!ownerElement() || !ownerElement()->isLazyLoadObserverActive());
}
void LocalFrame::changeLocation(FrameLoadRequest&& request)
{
loader().changeLocation(WTFMove(request));
}
void LocalFrame::didFinishLoadInAnotherProcess()
{
loader().provisionalLoadFailedInAnotherProcess();
}
void LocalFrame::invalidateContentEventRegionsIfNeeded(InvalidateContentEventRegionsReason reason)
{
if (!page() || !m_doc || !m_doc->renderView())
return;
bool needsUpdateForTouchEventHandlers = false;
bool needsUpdateForWheelEventHandlers = false;
bool needsUpdateForTouchActionElements = false;
bool needsUpdateForEditableElements = false;
bool needsUpdateForInteractionRegions = false;
#if ENABLE(WHEEL_EVENT_REGIONS)
needsUpdateForWheelEventHandlers = m_doc->hasWheelEventHandlers() || reason == InvalidateContentEventRegionsReason::EventHandlerChange;
#else
UNUSED_PARAM(reason);
#endif
#if ENABLE(TOUCH_EVENT_REGIONS)
needsUpdateForTouchEventHandlers = m_doc->hasTouchEventHandlers() || reason == InvalidateContentEventRegionsReason::EventHandlerChange;
#else
UNUSED_PARAM(reason);
#endif
#if ENABLE(TOUCH_ACTION_REGIONS)
// Document::mayHaveElementsWithNonAutoTouchAction never changes from true to false currently.
needsUpdateForTouchActionElements = m_doc->mayHaveElementsWithNonAutoTouchAction();
#endif
#if ENABLE(EDITABLE_REGION)
// Document::mayHaveEditableElements never changes from true to false currently.
needsUpdateForEditableElements = m_doc->mayHaveEditableElements() && page()->shouldBuildEditableRegion();
#endif
#if ENABLE(INTERACTION_REGIONS_IN_EVENT_REGION)
needsUpdateForInteractionRegions = page()->shouldBuildInteractionRegions();
#endif
if (!needsUpdateForTouchActionElements && !needsUpdateForEditableElements && !needsUpdateForWheelEventHandlers && !needsUpdateForInteractionRegions && !needsUpdateForTouchEventHandlers)
return;
if (!m_doc->renderView()->compositor().viewNeedsToInvalidateEventRegionOfEnclosingCompositingLayerForRepaint())
return;
if (RefPtr ownerElement = this->ownerElement())
ownerElement->protectedDocument()->invalidateEventRegionsForFrame(*ownerElement);
}
#if ENABLE(ORIENTATION_EVENTS)
void LocalFrame::orientationChanged()
{
Page::forEachDocumentFromMainFrame(*this, [newOrientation = orientation()] (Document& document) {
document.orientationChanged(newOrientation);
});
}
IntDegrees LocalFrame::orientation() const
{
if (RefPtr page = this->page())
return page->chrome().client().deviceOrientation();
return 0;
}
#endif // ENABLE(ORIENTATION_EVENTS)
static JSC::Yarr::RegularExpression createRegExpForLabels(const Vector<String>& labels)
{
// REVIEW- version of this call in FrameMac.mm caches based on the NSArray ptrs being
// the same across calls. We can't do that.
static NeverDestroyed<JSC::Yarr::RegularExpression> wordRegExp("\\w"_s);
StringBuilder pattern;
pattern.append('(');
for (unsigned i = 0, numLabels = labels.size(); i < numLabels; i++) {
auto& label = labels[i];
bool startsWithWordCharacter = false;
bool endsWithWordCharacter = false;
if (label.length()) {
StringView labelView { label };
startsWithWordCharacter = wordRegExp.get().match(labelView.left(1)) >= 0;
endsWithWordCharacter = wordRegExp.get().match(labelView.right(1)) >= 0;
}
// Search for word boundaries only if label starts/ends with "word characters".
// If we always searched for word boundaries, this wouldn't work for languages such as Japanese.
pattern.append(i ? "|"_s : ""_s, startsWithWordCharacter ? "\\b"_s : ""_s, label, endsWithWordCharacter ? "\\b"_s : ""_s);
}
pattern.append(')');
return JSC::Yarr::RegularExpression(pattern.toString(), { JSC::Yarr::Flags::IgnoreCase });
}
String LocalFrame::searchForLabelsAboveCell(const JSC::Yarr::RegularExpression& regExp, HTMLTableCellElement* cell, size_t* resultDistanceFromStartOfCell)
{
if (RefPtr aboveCell = cell->cellAbove()) {
// search within the above cell we found for a match
size_t lengthSearched = 0;
for (RefPtr textNode = TextNodeTraversal::firstWithin(*aboveCell); textNode; textNode = TextNodeTraversal::next(*textNode, aboveCell.get())) {
if (!textNode->renderer() || textNode->renderer()->style().usedVisibility() != Visibility::Visible)
continue;
// For each text chunk, run the regexp
String nodeString = textNode->data();
int pos = regExp.searchRev(nodeString);
if (pos >= 0) {
if (resultDistanceFromStartOfCell)
*resultDistanceFromStartOfCell = lengthSearched;
return nodeString.substring(pos, regExp.matchedLength());
}
lengthSearched += nodeString.length();
}
}
// Any reason in practice to search all cells in that are above cell?
if (resultDistanceFromStartOfCell)
*resultDistanceFromStartOfCell = notFound;
return String();
}
// FIXME: This should take an Element&.
String LocalFrame::searchForLabelsBeforeElement(const Vector<String>& labels, Element* element, size_t* resultDistance, bool* resultIsInCellAbove)
{
ASSERT(element);
JSC::Yarr::RegularExpression regExp = createRegExpForLabels(labels);
// We stop searching after we've seen this many chars
const unsigned int charsSearchedThreshold = 500;
// This is the absolute max we search. We allow a little more slop than
// charsSearchedThreshold, to make it more likely that we'll search whole nodes.
const unsigned int maxCharsSearched = 600;
// If the starting element is within a table, the cell that contains it
RefPtr<HTMLTableCellElement> startingTableCell;
bool searchedCellAbove = false;
if (resultDistance)
*resultDistance = notFound;
if (resultIsInCellAbove)
*resultIsInCellAbove = false;
// walk backwards in the node tree, until another element, or form, or end of tree
int unsigned lengthSearched = 0;
RefPtr<Node> n;
for (n = NodeTraversal::previous(*element); n && lengthSearched < charsSearchedThreshold; n = NodeTraversal::previous(*n)) {
// We hit another form element or the start of the form - bail out
if (is<HTMLFormElement>(*n))
break;
if (RefPtr element = dynamicDowncast<Element>(*n); element && element->isValidatedFormListedElement())
break;
if (n->hasTagName(tdTag) && !startingTableCell)
startingTableCell = downcast<HTMLTableCellElement>(n);
else if (is<HTMLTableRowElement>(*n) && startingTableCell) {
String result = searchForLabelsAboveCell(regExp, startingTableCell.get(), resultDistance);
if (!result.isEmpty()) {
if (resultIsInCellAbove)
*resultIsInCellAbove = true;
return result;
}
searchedCellAbove = true;
} else if (CheckedPtr renderText = dynamicDowncast<RenderText>(n->renderer()); renderText && renderText->style().usedVisibility() == Visibility::Visible) {
// For each text chunk, run the regexp
String nodeString = n->nodeValue();
// add 100 for slop, to make it more likely that we'll search whole nodes
if (lengthSearched + nodeString.length() > maxCharsSearched)
nodeString = nodeString.right(charsSearchedThreshold - lengthSearched);
int pos = regExp.searchRev(nodeString);
if (pos >= 0) {
if (resultDistance)
*resultDistance = lengthSearched;
return nodeString.substring(pos, regExp.matchedLength());
}
lengthSearched += nodeString.length();
}
}
// If we started in a cell, but bailed because we found the start of the form or the
// previous element, we still might need to search the row above us for a label.
if (startingTableCell && !searchedCellAbove) {
String result = searchForLabelsAboveCell(regExp, startingTableCell.get(), resultDistance);
if (!result.isEmpty()) {
if (resultIsInCellAbove)
*resultIsInCellAbove = true;
return result;
}
}
return String();
}
static String matchLabelsAgainstString(const Vector<String>& labels, const String& stringToMatch)
{
if (stringToMatch.isEmpty())
return String();
String mutableStringToMatch = stringToMatch;
// Make numbers and _'s in field names behave like word boundaries, e.g., "address2"
replace(mutableStringToMatch, JSC::Yarr::RegularExpression("\\d"_s), " "_s);
mutableStringToMatch = makeStringByReplacingAll(mutableStringToMatch, '_', ' ');
JSC::Yarr::RegularExpression regExp = createRegExpForLabels(labels);
// Use the largest match we can find in the whole string
int pos;
int length;
int bestPos = -1;
int bestLength = -1;
int start = 0;
do {
pos = regExp.match(mutableStringToMatch, start);
if (pos != -1) {
length = regExp.matchedLength();
if (length >= bestLength) {
bestPos = pos;
bestLength = length;
}
start = pos + 1;
}
} while (pos != -1);
if (bestPos != -1)
return mutableStringToMatch.substring(bestPos, bestLength);
return String();
}
String LocalFrame::matchLabelsAgainstElement(const Vector<String>& labels, Element* element)
{
// Match against the name element, then against the id element if no match is found for the name element.
// See 7538330 for one popular site that benefits from the id element check.
// FIXME: This code is mirrored in FrameMac.mm. It would be nice to make the Mac code call the platform-agnostic
// code, which would require converting the NSArray of NSStrings to a Vector of Strings somewhere along the way.
String resultFromNameAttribute = matchLabelsAgainstString(labels, element->getNameAttribute());
if (!resultFromNameAttribute.isEmpty())
return resultFromNameAttribute;
return matchLabelsAgainstString(labels, element->attributeWithoutSynchronization(idAttr));
}
#if PLATFORM(IOS_FAMILY)
void LocalFrame::setSelectionChangeCallbacksDisabled(bool selectionChangeCallbacksDisabled)
{
m_selectionChangeCallbacksDisabled = selectionChangeCallbacksDisabled;
}
bool LocalFrame::selectionChangeCallbacksDisabled() const
{
return m_selectionChangeCallbacksDisabled;
}
#endif // PLATFORM(IOS_FAMILY)
bool LocalFrame::requestDOMPasteAccess(DOMPasteAccessCategory pasteAccessCategory)
{
if (settings().javaScriptCanAccessClipboard() && settings().domPasteAllowed())
return true;
if (!m_doc)
return false;
if (editor().isPastingFromMenuOrKeyBinding())
return true;
if (!settings().domPasteAccessRequestsEnabled())
return false;
auto gestureToken = UserGestureIndicator::currentUserGesture();
if (!gestureToken || !gestureToken->processingUserGesture() || !gestureToken->canRequestDOMPaste())
return false;
switch (gestureToken->domPasteAccessPolicy()) {
case DOMPasteAccessPolicy::Granted:
return true;
case DOMPasteAccessPolicy::Denied:
return false;
case DOMPasteAccessPolicy::NotRequestedYet: {
auto* client = editor().client();
if (!client)
return false;
auto response = client->requestDOMPasteAccess(pasteAccessCategory, frameID(), m_doc->originIdentifierForPasteboard());
gestureToken->didRequestDOMPasteAccess(response);
switch (response) {
case DOMPasteAccessResponse::GrantedForCommand:
case DOMPasteAccessResponse::GrantedForGesture:
return true;
case DOMPasteAccessResponse::DeniedForGesture:
return false;
}
}
}
ASSERT_NOT_REACHED();
return false;
}
void LocalFrame::setPrinting(bool printing, const FloatSize& pageSize, const FloatSize& originalPageSize, float maximumShrinkRatio, AdjustViewSize shouldAdjustViewSize)
{
if (!view() || !document())
return;
RefPtr document = m_doc;
// In setting printing, we should not validate resources already cached for the document.
// See https://bugs.webkit.org/show_bug.cgi?id=43704
ResourceCacheValidationSuppressor validationSuppressor(document->cachedResourceLoader());
document->setPrinting(printing);
protectedView()->adjustMediaTypeForPrinting(printing);
// FIXME: Consider invoking Page::updateRendering or an equivalent.
document->styleScope().didChangeStyleSheetEnvironment();
document->evaluateMediaQueriesAndReportChanges();
if (!view())
return;
Ref frameView = *view();
if (shouldUsePrintingLayout())
frameView->forceLayoutForPagination(pageSize, originalPageSize, maximumShrinkRatio, shouldAdjustViewSize);
else {
frameView->forceLayout();
if (shouldAdjustViewSize == AdjustViewSize::Yes)
frameView->adjustViewSize();
}
// Subframes of the one we're printing don't lay out to the page size.
for (RefPtr child = tree().firstChild(); child; child = child->tree().nextSibling()) {
if (RefPtr localFrame = dynamicDowncast<LocalFrame>(child.get()))
localFrame->setPrinting(printing, FloatSize(), FloatSize(), 0, shouldAdjustViewSize);
}
}
bool LocalFrame::shouldUsePrintingLayout() const
{
// Only top frame being printed should be fit to page size.
// Subframes should be constrained by parents only.
SUPPRESS_UNCOUNTED_LOCAL auto* parent = dynamicDowncast<LocalFrame>(tree().parent());
return m_doc->printing() && (!parent || !parent->m_doc->printing());
}
FloatSize LocalFrame::resizePageRectsKeepingRatio(const FloatSize& originalSize, const FloatSize& expectedSize)
{
FloatSize resultSize;
if (!contentRenderer())
return FloatSize();
if (contentRenderer()->writingMode().isHorizontal()) {
ASSERT(std::abs(originalSize.width()) > std::numeric_limits<float>::epsilon());
float ratio = originalSize.height() / originalSize.width();
resultSize.setWidth(floorf(expectedSize.width()));
resultSize.setHeight(floorf(resultSize.width() * ratio));
} else {
ASSERT(std::abs(originalSize.height()) > std::numeric_limits<float>::epsilon());
float ratio = originalSize.width() / originalSize.height();
resultSize.setHeight(floorf(expectedSize.height()));
resultSize.setWidth(floorf(resultSize.height() * ratio));
}
return resultSize;
}
void LocalFrame::injectUserScripts(UserScriptInjectionTime injectionTime)
{
if (!page())
return;
if (loader().stateMachine().creatingInitialEmptyDocument() && !settings().shouldInjectUserScriptsInInitialEmptyDocument())
return;
RefPtr page = this->page();
page->protectedUserContentProvider()->forEachUserScript([this, injectionTime](DOMWrapperWorld& world, const UserScript& script) {
if (script.injectionTime() == injectionTime)
injectUserScriptImmediately(world, script);
});
}
void LocalFrame::injectUserScriptImmediately(DOMWrapperWorld& world, const UserScript& script)
{
Ref loader = this->loader();
#if ENABLE(APP_BOUND_DOMAINS)
if (loader->client().shouldEnableInAppBrowserPrivacyProtections()) {
if (RefPtr document = this->document())
document->addConsoleMessage(MessageSource::Security, MessageLevel::Warning, "Ignoring user script injection for non-app bound domain."_s);
FRAME_RELEASE_LOG_ERROR(Loading, "injectUserScriptImmediately: Ignoring user script injection for non app-bound domain");
return;
}
loader->client().notifyPageOfAppBoundBehavior();
#endif
RefPtr document = this->document();
if (!document)
return;
RefPtr page = document->page();
if (!page)
return;
if (script.injectedFrames() == UserContentInjectedFrames::InjectInTopFrameOnly && !isMainFrame())
return;
auto url = document->url();
if (RefPtr parentDocument = document->parentDocument()) {
switch (script.matchParentFrame()) {
case UserContentMatchParentFrame::ForOpaqueOrigins:
if (url.protocolIsAbout() || url.protocolIsBlob() || url.protocolIsData())
url = parentDocument->url();
break;
case UserContentMatchParentFrame::ForAboutBlank:
if (url.isAboutBlank())
url = parentDocument->url();
break;
case UserContentMatchParentFrame::Never:
break;
}
}
if (!UserContentURLPattern::matchesPatterns(url, script.allowlist(), script.blocklist()))
return;
page->setHasInjectedUserScript();
loader->client().willInjectUserScript(world);
checkedScript()->evaluateInWorldIgnoringException(ScriptSourceCode(script.source(), JSC::SourceTaintedOrigin::Untainted, URL(script.url())), world);
}
RenderView* LocalFrame::contentRenderer() const
{
return document() ? document()->renderView() : nullptr;
}
LocalFrame* LocalFrame::frameForWidget(const Widget& widget)
{
SUPPRESS_UNCOUNTED_LOCAL auto* renderer = RenderWidget::find(widget);
if (renderer)
return renderer->frameOwnerElement().document().frame();
// Assume all widgets are either a FrameView or owned by a RenderWidget.
// FIXME: That assumption is not right for scroll bars!
return &downcast<LocalFrameView>(widget).frame();
}
void LocalFrame::clearTimers(LocalFrameView *view, Document *document)
{
if (!view)
return;
view->checkedLayoutContext()->unscheduleLayout();
if (CheckedPtr timelines = document->timelinesController())
timelines->suspendAnimations();
view->protectedFrame()->eventHandler().stopAutoscrollTimer();
}
void LocalFrame::clearTimers()
{
clearTimers(protectedView().get(), protectedDocument().get());
}
CheckedRef<ScriptController> LocalFrame::checkedScript()
{
return m_script.get();
}
CheckedRef<const ScriptController> LocalFrame::checkedScript() const
{
return m_script.get();
}
void LocalFrame::willDetachPage()
{
if (RefPtr parent = dynamicDowncast<LocalFrame>(tree().parent()))
parent->loader().checkLoadComplete();
for (auto& observer : m_destructionObservers)
observer.willDetachPage();
// FIXME: It's unclear as to why this is called more than once, but it is,
// so page() could be NULL.
if (RefPtr<Page> page = this->page()) {
CheckedRef focusController = page->focusController();
if (focusController->focusedFrame() == this)
focusController->setFocusedFrame(nullptr);
}
CheckedRef script = this->script();
script->clearScriptObjects();
script->updatePlatformScriptObjects();
// We promise that the Frame is always connected to a Page while the render tree is live.
//
// The render tree can be torn down in a few different ways, but the two important ones are:
//
// - When calling Frame::setView() with a null FrameView*. This is always done before calling
// Frame::willDetachPage (this function.) Hence the assertion below.
//
// - When adding a document to the back/forward cache, the tree is torn down before instantiating
// the CachedPage+CachedFrame object tree.
ASSERT(!document() || !document()->renderView());
}
String LocalFrame::displayStringModifiedByEncoding(const String& str) const
{
return document() ? document()->displayStringModifiedByEncoding(str) : str;
}
VisiblePosition LocalFrame::visiblePositionForPoint(const IntPoint& framePoint) const
{
constexpr OptionSet<HitTestRequest::Type> hitType { HitTestRequest::Type::ReadOnly, HitTestRequest::Type::Active, HitTestRequest::Type::AllowVisibleChildFrameContentOnly };
HitTestResult result = eventHandler().hitTestResultAtPoint(framePoint, hitType);
RefPtr node = result.innerNonSharedNode();
if (!node)
return VisiblePosition();
CheckedPtr renderer = node->renderer();
if (!renderer)
return VisiblePosition();
VisiblePosition visiblePos = renderer->positionForPoint(result.localPoint(), HitTestSource::User, nullptr);
if (visiblePos.isNull())
visiblePos = firstPositionInOrBeforeNode(node.get());
return visiblePos;
}
Document* LocalFrame::documentAtPoint(const IntPoint& point)
{
if (!view())
return nullptr;
IntPoint pt = protectedView()->windowToContents(point);
HitTestResult result = HitTestResult(pt);
if (contentRenderer()) {
constexpr OptionSet<HitTestRequest::Type> hitType { HitTestRequest::Type::ReadOnly, HitTestRequest::Type::Active, HitTestRequest::Type::DisallowUserAgentShadowContent, HitTestRequest::Type::AllowChildFrameContent };
result = eventHandler().hitTestResultAtPoint(pt, hitType);
}
return result.innerNode() ? &result.innerNode()->document() : 0;
}
std::optional<SimpleRange> LocalFrame::rangeForPoint(const IntPoint& framePoint)
{
auto position = visiblePositionForPoint(framePoint);
SUPPRESS_UNCOUNTED_LOCAL auto containerText = position.deepEquivalent().containerText();
if (!containerText || !containerText->renderer() || containerText->renderer()->style().usedUserSelect() == UserSelect::None)
return std::nullopt;
if (auto previousCharacterRange = makeSimpleRange(position.previous(), position)) {
if (protectedEditor()->firstRectForRange(*previousCharacterRange).contains(framePoint))
return *previousCharacterRange;
}
if (auto nextCharacterRange = makeSimpleRange(position, position.next())) {
if (protectedEditor()->firstRectForRange(*nextCharacterRange).contains(framePoint))
return *nextCharacterRange;
}
return std::nullopt;
}
void LocalFrame::createView(const IntSize& viewportSize, const std::optional<Color>& backgroundColor, const IntSize& fixedLayoutSize, bool useFixedLayout, ScrollbarMode horizontalScrollbarMode, bool horizontalLock, ScrollbarMode verticalScrollbarMode, bool verticalLock)
{
ASSERT(page());
bool isRootFrame = this->isRootFrame();
if (isRootFrame && view())
protectedView()->setParentVisible(false);
setView(nullptr);
RefPtr<LocalFrameView> frameView;
if (isRootFrame) {
frameView = LocalFrameView::create(*this, viewportSize);
frameView->setFixedLayoutSize(fixedLayoutSize);
frameView->setUseFixedLayout(useFixedLayout);
} else
frameView = LocalFrameView::create(*this);
frameView->setScrollbarModes(horizontalScrollbarMode, verticalScrollbarMode, horizontalLock, verticalLock);
setView(frameView.copyRef());
frameView->updateBackgroundRecursively(backgroundColor);
if (isRootFrame)
frameView->setParentVisible(true);
if (CheckedPtr ownerRenderer = this->ownerRenderer())
ownerRenderer->setWidget(frameView);
protectedView()->setCanHaveScrollbars(scrollingMode() != ScrollbarMode::AlwaysOff);
}
LocalDOMWindow* LocalFrame::window() const
{
return document() ? document()->window() : nullptr;
}
RefPtr<LocalDOMWindow> LocalFrame::protectedWindow() const
{
return window();
}
DOMWindow* LocalFrame::virtualWindow() const
{
return window();
}
void LocalFrame::reinitializeDocumentSecurityContext()
{
if (RefPtr document = this->document())
document->initSecurityContext();
}
void LocalFrame::disconnectView()
{
setView(nullptr);
}
FrameView* LocalFrame::virtualView() const
{
return m_view.get();
}
FrameLoaderClient& LocalFrame::loaderClient()
{
return loader().client();
}
void LocalFrame::documentURLForConsoleLog(CompletionHandler<void(const URL&)>&& completionHandler)
{
RefPtr document = this->document();
if (!document)
return completionHandler({ });
completionHandler(document->url());
}
String LocalFrame::trackedRepaintRectsAsText() const
{
if (!m_view)
return String();
return protectedView()->trackedRepaintRectsAsText();
}
void LocalFrame::setPageZoomFactor(float factor)
{
setPageAndTextZoomFactors(factor, m_textZoomFactor);
}
void LocalFrame::setTextZoomFactor(float factor)
{
setPageAndTextZoomFactors(m_pageZoomFactor, factor);
}
void LocalFrame::setPageAndTextZoomFactors(float pageZoomFactor, float textZoomFactor)
{
if (m_pageZoomFactor == pageZoomFactor && m_textZoomFactor == textZoomFactor)
return;
RefPtr page = this->page();
if (!page)
return;
RefPtr document = this->document();
if (!document)
return;
protectedEditor()->dismissCorrectionPanelAsIgnored();
// Respect SVGs zoomAndPan="disabled" property in standalone SVG documents.
// FIXME: How to handle compound documents + zoomAndPan="disabled"? Needs SVG WG clarification.
if (RefPtr svgDocument = dynamicDowncast<SVGDocument>(*document); svgDocument && !svgDocument->zoomAndPanEnabled())
return;
std::optional<ScrollPosition> scrollPositionAfterZoomed;
if (m_pageZoomFactor != pageZoomFactor) {
// Compute the scroll position with scale after zooming to stay the same position in the content.
if (RefPtr view = this->view()) {
scrollPositionAfterZoomed = view->scrollPosition();
scrollPositionAfterZoomed->scale(pageZoomFactor / m_pageZoomFactor);
}
}
m_pageZoomFactor = pageZoomFactor;
m_textZoomFactor = textZoomFactor;
document->resolveStyle(Document::ResolveStyleType::Rebuild);
for (RefPtr child = tree().firstChild(); child; child = child->tree().nextSibling()) {
if (RefPtr localFrame = dynamicDowncast<LocalFrame>(child.get()))
localFrame->setPageAndTextZoomFactors(m_pageZoomFactor, m_textZoomFactor);
}
if (RefPtr view = this->view()) {
if (document->renderView() && document->renderView()->needsLayout() && view->didFirstLayout())
view->checkedLayoutContext()->layout();
// Scrolling to the calculated position must be done after the layout.
if (scrollPositionAfterZoomed)
view->setScrollPosition(scrollPositionAfterZoomed.value());
}
}
float LocalFrame::frameScaleFactor() const
{
RefPtr page = this->page();
// Main frame is scaled with respect to he container but inner frames are not scaled with respect to the main frame.
if (!page || !isMainFrame())
return 1;
if (page->delegatesScaling())
return 1;
return page->pageScaleFactor();
}
void LocalFrame::suspendActiveDOMObjectsAndAnimations()
{
bool wasSuspended = activeDOMObjectsAndAnimationsSuspended();
m_activeDOMObjectsAndAnimationsSuspendedCount++;
if (wasSuspended)
return;
// FIXME: Suspend/resume calls will not match if the frame is navigated, and gets a new document.
clearTimers(); // Suspends animations and pending relayouts.
if (RefPtr document = m_doc)
document->suspendScheduledTasks(ReasonForSuspension::PageWillBeSuspended);
}
void LocalFrame::resumeActiveDOMObjectsAndAnimations()
{
if (!activeDOMObjectsAndAnimationsSuspended())
return;
m_activeDOMObjectsAndAnimationsSuspendedCount--;
if (activeDOMObjectsAndAnimationsSuspended())
return;
if (!m_doc)
return;
Ref document = *m_doc;
// FIXME: Suspend/resume calls will not match if the frame is navigated, and gets a new document.
document->resumeScheduledTasks(ReasonForSuspension::PageWillBeSuspended);
// Frame::clearTimers() suspended animations and pending relayouts.
if (CheckedPtr timelines = document->timelinesController())
timelines->resumeAnimations();
if (RefPtr view = m_view)
view->checkedLayoutContext()->scheduleLayout();
}
void LocalFrame::deviceOrPageScaleFactorChanged()
{
for (RefPtr child = tree().firstChild(); child; child = child->tree().nextSibling()) {
if (RefPtr localFrame = dynamicDowncast<LocalFrame>(child.get()))
localFrame->deviceOrPageScaleFactorChanged();
}
if (CheckedPtr root = contentRenderer())
root->compositor().deviceOrPageScaleFactorChanged();
}
void LocalFrame::dropChildren()
{
ASSERT(isMainFrame());
while (RefPtr child = tree().firstChild())
tree().removeChild(*child);
}
FloatSize LocalFrame::screenSize() const
{
if (m_overrideScreenSize)
return m_overrideScreenSize->size;
auto defaultSize = screenRect(protectedView().get()).size();
RefPtr document = this->document();
if (!document)
return defaultSize;
RefPtr page = this->page();
if (!page)
return defaultSize;
if (page->shouldApplyScreenFingerprintingProtections(*document))
return page->chrome().client().screenSizeForFingerprintingProtections(*this, defaultSize);
return defaultSize;
}
void LocalFrame::setOverrideScreenSize(FloatSize&& screenSize)
{
if (m_overrideScreenSize && m_overrideScreenSize->size == screenSize)
return;
m_overrideScreenSize = makeUnique<OverrideScreenSize>(OverrideScreenSize { WTFMove(screenSize) });
if (RefPtr document = this->document())
document->updateViewportArguments();
}
void LocalFrame::selfOnlyRef()
{
ASSERT(isMainFrame());
if (m_selfOnlyRefCount++)
return;
ref();
}
void LocalFrame::selfOnlyDeref()
{
ASSERT(isMainFrame());
ASSERT(m_selfOnlyRefCount);
if (--m_selfOnlyRefCount)
return;
if (hasOneRef())
dropChildren();
deref();
}
String LocalFrame::debugDescription() const
{
StringBuilder builder;
builder.append("Frame 0x"_s, hex(reinterpret_cast<uintptr_t>(this), Lowercase));
if (isMainFrame())
builder.append(" (main frame)"_s);
if (RefPtr document = this->document())
builder.append(' ', document->documentURI());
return builder.toString();
}
TextStream& operator<<(TextStream& ts, const LocalFrame& frame)
{
ts << frame.debugDescription();
return ts;
}
void LocalFrame::resetScript()
{
ASSERT(windowProxy().frame() == this);
windowProxy().detachFromFrame();
resetWindowProxy();
m_script = makeUniqueRef<ScriptController>(*this);
}
LocalFrame* LocalFrame::fromJSContext(JSContextRef context)
{
JSC::JSGlobalObject* globalObjectObj = toJS(context);
if (auto* window = JSC::jsDynamicCast<JSDOMWindow*>(globalObjectObj))
return dynamicDowncast<LocalFrame>(window->wrapped().frame());
if (auto* serviceWorkerGlobalScope = JSC::jsDynamicCast<JSServiceWorkerGlobalScope*>(globalObjectObj))
return serviceWorkerGlobalScope->wrapped().serviceWorkerPage() ? dynamicDowncast<LocalFrame>(serviceWorkerGlobalScope->wrapped().serviceWorkerPage()->mainFrame()) : nullptr;
return nullptr;
}
LocalFrame* LocalFrame::contentFrameFromWindowOrFrameElement(JSContextRef context, JSValueRef valueRef)
{
ASSERT(context);
ASSERT(valueRef);
JSC::JSGlobalObject* globalObject = toJS(context);
JSC::JSValue value = toJS(globalObject, valueRef);
if (RefPtr window = JSDOMWindow::toWrapped(globalObject->vm(), value))
return dynamicDowncast<LocalFrame>(window->frame());
auto* jsNode = JSC::jsDynamicCast<JSNode*>(value);
if (!jsNode)
return nullptr;
RefPtr frameOwner = dynamicDowncast<HTMLFrameOwnerElement>(jsNode->wrapped());
return frameOwner ? dynamicDowncast<LocalFrame>(frameOwner->contentFrame()) : nullptr;
}
void LocalFrame::documentURLOrOriginDidChange()
{
if (!isMainFrame())
return;
RefPtr page = this->page();
RefPtr document = this->document();
if (page && document)
page->setMainFrameURLAndOrigin(document->url(), document->protectedSecurityOrigin());
}
void LocalFrame::dispatchLoadEventToParent()
{
if (is<RemoteFrame>(tree().parent()))
loader().client().dispatchLoadEventToOwnerElementInAnotherProcess();
else if (RefPtr owner = ownerElement())
owner->dispatchEvent(Event::create(eventNames().loadEvent, Event::CanBubble::No, Event::IsCancelable::No));
}
#if ENABLE(DATA_DETECTION)
DataDetectionResultsStorage& LocalFrame::dataDetectionResults()
{
if (!m_dataDetectionResults)
m_dataDetectionResults = makeUnique<DataDetectionResultsStorage>();
return *m_dataDetectionResults;
}
#endif
void LocalFrame::frameWasDisconnectedFromOwner() const
{
if (!m_doc)
return;
if (RefPtr window = m_doc->window())
window->willDetachDocumentFromFrame();
protectedDocument()->detachFromFrame();
}
void LocalFrame::storageAccessExceptionReceivedForDomain(const RegistrableDomain& domain)
{
if (!m_storageAccessExceptionDomains)
m_storageAccessExceptionDomains = makeUnique<HashSet<RegistrableDomain>>();
m_storageAccessExceptionDomains->add(domain);
}
bool LocalFrame::requestSkipUserActivationCheckForStorageAccess(const RegistrableDomain& domain)
{
if (!m_storageAccessExceptionDomains)
return false;
auto iter = m_storageAccessExceptionDomains->find(domain);
if (iter == m_storageAccessExceptionDomains->end())
return false;
// We only allow the domain to skip check once.
m_storageAccessExceptionDomains->remove(iter);
return true;
}
#if ENABLE(WINDOW_PROXY_PROPERTY_ACCESS_NOTIFICATION)
void LocalFrame::didAccessWindowProxyPropertyViaOpener(WindowProxyProperty property)
{
// FIXME: until we support restricted openers, report all property accesses as "other" to reduce
// the number of events logged.
property = WindowProxyProperty::Other;
if (m_accessedWindowProxyPropertiesViaOpener.contains(property))
return;
auto origin = SecurityOriginData::fromFrame(this);
if (origin.isNull() || origin.isOpaque())
return;
if (!opener() || !opener()->page())
return;
auto openerMainFrameOrigin = opener()->page()->mainFrameOrigin().data();
if (openerMainFrameOrigin.isNull() || openerMainFrameOrigin.isOpaque())
return;
auto site = RegistrableDomain(origin);
auto openerMainFrameSite = RegistrableDomain(openerMainFrameOrigin);
if (site == openerMainFrameSite)
return;
m_accessedWindowProxyPropertiesViaOpener.add(property);
loader().client().didAccessWindowProxyPropertyViaOpener(WTFMove(openerMainFrameOrigin), property);
}
#endif
String LocalFrame::customUserAgent() const
{
if (RefPtr documentLoader = loader().activeDocumentLoader())
return documentLoader->customUserAgent();
return { };
}
String LocalFrame::customUserAgentAsSiteSpecificQuirks() const
{
if (RefPtr documentLoader = loader().activeDocumentLoader())
return documentLoader->customUserAgentAsSiteSpecificQuirks();
return { };
}
String LocalFrame::customNavigatorPlatform() const
{
if (RefPtr documentLoader = loader().activeDocumentLoader())
return documentLoader->customNavigatorPlatform();
return { };
}
OptionSet<AdvancedPrivacyProtections> LocalFrame::advancedPrivacyProtections() const
{
if (auto* documentLoader = loader().activeDocumentLoader())
return documentLoader->advancedPrivacyProtections();
return { };
}
AutoplayPolicy LocalFrame::autoplayPolicy() const
{
if (RefPtr documentLoader = loader().activeDocumentLoader())
return documentLoader->autoplayPolicy();
return AutoplayPolicy::Default;
}
SandboxFlags LocalFrame::effectiveSandboxFlags() const
{
auto effectiveSandboxFlags = m_sandboxFlags;
if (RefPtr document = this->document())
effectiveSandboxFlags.add(document->sandboxFlags());
return effectiveSandboxFlags;
}
void LocalFrame::updateSandboxFlags(SandboxFlags flags, NotifyUIProcess notifyUIProcess)
{
Frame::updateSandboxFlags(flags, notifyUIProcess);
m_sandboxFlags = flags;
}
void LocalFrame::updateScrollingMode()
{
if (!ownerElement())
return;
m_scrollingMode = ownerElement()->scrollingMode();
if (RefPtr view = this->view())
view->setCanHaveScrollbars(m_scrollingMode != ScrollbarMode::AlwaysOff);
}
void LocalFrame::setScrollingMode(ScrollbarMode scrollingMode)
{
m_scrollingMode = scrollingMode;
if (RefPtr view = this->view())
view->setCanHaveScrollbars(m_scrollingMode != ScrollbarMode::AlwaysOff);
}
#if ENABLE(CONTENT_EXTENSIONS)
static String generateResourceMonitorErrorHTML(OptionSet<ColorScheme> colorScheme)
{
#if PLATFORM(COCOA) && HAVE(CUSTOM_IFRAME_UNLOADING_HTML)
#if HAVE(CUSTOM_IFRAME_UNLOADING_HTML_WITH_COLOR_SCHEME)
return generateResourceMonitorErrorHTMLForCocoa(colorScheme);
#else
UNUSED_PARAM(colorScheme);
return generateResourceMonitorErrorHTMLForCocoa();
#endif
#else
constexpr auto lightAndDarkColorScheme = ":root { color-scheme: light dark } "_s;
constexpr auto darkOnlyColorScheme = ":root { color-scheme: only dark } "_s;
constexpr auto lightStyle = "p { color: black } "_s;
constexpr auto darkStyle = "p { color: white } "_s;
constexpr auto empty = ""_s;
bool needDarkStyle = colorScheme.contains(ColorScheme::Dark);
bool needLightStyle = !needDarkStyle || colorScheme.contains(ColorScheme::Light);
bool conditionalStyle = needDarkStyle && needLightStyle;
const auto& colorSchemeStyle = conditionalStyle ? lightAndDarkColorScheme : needDarkStyle ? darkOnlyColorScheme : empty;
const auto& darkStyleOpen = conditionalStyle ? "@media (prefers-color-scheme: dark) { "_s : empty;
const auto& darkStyleClose = conditionalStyle ? "} "_s : empty;
return makeString(
"<style> body { background-color: gray }"_s,
colorSchemeStyle,
lightStyle,
darkStyleOpen,
(needDarkStyle ? darkStyle : empty),
darkStyleClose,
"</style><p>"_s,
WEB_UI_STRING("This frame is hidden for using too many system resources.", "Description HTML for frame unloaded by ResourceMonitor"),
"</p>"_s
);
#endif
}
static DiagnosticLoggingClient::ValueDictionary valueDictionaryForResult(bool unloaded)
{
DiagnosticLoggingClient::ValueDictionary dictionary;
dictionary.set(DiagnosticLoggingKeys::unloadCountKey(), (unloaded ? 1 : 0));
dictionary.set(DiagnosticLoggingKeys::unloadPreventedByThrottlerCountKey(), unloaded ? 0 : 1);
dictionary.set(DiagnosticLoggingKeys::unloadPreventedByStickyActivationCountKey(), 0);
return dictionary;
}
void LocalFrame::showResourceMonitoringError()
{
RefPtr iframeElement = dynamicDowncast<HTMLIFrameElement>(ownerElement());
RefPtr document = this->document();
if (!iframeElement || !document)
return;
URL url;
URL mainFrameURL;
if (document)
url = document->url();
if (RefPtr page = this->page()) {
mainFrameURL = page->mainFrameURL();
page->diagnosticLoggingClient().logDiagnosticMessageWithValueDictionary(DiagnosticLoggingKeys::iframeResourceMonitoringKey(), "IFrame ResourceMonitoring Unloaded"_s, valueDictionaryForResult(true), ShouldSample::No);
}
FRAME_RELEASE_LOG(ResourceMonitoring, "Detected excessive network usage in frame at %" SENSITIVE_LOG_STRING " and main frame at %" SENSITIVE_LOG_STRING ": unloading", url.isValid() ? url.string().utf8().data() : "invalid", mainFrameURL.isValid() ? mainFrameURL.string().utf8().data() : "invalid");
document->addConsoleMessage(MessageSource::ContentBlocker, MessageLevel::Error, makeString("Frame was unloaded because its network usage exceeded the limit: "_s, ResourceMonitorChecker::singleton().networkUsageThreshold(), " bytes, url="_s, url.string()));
for (RefPtr<Frame> frame = this; frame; frame = frame->tree().traverseNext()) {
if (RefPtr localFrame = dynamicDowncast<LocalFrame>(frame)) {
if (RefPtr window = localFrame->window())
window->removeAllEventListeners();
}
}
OptionSet<ColorScheme> colorScheme { ColorScheme::Light };
#if ENABLE(DARK_MODE_CSS)
if (CheckedPtr style = iframeElement->existingComputedStyle())
colorScheme = document->resolvedColorScheme(style.get());
#endif
iframeElement->setSrcdoc(generateResourceMonitorErrorHTML(colorScheme), SubstituteData::SessionHistoryVisibility::Hidden);
}
void LocalFrame::reportResourceMonitoringWarning()
{
URL url;
URL mainFrameURL;
if (RefPtr document = this->document())
url = document->url();
if (RefPtr page = this->page()) {
mainFrameURL = page->mainFrameURL();
page->diagnosticLoggingClient().logDiagnosticMessageWithValueDictionary(DiagnosticLoggingKeys::iframeResourceMonitoringKey(), "IFrame ResourceMonitoring Throttled"_s, valueDictionaryForResult(false), ShouldSample::No);
}
FRAME_RELEASE_LOG(ResourceMonitoring, "Detected excessive network usage in frame at %" SENSITIVE_LOG_STRING " and main frame at %" SENSITIVE_LOG_STRING ": not unloading due to global limits", url.isValid() ? url.string().utf8().data() : "invalid", mainFrameURL.isValid() ? mainFrameURL.string().utf8().data() : "invalid");
if (RefPtr document = this->document())
document->addConsoleMessage(MessageSource::ContentBlocker, MessageLevel::Warning, "Frame's network usage exceeded the limit."_s);
}
#endif
static String generateFrameMemoryMonitorErrorHTML(OptionSet<ColorScheme> colorScheme)
{
constexpr auto lightAndDarkColorScheme = ":root { color-scheme: light dark } "_s;
constexpr auto darkOnlyColorScheme = ":root { color-scheme: only dark } "_s;
constexpr auto lightStyle = "p { color: black } "_s;
constexpr auto darkStyle = "p { color: white } "_s;
constexpr auto empty = ""_s;
bool needDarkStyle = colorScheme.contains(ColorScheme::Dark);
bool needLightStyle = !needDarkStyle || colorScheme.contains(ColorScheme::Light);
bool conditionalStyle = needDarkStyle && needLightStyle;
const auto& colorSchemeStyle = conditionalStyle ? lightAndDarkColorScheme : needDarkStyle ? darkOnlyColorScheme : empty;
const auto& darkStyleOpen = conditionalStyle ? "@media (prefers-color-scheme: dark) { "_s : empty;
const auto& darkStyleClose = conditionalStyle ? "} "_s : empty;
return makeString(
"<style> body { background-color: gray }"_s,
colorSchemeStyle,
lightStyle,
darkStyleOpen,
(needDarkStyle ? darkStyle : empty),
darkStyleClose,
"</style><p>"_s,
WEB_UI_STRING("This frame is hidden for using too many system resources.", "Description HTML for frame unloaded by ResourceMonitor"),
"</p>"_s
);
}
void LocalFrame::showMemoryMonitorError()
{
RefPtr iframeElement = dynamicDowncast<HTMLIFrameElement>(ownerElement());
RefPtr document = this->document();
if (!iframeElement || !document)
return;
for (RefPtr<Frame> frame = this; frame; frame = frame->tree().traverseNext()) {
if (RefPtr localFrame = dynamicDowncast<LocalFrame>(frame)) {
if (RefPtr window = localFrame->window())
window->removeAllEventListeners();
}
}
OptionSet<ColorScheme> colorScheme { ColorScheme::Light };
#if ENABLE(DARK_MODE_CSS)
if (CheckedPtr style = iframeElement->existingComputedStyle())
colorScheme = document->resolvedColorScheme(style.get());
#endif
iframeElement->setSrcdoc(generateFrameMemoryMonitorErrorHTML(colorScheme), SubstituteData::SessionHistoryVisibility::Hidden);
}
bool LocalFrame::frameCanCreatePaymentSession() const
{
#if ENABLE(APPLE_PAY)
if (auto* documentLoader = loader().activeDocumentLoader())
return PaymentSession::isSecureForSession(documentLoader->response().url(), documentLoader->response().certificateInfo());
return false;
#else
return false;
#endif
}
RefPtr<SecurityOrigin> LocalFrame::frameDocumentSecurityOrigin() const
{
if (RefPtr document = this->document())
return &document->securityOrigin();
return nullptr;
}
} // namespace WebCore
#undef FRAME_RELEASE_LOG_ERROR