| Index: third_party/WebKit/Source/core/frame/FrameView.cpp
|
| diff --git a/third_party/WebKit/Source/core/frame/FrameView.cpp b/third_party/WebKit/Source/core/frame/FrameView.cpp
|
| index d72cf5eeb900e3de2c4832ecce03c1e90cefbd1e..5c8daac7b6ac0bc4e8d64bb70c4a93f61fe64d14 100644
|
| --- a/third_party/WebKit/Source/core/frame/FrameView.cpp
|
| +++ b/third_party/WebKit/Source/core/frame/FrameView.cpp
|
| @@ -33,11 +33,8 @@
|
| #include "core/css/resolver/StyleResolver.h"
|
| #include "core/dom/AXObjectCache.h"
|
| #include "core/dom/DOMNodeIds.h"
|
| -#include "core/dom/ElementVisibilityObserver.h"
|
| #include "core/dom/Fullscreen.h"
|
| -#include "core/dom/IntersectionObserverCallback.h"
|
| #include "core/dom/IntersectionObserverController.h"
|
| -#include "core/dom/IntersectionObserverInit.h"
|
| #include "core/editing/EditingUtilities.h"
|
| #include "core/editing/FrameSelection.h"
|
| #include "core/editing/RenderedPosition.h"
|
| @@ -156,6 +153,10 @@
|
| m_inSynchronousPostLayout(false),
|
| m_postLayoutTasksTimer(this, &FrameView::postLayoutTimerFired),
|
| m_updateWidgetsTimer(this, &FrameView::updateWidgetsTimerFired),
|
| + m_renderThrottlingObserverNotificationFactory(
|
| + CancellableTaskFactory::create(
|
| + this,
|
| + &FrameView::notifyRenderThrottlingObservers)),
|
| m_isTransparent(false),
|
| m_baseBackgroundColor(Color::white),
|
| m_mediaType(MediaTypeNames::screen),
|
| @@ -168,6 +169,7 @@
|
| m_browserControlsViewportAdjustment(0),
|
| m_needsUpdateWidgetGeometries(false),
|
| m_needsUpdateViewportIntersection(true),
|
| + m_needsUpdateViewportIntersectionInSubtree(true),
|
| #if ENABLE(ASSERT)
|
| m_hasBeenDisposed(false),
|
| #endif
|
| @@ -178,7 +180,9 @@
|
| m_scrollbarsSuppressed(false),
|
| m_inUpdateScrollbars(false),
|
| m_frameTimingRequestsDirty(true),
|
| + m_viewportIntersectionValid(false),
|
| m_hiddenForThrottling(false),
|
| + m_crossOriginForThrottling(false),
|
| m_subtreeThrottled(false),
|
| m_currentUpdateLifecyclePhasesTargetState(
|
| DocumentLifecycle::Uninitialized),
|
| @@ -218,7 +222,6 @@
|
| visitor->trace(m_autoSizeInfo);
|
| visitor->trace(m_children);
|
| visitor->trace(m_viewportScrollableArea);
|
| - visitor->trace(m_visibilityObserver);
|
| visitor->trace(m_scrollAnchor);
|
| visitor->trace(m_anchoringAdjustmentQueue);
|
| visitor->trace(m_scrollbarManager);
|
| @@ -281,28 +284,6 @@
|
| setCanHaveScrollbars(false);
|
| }
|
|
|
| -void FrameView::setupRenderThrottling() {
|
| - if (m_visibilityObserver)
|
| - return;
|
| -
|
| - // We observe the frame owner element instead of the document element, because
|
| - // if the document has no content we can falsely think the frame is invisible.
|
| - // Note that this means we cannot throttle top-level frames or (currently)
|
| - // frames whose owner element is remote.
|
| - Element* targetElement = frame().deprecatedLocalOwner();
|
| - if (!targetElement)
|
| - return;
|
| -
|
| - m_visibilityObserver = new ElementVisibilityObserver(
|
| - targetElement, WTF::bind(
|
| - [](FrameView* frameView, bool isVisible) {
|
| - frameView->updateRenderThrottlingStatus(
|
| - !isVisible, frameView->m_subtreeThrottled);
|
| - },
|
| - wrapWeakPersistent(this)));
|
| - m_visibilityObserver->start();
|
| -}
|
| -
|
| void FrameView::dispose() {
|
| RELEASE_ASSERT(!isInPerformLayout());
|
|
|
| @@ -329,6 +310,8 @@
|
|
|
| m_postLayoutTasksTimer.stop();
|
| m_didScrollTimer.stop();
|
| +
|
| + m_renderThrottlingObserverNotificationFactory->cancel();
|
|
|
| // FIXME: Do we need to do something here for OOPI?
|
| HTMLFrameOwnerElement* ownerElement = m_frame->deprecatedLocalOwner();
|
| @@ -3434,10 +3417,6 @@
|
|
|
| updateScrollableAreaSet();
|
| setNeedsUpdateViewportIntersection();
|
| - setupRenderThrottling();
|
| -
|
| - if (parentFrameView())
|
| - m_subtreeThrottled = parentFrameView()->canThrottleRendering();
|
| }
|
|
|
| void FrameView::removeChild(Widget* child) {
|
| @@ -4354,13 +4333,73 @@
|
| }
|
|
|
| void FrameView::setNeedsUpdateViewportIntersection() {
|
| + m_needsUpdateViewportIntersection = true;
|
| for (FrameView* parent = parentFrameView(); parent;
|
| parent = parent->parentFrameView())
|
| parent->m_needsUpdateViewportIntersectionInSubtree = true;
|
| }
|
|
|
| +void FrameView::updateViewportIntersectionIfNeeded() {
|
| + if (!m_needsUpdateViewportIntersection)
|
| + return;
|
| + m_needsUpdateViewportIntersection = false;
|
| + m_viewportIntersectionValid = true;
|
| + FrameView* parent = parentFrameView();
|
| + if (!parent) {
|
| + HTMLFrameOwnerElement* element = frame().deprecatedLocalOwner();
|
| + if (!element)
|
| + frame().document()->maybeRecordLoadReason(WouldLoadOutOfProcess);
|
| + // Having no layout object means the frame is not drawn.
|
| + else if (!element->layoutObject())
|
| + frame().document()->maybeRecordLoadReason(WouldLoadDisplayNone);
|
| + m_viewportIntersection = frameRect();
|
| + return;
|
| + }
|
| + ASSERT(!parent->m_needsUpdateViewportIntersection);
|
| +
|
| + bool parentLoaded = parent->frame().document()->wouldLoadReason() > Created;
|
| + // If the parent wasn't loaded, the children won't be either.
|
| + if (parentLoaded) {
|
| + if (frameRect().isEmpty())
|
| + frame().document()->maybeRecordLoadReason(WouldLoadZeroByZero);
|
| + else if (frameRect().maxY() < 0 && frameRect().maxX() < 0)
|
| + frame().document()->maybeRecordLoadReason(WouldLoadAboveAndLeft);
|
| + else if (frameRect().maxY() < 0)
|
| + frame().document()->maybeRecordLoadReason(WouldLoadAbove);
|
| + else if (frameRect().maxX() < 0)
|
| + frame().document()->maybeRecordLoadReason(WouldLoadLeft);
|
| + }
|
| +
|
| + // If our parent is hidden, then we are too.
|
| + if (parent->m_viewportIntersection.isEmpty()) {
|
| + m_viewportIntersection = parent->m_viewportIntersection;
|
| + return;
|
| + }
|
| +
|
| + // Transform our bounds into the root frame's content coordinate space,
|
| + // making sure we have valid layout data in our parent document. If our
|
| + // parent is throttled, we'll use possible stale layout information and
|
| + // rely on the fact that another lifecycle update will be scheduled once
|
| + // our parent becomes unthrottled.
|
| + ASSERT(parent->lifecycle().state() >= DocumentLifecycle::LayoutClean ||
|
| + parent->shouldThrottleRendering());
|
| + m_viewportIntersection = parent->contentsToRootFrame(frameRect());
|
| +
|
| + // TODO(skyostil): Expand the viewport to make it less likely to see stale
|
| + // content while scrolling.
|
| + IntRect viewport = parent->m_viewportIntersection;
|
| + m_viewportIntersection.intersect(viewport);
|
| +
|
| + if (parentLoaded && !m_viewportIntersection.isEmpty())
|
| + frame().document()->maybeRecordLoadReason(WouldLoadVisible);
|
| +}
|
| +
|
| void FrameView::updateViewportIntersectionsForSubtree(
|
| DocumentLifecycle::LifecycleState targetState) {
|
| + bool hadValidIntersection = m_viewportIntersectionValid;
|
| + bool hadEmptyIntersection = m_viewportIntersection.isEmpty();
|
| + updateViewportIntersectionIfNeeded();
|
| +
|
| // Notify javascript IntersectionObservers
|
| if (targetState == DocumentLifecycle::PaintClean &&
|
| frame().document()->intersectionObserverController())
|
| @@ -4369,6 +4408,15 @@
|
| ->intersectionObserverController()
|
| ->computeTrackedIntersectionObservations();
|
|
|
| + // Adjust render throttling for iframes based on visibility
|
| + bool shouldNotify = !hadValidIntersection ||
|
| + hadEmptyIntersection != m_viewportIntersection.isEmpty();
|
| + if (shouldNotify &&
|
| + !m_renderThrottlingObserverNotificationFactory->isPending())
|
| + m_frame->frameScheduler()->unthrottledTaskRunner()->postTask(
|
| + BLINK_FROM_HERE,
|
| + m_renderThrottlingObserverNotificationFactory->cancelAndCreate());
|
| +
|
| if (!m_needsUpdateViewportIntersectionInSubtree)
|
| return;
|
| m_needsUpdateViewportIntersectionInSubtree = false;
|
| @@ -4382,41 +4430,66 @@
|
| }
|
| }
|
|
|
| -void FrameView::updateRenderThrottlingStatusForTesting() {
|
| - m_visibilityObserver->deliverObservationsForTesting();
|
| -}
|
| -
|
| -void FrameView::updateRenderThrottlingStatus(bool hidden,
|
| - bool subtreeThrottled) {
|
| - TRACE_EVENT0("blink", "FrameView::updateRenderThrottlingStatus");
|
| +void FrameView::updateThrottlingStatus() {
|
| + // Only offscreen frames can be throttled. Note that we disallow throttling
|
| + // of 0x0 frames because some sites use them to drive UI logic.
|
| + DCHECK(m_viewportIntersectionValid);
|
| + m_hiddenForThrottling =
|
| + m_viewportIntersection.isEmpty() && !frameRect().isEmpty();
|
| +
|
| + // We only throttle the rendering pipeline in cross-origin frames. This is
|
| + // to avoid a situation where an ancestor frame directly depends on the
|
| + // pipeline timing of a descendant and breaks as a result of throttling.
|
| + // The rationale is that cross-origin frames must already communicate with
|
| + // asynchronous messages, so they should be able to tolerate some delay in
|
| + // receiving replies from a throttled peer.
|
| + //
|
| + // Check if we can access our parent's security origin.
|
| + m_crossOriginForThrottling = false;
|
| + // If any of our parents are throttled, we must be too.
|
| + m_subtreeThrottled = false;
|
| + const SecurityOrigin* origin = frame().securityContext()->getSecurityOrigin();
|
| + for (Frame* parentFrame = m_frame->tree().parent(); parentFrame;
|
| + parentFrame = parentFrame->tree().parent()) {
|
| + const SecurityOrigin* parentOrigin =
|
| + parentFrame->securityContext()->getSecurityOrigin();
|
| + if (!origin->canAccess(parentOrigin))
|
| + m_crossOriginForThrottling = true;
|
| + if (parentFrame->isLocalFrame() && toLocalFrame(parentFrame)->view() &&
|
| + toLocalFrame(parentFrame)->view()->canThrottleRendering())
|
| + m_subtreeThrottled = true;
|
| + }
|
| + m_frame->frameScheduler()->setFrameVisible(!m_hiddenForThrottling);
|
| + m_frame->frameScheduler()->setCrossOrigin(m_crossOriginForThrottling);
|
| +}
|
| +
|
| +void FrameView::notifyRenderThrottlingObserversForTesting() {
|
| + DCHECK(m_renderThrottlingObserverNotificationFactory->isPending());
|
| + notifyRenderThrottlingObservers();
|
| +}
|
| +
|
| +void FrameView::notifyRenderThrottlingObservers() {
|
| + TRACE_EVENT0("blink", "FrameView::notifyRenderThrottlingObservers");
|
| DCHECK(!isInPerformLayout());
|
| - DCHECK(!m_frame->document() || !m_frame->document()->inStyleRecalc());
|
| + DCHECK(frame().document());
|
| + DCHECK(!frame().document()->inStyleRecalc());
|
| bool wasThrottled = canThrottleRendering();
|
|
|
| - // Note that we disallow throttling of 0x0 frames because some sites use
|
| - // them to drive UI logic.
|
| - m_hiddenForThrottling = hidden && !frameRect().isEmpty();
|
| - m_subtreeThrottled = subtreeThrottled;
|
| -
|
| - bool isThrottled = canThrottleRendering();
|
| - bool becameUnthrottled = wasThrottled && !isThrottled;
|
| -
|
| - // If this FrameView became unthrottled or throttled, we must make sure all
|
| - // its children are notified synchronously. Otherwise we 1) might attempt to
|
| - // paint one of the children with an out-of-date layout before
|
| - // |updateRenderThrottlingStatus| has made it throttled or 2) fail to
|
| - // unthrottle a child whose parent is unthrottled by a later notification.
|
| - if (wasThrottled != isThrottled) {
|
| - for (const Member<Widget>& child : *children()) {
|
| - if (child->isFrameView()) {
|
| - FrameView* childView = toFrameView(child);
|
| - childView->updateRenderThrottlingStatus(
|
| - childView->m_hiddenForThrottling, isThrottled);
|
| - }
|
| - }
|
| - }
|
| -
|
| + updateThrottlingStatus();
|
| +
|
| + bool becameThrottled = !wasThrottled && canThrottleRendering();
|
| + bool becameUnthrottled = wasThrottled && !canThrottleRendering();
|
| ScrollingCoordinator* scrollingCoordinator = this->scrollingCoordinator();
|
| + if (becameThrottled) {
|
| + // If this FrameView became throttled, we must make sure all of its
|
| + // children become throttled at the same time. Otherwise we might
|
| + // attempt to paint one of the children with an out-of-date layout
|
| + // before |notifyRenderThrottlingObservers| has made it throttled.
|
| + forAllNonThrottledFrameViews([](FrameView& frameView) {
|
| + frameView.m_subtreeThrottled = true;
|
| + DCHECK(frameView.canThrottleRendering());
|
| + });
|
| + }
|
| if (becameUnthrottled) {
|
| // ScrollingCoordinator needs to update according to the new throttling
|
| // status.
|
| @@ -4440,39 +4513,9 @@
|
| hasHandlers)
|
| scrollingCoordinator->touchEventTargetRectsDidChange();
|
|
|
| - FrameView* parent = parentFrameView();
|
| - if (frame().document()->frame()) {
|
| - if (!parent) {
|
| - HTMLFrameOwnerElement* element = frame().deprecatedLocalOwner();
|
| - if (!element)
|
| - frame().document()->maybeRecordLoadReason(WouldLoadOutOfProcess);
|
| - // Having no layout object means the frame is not drawn.
|
| - else if (!element->layoutObject())
|
| - frame().document()->maybeRecordLoadReason(WouldLoadDisplayNone);
|
| - } else {
|
| - // Assume the main frame has always loaded since we don't track its
|
| - // visibility.
|
| - bool parentLoaded =
|
| - !parent->parentFrameView() ||
|
| - parent->frame().document()->wouldLoadReason() > Created;
|
| - // If the parent wasn't loaded, the children won't be either.
|
| - if (parentLoaded) {
|
| - if (frameRect().isEmpty())
|
| - frame().document()->maybeRecordLoadReason(WouldLoadZeroByZero);
|
| - else if (frameRect().maxY() < 0 && frameRect().maxX() < 0)
|
| - frame().document()->maybeRecordLoadReason(WouldLoadAboveAndLeft);
|
| - else if (frameRect().maxY() < 0)
|
| - frame().document()->maybeRecordLoadReason(WouldLoadAbove);
|
| - else if (frameRect().maxX() < 0)
|
| - frame().document()->maybeRecordLoadReason(WouldLoadLeft);
|
| - else if (!m_hiddenForThrottling)
|
| - frame().document()->maybeRecordLoadReason(WouldLoadVisible);
|
| - }
|
| - }
|
| - }
|
| -
|
| #if DCHECK_IS_ON()
|
| // Make sure we never have an unthrottled frame inside a throttled one.
|
| + FrameView* parent = parentFrameView();
|
| while (parent) {
|
| DCHECK(canThrottleRendering() || !parent->canThrottleRendering());
|
| parent = parent->parentFrameView();
|
| @@ -4487,14 +4530,8 @@
|
| bool FrameView::canThrottleRendering() const {
|
| if (!RuntimeEnabledFeatures::renderingPipelineThrottlingEnabled())
|
| return false;
|
| - // We only throttle the rendering pipeline in cross-origin frames. This is
|
| - // to avoid a situation where an ancestor frame directly depends on the
|
| - // pipeline timing of a descendant and breaks as a result of throttling.
|
| - // The rationale is that cross-origin frames must already communicate with
|
| - // asynchronous messages, so they should be able to tolerate some delay in
|
| - // receiving replies from a throttled peer.
|
| return m_subtreeThrottled ||
|
| - (m_hiddenForThrottling && m_frame->isCrossOriginSubframe());
|
| + (m_hiddenForThrottling && m_crossOriginForThrottling);
|
| }
|
|
|
| void FrameView::setInitialViewportSize(const IntSize& viewportSize) {
|
|
|