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 d53a8a73c432037dc734dc2e4868b9d675873f84..1f23640e13d02aa4fb5a5aa37cb655337ae03fd4 100644 |
--- a/third_party/WebKit/Source/core/frame/FrameView.cpp |
+++ b/third_party/WebKit/Source/core/frame/FrameView.cpp |
@@ -93,9 +93,11 @@ |
#include "platform/graphics/GraphicsLayer.h" |
#include "platform/graphics/GraphicsLayerDebugInfo.h" |
#include "platform/graphics/paint/DisplayItemList.h" |
+#include "platform/scheduler/CancellableTaskFactory.h" |
#include "platform/scroll/ScrollAnimator.h" |
#include "platform/text/TextStream.h" |
#include "public/platform/WebDisplayItemList.h" |
+#include "public/platform/WebFrameScheduler.h" |
#include "wtf/CurrentTime.h" |
#include "wtf/StdLibExtras.h" |
#include "wtf/TemporaryChange.h" |
@@ -119,6 +121,7 @@ FrameView::FrameView(LocalFrame* frame) |
, m_inSynchronousPostLayout(false) |
, m_postLayoutTasksTimer(this, &FrameView::postLayoutTimerFired) |
, m_updateWidgetsTimer(this, &FrameView::updateWidgetsTimerFired) |
+ , m_intersectionObserverNotificationFactory(CancellableTaskFactory::create(this, &FrameView::notifyIntersectionObservers)) |
, m_isTransparent(false) |
, m_baseBackgroundColor(Color::white) |
, m_mediaType(MediaTypeNames::screen) |
@@ -130,6 +133,7 @@ FrameView::FrameView(LocalFrame* frame) |
, m_didScrollTimer(this, &FrameView::didScrollTimerFired) |
, m_topControlsViewportAdjustment(0) |
, m_needsUpdateWidgetPositions(false) |
+ , m_needsUpdateViewportIntersection(true) |
#if ENABLE(ASSERT) |
, m_hasBeenDisposed(false) |
#endif |
@@ -141,7 +145,9 @@ FrameView::FrameView(LocalFrame* frame) |
, m_scrollbarsSuppressed(false) |
, m_inUpdateScrollbars(false) |
, m_frameTimingRequestsDirty(true) |
- |
+ , m_viewportIntersectionValid(false) |
+ , m_viewportVisibilityForThrottling(ViewportVisibility::Visible) |
+ , m_securityOriginStatusForThrottling(SecurityOriginStatus::IsSameOrigin) |
{ |
ASSERT(m_frame); |
init(); |
@@ -267,6 +273,7 @@ void FrameView::dispose() |
if (m_didScrollTimer.isActive()) |
m_didScrollTimer.stop(); |
+ m_intersectionObserverNotificationFactory->cancel(); |
// FIXME: Do we need to do something here for OOPI? |
HTMLFrameOwnerElement* ownerElement = m_frame->deprecatedLocalOwner(); |
@@ -877,7 +884,7 @@ void FrameView::layout() |
ScriptForbiddenScope forbidScript; |
- if (isInPerformLayout() || !m_frame->document()->isActive()) |
+ if (isInPerformLayout() || !m_frame->document()->isActive() || shouldThrottleStyleLayoutAndCompositingUpdates()) |
return; |
TRACE_EVENT0("blink,benchmark", "FrameView::layout"); |
@@ -1061,6 +1068,9 @@ void FrameView::layout() |
// See http://crbug.com/306706 |
void FrameView::invalidateTreeIfNeeded(PaintInvalidationState& paintInvalidationState) |
{ |
+ if (shouldThrottleRenderingPipeline()) |
+ return; |
+ |
lifecycle().advanceTo(DocumentLifecycle::InPaintInvalidation); |
ASSERT(layoutView()); |
@@ -1270,6 +1280,8 @@ bool FrameView::shouldSetCursor() const |
void FrameView::scrollContentsIfNeededRecursive() |
{ |
+ if (shouldThrottleStyleLayoutAndCompositingUpdates()) |
+ return; |
scrollContentsIfNeeded(); |
for (Frame* child = m_frame->tree().firstChild(); child; child = child->tree().nextSibling()) { |
@@ -1280,6 +1292,17 @@ void FrameView::scrollContentsIfNeededRecursive() |
} |
} |
+void FrameView::setLifecycleThrottlingModeForSubtree(DocumentLifecycle::ThrottlingMode throttlingMode) |
+{ |
+ ASSERT(m_frame->isLocalRoot()); |
+ for (Frame* frame = m_frame.get(); frame; frame = frame->tree().traverseNext(m_frame.get())) { |
+ if (!frame->isLocalFrame()) |
+ continue; |
+ if (FrameView* view = toLocalFrame(frame)->view()) |
+ view->lifecycle().setThrottlingMode(throttlingMode); |
+ } |
+} |
+ |
bool FrameView::invalidateViewportConstrainedObjects() |
{ |
for (const auto& viewportConstrainedObject : *m_viewportConstrainedObjects) { |
@@ -1690,7 +1713,8 @@ void FrameView::scheduleRelayout() |
return; |
m_hasPendingLayout = true; |
- page()->animator().scheduleVisualUpdate(m_frame.get()); |
+ if (!shouldThrottleRenderingPipeline()) |
+ page()->animator().scheduleVisualUpdate(m_frame.get()); |
lifecycle().ensureStateAtMost(DocumentLifecycle::StyleClean); |
} |
@@ -2380,23 +2404,23 @@ void FrameView::updateWidgetPositionsIfNeeded() |
updateWidgetPositions(); |
} |
-void FrameView::updateAllLifecyclePhases(const LayoutRect& interestRect) |
+void FrameView::updateAllLifecyclePhases(DocumentLifecycle::ThrottlingMode throttlingMode, const LayoutRect& interestRect) |
{ |
- frame().localFrameRoot()->view()->updateLifecyclePhasesInternal(AllPhases, interestRect); |
+ frame().localFrameRoot()->view()->updateLifecyclePhasesInternal(AllPhases, throttlingMode, interestRect); |
} |
// TODO(chrishtr): add a scrolling update lifecycle phase. |
-void FrameView::updateLifecycleToCompositingCleanPlusScrolling() |
+void FrameView::updateLifecycleToCompositingCleanPlusScrolling(DocumentLifecycle::ThrottlingMode throttlingMode) |
{ |
- frame().localFrameRoot()->view()->updateLifecyclePhasesInternal(OnlyUpToCompositingCleanPlusScrolling); |
+ frame().localFrameRoot()->view()->updateLifecyclePhasesInternal(OnlyUpToCompositingCleanPlusScrolling, throttlingMode, LayoutRect::infiniteRect()); |
} |
-void FrameView::updateLifecycleToLayoutClean() |
+void FrameView::updateLifecycleToLayoutClean(DocumentLifecycle::ThrottlingMode throttlingMode) |
{ |
- frame().localFrameRoot()->view()->updateLifecyclePhasesInternal(OnlyUpToLayoutClean); |
+ frame().localFrameRoot()->view()->updateLifecyclePhasesInternal(OnlyUpToLayoutClean, throttlingMode, LayoutRect::infiniteRect()); |
} |
-void FrameView::updateLifecyclePhasesInternal(LifeCycleUpdateOption phases, const LayoutRect& interestRect) |
+void FrameView::updateLifecyclePhasesInternal(LifeCycleUpdateOption phases, DocumentLifecycle::ThrottlingMode throttlingMode, const LayoutRect& interestRect) |
{ |
// This must be called from the root frame, since it recurses down, not up. |
// Otherwise the lifecycles of the frames might be out of sync. |
@@ -2405,13 +2429,12 @@ void FrameView::updateLifecyclePhasesInternal(LifeCycleUpdateOption phases, cons |
// Updating layout can run script, which can tear down the FrameView. |
RefPtrWillBeRawPtr<FrameView> protector(this); |
+ setLifecycleThrottlingModeForSubtree(throttlingMode); |
ojan
2015/10/13 23:31:29
This call and the one below are unconditional walk
Sami
2015/10/14 16:52:11
Yeah, I wasn't crazy about the extra tree walks ei
|
updateStyleAndLayoutIfNeededRecursive(); |
ASSERT(lifecycle().state() >= DocumentLifecycle::LayoutClean); |
- if (phases == OnlyUpToLayoutClean) |
- return; |
- |
- if (LayoutView* view = layoutView()) { |
+ LayoutView* view = layoutView(); |
+ if (view && phases != OnlyUpToLayoutClean) { |
TRACE_EVENT1("devtools.timeline", "UpdateLayerTree", "data", InspectorUpdateLayerTreeEvent::data(m_frame.get())); |
// This was required for slimming paint v1 but is only temporarily |
@@ -2447,6 +2470,9 @@ void FrameView::updateLifecyclePhasesInternal(LifeCycleUpdateOption phases, cons |
|| (RuntimeEnabledFeatures::slimmingPaintSynchronizedPaintingEnabled() && lifecycle().state() == DocumentLifecycle::PaintClean)); |
} |
} |
+ |
+ updateViewportIntersectionsForSubtree(); |
+ setLifecycleThrottlingModeForSubtree(DocumentLifecycle::ThrottlingMode::Disallow); |
} |
void FrameView::updatePaintProperties() |
@@ -2530,6 +2556,9 @@ void FrameView::updateFrameTimingRequestsIfNeeded() |
void FrameView::updateStyleAndLayoutIfNeededRecursive() |
{ |
+ if (shouldThrottleStyleLayoutAndCompositingUpdates()) |
+ return; |
+ |
// We have to crawl our entire subtree looking for any FrameViews that need |
// layout and make sure they are up to date. |
// Mac actually tests for intersection with the dirty region and tries not to |
@@ -2584,6 +2613,11 @@ void FrameView::updateStyleAndLayoutIfNeededRecursive() |
void FrameView::invalidateTreeIfNeededRecursive() |
{ |
ASSERT(layoutView()); |
+ |
+ // We need to stop recursing here since a child frame view might not be throttled |
+ // even though we are (e.g., it didn't compute its visibility yet). |
+ if (shouldThrottleRenderingPipeline()) |
+ return; |
TRACE_EVENT1("blink", "FrameView::invalidateTreeIfNeededRecursive", "root", layoutView()->debugName().ascii()); |
Vector<LayoutObject*> pendingDelayedPaintInvalidations; |
@@ -2902,6 +2936,7 @@ void FrameView::setParent(Widget* parentView) |
toFrameView(parent())->adjustScrollbarsAvoidingResizerCount(m_scrollbarsAvoidingResizer); |
updateScrollableAreaSet(); |
+ m_needsUpdateViewportIntersection = true; |
} |
void FrameView::removeChild(Widget* child) |
@@ -2959,6 +2994,7 @@ void FrameView::frameRectsChanged() |
if (layoutSizeFixedToFrameSize()) |
setLayoutSizeInternal(frameRect().size()); |
+ m_needsUpdateViewportIntersection = true; |
for (const auto& child : m_children) |
child->frameRectsChanged(); |
} |
@@ -3735,11 +3771,16 @@ void FrameView::paint(GraphicsContext* context, const IntRect& rect) const |
void FrameView::paint(GraphicsContext* context, const GlobalPaintFlags globalPaintFlags, const IntRect& rect) const |
{ |
+ // TODO(skyostil): Remove this early-out in favor of painting cached scrollbars. |
+ if (shouldThrottleRenderingPipeline()) |
+ return; |
FramePainter(*this).paint(context, globalPaintFlags, rect); |
} |
void FrameView::paintContents(GraphicsContext* context, const GlobalPaintFlags globalPaintFlags, const IntRect& damageRect) const |
{ |
+ if (shouldThrottleRenderingPipeline()) |
+ return; |
FramePainter(*this).paintContents(context, globalPaintFlags, damageRect); |
} |
@@ -3927,6 +3968,9 @@ void FrameView::collectFrameTimingRequests(GraphicsLayerFrameTimingRequests& gra |
LocalFrame* localFrame = toLocalFrame(frame); |
LayoutRect viewRect = localFrame->contentLayoutObject()->viewRect(); |
const LayoutBoxModelObject* paintInvalidationContainer = localFrame->contentLayoutObject()->containerForPaintInvalidation(); |
+ // If the frame is being throttled, its compositing state may not be up to date. |
+ if (!paintInvalidationContainer->enclosingLayer()->isAllowedToQueryCompositingState()) |
+ return; |
const GraphicsLayer* graphicsLayer = paintInvalidationContainer->enclosingLayer()->graphicsLayerBacking(); |
if (!graphicsLayer) |
@@ -3937,4 +3981,108 @@ void FrameView::collectFrameTimingRequests(GraphicsLayerFrameTimingRequests& gra |
graphicsLayerTimingRequests.add(graphicsLayer, Vector<std::pair<int64_t, WebRect>>()).storedValue->value.append(std::make_pair(m_frame->frameID(), enclosingIntRect(viewRect))); |
} |
+void FrameView::updateViewportIntersectionIfNeeded() |
+{ |
+ // TODO(skyostil): Replace this with a real intersection observer. |
+ |
+ // Make sure we have valid layout data in our parent view. |
+ ASSERT(!parentFrameView() || parentFrameView()->lifecycle().state() >= DocumentLifecycle::LayoutClean); |
+ if (!m_needsUpdateViewportIntersection) |
+ return; |
+ |
+ // Our frame's bounds in our parent's content coordinates. |
+ m_viewportIntersection = frameRect(); |
+ m_needsUpdateViewportIntersection = false; |
+ m_viewportIntersectionValid = true; |
+ |
+ FrameView* parent = parentFrameView(); |
+ if (!parent) |
+ 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. |
+ m_viewportIntersection = parent->contentsToRootFrame(m_viewportIntersection); |
+ |
+ // 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() |
+{ |
+ bool hadValidIntersection = m_viewportIntersectionValid; |
+ bool hadEmptyIntersection = m_viewportIntersection.isEmpty(); |
+ updateViewportIntersectionIfNeeded(); |
+ bool shouldNotify = !hadValidIntersection || hadEmptyIntersection != m_viewportIntersection.isEmpty(); |
+ if (shouldNotify && !m_intersectionObserverNotificationFactory->isPending()) |
+ m_frame->frameScheduler()->timerTaskRunner()->postTask(FROM_HERE, m_intersectionObserverNotificationFactory->cancelAndCreate()); |
+ |
+ // If this view is being throttled, don't recurse to update viewport |
+ // intersections for its children because their frame coordinates may not |
+ // be up to date. |
+ if (shouldThrottleStyleLayoutAndCompositingUpdates()) |
+ return; |
+ |
+ for (Frame* child = m_frame->tree().firstChild(); child; child = child->tree().nextSibling()) { |
+ if (!child->isLocalFrame()) |
+ continue; |
+ if (FrameView* view = toLocalFrame(child)->view()) |
+ view->updateViewportIntersectionsForSubtree(); |
+ } |
+} |
+ |
+void FrameView::notifyIntersectionObservers() |
+{ |
+ TRACE_EVENT0("blink", "FrameView::notifyIntersectionObservers"); |
+ ASSERT(!isInPerformLayout()); |
+ bool wasThrottled = shouldThrottleRenderingPipeline(); |
+ |
+ // Only offscreen frames can be throttled. |
+ m_viewportVisibilityForThrottling = (m_viewportIntersectionValid && m_viewportIntersection.isEmpty()) ? ViewportVisibility::Hidden : ViewportVisibility::Visible; |
+ |
+ // 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_securityOriginStatusForThrottling = SecurityOriginStatus::IsSameOrigin; |
+ Frame* parentFrame = m_frame->tree().parent(); |
+ const SecurityOrigin* origin = frame().securityContext()->securityOrigin(); |
+ while (parentFrame) { |
+ const SecurityOrigin* parentOrigin = parentFrame->securityContext()->securityOrigin(); |
+ if (!origin->canAccess(parentOrigin)) { |
+ m_securityOriginStatusForThrottling = SecurityOriginStatus::IsCrossOrigin; |
+ break; |
+ } |
+ parentFrame = parentFrame->tree().parent(); |
+ } |
+ |
+ bool becameUnthrottled = wasThrottled && !shouldThrottleRenderingPipeline(); |
+ if (becameUnthrottled) |
+ page()->animator().scheduleVisualUpdate(m_frame.get()); |
+} |
+ |
+bool FrameView::shouldThrottleStyleLayoutAndCompositingUpdates() const |
+{ |
+ return lifecycle().throttlingMode() == DocumentLifecycle::ThrottlingMode::Allow |
+ && shouldThrottleRenderingPipeline(); |
+} |
+ |
+bool FrameView::shouldThrottleRenderingPipeline() const |
+{ |
+ if (!RuntimeEnabledFeatures::renderingPipelineThrottlingEnabled()) |
+ return false; |
+ return m_viewportVisibilityForThrottling == ViewportVisibility::Hidden |
+ && m_securityOriginStatusForThrottling == SecurityOriginStatus::IsCrossOrigin; |
+} |
+ |
} // namespace blink |