| 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 4bd251c5c7dc6db0a55d95c38897c38bc9441fae..25efc05adacd98f533f6369faea68ccb175647e2 100644
|
| --- a/third_party/WebKit/Source/core/frame/FrameView.cpp
|
| +++ b/third_party/WebKit/Source/core/frame/FrameView.cpp
|
| @@ -33,7 +33,9 @@
|
| #include "core/dom/AXObjectCache.h"
|
| #include "core/dom/DOMNodeIds.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"
|
| @@ -151,7 +153,6 @@ FrameView::FrameView(LocalFrame* frame)
|
| , 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)
|
| @@ -163,7 +164,6 @@ FrameView::FrameView(LocalFrame* frame)
|
| , m_didScrollTimer(this, &FrameView::didScrollTimerFired)
|
| , m_topControlsViewportAdjustment(0)
|
| , m_needsUpdateWidgetGeometries(false)
|
| - , m_needsUpdateViewportIntersection(true)
|
| , m_needsUpdateViewportIntersectionInSubtree(true)
|
| #if ENABLE(ASSERT)
|
| , m_hasBeenDisposed(false)
|
| @@ -176,9 +176,7 @@ FrameView::FrameView(LocalFrame* frame)
|
| , 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)
|
| , m_needsScrollbarsUpdate(false)
|
| @@ -223,6 +221,7 @@ DEFINE_TRACE(FrameView)
|
| visitor->trace(m_children);
|
| visitor->trace(m_viewportScrollableArea);
|
| visitor->trace(m_scrollAnchor);
|
| + visitor->trace(m_intersectionObserver);
|
| Widget::trace(visitor);
|
| ScrollableArea::trace(visitor);
|
| }
|
| @@ -280,6 +279,36 @@ void FrameView::init()
|
| setCanHaveScrollbars(false);
|
| }
|
|
|
| +void FrameView::setupRenderThrottling()
|
| +{
|
| + if (m_intersectionObserver || !frame().document() || !frame().document()->frame() || !frame().document()->documentElement())
|
| + return;
|
| +
|
| + ExecutionContext* context;
|
| + IntersectionObserverInit observerInit;
|
| + // Intersection observer does not currently support remote frames. As a
|
| + // workaround we monitor visibility up to the local root (crbug.com/615156).
|
| + Frame* mainFrame = frame().tree().top();
|
| + if (!mainFrame || !mainFrame->isLocalFrame()) {
|
| + observerInit.setRoot(frame().localFrameRoot()->document()->documentElement());
|
| + context = &observerInit.root()->document();
|
| + } else {
|
| + context = toLocalFrame(mainFrame)->document();
|
| + }
|
| +
|
| + IntersectionObserverCallback* callback = new IntersectionObserverCallback(this, context);
|
| + m_intersectionObserver = IntersectionObserver::create(observerInit, *callback, ASSERT_NO_EXCEPTION);
|
| + m_intersectionObserver->setLowLatency(true);
|
| + m_intersectionObserver->observe(frame().document()->documentElement());
|
| +
|
| + // Initially the intersection observer assumes the target is hidden. If it
|
| + // actually is, no notification about this will be delivered. To work around
|
| + // this, we set the observer into an undefined state so the first
|
| + // notification will always get delivered.
|
| + for (auto& observation : m_intersectionObserver->observations())
|
| + observation->setLastThresholdIndex(std::numeric_limits<unsigned>::max());
|
| +}
|
| +
|
| void FrameView::dispose()
|
| {
|
| RELEASE_ASSERT(!isInPerformLayout());
|
| @@ -310,7 +339,6 @@ void FrameView::dispose()
|
|
|
| if (m_didScrollTimer.isActive())
|
| m_didScrollTimer.stop();
|
| - m_renderThrottlingObserverNotificationFactory->cancel();
|
|
|
| // FIXME: Do we need to do something here for OOPI?
|
| HTMLFrameOwnerElement* ownerElement = m_frame->deprecatedLocalOwner();
|
| @@ -3190,6 +3218,8 @@ void FrameView::setParent(Widget* parentView)
|
|
|
| updateScrollableAreaSet();
|
| setNeedsUpdateViewportIntersection();
|
| +
|
| + setupRenderThrottling();
|
| }
|
|
|
| void FrameView::removeChild(Widget* child)
|
| @@ -4168,58 +4198,16 @@ void FrameView::collectAnnotatedRegions(LayoutObject& layoutObject, Vector<Annot
|
|
|
| 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) {
|
| - m_viewportIntersection = frameRect();
|
| - return;
|
| - }
|
| - ASSERT(!parent->m_needsUpdateViewportIntersection);
|
| -
|
| - // 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);
|
| -}
|
| -
|
| 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())
|
| frame().document()->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;
|
| @@ -4232,63 +4220,39 @@ void FrameView::updateViewportIntersectionsForSubtree(DocumentLifecycle::Lifecyc
|
| }
|
| }
|
|
|
| -void FrameView::updateThrottlingStatus()
|
| -{
|
| - // Only offscreen frames can be throttled.
|
| - DCHECK(m_viewportIntersectionValid);
|
| - m_hiddenForThrottling = m_viewportIntersection.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()
|
| +void FrameView::updateRenderThrottlingStatusForTesting()
|
| {
|
| - DCHECK(m_renderThrottlingObserverNotificationFactory->isPending());
|
| - notifyRenderThrottlingObservers();
|
| + m_intersectionObserver->deliver();
|
| }
|
|
|
| -void FrameView::notifyRenderThrottlingObservers()
|
| +void FrameView::updateRenderThrottlingStatus(bool hidden, bool subtreeThrottled)
|
| {
|
| TRACE_EVENT0("blink", "FrameView::notifyRenderThrottlingObservers");
|
| DCHECK(!isInPerformLayout());
|
| DCHECK(!m_frame->document() || !m_frame->document()->inStyleRecalc());
|
| bool wasThrottled = canThrottleRendering();
|
|
|
| - updateThrottlingStatus();
|
| + m_hiddenForThrottling = hidden;
|
| + 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);
|
| + }
|
| + }
|
| + }
|
|
|
| - 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.
|
| if (scrollingCoordinator)
|
| @@ -4327,7 +4291,43 @@ bool FrameView::canThrottleRendering() const
|
| {
|
| if (!RuntimeEnabledFeatures::renderingPipelineThrottlingEnabled())
|
| return false;
|
| - return m_subtreeThrottled || (m_hiddenForThrottling && m_crossOriginForThrottling);
|
| + // 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());
|
| +}
|
| +
|
| +FrameView::IntersectionObserverCallback::IntersectionObserverCallback(FrameView* frameView, ExecutionContext* context)
|
| + : m_frameView(frameView)
|
| + , m_context(context)
|
| +{
|
| +}
|
| +
|
| +void FrameView::IntersectionObserverCallback::handleEvent(const HeapVector<Member<IntersectionObserverEntry>>& entries, IntersectionObserver&)
|
| +{
|
| + if (!m_frameView)
|
| + return;
|
| + bool wasHidden = m_frameView->m_hiddenForThrottling;
|
| + bool isHidden = wasHidden;
|
| + if (!entries.isEmpty())
|
| + isHidden = !entries.last()->intersectionRatio();
|
| + if (wasHidden != isHidden)
|
| + m_frameView->updateRenderThrottlingStatus(isHidden, m_frameView->m_subtreeThrottled);
|
| +}
|
| +
|
| +ExecutionContext* FrameView::IntersectionObserverCallback::getExecutionContext() const
|
| +{
|
| + return m_context.get();
|
| +}
|
| +
|
| +DEFINE_TRACE(FrameView::IntersectionObserverCallback)
|
| +{
|
| + blink::IntersectionObserverCallback::trace(visitor);
|
| + visitor->trace(m_frameView);
|
| + visitor->trace(m_context);
|
| }
|
|
|
| } // namespace blink
|
|
|