Chromium Code Reviews| 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 |