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 5cfb85f3f5f5fddc20055ed245de877eeb10987f..bbb6f05a4c93834056288d7a43c3ec90704072c6 100644 |
--- a/third_party/WebKit/Source/core/frame/FrameView.cpp |
+++ b/third_party/WebKit/Source/core/frame/FrameView.cpp |
@@ -34,7 +34,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" |
@@ -153,10 +155,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), |
@@ -169,7 +167,6 @@ FrameView::FrameView(LocalFrame* frame) |
m_topControlsViewportAdjustment(0), |
m_needsUpdateWidgetGeometries(false), |
m_needsUpdateViewportIntersection(true), |
- m_needsUpdateViewportIntersectionInSubtree(true), |
#if ENABLE(ASSERT) |
m_hasBeenDisposed(false), |
#endif |
@@ -180,9 +177,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), |
@@ -223,6 +218,7 @@ DEFINE_TRACE(FrameView) { |
visitor->trace(m_verticalScrollbar); |
visitor->trace(m_children); |
visitor->trace(m_viewportScrollableArea); |
+ visitor->trace(m_intersectionObserver); |
visitor->trace(m_scrollAnchor); |
Widget::trace(visitor); |
ScrollableArea::trace(visitor); |
@@ -283,6 +279,40 @@ void FrameView::init() { |
setCanHaveScrollbars(false); |
} |
+void FrameView::setupRenderThrottling() { |
+ if (m_intersectionObserver || !frame().document() || |
+ !frame().document()->frame() || !frame().document()->documentElement() || |
+ !frame().document()->documentElement()->isConnected()) { |
+ 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->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()); |
ojan
2016/10/14 02:25:47
This is digging into the guts of IntersectionObser
Sami
2016/10/25 17:47:12
Yeah, sounds good. I've added an explicit method f
|
+} |
+ |
void FrameView::dispose() { |
RELEASE_ASSERT(!isInPerformLayout()); |
@@ -310,8 +340,6 @@ void FrameView::dispose() { |
m_postLayoutTasksTimer.stop(); |
m_didScrollTimer.stop(); |
- m_renderThrottlingObserverNotificationFactory->cancel(); |
- |
// FIXME: Do we need to do something here for OOPI? |
HTMLFrameOwnerElement* ownerElement = m_frame->deprecatedLocalOwner(); |
// TODO(dcheng): It seems buggy that we can have an owner element that |
@@ -3342,6 +3370,10 @@ void FrameView::setParent(Widget* parentView) { |
updateScrollableAreaSet(); |
setNeedsUpdateViewportIntersection(); |
+ setupRenderThrottling(); |
+ |
+ if (parentFrameView()) |
+ m_subtreeThrottled = parentFrameView()->canThrottleRendering(); |
} |
void FrameView::removeChild(Widget* child) { |
@@ -4298,51 +4330,13 @@ void FrameView::collectAnnotatedRegions( |
} |
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()) |
@@ -4351,15 +4345,6 @@ void FrameView::updateViewportIntersectionsForSubtree( |
->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; |
@@ -4373,68 +4358,41 @@ void FrameView::updateViewportIntersectionsForSubtree( |
} |
} |
-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::updateRenderThrottlingStatusForTesting() { |
+ m_intersectionObserver->deliver(); |
} |
-void FrameView::notifyRenderThrottlingObservers() { |
- TRACE_EVENT0("blink", "FrameView::notifyRenderThrottlingObservers"); |
+void FrameView::updateRenderThrottlingStatus(bool hidden, |
+ bool subtreeThrottled) { |
+ TRACE_EVENT0("blink", "FrameView::updateRenderThrottlingStatus"); |
DCHECK(!isInPerformLayout()); |
- DCHECK(frame().document()); |
- DCHECK(!frame().document()->inStyleRecalc()); |
+ DCHECK(!m_frame->document() || !m_frame->document()->inStyleRecalc()); |
bool wasThrottled = canThrottleRendering(); |
- updateThrottlingStatus(); |
- |
- frame().document()->onVisibilityMaybeChanged(!m_hiddenForThrottling); |
+ // 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); |
+ } |
+ } |
+ } |
- 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. |
@@ -4466,6 +4424,9 @@ void FrameView::notifyRenderThrottlingObservers() { |
parent = parent->parentFrameView(); |
} |
#endif |
+ |
+ if (frame().document()->frame()) |
+ frame().document()->onVisibilityMaybeChanged(!hidden); |
} |
bool FrameView::shouldThrottleRendering() const { |
@@ -4475,8 +4436,45 @@ bool FrameView::shouldThrottleRendering() const { |
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_crossOriginForThrottling); |
+ (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 |