Commit e952fd3e authored by David Bokan's avatar David Bokan Committed by Commit Bot

[root-scroller] Select implicit from scrollable elements

To make implicit root scrollers promote and demote more reliably we
need to separate the notion of being a valid candidate from being a
valid root scroller. Currently, we build a list of candidates at
layout that we restrict to only valid implicit root scrollers. When
layout is finished we evaluate this list to determine which of the
elements will be promoted. Those that are no longer valid are removed
from the list.

Unfortunately this doesn't work too well since validity can change for
non-local reasons. For example, a parent may change its transform,
making its child viewport-filling. Thus, to make this work we'd have
to reevaluate candidates and validity everytime virtually anything on
the page changes.

This patch changes this method to make any currently scrollable element
a valid candidate. The list of candidates is maintained by listening to
PaintLayerScrollableAreas for when they become scrollable (allow
scrolling and have overflow), rather than hooking into layout for every
object. This list should be fairly small but it's more stable over the
life of a page so we can be more confident it truly contains all the
candidates we care about when we get to reevaluating which, if any,
should be promoted.

Bug: 838683
Cq-Include-Trybots: master.tryserver.blink:linux_trusty_blink_rel;master.tryserver.chromium.linux:linux_layout_tests_slimming_paint_v2
Change-Id: I6d5c3d876d885fec638b46e992f3026e418737b7
Reviewed-on: https://chromium-review.googlesource.com/1038648
Commit-Queue: David Bokan <bokan@chromium.org>
Reviewed-by: default avatarChris Harrelson <chrishtr@chromium.org>
Reviewed-by: default avatarPhilip Rogers <pdr@chromium.org>
Cr-Commit-Position: refs/heads/master@{#559495}
parent 49873f22
......@@ -420,11 +420,6 @@ void LayoutBlock::UpdateAfterLayout() {
InvalidateStickyConstraints();
LayoutBox::UpdateAfterLayout();
// Must happen after LayoutBox::UpdateAfterLayout since overflow and
// scrollability is calculated there.
if (RuntimeEnabledFeatures::ImplicitRootScrollerEnabled() && GetNode())
GetDocument().GetRootScrollerController().ConsiderForImplicit(*GetNode());
}
void LayoutBlock::UpdateLayout() {
......
......@@ -61,11 +61,4 @@ void LayoutIFrame::UpdateLayout() {
ClearNeedsLayout();
}
void LayoutIFrame::UpdateAfterLayout() {
if (RuntimeEnabledFeatures::ImplicitRootScrollerEnabled() && GetNode())
GetDocument().GetRootScrollerController().ConsiderForImplicit(*GetNode());
LayoutEmbeddedContent::UpdateAfterLayout();
}
} // namespace blink
......@@ -41,7 +41,6 @@ class LayoutIFrame final : public LayoutEmbeddedContent {
bool IsInlineBlockOrInlineTable() const override;
void UpdateLayout() override;
void UpdateAfterLayout() override;
bool IsOfType(LayoutObjectType type) const override {
return type == kLayoutObjectLayoutIFrame ||
......
......@@ -142,6 +142,30 @@ tree construction relative to the outer viewport's bounds\_delta mentioned
above will apply to any layer marked with this bit. This makes position: fixed
layers for all rootScroller ancestors get translated by the URL bar delta.
### Implicit Promotion
When the ImplicitRootScrollerEnabled setting is enabled, Blink may choose an
element on the page to make the effective root scroller, without input from
the page itself. This attempts to provide a better experience on existing pages
that use an iframe or div to scroll the content of the page.
#### How it works
- When a PaintLayerScrollableArea becomes scrollable - that is, it has
overflow: auto or scroll and actuallly has overflowing content - it adds
itself to the RootScrollerController's implicit candidate list.
- At root scroller evaluation time, we look at each candidate. Those that are
no longer valid candidates (i.e. no longer scroll overflow) are removed from
the list.
- From the remaining entries, we look only at candidates that meet some
additional criteria, specified by IsValidImplicit().
- If more than one candidate is a valid implicit, we promote the one with the
higher z-index. If there's a tie, we don't promote any elements implicitly.
## ViewportScrollCallback
The ViewportScrollCallback is a Scroll Customization apply-scroll callback
......
......@@ -66,6 +66,36 @@ bool FillsViewport(const Element& element, bool check_location) {
return bounding_box.Location() == LayoutPoint::Zero();
}
// If the element is an iframe this grabs the ScrollableArea for the owned
// LayoutView.
PaintLayerScrollableArea* GetScrollableArea(const Element& element) {
if (element.IsFrameOwnerElement()) {
const HTMLFrameOwnerElement* frame_owner =
ToHTMLFrameOwnerElement(&element);
if (!frame_owner->ContentFrame())
return nullptr;
if (!frame_owner->ContentFrame()->IsLocalFrame())
return nullptr;
LocalFrameView* frame_view =
ToLocalFrameView(frame_owner->OwnedEmbeddedContentView());
if (!frame_view)
return nullptr;
// Not supported with RLS turned off.
if (!RuntimeEnabledFeatures::RootLayerScrollingEnabled())
return nullptr;
return ToPaintLayerScrollableArea(
frame_view->LayoutViewportScrollableArea());
}
DCHECK(element.GetLayoutObject()->IsBox());
return ToLayoutBox(element.GetLayoutObject())->GetScrollableArea();
}
} // namespace
// static
......@@ -140,11 +170,6 @@ void RootScrollerController::DidResizeFrameView() {
void RootScrollerController::DidUpdateIFrameFrameView(
HTMLFrameOwnerElement& element) {
if (RuntimeEnabledFeatures::ImplicitRootScrollerEnabled()) {
ConsiderForImplicit(element);
ProcessImplicitCandidates();
}
if (&element != root_scroller_.Get() && &element != implicit_root_scroller_)
return;
......@@ -235,6 +260,25 @@ bool RootScrollerController::IsValidRootScroller(const Element& element) const {
return true;
}
bool RootScrollerController::IsValidImplicitCandidate(
const Element& element) const {
if (!element.IsInTreeScope())
return false;
if (!element.GetLayoutObject())
return false;
// Ignore anything inside a FlowThread (multi-col, paginated, etc.).
if (element.GetLayoutObject()->IsInsideFlowThread())
return false;
PaintLayerScrollableArea* scrollable_area = GetScrollableArea(element);
if (!scrollable_area || !scrollable_area->ScrollsOverflow())
return false;
return true;
}
bool RootScrollerController::IsValidImplicit(const Element& element) const {
// Valid implicit root scroller are a subset of valid root scrollers.
if (!IsValidRootScroller(element))
......@@ -248,12 +292,11 @@ bool RootScrollerController::IsValidImplicit(const Element& element) const {
if (style->HasOpacity() || style->Visibility() != EVisibility::kVisible)
return false;
// Valid, visible iframes can always be implicitly promoted.
if (element.IsFrameOwnerElement())
return true;
PaintLayerScrollableArea* scrollable_area = GetScrollableArea(element);
if (!scrollable_area)
return false;
LayoutBox* box = ToLayoutBox(element.GetLayoutObject());
return box->GetScrollableArea()->ScrollsOverflow();
return scrollable_area->ScrollsOverflow();
}
void RootScrollerController::ApplyRootScrollerProperties(Node& node) {
......@@ -334,7 +377,8 @@ void RootScrollerController::ProcessImplicitCandidates() {
HeapHashSet<WeakMember<Element>> copy(implicit_candidates_);
for (auto& element : copy) {
if (!IsValidImplicit(*element)) {
implicit_candidates_.erase(element);
if (!IsValidImplicitCandidate(*element))
implicit_candidates_.erase(element);
continue;
}
......@@ -387,23 +431,10 @@ void RootScrollerController::ConsiderForImplicit(Node& node) {
if (document_->GetPage()->GetChromeClient().IsPopup())
return;
if (!node.IsElementNode() || !node.GetLayoutObject())
if (!node.IsElementNode())
return;
DCHECK(node.GetLayoutObject()->IsBox());
if (!node.IsFrameOwnerElement()) {
LayoutBox* box = ToLayoutBox(node.GetLayoutObject());
if (!box->GetScrollableArea())
return;
if (!box->GetScrollableArea()->ScrollsOverflow())
return;
}
// Check only the size since we wont have location information at this point
// in layout.
if (!FillsViewport(ToElement(node), false))
if (!IsValidImplicitCandidate(ToElement(node)))
return;
implicit_candidates_.insert(&ToElement(node));
......
......@@ -118,6 +118,14 @@ class CORE_EXPORT RootScrollerController
// set as the root scroller (in addition to being a valid root scroller).
bool IsValidImplicit(const Element&) const;
// Determines whether the given element is eligable as a candidate to be
// implicitly promoted. Intuitively, thiis is any "live" scroller on the page.
// We add these to a list of candidates and after layout we go through the
// list and promote the best candidate that satisfies the more exhaustive
// conditions set by IsValidImplicit above. At that time we also prune the
// list of any elements that no longer satisfy IsValidImplicitCandidate.
bool IsValidImplicitCandidate(const Element&) const;
// Set certain properties to the effective root scroller. Called when a Node
// becomes or unbecomes the effective root scroller.
void ApplyRootScrollerProperties(Node&);
......
......@@ -2106,6 +2106,21 @@ void PaintLayerScrollableArea::UpdateScrollableAreaSet() {
if (did_scroll_overflow == ScrollsOverflow())
return;
if (RuntimeEnabledFeatures::ImplicitRootScrollerEnabled() &&
scrolls_overflow_) {
if (GetLayoutBox()->IsLayoutView()) {
if (Element* owner = GetLayoutBox()->GetDocument().LocalOwner()) {
owner->GetDocument().GetRootScrollerController().ConsiderForImplicit(
*owner);
}
} else {
GetLayoutBox()
->GetDocument()
.GetRootScrollerController()
.ConsiderForImplicit(*GetLayoutBox()->GetNode());
}
}
// The scroll and scroll offset properties depend on |scrollsOverflow| (see:
// PaintPropertyTreeBuilder::updateScrollAndScrollTranslation).
GetLayoutBox()->SetNeedsPaintPropertyUpdate();
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment