Commit 8ca9a2f2 authored by Rakina Zata Amni's avatar Rakina Zata Amni Committed by Commit Bot

[DL]: Handle whitespace reattachment for locked elements

ReattachLayoutTree/RebuildLayoutTree can't be called on elements
with the NeedsStyleRecalc/ChildNeedsStyleRecalc flag set, which might
be the case for locked elements because we block style recalc.
Marking of the NeedsReattachLayoutTree/ChildNeedsReattachLayoutTree
is done inside RecalcStyle and whitespace reattachment marking.
The first case is OK because we already block RecalcStyle, this CL is
fixing the second case.

In this CL, we save a per-DisplayLockContext |whitespace_reattach_set_|
that will save locked elements needing whitespace reattachment. After
we finished style recalc on locked elements (after forced update or
when the budget allows), we will mark those elements as needing layout
tree reattachment, just like the global whitespace reattachment in
StyleEngine.

Bug: 882663, 912949
Change-Id: Ib83845601db24342a30d556689c2a08244491a33
Reviewed-on: https://chromium-review.googlesource.com/c/1460581
Commit-Queue: Rakina Zata Amni <rakina@chromium.org>
Reviewed-by: default avatarRune Lillesveen <futhark@chromium.org>
Reviewed-by: default avatarvmpstr <vmpstr@chromium.org>
Cr-Commit-Position: refs/heads/master@{#635430}
parent a20c4044
...@@ -50,6 +50,7 @@ ...@@ -50,6 +50,7 @@
#include "third_party/blink/renderer/core/css/style_sheet_contents.h" #include "third_party/blink/renderer/core/css/style_sheet_contents.h"
#include "third_party/blink/renderer/core/dom/element.h" #include "third_party/blink/renderer/core/dom/element.h"
#include "third_party/blink/renderer/core/dom/element_traversal.h" #include "third_party/blink/renderer/core/dom/element_traversal.h"
#include "third_party/blink/renderer/core/dom/flat_tree_traversal.h"
#include "third_party/blink/renderer/core/dom/layout_tree_builder_traversal.h" #include "third_party/blink/renderer/core/dom/layout_tree_builder_traversal.h"
#include "third_party/blink/renderer/core/dom/node_computed_style.h" #include "third_party/blink/renderer/core/dom/node_computed_style.h"
#include "third_party/blink/renderer/core/dom/processing_instruction.h" #include "third_party/blink/renderer/core/dom/processing_instruction.h"
...@@ -1573,6 +1574,30 @@ void StyleEngine::MarkForWhitespaceReattachment() { ...@@ -1573,6 +1574,30 @@ void StyleEngine::MarkForWhitespaceReattachment() {
for (auto element : whitespace_reattach_set_) { for (auto element : whitespace_reattach_set_) {
if (element->NeedsReattachLayoutTree() || !element->GetLayoutObject()) if (element->NeedsReattachLayoutTree() || !element->GetLayoutObject())
continue; continue;
if (RuntimeEnabledFeatures::DisplayLockingEnabled() &&
GetDocument().LockedDisplayLockCount() > 0) {
// This element might be located inside a display locked subtree, so we
// might mark it for ReattachLayoutTree later on instead.
// TODO(crbug.com/924550): Once we figure out a more efficient way to
// determine whether we're inside a locked subtree or not, change this.
bool is_in_locked_subtree = false;
for (const Node& ancestor :
FlatTreeTraversal::InclusiveAncestorsOf(*element)) {
if (!ancestor.IsElementNode())
continue;
if (auto* context = ToElement(ancestor).GetDisplayLockContext()) {
if (!context->IsLocked())
continue;
is_in_locked_subtree = true;
context->AddToWhitespaceReattachSet(*element);
break;
}
}
if (is_in_locked_subtree)
continue;
DCHECK(!element->NeedsStyleRecalc());
DCHECK(!element->ChildNeedsStyleRecalc());
}
if (Node* first_child = LayoutTreeBuilderTraversal::FirstChild(*element)) if (Node* first_child = LayoutTreeBuilderTraversal::FirstChild(*element))
first_child->MarkAncestorsWithChildNeedsReattachLayoutTree(); first_child->MarkAncestorsWithChildNeedsReattachLayoutTree();
} }
......
...@@ -78,6 +78,7 @@ void DisplayLockContext::Trace(blink::Visitor* visitor) { ...@@ -78,6 +78,7 @@ void DisplayLockContext::Trace(blink::Visitor* visitor) {
visitor->Trace(acquire_resolver_); visitor->Trace(acquire_resolver_);
visitor->Trace(element_); visitor->Trace(element_);
visitor->Trace(document_); visitor->Trace(document_);
visitor->Trace(whitespace_reattach_set_);
ScriptWrappable::Trace(visitor); ScriptWrappable::Trace(visitor);
ActiveScriptWrappable::Trace(visitor); ActiveScriptWrappable::Trace(visitor);
ContextLifecycleObserver::Trace(visitor); ContextLifecycleObserver::Trace(visitor);
...@@ -294,6 +295,7 @@ void DisplayLockContext::DidStyle() { ...@@ -294,6 +295,7 @@ void DisplayLockContext::DidStyle() {
return; return;
} }
MarkElementsForWhitespaceReattachment();
// We must have "contain: style layout" for display locking. // We must have "contain: style layout" for display locking.
// Note that we should also have this containment even if we're forcing // Note that we should also have this containment even if we're forcing
// this update to happen. Otherwise, proceeding with layout may cause // this update to happen. Otherwise, proceeding with layout may cause
...@@ -514,6 +516,22 @@ std::unique_ptr<DisplayLockBudget> DisplayLockContext::CreateNewBudget() { ...@@ -514,6 +516,22 @@ std::unique_ptr<DisplayLockBudget> DisplayLockContext::CreateNewBudget() {
return nullptr; return nullptr;
} }
void DisplayLockContext::AddToWhitespaceReattachSet(Element& element) {
whitespace_reattach_set_.insert(&element);
}
void DisplayLockContext::MarkElementsForWhitespaceReattachment() {
for (auto element : whitespace_reattach_set_) {
if (!element || element->NeedsReattachLayoutTree() ||
!element->GetLayoutObject())
continue;
if (Node* first_child = LayoutTreeBuilderTraversal::FirstChild(*element))
first_child->MarkAncestorsWithChildNeedsReattachLayoutTree();
}
whitespace_reattach_set_.clear();
}
bool DisplayLockContext::MarkAncestorsForStyleRecalcIfNeeded() { bool DisplayLockContext::MarkAncestorsForStyleRecalcIfNeeded() {
if (IsElementDirtyForStyleRecalc()) { if (IsElementDirtyForStyleRecalc()) {
element_->MarkAncestorsWithChildNeedsStyleRecalc(); element_->MarkAncestorsWithChildNeedsStyleRecalc();
......
...@@ -151,6 +151,8 @@ class CORE_EXPORT DisplayLockContext final ...@@ -151,6 +151,8 @@ class CORE_EXPORT DisplayLockContext final
// right document's view. // right document's view.
void DidMoveToNewDocument(Document& old_document); void DidMoveToNewDocument(Document& old_document);
void AddToWhitespaceReattachSet(Element& element);
// LifecycleNotificationObserver overrides. // LifecycleNotificationObserver overrides.
void WillStartLifecycleUpdate() override; void WillStartLifecycleUpdate() override;
void DidFinishLifecycleUpdate() override; void DidFinishLifecycleUpdate() override;
...@@ -190,6 +192,10 @@ class CORE_EXPORT DisplayLockContext final ...@@ -190,6 +192,10 @@ class CORE_EXPORT DisplayLockContext final
// Initiate an update. // Initiate an update.
void StartUpdateIfNeeded(); void StartUpdateIfNeeded();
// Marks ancestors of elements in |whitespace_reattach_set_| with
// ChildNeedsReattachLayoutTree and clears the set.
void MarkElementsForWhitespaceReattachment();
// The following functions propagate dirty bits from the locked element up to // The following functions propagate dirty bits from the locked element up to
// the ancestors in order to be reached. They return true if the element or // the ancestors in order to be reached. They return true if the element or
// its subtree were dirty, and false otherwise. // its subtree were dirty, and false otherwise.
...@@ -252,6 +258,15 @@ class CORE_EXPORT DisplayLockContext final ...@@ -252,6 +258,15 @@ class CORE_EXPORT DisplayLockContext final
WeakMember<Element> element_; WeakMember<Element> element_;
WeakMember<Document> document_; WeakMember<Document> document_;
// See StyleEngine's |whitespace_reattach_set_|.
// Set of elements that had at least one rendered children removed
// since its last lifecycle update. For such elements that are located
// in a locked subtree, we save it here instead of the global set in
// StyleEngine because we don't want to accidentally mark elements
// in a locked subtree for layout tree reattachment before we did
// style recalc on them.
HeapHashSet<Member<Element>> whitespace_reattach_set_;
StateChangeHelper state_; StateChangeHelper state_;
LayoutRect pending_frame_rect_; LayoutRect pending_frame_rect_;
base::Optional<LayoutRect> locked_frame_rect_; base::Optional<LayoutRect> locked_frame_rect_;
......
...@@ -279,6 +279,89 @@ TEST_F(DisplayLockContextTest, LockedElementIsNotSearchableViaFindInPage) { ...@@ -279,6 +279,89 @@ TEST_F(DisplayLockContextTest, LockedElementIsNotSearchableViaFindInPage) {
client.Reset(); client.Reset();
} }
TEST_F(DisplayLockContextTest, CallUpdateStyleAndLayoutAfterChange) {
ResizeAndFocus();
SetHtmlInnerHTML(R"HTML(
<style>
#container {
width: 100px;
height: 100px;
contain: content;
}
</style>
<body><div id="container"><b>t</b>esting</div></body>
)HTML");
auto* element = GetDocument().getElementById("container");
auto* script_state = ToScriptStateForMainWorld(GetDocument().GetFrame());
{
ScriptState::Scope scope(script_state);
element->getDisplayLockForBindings()->acquire(script_state, nullptr);
}
UpdateAllLifecyclePhasesForTest();
// Sanity checks to ensure the element is locked.
EXPECT_FALSE(element->GetDisplayLockContext()->ShouldStyle());
EXPECT_FALSE(element->GetDisplayLockContext()->ShouldLayout());
EXPECT_FALSE(element->GetDisplayLockContext()->ShouldPaint());
EXPECT_EQ(GetDocument().LockedDisplayLockCount(), 1);
EXPECT_EQ(GetDocument().ActivationBlockingDisplayLockCount(), 1);
EXPECT_FALSE(element->NeedsStyleRecalc());
EXPECT_FALSE(element->ChildNeedsStyleRecalc());
EXPECT_FALSE(element->NeedsReattachLayoutTree());
EXPECT_FALSE(element->ChildNeedsReattachLayoutTree());
// Testing whitespace reattachment, shouldn't mark for reattachment.
element->firstChild()->remove();
EXPECT_FALSE(element->NeedsStyleRecalc());
EXPECT_FALSE(element->ChildNeedsStyleRecalc());
EXPECT_FALSE(element->NeedsReattachLayoutTree());
EXPECT_FALSE(element->ChildNeedsReattachLayoutTree());
GetDocument().UpdateStyleAndLayoutIgnorePendingStylesheets();
EXPECT_FALSE(element->NeedsStyleRecalc());
EXPECT_FALSE(element->ChildNeedsStyleRecalc());
EXPECT_FALSE(element->NeedsReattachLayoutTree());
EXPECT_FALSE(element->ChildNeedsReattachLayoutTree());
// Testing whitespace reattachment + dirty style.
element->SetInnerHTMLFromString("<div>something</div>");
EXPECT_FALSE(element->NeedsStyleRecalc());
EXPECT_TRUE(element->ChildNeedsStyleRecalc());
EXPECT_FALSE(element->NeedsReattachLayoutTree());
EXPECT_FALSE(element->ChildNeedsReattachLayoutTree());
GetDocument().UpdateStyleAndLayoutIgnorePendingStylesheets();
EXPECT_FALSE(element->NeedsStyleRecalc());
EXPECT_TRUE(element->ChildNeedsStyleRecalc());
EXPECT_FALSE(element->NeedsReattachLayoutTree());
EXPECT_FALSE(element->ChildNeedsReattachLayoutTree());
{
ScriptState::Scope scope(script_state);
element->getDisplayLockForBindings()->commit(script_state);
}
EXPECT_FALSE(element->NeedsStyleRecalc());
EXPECT_TRUE(element->ChildNeedsStyleRecalc());
EXPECT_FALSE(element->NeedsReattachLayoutTree());
EXPECT_FALSE(element->ChildNeedsReattachLayoutTree());
// Simulating style recalc happening, will mark for reattachment.
element->ClearChildNeedsStyleRecalc();
element->firstChild()->ClearNeedsStyleRecalc();
element->GetDisplayLockContext()->DidStyle();
EXPECT_FALSE(element->NeedsStyleRecalc());
EXPECT_FALSE(element->ChildNeedsStyleRecalc());
EXPECT_FALSE(element->NeedsReattachLayoutTree());
EXPECT_TRUE(element->ChildNeedsReattachLayoutTree());
}
TEST_F(DisplayLockContextTest, LockedElementAndDescendantsAreNotFocusable) { TEST_F(DisplayLockContextTest, LockedElementAndDescendantsAreNotFocusable) {
ResizeAndFocus(); ResizeAndFocus();
SetHtmlInnerHTML(R"HTML( SetHtmlInnerHTML(R"HTML(
......
...@@ -2442,6 +2442,7 @@ StyleRecalcChange Element::RecalcOwnStyle(const StyleRecalcChange change) { ...@@ -2442,6 +2442,7 @@ StyleRecalcChange Element::RecalcOwnStyle(const StyleRecalcChange change) {
void Element::RebuildLayoutTree(WhitespaceAttacher& whitespace_attacher) { void Element::RebuildLayoutTree(WhitespaceAttacher& whitespace_attacher) {
DCHECK(InActiveDocument()); DCHECK(InActiveDocument());
DCHECK(parentNode()); DCHECK(parentNode());
DCHECK(!StyleRecalcBlockedByDisplayLock());
if (NeedsReattachLayoutTree()) { if (NeedsReattachLayoutTree()) {
AttachContext reattach_context; AttachContext reattach_context;
......
...@@ -897,6 +897,7 @@ class CORE_EXPORT Element : public ContainerNode { ...@@ -897,6 +897,7 @@ class CORE_EXPORT Element : public ContainerNode {
DisplayLockContext* GetDisplayLockContext() const; DisplayLockContext* GetDisplayLockContext() const;
bool StyleRecalcBlockedByDisplayLock() const; bool StyleRecalcBlockedByDisplayLock() const;
void ActivateDisplayLockIfNeeded(); void ActivateDisplayLockIfNeeded();
protected: protected:
......
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