Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(323)

Unified Diff: third_party/WebKit/Source/core/frame/FrameView.cpp

Issue 2272773002: Use intersection observer to control frame throttling (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Adjust OOPIF expectations Created 4 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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..4f9e156aebba811deca31bc342b06bde8a838007 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,31 @@ void FrameView::init()
setCanHaveScrollbars(false);
}
+void FrameView::setupRenderThrottling()
+{
+ if (m_intersectionObserver || !frame().document() || !frame().document()->frame() || !frame().document()->documentElement())
+ return;
+
szager1 2016/08/30 23:57:12 if (frame().isLocalRoot()) return;
Sami 2016/08/31 11:08:56 Any particular reason why? That would mean we don'
szager1 2016/08/31 20:54:26 OK; that's a weird scenario, but if that's the beh
+ IntersectionObserverInit observerInit;
+ // Intersection observer does not currently support remote frames. As a
szager1 2016/08/30 23:57:12 Is there a nice way to combine this with the stati
Sami 2016/08/31 11:08:55 I gave this a try but couldn't find a good way to
+ // 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());
+
+ IntersectionObserverCallback* callback = new IntersectionObserverCallback(this);
+ 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 +334,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 +3213,8 @@ void FrameView::setParent(Widget* parentView)
updateScrollableAreaSet();
setNeedsUpdateViewportIntersection();
+
+ setupRenderThrottling();
}
void FrameView::removeChild(Widget* child)
@@ -4168,58 +4193,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 +4215,39 @@ void FrameView::updateViewportIntersectionsForSubtree(DocumentLifecycle::Lifecyc
}
}
-void FrameView::updateThrottlingStatus()
+void FrameView::updateRenderThrottlingStatusForTesting()
{
- // 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()
-{
- 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 +4286,39 @@ 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)
+ : m_frameView(std::move(frameView))
szager1 2016/08/30 23:57:12 Why std::move?
Sami 2016/08/31 11:08:55 Oops, looks like I'm typing it out of habit alread
+{
+}
+
+void FrameView::IntersectionObserverCallback::handleEvent(const HeapVector<Member<IntersectionObserverEntry>>& entries, IntersectionObserver&)
+{
+ bool wasHidden = m_frameView->m_hiddenForThrottling;
+ bool isHidden = wasHidden;
+ for (const auto& entry : entries)
szager1 2016/08/30 23:57:11 if (entries.length()) isHidden = !entries.last()
Sami 2016/08/31 11:08:56 Neat, thanks!
+ isHidden = !entry->intersectionRatio();
+ if (wasHidden != isHidden)
+ m_frameView->updateRenderThrottlingStatus(isHidden, m_frameView->m_subtreeThrottled);
szager1 2016/08/30 23:57:12 I would expect that you need a friend declaration
Sami 2016/08/31 11:08:55 This is an inner class and since C++11 it's automa
+}
+
+ExecutionContext* FrameView::IntersectionObserverCallback::getExecutionContext() const
+{
+ return m_frameView->frame().document();
szager1 2016/08/30 23:57:12 I'm not sure it matters, which ExecutionContext is
Sami 2016/08/31 11:08:56 I wasn't sure about that either. I've now made it
+}
+
+DEFINE_TRACE(FrameView::IntersectionObserverCallback)
+{
+ blink::IntersectionObserverCallback::trace(visitor);
+ visitor->trace(m_frameView);
}
} // namespace blink

Powered by Google App Engine
This is Rietveld 408576698