blob: a84265aa7936c34892cc9e071a990b9ad1e51168 [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-2016 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 "Frame.h"
#include "ApplyStyleCommand.h"
#include "BackForwardController.h"
#include "CSSAnimationController.h"
#include "CSSComputedStyleDeclaration.h"
#include "CSSPropertyNames.h"
#include "CachedCSSStyleSheet.h"
#include "CachedResourceLoader.h"
#include "Chrome.h"
#include "ChromeClient.h"
#include "DOMWindow.h"
#include "DocumentTimeline.h"
#include "DocumentType.h"
#include "Editing.h"
#include "Editor.h"
#include "EditorClient.h"
#include "Event.h"
#include "EventHandler.h"
#include "EventNames.h"
#include "FloatQuad.h"
#include "FocusController.h"
#include "FrameDestructionObserver.h"
#include "FrameLoader.h"
#include "FrameLoaderClient.h"
#include "FrameSelection.h"
#include "FrameView.h"
#include "GraphicsContext.h"
#include "GraphicsLayer.h"
#include "HTMLDocument.h"
#include "HTMLFormControlElement.h"
#include "HTMLFormElement.h"
#include "HTMLFrameElementBase.h"
#include "HTMLNames.h"
#include "HTMLTableCellElement.h"
#include "HTMLTableRowElement.h"
#include "HitTestResult.h"
#include "ImageBuffer.h"
#include "InspectorInstrumentation.h"
#include "JSWindowProxy.h"
#include "Logging.h"
#include "MathMLNames.h"
#include "MediaFeatureNames.h"
#include "NavigationScheduler.h"
#include "Navigator.h"
#include "NodeList.h"
#include "NodeTraversal.h"
#include "Page.h"
#include "PageCache.h"
#include "RenderLayerCompositor.h"
#include "RenderTableCell.h"
#include "RenderText.h"
#include "RenderTextControl.h"
#include "RenderTheme.h"
#include "RenderView.h"
#include "RenderWidget.h"
#include "RuntimeEnabledFeatures.h"
#include "SVGDocument.h"
#include "SVGDocumentExtensions.h"
#include "SVGNames.h"
#include "ScriptController.h"
#include "ScriptSourceCode.h"
#include "ScrollingCoordinator.h"
#include "Settings.h"
#include "StyleProperties.h"
#include "StyleScope.h"
#include "TextNodeTraversal.h"
#include "TextResourceDecoder.h"
#include "UserContentController.h"
#include "UserContentURLPattern.h"
#include "UserScript.h"
#include "UserTypingGestureIndicator.h"
#include "VisibleUnits.h"
#include "WebKitFontFamilyNames.h"
#include "XLinkNames.h"
#include "XMLNSNames.h"
#include "XMLNames.h"
#include "markup.h"
#include "npruntime_impl.h"
#include "runtime_root.h"
#include <JavaScriptCore/RegularExpression.h>
#include <wtf/RefCountedLeakCounter.h>
#include <wtf/StdLibExtras.h>
#include <wtf/text/StringBuilder.h>
#if PLATFORM(IOS)
#include "WKContentObservation.h"
#endif
namespace WebCore {
using namespace HTMLNames;
#if PLATFORM(IOS)
static const Seconds scrollFrequency { 1000_s / 60. };
#endif
DEFINE_DEBUG_ONLY_GLOBAL(WTF::RefCountedLeakCounter, frameCounter, ("Frame"));
static inline Frame* parentFromOwnerElement(HTMLFrameOwnerElement* ownerElement)
{
if (!ownerElement)
return 0;
return ownerElement->document().frame();
}
static inline float parentPageZoomFactor(Frame* frame)
{
Frame* parent = frame->tree().parent();
if (!parent)
return 1;
return parent->pageZoomFactor();
}
static inline float parentTextZoomFactor(Frame* frame)
{
Frame* parent = frame->tree().parent();
if (!parent)
return 1;
return parent->textZoomFactor();
}
Frame::Frame(Page& page, HTMLFrameOwnerElement* ownerElement, FrameLoaderClient& frameLoaderClient)
: m_mainFrame(ownerElement ? page.mainFrame() : *this)
, m_page(&page)
, m_settings(&page.settings())
, m_treeNode(*this, parentFromOwnerElement(ownerElement))
, m_loader(makeUniqueRef<FrameLoader>(*this, frameLoaderClient))
, m_navigationScheduler(makeUniqueRef<NavigationScheduler>(*this))
, m_ownerElement(ownerElement)
, m_script(makeUniqueRef<ScriptController>(*this))
, m_editor(makeUniqueRef<Editor>(*this))
, m_selection(makeUniqueRef<FrameSelection>(this))
, m_animationController(makeUniqueRef<CSSAnimationController>(*this))
#if PLATFORM(IOS)
, m_overflowAutoScrollTimer(*this, &Frame::overflowAutoScrollTimerFired)
, m_selectionChangeCallbacksDisabled(false)
#endif
, m_pageZoomFactor(parentPageZoomFactor(this))
, m_textZoomFactor(parentTextZoomFactor(this))
, m_activeDOMObjectsAndAnimationsSuspendedCount(0)
, m_eventHandler(makeUniqueRef<EventHandler>(*this))
{
AtomicString::init();
HTMLNames::init();
QualifiedName::init();
MediaFeatureNames::init();
SVGNames::init();
XLinkNames::init();
MathMLNames::init();
XMLNSNames::init();
XMLNames::init();
WebKitFontFamilyNames::init();
if (ownerElement) {
m_mainFrame.selfOnlyRef();
page.incrementSubframeCount();
ownerElement->setContentFrame(this);
}
#ifndef NDEBUG
frameCounter.increment();
#endif
// Pause future ActiveDOMObjects if this frame is being created while the page is in a paused state.
Frame* parent = parentFromOwnerElement(ownerElement);
if (parent && parent->activeDOMObjectsAndAnimationsSuspended())
suspendActiveDOMObjectsAndAnimations();
}
void Frame::init()
{
m_loader->init();
}
Ref<Frame> Frame::create(Page* page, HTMLFrameOwnerElement* ownerElement, FrameLoaderClient* client)
{
ASSERT(page);
ASSERT(client);
return adoptRef(*new Frame(*page, ownerElement, *client));
}
Frame::~Frame()
{
setView(nullptr);
loader().cancelAndClear();
// 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();
if (!isMainFrame())
m_mainFrame.selfOnlyDeref();
}
void Frame::addDestructionObserver(FrameDestructionObserver* observer)
{
m_destructionObservers.add(observer);
}
void Frame::removeDestructionObserver(FrameDestructionObserver* observer)
{
m_destructionObservers.remove(observer);
}
void Frame::setView(RefPtr<FrameView>&& 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 (m_view)
m_view->prepareForDetach();
// Prepare for destruction now, so any unload event handlers get run and the DOMWindow 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->pageCacheState() != Document::InPageCache)
m_doc->prepareForDestruction();
if (m_view)
m_view->layoutContext().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 Frame::setDocument(RefPtr<Document>&& newDocument)
{
ASSERT(!newDocument || newDocument->frame() == this);
if (m_documentIsBeingReplaced)
return;
m_documentIsBeingReplaced = true;
if (isMainFrame()) {
if (m_page)
m_page->didChangeMainDocument();
m_loader->client().dispatchDidChangeMainDocument();
}
#if ENABLE(ATTACHMENT_ELEMENT)
if (m_doc) {
for (auto& attachment : m_doc->attachmentElementsByIdentifier().values())
editor().didRemoveAttachmentElement(attachment);
}
#endif
if (m_doc && m_doc->pageCacheState() != Document::InPageCache)
m_doc->prepareForDestruction();
m_doc = newDocument.copyRef();
ASSERT(!m_doc || m_doc->domWindow());
ASSERT(!m_doc || m_doc->domWindow()->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 (m_doc) {
for (auto& attachment : m_doc->attachmentElementsByIdentifier().values())
editor().didInsertAttachmentElement(attachment);
}
#endif
InspectorInstrumentation::frameDocumentUpdated(*this);
m_documentIsBeingReplaced = false;
}
#if ENABLE(ORIENTATION_EVENTS)
void Frame::orientationChanged()
{
Vector<Ref<Frame>> frames;
for (Frame* frame = this; frame; frame = frame->tree().traverseNext())
frames.append(*frame);
auto newOrientation = orientation();
for (auto& frame : frames) {
if (Document* document = frame->document())
document->orientationChanged(newOrientation);
}
}
int Frame::orientation() const
{
if (m_page)
return m_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");
StringBuilder pattern;
pattern.append('(');
unsigned int numLabels = labels.size();
unsigned int i;
for (i = 0; i < numLabels; i++) {
String label = labels[i];
bool startsWithWordChar = false;
bool endsWithWordChar = false;
if (label.length()) {
startsWithWordChar = wordRegExp.get().match(label.substring(0, 1)) >= 0;
endsWithWordChar = wordRegExp.get().match(label.substring(label.length() - 1, 1)) >= 0;
}
if (i)
pattern.append('|');
// 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.
if (startsWithWordChar)
pattern.appendLiteral("\\b");
pattern.append(label);
if (endsWithWordChar)
pattern.appendLiteral("\\b");
}
pattern.append(')');
return JSC::Yarr::RegularExpression(pattern.toString(), JSC::Yarr::TextCaseInsensitive);
}
String Frame::searchForLabelsAboveCell(const JSC::Yarr::RegularExpression& regExp, HTMLTableCellElement* cell, size_t* resultDistanceFromStartOfCell)
{
HTMLTableCellElement* aboveCell = cell->cellAbove();
if (aboveCell) {
// search within the above cell we found for a match
size_t lengthSearched = 0;
for (Text* textNode = TextNodeTraversal::firstWithin(*aboveCell); textNode; textNode = TextNodeTraversal::next(*textNode, aboveCell)) {
if (!textNode->renderer() || textNode->renderer()->style().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 Frame::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
HTMLTableCellElement* startingTableCell = nullptr;
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;
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) || is<HTMLFormControlElement>(*n))
break;
if (n->hasTagName(tdTag) && !startingTableCell)
startingTableCell = downcast<HTMLTableCellElement>(n);
else if (is<HTMLTableRowElement>(*n) && startingTableCell) {
String result = searchForLabelsAboveCell(regExp, startingTableCell, resultDistance);
if (!result.isEmpty()) {
if (resultIsInCellAbove)
*resultIsInCellAbove = true;
return result;
}
searchedCellAbove = true;
} else if (n->isTextNode() && n->renderer() && n->renderer()->style().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, 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"), " ");
mutableStringToMatch.replace('_', ' ');
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 Frame::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)
void Frame::scrollOverflowLayer(RenderLayer* layer, const IntRect& visibleRect, const IntRect& exposeRect)
{
if (!layer)
return;
RenderBox* box = layer->renderBox();
if (!box)
return;
if (visibleRect.intersects(exposeRect))
return;
// FIXME: Why isn't this just calling RenderLayer::scrollRectToVisible()?
ScrollOffset scrollOffset = layer->scrollOffset();
int exposeLeft = exposeRect.x();
int exposeRight = exposeLeft + exposeRect.width();
int clientWidth = roundToInt(box->clientWidth());
if (exposeLeft <= 0)
scrollOffset.setX(std::max(0, scrollOffset.x() + exposeLeft - clientWidth / 2));
else if (exposeRight >= clientWidth)
scrollOffset.setX(std::min(box->scrollWidth() - clientWidth, scrollOffset.x() + clientWidth / 2));
int exposeTop = exposeRect.y();
int exposeBottom = exposeTop + exposeRect.height();
int clientHeight = roundToInt(box->clientHeight());
if (exposeTop <= 0)
scrollOffset.setY(std::max(0, scrollOffset.y() + exposeTop - clientHeight / 2));
else if (exposeBottom >= clientHeight)
scrollOffset.setY(std::min(box->scrollHeight() - clientHeight, scrollOffset.y() + clientHeight / 2));
layer->scrollToOffset(scrollOffset, ScrollClamping::Unclamped);
selection().setCaretRectNeedsUpdate();
selection().updateAppearance();
}
void Frame::overflowAutoScrollTimerFired()
{
if (!eventHandler().mousePressed() || checkOverflowScroll(PerformOverflowScroll) == OverflowScrollNone) {
if (m_overflowAutoScrollTimer.isActive())
m_overflowAutoScrollTimer.stop();
}
}
void Frame::startOverflowAutoScroll(const IntPoint& mousePosition)
{
m_overflowAutoScrollPos = mousePosition;
if (m_overflowAutoScrollTimer.isActive())
return;
if (checkOverflowScroll(DoNotPerformOverflowScroll) == OverflowScrollNone)
return;
m_overflowAutoScrollTimer.startRepeating(scrollFrequency);
m_overflowAutoScrollDelta = 3;
}
int Frame::checkOverflowScroll(OverflowScrollAction action)
{
Position extent = selection().selection().extent();
if (extent.isNull())
return OverflowScrollNone;
RenderObject* renderer = extent.deprecatedNode()->renderer();
if (!renderer)
return OverflowScrollNone;
FrameView* view = this->view();
if (!view)
return OverflowScrollNone;
RenderBlock* containingBlock = renderer->containingBlock();
if (!containingBlock || !containingBlock->hasOverflowClip())
return OverflowScrollNone;
RenderLayer* layer = containingBlock->layer();
ASSERT(layer);
IntRect visibleRect = IntRect(view->scrollX(), view->scrollY(), view->visibleWidth(), view->visibleHeight());
IntPoint position = m_overflowAutoScrollPos;
if (visibleRect.contains(position.x(), position.y()))
return OverflowScrollNone;
int scrollType = 0;
int deltaX = 0;
int deltaY = 0;
IntPoint selectionPosition;
// This constant will make the selection draw a little bit beyond the edge of the visible area.
// This prevents a visual glitch, in that you can fail to select a portion of a character that
// is being rendered right at the edge of the visible rectangle.
// FIXME: This probably needs improvement, and may need to take the font size into account.
static const int scrollBoundsAdjustment = 3;
// FIXME: Make a small buffer at the end of a visible rectangle so that autoscrolling works
// even if the visible extends to the limits of the screen.
if (position.x() < visibleRect.x()) {
scrollType |= OverflowScrollLeft;
if (action == PerformOverflowScroll) {
deltaX -= static_cast<int>(m_overflowAutoScrollDelta);
selectionPosition.setX(view->scrollX() - scrollBoundsAdjustment);
}
} else if (position.x() > visibleRect.maxX()) {
scrollType |= OverflowScrollRight;
if (action == PerformOverflowScroll) {
deltaX += static_cast<int>(m_overflowAutoScrollDelta);
selectionPosition.setX(view->scrollX() + view->visibleWidth() + scrollBoundsAdjustment);
}
}
if (position.y() < visibleRect.y()) {
scrollType |= OverflowScrollUp;
if (action == PerformOverflowScroll) {
deltaY -= static_cast<int>(m_overflowAutoScrollDelta);
selectionPosition.setY(view->scrollY() - scrollBoundsAdjustment);
}
} else if (position.y() > visibleRect.maxY()) {
scrollType |= OverflowScrollDown;
if (action == PerformOverflowScroll) {
deltaY += static_cast<int>(m_overflowAutoScrollDelta);
selectionPosition.setY(view->scrollY() + view->visibleHeight() + scrollBoundsAdjustment);
}
}
Ref<Frame> protectedThis(*this);
if (action == PerformOverflowScroll && (deltaX || deltaY)) {
layer->scrollToOffset(layer->scrollOffset() + IntSize(deltaX, deltaY), ScrollClamping::Unclamped);
// Handle making selection.
VisiblePosition visiblePosition(renderer->positionForPoint(selectionPosition, nullptr));
if (visiblePosition.isNotNull()) {
VisibleSelection visibleSelection = selection().selection();
visibleSelection.setExtent(visiblePosition);
if (selection().granularity() != CharacterGranularity)
visibleSelection.expandUsingGranularity(selection().granularity());
if (selection().shouldChangeSelection(visibleSelection))
selection().setSelection(visibleSelection);
}
m_overflowAutoScrollDelta *= 1.02f; // Accelerate the scroll
}
return scrollType;
}
void Frame::setSelectionChangeCallbacksDisabled(bool selectionChangeCallbacksDisabled)
{
m_selectionChangeCallbacksDisabled = selectionChangeCallbacksDisabled;
}
bool Frame::selectionChangeCallbacksDisabled() const
{
return m_selectionChangeCallbacksDisabled;
}
#endif // PLATFORM(IOS)
void Frame::setPrinting(bool printing, const FloatSize& pageSize, const FloatSize& originalPageSize, float maximumShrinkRatio, AdjustViewSizeOrNot shouldAdjustViewSize)
{
// 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(m_doc->cachedResourceLoader());
m_doc->setPrinting(printing);
if (auto* frameView = view()) {
frameView->adjustMediaTypeForPrinting(printing);
m_doc->styleScope().didChangeStyleSheetEnvironment();
if (shouldUsePrintingLayout())
frameView->forceLayoutForPagination(pageSize, originalPageSize, maximumShrinkRatio, shouldAdjustViewSize);
else {
frameView->forceLayout();
if (shouldAdjustViewSize == AdjustViewSize)
frameView->adjustViewSize();
}
}
// Subframes of the one we're printing don't lay out to the page size.
for (RefPtr<Frame> child = tree().firstChild(); child; child = child->tree().nextSibling())
child->setPrinting(printing, FloatSize(), FloatSize(), 0, shouldAdjustViewSize);
}
bool Frame::shouldUsePrintingLayout() const
{
// Only top frame being printed should be fit to page size.
// Subframes should be constrained by parents only.
return m_doc->printing() && (!tree().parent() || !tree().parent()->m_doc->printing());
}
FloatSize Frame::resizePageRectsKeepingRatio(const FloatSize& originalSize, const FloatSize& expectedSize)
{
FloatSize resultSize;
if (!contentRenderer())
return FloatSize();
if (contentRenderer()->style().isHorizontalWritingMode()) {
ASSERT(fabs(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(fabs(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 Frame::injectUserScripts(UserScriptInjectionTime injectionTime)
{
if (!m_page)
return;
if (loader().stateMachine().creatingInitialEmptyDocument() && !settings().shouldInjectUserScriptsInInitialEmptyDocument())
return;
m_page->userContentProvider().forEachUserScript([this, protectedThis = makeRef(*this), injectionTime](DOMWrapperWorld& world, const UserScript& script) {
auto* document = this->document();
if (!document)
return;
if (script.injectedFrames() == InjectInTopFrameOnly && ownerElement())
return;
if (script.injectionTime() == injectionTime && UserContentURLPattern::matchesPatterns(document->url(), script.whitelist(), script.blacklist())) {
if (m_page)
m_page->setAsRunningUserScripts();
m_script->evaluateInWorld(ScriptSourceCode(script.source(), script.url()), world);
}
});
}
RenderView* Frame::contentRenderer() const
{
return document() ? document()->renderView() : nullptr;
}
RenderWidget* Frame::ownerRenderer() const
{
auto* ownerElement = m_ownerElement;
if (!ownerElement)
return nullptr;
auto* object = ownerElement->renderer();
// FIXME: If <object> is ever fixed to disassociate itself from frames
// that it has started but canceled, then this can turn into an ASSERT
// since m_ownerElement would be nullptr when the load is canceled.
// https://bugs.webkit.org/show_bug.cgi?id=18585
if (!is<RenderWidget>(object))
return nullptr;
return downcast<RenderWidget>(object);
}
Frame* Frame::frameForWidget(const Widget& widget)
{
if (auto* renderer = RenderWidget::find(widget))
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<FrameView>(widget).frame();
}
void Frame::clearTimers(FrameView *view, Document *document)
{
if (view) {
view->layoutContext().unscheduleLayout();
if (RuntimeEnabledFeatures::sharedFeatures().cssAnimationsAndCSSTransitionsBackedByWebAnimationsEnabled())
document->timeline().suspendAnimations();
else
view->frame().animation().suspendAnimationsForDocument(document);
view->frame().eventHandler().stopAutoscrollTimer();
}
}
void Frame::clearTimers()
{
clearTimers(m_view.get(), document());
}
void Frame::willDetachPage()
{
if (Frame* parent = 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 (page() && page()->focusController().focusedFrame() == this)
page()->focusController().setFocusedFrame(nullptr);
if (page() && page()->scrollingCoordinator() && m_view)
page()->scrollingCoordinator()->willDestroyScrollableArea(*m_view);
#if PLATFORM(IOS)
if (WebThreadCountOfObservedContentModifiers() > 0 && m_page)
m_page->chrome().client().clearContentChangeObservers(*this);
#endif
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 page cache, the tree is torn down before instantiating
// the CachedPage+CachedFrame object tree.
ASSERT(!document() || !document()->renderView());
}
void Frame::disconnectOwnerElement()
{
if (m_ownerElement) {
m_ownerElement->clearContentFrame();
if (m_page)
m_page->decrementSubframeCount();
}
m_ownerElement = nullptr;
}
String Frame::displayStringModifiedByEncoding(const String& str) const
{
return document() ? document()->displayStringModifiedByEncoding(str) : str;
}
VisiblePosition Frame::visiblePositionForPoint(const IntPoint& framePoint) const
{
HitTestResult result = eventHandler().hitTestResultAtPoint(framePoint, HitTestRequest::ReadOnly | HitTestRequest::Active);
Node* node = result.innerNonSharedNode();
if (!node)
return VisiblePosition();
auto renderer = node->renderer();
if (!renderer)
return VisiblePosition();
VisiblePosition visiblePos = renderer->positionForPoint(result.localPoint(), nullptr);
if (visiblePos.isNull())
visiblePos = firstPositionInOrBeforeNode(node);
return visiblePos;
}
Document* Frame::documentAtPoint(const IntPoint& point)
{
if (!view())
return nullptr;
IntPoint pt = view()->windowToContents(point);
HitTestResult result = HitTestResult(pt);
if (contentRenderer())
result = eventHandler().hitTestResultAtPoint(pt);
return result.innerNode() ? &result.innerNode()->document() : 0;
}
RefPtr<Range> Frame::rangeForPoint(const IntPoint& framePoint)
{
VisiblePosition position = visiblePositionForPoint(framePoint);
if (position.isNull())
return nullptr;
Position deepPosition = position.deepEquivalent();
Text* containerText = deepPosition.containerText();
if (!containerText || !containerText->renderer() || containerText->renderer()->style().userSelect() == SELECT_NONE)
return nullptr;
VisiblePosition previous = position.previous();
if (previous.isNotNull()) {
RefPtr<Range> previousCharacterRange = makeRange(previous, position);
LayoutRect rect = editor().firstRectForRange(previousCharacterRange.get());
if (rect.contains(framePoint))
return previousCharacterRange;
}
VisiblePosition next = position.next();
if (RefPtr<Range> nextCharacterRange = makeRange(position, next)) {
LayoutRect rect = editor().firstRectForRange(nextCharacterRange.get());
if (rect.contains(framePoint))
return nextCharacterRange;
}
return nullptr;
}
void Frame::createView(const IntSize& viewportSize, const Color& backgroundColor, bool transparent,
const IntSize& fixedLayoutSize, const IntRect& fixedVisibleContentRect,
bool useFixedLayout, ScrollbarMode horizontalScrollbarMode, bool horizontalLock,
ScrollbarMode verticalScrollbarMode, bool verticalLock)
{
ASSERT(m_page);
bool isMainFrame = this->isMainFrame();
if (isMainFrame && view())
view()->setParentVisible(false);
setView(nullptr);
RefPtr<FrameView> frameView;
if (isMainFrame) {
frameView = FrameView::create(*this, viewportSize);
frameView->setFixedLayoutSize(fixedLayoutSize);
#if USE(COORDINATED_GRAPHICS)
frameView->setFixedVisibleContentRect(fixedVisibleContentRect);
#else
UNUSED_PARAM(fixedVisibleContentRect);
#endif
frameView->setUseFixedLayout(useFixedLayout);
} else
frameView = FrameView::create(*this);
frameView->setScrollbarModes(horizontalScrollbarMode, verticalScrollbarMode, horizontalLock, verticalLock);
setView(frameView.copyRef());
if (backgroundColor.isValid())
frameView->updateBackgroundRecursively(backgroundColor, transparent);
if (isMainFrame)
frameView->setParentVisible(true);
if (ownerRenderer())
ownerRenderer()->setWidget(frameView);
if (HTMLFrameOwnerElement* owner = ownerElement())
view()->setCanHaveScrollbars(owner->scrollingMode() != ScrollbarAlwaysOff);
}
DOMWindow* Frame::window() const
{
return document() ? document()->domWindow() : nullptr;
}
AbstractDOMWindow* Frame::virtualWindow() const
{
return window();
}
String Frame::layerTreeAsText(LayerTreeFlags flags) const
{
document()->updateLayout();
if (!contentRenderer())
return String();
return contentRenderer()->compositor().layerTreeAsText(flags);
}
String Frame::trackedRepaintRectsAsText() const
{
if (!m_view)
return String();
return m_view->trackedRepaintRectsAsText();
}
void Frame::setPageZoomFactor(float factor)
{
setPageAndTextZoomFactors(factor, m_textZoomFactor);
}
void Frame::setTextZoomFactor(float factor)
{
setPageAndTextZoomFactors(m_pageZoomFactor, factor);
}
void Frame::setPageAndTextZoomFactors(float pageZoomFactor, float textZoomFactor)
{
if (m_pageZoomFactor == pageZoomFactor && m_textZoomFactor == textZoomFactor)
return;
Page* page = this->page();
if (!page)
return;
Document* document = this->document();
if (!document)
return;
m_editor->dismissCorrectionPanelAsIgnored();
// Respect SVGs zoomAndPan="disabled" property in standalone SVG documents.
// FIXME: How to handle compound documents + zoomAndPan="disabled"? Needs SVG WG clarification.
if (is<SVGDocument>(*document) && !downcast<SVGDocument>(*document).zoomAndPanEnabled())
return;
if (m_pageZoomFactor != pageZoomFactor) {
if (FrameView* view = this->view()) {
// Update the scroll position when doing a full page zoom, so the content stays in relatively the same position.
LayoutPoint scrollPosition = view->scrollPosition();
float percentDifference = (pageZoomFactor / m_pageZoomFactor);
view->setScrollPosition(IntPoint(scrollPosition.x() * percentDifference, scrollPosition.y() * percentDifference));
}
}
m_pageZoomFactor = pageZoomFactor;
m_textZoomFactor = textZoomFactor;
document->resolveStyle(Document::ResolveStyleType::Rebuild);
for (RefPtr<Frame> child = tree().firstChild(); child; child = child->tree().nextSibling())
child->setPageAndTextZoomFactors(m_pageZoomFactor, m_textZoomFactor);
if (FrameView* view = this->view()) {
if (document->renderView() && document->renderView()->needsLayout() && view->didFirstLayout())
view->layoutContext().layout();
}
}
float Frame::frameScaleFactor() const
{
Page* 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 || &page->mainFrame() != this || settings().delegatesPageScaling())
return 1;
return page->pageScaleFactor();
}
void Frame::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 (m_doc)
m_doc->suspendScheduledTasks(ActiveDOMObject::PageWillBeSuspended);
}
void Frame::resumeActiveDOMObjectsAndAnimations()
{
if (!activeDOMObjectsAndAnimationsSuspended())
return;
m_activeDOMObjectsAndAnimationsSuspendedCount--;
if (activeDOMObjectsAndAnimationsSuspended())
return;
if (!m_doc)
return;
// FIXME: Suspend/resume calls will not match if the frame is navigated, and gets a new document.
m_doc->resumeScheduledTasks(ActiveDOMObject::PageWillBeSuspended);
// Frame::clearTimers() suspended animations and pending relayouts.
animation().resumeAnimationsForDocument(m_doc.get());
if (m_view)
m_view->layoutContext().scheduleLayout();
}
void Frame::deviceOrPageScaleFactorChanged()
{
for (RefPtr<Frame> child = tree().firstChild(); child; child = child->tree().nextSibling())
child->deviceOrPageScaleFactorChanged();
if (RenderView* root = contentRenderer())
root->compositor().deviceOrPageScaleFactorChanged();
}
bool Frame::isURLAllowed(const URL& url) const
{
// We allow one level of self-reference because some sites depend on that,
// but we don't allow more than one.
if (m_page->subframeCount() >= Page::maxNumberOfFrames)
return false;
bool foundSelfReference = false;
for (const Frame* frame = this; frame; frame = frame->tree().parent()) {
if (equalIgnoringFragmentIdentifier(frame->document()->url(), url)) {
if (foundSelfReference)
return false;
foundSelfReference = true;
}
}
return true;
}
bool Frame::isAlwaysOnLoggingAllowed() const
{
return page() && page()->isAlwaysOnLoggingAllowed();
}
void Frame::dropChildren()
{
ASSERT(isMainFrame());
while (Frame* child = tree().firstChild())
tree().removeChild(*child);
}
void Frame::selfOnlyRef()
{
ASSERT(isMainFrame());
if (m_selfOnlyRefCount++)
return;
ref();
}
void Frame::selfOnlyDeref()
{
ASSERT(isMainFrame());
ASSERT(m_selfOnlyRefCount);
if (--m_selfOnlyRefCount)
return;
if (hasOneRef())
dropChildren();
deref();
}
} // namespace WebCore