blob: 9118d6d867fd589230d66cb9460bb78896b235dd [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/core/display_lock/display_lock_context.h"
#include <memory>
#include <utility>
#include "base/memory/ptr_util.h"
#include "base/test/scoped_feature_list.h"
#include "cc/base/features.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
#include "third_party/blink/renderer/core/css/style_change_reason.h"
#include "third_party/blink/renderer/core/display_lock/display_lock_document_state.h"
#include "third_party/blink/renderer/core/display_lock/display_lock_utilities.h"
#include "third_party/blink/renderer/core/dom/dom_token_list.h"
#include "third_party/blink/renderer/core/dom/events/native_event_listener.h"
#include "third_party/blink/renderer/core/dom/node_computed_style.h"
#include "third_party/blink/renderer/core/editing/ephemeral_range.h"
#include "third_party/blink/renderer/core/editing/finder/text_finder.h"
#include "third_party/blink/renderer/core/editing/markers/document_marker_controller.h"
#include "third_party/blink/renderer/core/editing/visible_units.h"
#include "third_party/blink/renderer/core/frame/find_in_page.h"
#include "third_party/blink/renderer/core/frame/frame_test_helpers.h"
#include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
#include "third_party/blink/renderer/core/geometry/dom_rect.h"
#include "third_party/blink/renderer/core/html/html_template_element.h"
#include "third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.h"
#include "third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.h"
#include "third_party/blink/renderer/core/paint/paint_layer.h"
#include "third_party/blink/renderer/core/style/computed_style.h"
#include "third_party/blink/renderer/core/testing/core_unit_test_helper.h"
#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
namespace blink {
namespace {
class DisplayLockTestFindInPageClient : public mojom::blink::FindInPageClient {
public:
DisplayLockTestFindInPageClient()
: find_results_are_ready_(false), active_index_(-1), count_(-1) {}
~DisplayLockTestFindInPageClient() override = default;
void SetFrame(WebLocalFrameImpl* frame) {
frame->GetFindInPage()->SetClient(receiver_.BindNewPipeAndPassRemote());
}
void SetNumberOfMatches(
int request_id,
unsigned int current_number_of_matches,
mojom::blink::FindMatchUpdateType final_update) final {
count_ = current_number_of_matches;
find_results_are_ready_ =
(final_update == mojom::blink::FindMatchUpdateType::kFinalUpdate);
}
void SetActiveMatch(int request_id,
const gfx::Rect& active_match_rect,
int active_match_ordinal,
mojom::blink::FindMatchUpdateType final_update) final {
active_match_rect_ = IntRect(active_match_rect);
active_index_ = active_match_ordinal;
find_results_are_ready_ =
(final_update == mojom::blink::FindMatchUpdateType::kFinalUpdate);
}
bool FindResultsAreReady() const { return find_results_are_ready_; }
int Count() const { return count_; }
int ActiveIndex() const { return active_index_; }
IntRect ActiveMatchRect() const { return active_match_rect_; }
void Reset() {
find_results_are_ready_ = false;
count_ = -1;
active_index_ = -1;
active_match_rect_ = IntRect();
}
private:
IntRect active_match_rect_;
bool find_results_are_ready_;
int active_index_;
int count_;
mojo::Receiver<mojom::blink::FindInPageClient> receiver_{this};
};
class DisplayLockEmptyEventListener final : public NativeEventListener {
public:
void Invoke(ExecutionContext*, Event*) final {}
};
} // namespace
class DisplayLockContextTest
: public testing::Test,
private ScopedCSSContentVisibilityHiddenMatchableForTest {
public:
DisplayLockContextTest()
: ScopedCSSContentVisibilityHiddenMatchableForTest(true) {}
void SetUp() override { web_view_helper_.Initialize(); }
void TearDown() override { web_view_helper_.Reset(); }
Document& GetDocument() {
return *static_cast<Document*>(
web_view_helper_.LocalMainFrame()->GetDocument());
}
FindInPage* GetFindInPage() {
return web_view_helper_.LocalMainFrame()->GetFindInPage();
}
WebLocalFrameImpl* LocalMainFrame() {
return web_view_helper_.LocalMainFrame();
}
void UpdateAllLifecyclePhasesForTest() {
GetDocument().View()->UpdateAllLifecyclePhasesForTest();
}
void SetHtmlInnerHTML(const char* content) {
GetDocument().documentElement()->setInnerHTML(String::FromUTF8(content));
UpdateAllLifecyclePhasesForTest();
}
void ResizeAndFocus() {
web_view_helper_.Resize(gfx::Size(640, 480));
web_view_helper_.GetWebView()->MainFrameWidget()->SetFocus(true);
test::RunPendingTasks();
}
void LockElement(Element& element, bool activatable) {
StringBuilder value;
value.Append("content-visibility: hidden");
if (activatable)
value.Append("-matchable");
element.setAttribute(html_names::kStyleAttr, value.ToAtomicString());
UpdateAllLifecyclePhasesForTest();
}
void CommitElement(Element& element, bool update_lifecycle = true) {
element.setAttribute(html_names::kStyleAttr, "");
if (update_lifecycle)
UpdateAllLifecyclePhasesForTest();
}
void UnlockImmediate(DisplayLockContext* context) {
context->SetRequestedState(EContentVisibility::kVisible);
}
mojom::blink::FindOptionsPtr FindOptions(bool new_session = true) {
auto find_options = mojom::blink::FindOptions::New();
find_options->run_synchronously_for_testing = true;
find_options->new_session = new_session;
find_options->forward = true;
return find_options;
}
void Find(String search_text,
DisplayLockTestFindInPageClient& client,
bool new_session = true) {
client.Reset();
GetFindInPage()->Find(FAKE_FIND_ID, search_text, FindOptions(new_session));
test::RunPendingTasks();
}
bool ReattachWasBlocked(DisplayLockContext* context) {
return context->reattach_layout_tree_was_blocked_;
}
const int FAKE_FIND_ID = 1;
private:
frame_test_helpers::WebViewHelper web_view_helper_;
};
TEST_F(DisplayLockContextTest, LockAfterAppendStyleDirtyBits) {
SetHtmlInnerHTML(R"HTML(
<style>
div {
width: 100px;
height: 100px;
contain: style layout paint;
}
</style>
<body><div id="container"><div id="child"></div></div></body>
)HTML");
auto* element = GetDocument().getElementById("container");
LockElement(*element, false);
// Finished acquiring the lock.
EXPECT_FALSE(element->GetDisplayLockContext()->ShouldStyleChildren());
EXPECT_FALSE(element->GetDisplayLockContext()->ShouldLayoutChildren());
EXPECT_FALSE(element->GetDisplayLockContext()->ShouldPaintChildren());
EXPECT_EQ(
GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 1);
// If the element is dirty, style recalc would handle it in the next recalc.
element->setAttribute(html_names::kStyleAttr,
"content-visibility: hidden; color: red;");
EXPECT_TRUE(GetDocument().body()->ChildNeedsStyleRecalc());
EXPECT_TRUE(element->NeedsStyleRecalc());
EXPECT_FALSE(element->ChildNeedsStyleRecalc());
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(element->NeedsStyleRecalc());
EXPECT_FALSE(element->ChildNeedsStyleRecalc());
EXPECT_TRUE(element->GetComputedStyle());
EXPECT_EQ(
element->GetComputedStyle()->VisitedDependentColor(GetCSSPropertyColor()),
MakeRGB(255, 0, 0));
// Manually commit the lock so that we can verify which dirty bits get
// propagated.
UnlockImmediate(element->GetDisplayLockContext());
element->setAttribute(html_names::kStyleAttr, "color: red;");
auto* child = GetDocument().getElementById("child");
EXPECT_TRUE(GetDocument().body()->ChildNeedsStyleRecalc());
EXPECT_TRUE(element->NeedsStyleRecalc());
EXPECT_FALSE(element->ChildNeedsStyleRecalc());
EXPECT_FALSE(child->NeedsStyleRecalc());
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(GetDocument().body()->ChildNeedsStyleRecalc());
EXPECT_FALSE(element->NeedsStyleRecalc());
EXPECT_FALSE(element->ChildNeedsStyleRecalc());
EXPECT_FALSE(child->NeedsStyleRecalc());
// Lock the child.
child->setAttribute(html_names::kStyleAttr,
"content-visibility: hidden; color: blue;");
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(GetDocument().body()->ChildNeedsStyleRecalc());
EXPECT_FALSE(element->NeedsStyleRecalc());
EXPECT_FALSE(element->ChildNeedsStyleRecalc());
EXPECT_FALSE(child->NeedsStyleRecalc());
ASSERT_TRUE(child->GetComputedStyle());
EXPECT_EQ(
child->GetComputedStyle()->VisitedDependentColor(GetCSSPropertyColor()),
MakeRGB(0, 0, 255));
UnlockImmediate(child->GetDisplayLockContext());
child->setAttribute(html_names::kStyleAttr, "color: blue;");
EXPECT_TRUE(GetDocument().body()->ChildNeedsStyleRecalc());
EXPECT_FALSE(element->NeedsStyleRecalc());
EXPECT_TRUE(element->ChildNeedsStyleRecalc());
EXPECT_TRUE(child->NeedsStyleRecalc());
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(GetDocument().body()->ChildNeedsStyleRecalc());
EXPECT_FALSE(element->NeedsStyleRecalc());
EXPECT_FALSE(element->ChildNeedsStyleRecalc());
EXPECT_FALSE(child->NeedsStyleRecalc());
ASSERT_TRUE(child->GetComputedStyle());
EXPECT_EQ(
child->GetComputedStyle()->VisitedDependentColor(GetCSSPropertyColor()),
MakeRGB(0, 0, 255));
}
TEST_F(DisplayLockContextTest, LockedElementIsNotSearchableViaFindInPage) {
ResizeAndFocus();
SetHtmlInnerHTML(R"HTML(
<style>
#container {
width: 100px;
height: 100px;
contain: style layout paint;
}
</style>
<body><div id="container">testing</div></body>
)HTML");
const String search_text = "testing";
DisplayLockTestFindInPageClient client;
client.SetFrame(LocalMainFrame());
auto* container = GetDocument().getElementById("container");
LockElement(*container, false /* activatable */);
Find(search_text, client);
EXPECT_EQ(0, client.Count());
// Check if we can find the result after we commit.
CommitElement(*container);
Find(search_text, client);
EXPECT_EQ(1, client.Count());
}
TEST_F(DisplayLockContextTest,
ActivatableLockedElementIsSearchableViaFindInPage) {
ResizeAndFocus();
SetHtmlInnerHTML(R"HTML(
<style>
.spacer {
height: 10000px;
}
#container {
width: 100px;
height: 100px;
contain: style layout paint;
}
</style>
<body><div class=spacer></div><div id="container">testing</div></body>
)HTML");
const String search_text = "testing";
DisplayLockTestFindInPageClient client;
client.SetFrame(LocalMainFrame());
// Finds on a normal element.
Find(search_text, client);
EXPECT_EQ(1, client.Count());
// Clears selections since we're going to use the same query next time.
GetFindInPage()->StopFinding(
mojom::StopFindAction::kStopFindActionClearSelection);
auto* container = GetDocument().getElementById("container");
LockElement(*container, true /* activatable */);
EXPECT_TRUE(container->GetDisplayLockContext()->IsLocked());
// Check if we can still get the same result with the same query.
Find(search_text, client);
EXPECT_EQ(1, client.Count());
EXPECT_TRUE(container->GetDisplayLockContext()->IsLocked());
EXPECT_GT(GetDocument().scrollingElement()->scrollTop(), 1000);
}
TEST_F(DisplayLockContextTest, FindInPageContinuesAfterRelock) {
ResizeAndFocus();
SetHtmlInnerHTML(R"HTML(
<style>
.spacer {
height: 10000px;
}
#container {
width: 100px;
height: 100px;
}
.auto { content-visibility: auto }
</style>
<body><div class=spacer></div><div id="container" class=auto>testing</div></body>
)HTML");
const String search_text = "testing";
DisplayLockTestFindInPageClient client;
client.SetFrame(LocalMainFrame());
// Finds on a normal element.
Find(search_text, client);
EXPECT_EQ(1, client.Count());
auto* container = GetDocument().getElementById("container");
GetDocument().scrollingElement()->setScrollTop(0);
UpdateAllLifecyclePhasesForTest();
UpdateAllLifecyclePhasesForTest();
EXPECT_TRUE(container->GetDisplayLockContext()->IsLocked());
// Clears selections since we're going to use the same query next time.
GetFindInPage()->StopFinding(
mojom::StopFindAction::kStopFindActionKeepSelection);
UpdateAllLifecyclePhasesForTest();
// This should not crash.
Find(search_text, client, false);
EXPECT_EQ(1, client.Count());
}
TEST_F(DisplayLockContextTest, FindInPageTargetBelowLockedSize) {
ResizeAndFocus();
SetHtmlInnerHTML(R"HTML(
<style>
.spacer { height: 1000px; }
#container { contain-intrinsic-size: 1px; }
.auto { content-visibility: auto }
</style>
<body>
<div class=spacer></div>
<div id=container class=auto>
<div class=spacer></div>
<div id=target>testing</div>
</div>
<div class=spacer></div>
<div class=spacer></div>
</body>
)HTML");
const String search_text = "testing";
DisplayLockTestFindInPageClient client;
client.SetFrame(LocalMainFrame());
Find(search_text, client);
EXPECT_EQ(1, client.Count());
auto* container = GetDocument().getElementById("container");
// The container should be unlocked.
EXPECT_FALSE(container->GetDisplayLockContext()->IsLocked());
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(container->GetDisplayLockContext()->IsLocked());
if (RuntimeEnabledFeatures::FractionalScrollOffsetsEnabled())
EXPECT_FLOAT_EQ(GetDocument().scrollingElement()->scrollTop(), 1768.5);
else
EXPECT_FLOAT_EQ(GetDocument().scrollingElement()->scrollTop(), 1768);
}
TEST_F(DisplayLockContextTest,
ActivatableLockedElementTickmarksAreAtLockedRoots) {
ResizeAndFocus();
SetHtmlInnerHTML(R"HTML(
<style>
body {
margin: 0;
padding: 0;
}
.small {
width: 100px;
height: 100px;
}
.medium {
width: 150px;
height: 150px;
}
.large {
width: 200px;
height: 200px;
}
</style>
<body>
testing
<div id="container1" class=small>testing</div>
<div id="container2" class=medium>testing</div>
<div id="container3" class=large>
<div id="container4" class=medium>testing</div>
</div>
<div id="container5" class=small>testing</div>
</body>
)HTML");
const String search_text = "testing";
DisplayLockTestFindInPageClient client;
client.SetFrame(LocalMainFrame());
auto* container1 = GetDocument().getElementById("container1");
auto* container2 = GetDocument().getElementById("container2");
auto* container3 = GetDocument().getElementById("container3");
auto* container4 = GetDocument().getElementById("container4");
auto* container5 = GetDocument().getElementById("container5");
LockElement(*container5, false /* activatable */);
LockElement(*container4, true /* activatable */);
LockElement(*container3, true /* activatable */);
LockElement(*container2, true /* activatable */);
LockElement(*container1, true /* activatable */);
EXPECT_TRUE(container1->GetDisplayLockContext()->IsLocked());
EXPECT_TRUE(container2->GetDisplayLockContext()->IsLocked());
EXPECT_TRUE(container3->GetDisplayLockContext()->IsLocked());
EXPECT_TRUE(container4->GetDisplayLockContext()->IsLocked());
EXPECT_TRUE(container5->GetDisplayLockContext()->IsLocked());
// Do a find-in-page.
Find(search_text, client);
// "testing" outside of the container divs, and 3 inside activatable divs.
EXPECT_EQ(4, client.Count());
auto tick_rects = GetDocument().Markers().LayoutRectsForTextMatchMarkers();
ASSERT_EQ(4u, tick_rects.size());
// Sort the layout rects by y coordinate for deterministic checks below.
std::sort(tick_rects.begin(), tick_rects.end(),
[](const IntRect& a, const IntRect& b) { return a.Y() < b.Y(); });
int y_offset = tick_rects[0].Height();
// The first tick rect will be based on the text itself, so we don't need to
// check that. The next three should be the small, medium and large rects,
// since those are the locked roots.
EXPECT_EQ(IntRect(0, y_offset, 100, 100), tick_rects[1]);
y_offset += tick_rects[1].Height();
EXPECT_EQ(IntRect(0, y_offset, 150, 150), tick_rects[2]);
y_offset += tick_rects[2].Height();
EXPECT_EQ(IntRect(0, y_offset, 200, 200), tick_rects[3]);
}
TEST_F(DisplayLockContextTest,
FindInPageWhileLockedContentChangesDoesNotCrash) {
ResizeAndFocus();
SetHtmlInnerHTML(R"HTML(
<style>
#container {
width: 100px;
height: 100px;
contain: style layout paint;
}
</style>
<body>testing<div id="container">testing</div></body>
)HTML");
const String search_text = "testing";
DisplayLockTestFindInPageClient client;
client.SetFrame(LocalMainFrame());
// Lock the container.
auto* container = GetDocument().getElementById("container");
LockElement(*container, true /* activatable */);
EXPECT_TRUE(container->GetDisplayLockContext()->IsLocked());
// Find the first "testing", container still locked since the match is outside
// the container.
Find(search_text, client);
EXPECT_EQ(2, client.Count());
EXPECT_TRUE(container->GetDisplayLockContext()->IsLocked());
// Change the inner text, this should not DCHECK.
container->setInnerHTML("please don't DCHECK");
UpdateAllLifecyclePhasesForTest();
}
TEST_F(DisplayLockContextTest, FindInPageWithChangedContent) {
if (!RuntimeEnabledFeatures::LayoutNGEnabled())
return;
ResizeAndFocus();
SetHtmlInnerHTML(R"HTML(
<style>
#container {
width: 100px;
height: 100px;
contain: style layout paint;
}
</style>
<body><div id="container">testing</div></body>
)HTML");
// Check if the result is correct if we update the contents.
auto* container = GetDocument().getElementById("container");
LockElement(*container, true /* activatable */);
EXPECT_TRUE(container->GetDisplayLockContext()->IsLocked());
container->setInnerHTML(
"testing"
"<div>testing</div>"
"tes<div style='display:none;'>x</div>ting");
DisplayLockTestFindInPageClient client;
client.SetFrame(LocalMainFrame());
Find("testing", client);
EXPECT_EQ(3, client.Count());
EXPECT_TRUE(container->GetDisplayLockContext()->IsLocked());
}
TEST_F(DisplayLockContextTest, FindInPageWithNoMatchesWontUnlock) {
ResizeAndFocus();
SetHtmlInnerHTML(R"HTML(
<style>
#container {
width: 100px;
height: 100px;
contain: style layout paint;
}
</style>
<body><div id="container">tes<div>ting</div><div style='display:none;'>testing</div></div></body>
)HTML");
auto* container = GetDocument().getElementById("container");
LockElement(*container, true /* activatable */);
LockElement(*container, true /* activatable */);
EXPECT_TRUE(container->GetDisplayLockContext()->IsLocked());
DisplayLockTestFindInPageClient client;
client.SetFrame(LocalMainFrame());
Find("testing", client);
// No results found, container stays locked.
EXPECT_EQ(0, client.Count());
EXPECT_TRUE(container->GetDisplayLockContext()->IsLocked());
}
TEST_F(DisplayLockContextTest,
NestedActivatableLockedElementIsSearchableViaFindInPage) {
ResizeAndFocus();
SetHtmlInnerHTML(R"HTML(
<body>
<style>
div {
width: 100px;
height: 100px;
contain: style layout;
}
</style>
<div id='container'>
<div>testing1</div>
<div id='activatable'>
testing2
<div id='nestedNonActivatable'>
testing3
</div>
</div>
<div id='nonActivatable'>testing4</div>
</div>
"</body>"
)HTML");
auto* container = GetDocument().getElementById("container");
auto* activatable = GetDocument().getElementById("activatable");
auto* non_activatable = GetDocument().getElementById("nonActivatable");
auto* nested_non_activatable =
GetDocument().getElementById("nestedNonActivatable");
LockElement(*non_activatable, false /* activatable */);
LockElement(*nested_non_activatable, false /* activatable */);
LockElement(*activatable, true /* activatable */);
LockElement(*container, true /* activatable */);
EXPECT_TRUE(container->GetDisplayLockContext()->IsLocked());
EXPECT_TRUE(activatable->GetDisplayLockContext()->IsLocked());
EXPECT_TRUE(non_activatable->GetDisplayLockContext()->IsLocked());
EXPECT_TRUE(nested_non_activatable->GetDisplayLockContext()->IsLocked());
// We can find testing1 and testing2.
DisplayLockTestFindInPageClient client;
client.SetFrame(LocalMainFrame());
Find("testing", client);
EXPECT_EQ(2, client.Count());
EXPECT_EQ(1, client.ActiveIndex());
EXPECT_TRUE(container->GetDisplayLockContext()->IsLocked());
EXPECT_TRUE(activatable->GetDisplayLockContext()->IsLocked());
EXPECT_TRUE(non_activatable->GetDisplayLockContext()->IsLocked());
EXPECT_TRUE(nested_non_activatable->GetDisplayLockContext()->IsLocked());
}
TEST_F(DisplayLockContextTest,
NestedActivatableLockedElementIsNotUnlockedByFindInPage) {
ResizeAndFocus();
SetHtmlInnerHTML(R"HTML(
<body>
<style>
div {
width: 100px;
height: 100px;
contain: style layout;
}
</style>
<div id='container'>
<div id='child'>testing1</div>
</div>
)HTML");
auto* container = GetDocument().getElementById("container");
auto* child = GetDocument().getElementById("child");
LockElement(*child, true /* activatable */);
LockElement(*container, true /* activatable */);
EXPECT_TRUE(container->GetDisplayLockContext()->IsLocked());
EXPECT_TRUE(child->GetDisplayLockContext()->IsLocked());
// We can find testing1 and testing2.
DisplayLockTestFindInPageClient client;
client.SetFrame(LocalMainFrame());
Find("testing", client);
EXPECT_EQ(1, client.Count());
EXPECT_EQ(1, client.ActiveIndex());
EXPECT_TRUE(container->GetDisplayLockContext()->IsLocked());
EXPECT_TRUE(child->GetDisplayLockContext()->IsLocked());
}
TEST_F(DisplayLockContextTest, CallUpdateStyleAndLayoutAfterChange) {
ResizeAndFocus();
SetHtmlInnerHTML(R"HTML(
<style>
#container {
width: 100px;
height: 100px;
contain: style layout paint;
}
</style>
<body><div id="container"><b>t</b>esting</div></body>
)HTML");
auto* element = GetDocument().getElementById("container");
LockElement(*element, false);
// Sanity checks to ensure the element is locked.
EXPECT_TRUE(element->GetDisplayLockContext()->IsLocked());
EXPECT_FALSE(element->GetDisplayLockContext()->ShouldStyleChildren());
EXPECT_FALSE(element->GetDisplayLockContext()->ShouldLayoutChildren());
EXPECT_FALSE(element->GetDisplayLockContext()->ShouldPaintChildren());
EXPECT_EQ(
GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 1);
EXPECT_EQ(GetDocument()
.GetDisplayLockDocumentState()
.DisplayLockBlockingAllActivationCount(),
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().UpdateStyleAndLayout(DocumentUpdateReason::kTest);
EXPECT_FALSE(element->NeedsStyleRecalc());
EXPECT_FALSE(element->ChildNeedsStyleRecalc());
EXPECT_FALSE(element->NeedsReattachLayoutTree());
EXPECT_FALSE(element->ChildNeedsReattachLayoutTree());
// Testing whitespace reattachment + dirty style.
element->setInnerHTML("<div>something</div>");
EXPECT_FALSE(element->NeedsStyleRecalc());
EXPECT_TRUE(element->ChildNeedsStyleRecalc());
EXPECT_FALSE(element->NeedsReattachLayoutTree());
EXPECT_FALSE(element->ChildNeedsReattachLayoutTree());
GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest);
EXPECT_FALSE(element->NeedsStyleRecalc());
EXPECT_TRUE(element->ChildNeedsStyleRecalc());
EXPECT_FALSE(element->NeedsReattachLayoutTree());
EXPECT_FALSE(element->ChildNeedsReattachLayoutTree());
// Manually start commit, so that we can verify which dirty bits get
// propagated.
UnlockImmediate(element->GetDisplayLockContext());
EXPECT_TRUE(element->ChildNeedsStyleRecalc());
EXPECT_FALSE(element->NeedsReattachLayoutTree());
EXPECT_FALSE(element->ChildNeedsReattachLayoutTree());
// Simulating style recalc happening, will mark for reattachment.
GetDocument().Lifecycle().AdvanceTo(DocumentLifecycle::kInStyleRecalc);
element->ClearChildNeedsStyleRecalc();
element->firstChild()->ClearNeedsStyleRecalc();
element->GetDisplayLockContext()->DidStyleChildren();
GetDocument().Lifecycle().AdvanceTo(DocumentLifecycle::kStyleClean);
EXPECT_FALSE(element->ChildNeedsStyleRecalc());
EXPECT_FALSE(element->NeedsReattachLayoutTree());
EXPECT_TRUE(element->ChildNeedsReattachLayoutTree());
}
TEST_F(DisplayLockContextTest, CallUpdateStyleAndLayoutAfterChangeCSS) {
ResizeAndFocus();
SetHtmlInnerHTML(R"HTML(
<style>
#container {
width: 100px;
height: 100px;
contain: style layout paint;
}
.bg {
background: blue;
}
.locked {
content-visibility: hidden;
}
</style>
<body><div class=locked id="container"><b>t</b>esting<div id=inner></div></div></body>
)HTML");
auto* element = GetDocument().getElementById("container");
auto* inner = GetDocument().getElementById("inner");
// Sanity checks to ensure the element is locked.
EXPECT_FALSE(element->GetDisplayLockContext()->ShouldStyleChildren());
EXPECT_FALSE(element->GetDisplayLockContext()->ShouldLayoutChildren());
EXPECT_FALSE(element->GetDisplayLockContext()->ShouldPaintChildren());
EXPECT_EQ(
GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 1);
EXPECT_EQ(GetDocument()
.GetDisplayLockDocumentState()
.DisplayLockBlockingAllActivationCount(),
1);
EXPECT_TRUE(ReattachWasBlocked(element->GetDisplayLockContext()));
// Note that we didn't create a layout object for inner, since the layout tree
// attachment was blocked.
EXPECT_FALSE(inner->GetLayoutObject());
EXPECT_FALSE(element->NeedsStyleRecalc());
EXPECT_FALSE(element->ChildNeedsStyleRecalc());
EXPECT_FALSE(element->NeedsReattachLayoutTree());
EXPECT_FALSE(element->ChildNeedsReattachLayoutTree());
element->classList().Remove("locked");
// Class list changed, so we should need self style change.
EXPECT_TRUE(element->NeedsStyleRecalc());
EXPECT_FALSE(element->ChildNeedsStyleRecalc());
EXPECT_FALSE(element->NeedsReattachLayoutTree());
EXPECT_FALSE(element->ChildNeedsReattachLayoutTree());
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(element->NeedsStyleRecalc());
EXPECT_FALSE(element->ChildNeedsStyleRecalc());
EXPECT_FALSE(element->NeedsReattachLayoutTree());
EXPECT_FALSE(element->ChildNeedsReattachLayoutTree());
// Because we upgraded our style change, we created a layout object for inner.
EXPECT_TRUE(inner->GetLayoutObject());
}
TEST_F(DisplayLockContextTest, LockedElementAndDescendantsAreNotFocusable) {
ResizeAndFocus();
SetHtmlInnerHTML(R"HTML(
<style>
#container {
width: 100px;
height: 100px;
contain: style layout paint;
}
</style>
<body>
<div id="container">
<input id="textfield", type="text">
</div>
</body>
)HTML");
// We start off as being focusable.
ASSERT_TRUE(GetDocument().getElementById("textfield")->IsKeyboardFocusable());
ASSERT_TRUE(GetDocument().getElementById("textfield")->IsMouseFocusable());
ASSERT_TRUE(GetDocument().getElementById("textfield")->IsFocusable());
EXPECT_EQ(
GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 0);
EXPECT_EQ(GetDocument()
.GetDisplayLockDocumentState()
.DisplayLockBlockingAllActivationCount(),
0);
auto* element = GetDocument().getElementById("container");
LockElement(*element, false);
// Sanity checks to ensure the element is locked.
EXPECT_FALSE(element->GetDisplayLockContext()->ShouldStyleChildren());
EXPECT_FALSE(element->GetDisplayLockContext()->ShouldLayoutChildren());
EXPECT_FALSE(element->GetDisplayLockContext()->ShouldPaintChildren());
EXPECT_EQ(
GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 1);
EXPECT_EQ(GetDocument()
.GetDisplayLockDocumentState()
.DisplayLockBlockingAllActivationCount(),
1);
// The input should not be focusable now.
EXPECT_FALSE(
GetDocument().getElementById("textfield")->IsKeyboardFocusable());
EXPECT_FALSE(GetDocument().getElementById("textfield")->IsMouseFocusable());
EXPECT_FALSE(GetDocument().getElementById("textfield")->IsFocusable());
// Calling explicit focus() should also not focus the element.
GetDocument().getElementById("textfield")->focus();
EXPECT_FALSE(GetDocument().FocusedElement());
// Now commit the lock and ensure we can focus the input
CommitElement(*element);
EXPECT_TRUE(element->GetDisplayLockContext()->ShouldStyleChildren());
EXPECT_TRUE(element->GetDisplayLockContext()->ShouldLayoutChildren());
EXPECT_TRUE(element->GetDisplayLockContext()->ShouldPaintChildren());
UpdateAllLifecyclePhasesForTest();
EXPECT_EQ(
GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 0);
EXPECT_EQ(GetDocument()
.GetDisplayLockDocumentState()
.DisplayLockBlockingAllActivationCount(),
0);
EXPECT_TRUE(GetDocument().getElementById("textfield")->IsKeyboardFocusable());
EXPECT_TRUE(GetDocument().getElementById("textfield")->IsMouseFocusable());
EXPECT_TRUE(GetDocument().getElementById("textfield")->IsFocusable());
// Calling explicit focus() should focus the element
GetDocument().getElementById("textfield")->focus();
EXPECT_EQ(GetDocument().FocusedElement(),
GetDocument().getElementById("textfield"));
}
TEST_F(DisplayLockContextTest, DisplayLockPreventsActivation) {
ResizeAndFocus();
SetHtmlInnerHTML(R"HTML(
<body>
<div id="shadowHost">
<div id="slotted"></div>
</div>
</body>
)HTML");
auto* host = GetDocument().getElementById("shadowHost");
auto* slotted = GetDocument().getElementById("slotted");
ASSERT_FALSE(
host->DisplayLockPreventsActivation(DisplayLockActivationReason::kAny));
ASSERT_FALSE(slotted->DisplayLockPreventsActivation(
DisplayLockActivationReason::kAny));
ShadowRoot& shadow_root =
host->AttachShadowRootInternal(ShadowRootType::kOpen);
shadow_root.setInnerHTML(
"<div id='container' style='contain:style layout "
"paint;'><slot></slot></div>");
UpdateAllLifecyclePhasesForTest();
auto* container = shadow_root.getElementById("container");
EXPECT_FALSE(
host->DisplayLockPreventsActivation(DisplayLockActivationReason::kAny));
EXPECT_FALSE(container->DisplayLockPreventsActivation(
DisplayLockActivationReason::kAny));
EXPECT_FALSE(slotted->DisplayLockPreventsActivation(
DisplayLockActivationReason::kAny));
LockElement(*container, false);
EXPECT_EQ(
GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 1);
EXPECT_EQ(GetDocument()
.GetDisplayLockDocumentState()
.DisplayLockBlockingAllActivationCount(),
1);
EXPECT_FALSE(
host->DisplayLockPreventsActivation(DisplayLockActivationReason::kAny));
EXPECT_TRUE(container->DisplayLockPreventsActivation(
DisplayLockActivationReason::kAny));
EXPECT_TRUE(slotted->DisplayLockPreventsActivation(
DisplayLockActivationReason::kAny));
// Ensure that we resolve the acquire callback, thus finishing the acquire
// step.
UpdateAllLifecyclePhasesForTest();
CommitElement(*container);
EXPECT_EQ(
GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 0);
EXPECT_EQ(GetDocument()
.GetDisplayLockDocumentState()
.DisplayLockBlockingAllActivationCount(),
0);
EXPECT_FALSE(
host->DisplayLockPreventsActivation(DisplayLockActivationReason::kAny));
EXPECT_FALSE(container->DisplayLockPreventsActivation(
DisplayLockActivationReason::kAny));
EXPECT_FALSE(slotted->DisplayLockPreventsActivation(
DisplayLockActivationReason::kAny));
UpdateAllLifecyclePhasesForTest();
EXPECT_EQ(
GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 0);
EXPECT_EQ(GetDocument()
.GetDisplayLockDocumentState()
.DisplayLockBlockingAllActivationCount(),
0);
EXPECT_FALSE(
host->DisplayLockPreventsActivation(DisplayLockActivationReason::kAny));
EXPECT_FALSE(container->DisplayLockPreventsActivation(
DisplayLockActivationReason::kAny));
EXPECT_FALSE(slotted->DisplayLockPreventsActivation(
DisplayLockActivationReason::kAny));
SetHtmlInnerHTML(R"HTML(
<body>
<div id="nonviewport" style="content-visibility: hidden-matchable">
</div>
</body>
)HTML");
auto* non_viewport = GetDocument().getElementById("nonviewport");
EXPECT_EQ(
GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 1);
EXPECT_EQ(GetDocument()
.GetDisplayLockDocumentState()
.DisplayLockBlockingAllActivationCount(),
0);
EXPECT_FALSE(non_viewport->DisplayLockPreventsActivation(
DisplayLockActivationReason::kAny));
EXPECT_FALSE(non_viewport->DisplayLockPreventsActivation(
DisplayLockActivationReason::kFindInPage));
EXPECT_TRUE(non_viewport->DisplayLockPreventsActivation(
DisplayLockActivationReason::kUserFocus));
}
TEST_F(DisplayLockContextTest,
LockedElementAndFlatTreeDescendantsAreNotFocusable) {
ResizeAndFocus();
SetHtmlInnerHTML(R"HTML(
<body>
<div id="shadowHost">
<input id="textfield" type="text">
</div>
</body>
)HTML");
auto* host = GetDocument().getElementById("shadowHost");
auto* text_field = GetDocument().getElementById("textfield");
ShadowRoot& shadow_root =
host->AttachShadowRootInternal(ShadowRootType::kOpen);
shadow_root.setInnerHTML(
"<div id='container' style='contain:style layout "
"paint;'><slot></slot></div>");
UpdateAllLifecyclePhasesForTest();
ASSERT_TRUE(text_field->IsKeyboardFocusable());
ASSERT_TRUE(text_field->IsMouseFocusable());
ASSERT_TRUE(text_field->IsFocusable());
auto* element = shadow_root.getElementById("container");
LockElement(*element, false);
// Sanity checks to ensure the element is locked.
EXPECT_FALSE(element->GetDisplayLockContext()->ShouldStyleChildren());
EXPECT_FALSE(element->GetDisplayLockContext()->ShouldLayoutChildren());
EXPECT_FALSE(element->GetDisplayLockContext()->ShouldPaintChildren());
EXPECT_EQ(
GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 1);
EXPECT_EQ(GetDocument()
.GetDisplayLockDocumentState()
.DisplayLockBlockingAllActivationCount(),
1);
// The input should not be focusable now.
EXPECT_FALSE(text_field->IsKeyboardFocusable());
EXPECT_FALSE(text_field->IsMouseFocusable());
EXPECT_FALSE(text_field->IsFocusable());
// Calling explicit focus() should also not focus the element.
text_field->focus();
EXPECT_FALSE(GetDocument().FocusedElement());
}
TEST_F(DisplayLockContextTest, LockedCountsWithMultipleLocks) {
ResizeAndFocus();
SetHtmlInnerHTML(R"HTML(
<style>
.container {
width: 100px;
height: 100px;
contain: style layout paint;
}
</style>
<body>
<div id="one" class="container">
<div id="two" class="container"></div>
</div>
<div id="three" class="container"></div>
</body>
)HTML");
EXPECT_EQ(
GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 0);
EXPECT_EQ(GetDocument()
.GetDisplayLockDocumentState()
.DisplayLockBlockingAllActivationCount(),
0);
auto* one = GetDocument().getElementById("one");
auto* two = GetDocument().getElementById("two");
auto* three = GetDocument().getElementById("three");
LockElement(*one, false);
EXPECT_EQ(
GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 1);
EXPECT_EQ(GetDocument()
.GetDisplayLockDocumentState()
.DisplayLockBlockingAllActivationCount(),
1);
LockElement(*two, false);
// Because |two| is nested, the lock counts aren't updated since the lock
// doesn't actually take effect until style can determine that we should lock.
EXPECT_EQ(
GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 1);
EXPECT_EQ(GetDocument()
.GetDisplayLockDocumentState()
.DisplayLockBlockingAllActivationCount(),
1);
LockElement(*three, false);
EXPECT_EQ(
GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 2);
EXPECT_EQ(GetDocument()
.GetDisplayLockDocumentState()
.DisplayLockBlockingAllActivationCount(),
2);
// Now commit the outer lock.
CommitElement(*one);
// The counts remain the same since now the inner lock is determined to be
// locked.
EXPECT_EQ(
GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 2);
EXPECT_EQ(GetDocument()
.GetDisplayLockDocumentState()
.DisplayLockBlockingAllActivationCount(),
2);
// Commit the inner lock.
CommitElement(*two);
// Both inner and outer locks should have committed.
EXPECT_EQ(
GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 1);
EXPECT_EQ(GetDocument()
.GetDisplayLockDocumentState()
.DisplayLockBlockingAllActivationCount(),
1);
// Commit the sibling lock.
CommitElement(*three);
// Both inner and outer locks should have committed.
EXPECT_EQ(
GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 0);
EXPECT_EQ(GetDocument()
.GetDisplayLockDocumentState()
.DisplayLockBlockingAllActivationCount(),
0);
}
TEST_F(DisplayLockContextTest, ActivatableNotCountedAsBlocking) {
ResizeAndFocus();
SetHtmlInnerHTML(R"HTML(
<style>
.container {
width: 100px;
height: 100px;
contain: style layout paint;
}
</style>
<body>
<div id="activatable" class="container"></div>
<div id="nonActivatable" class="container"></div>
</body>
)HTML");
EXPECT_EQ(
GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 0);
EXPECT_EQ(GetDocument()
.GetDisplayLockDocumentState()
.DisplayLockBlockingAllActivationCount(),
0);
auto* activatable = GetDocument().getElementById("activatable");
auto* non_activatable = GetDocument().getElementById("nonActivatable");
// Initial display lock context should be activatable, since nothing skipped
// activation for it.
EXPECT_TRUE(activatable->EnsureDisplayLockContext().IsActivatable(
DisplayLockActivationReason::kAny));
LockElement(*activatable, true);
EXPECT_EQ(
GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 1);
EXPECT_EQ(GetDocument()
.GetDisplayLockDocumentState()
.DisplayLockBlockingAllActivationCount(),
0);
EXPECT_TRUE(activatable->GetDisplayLockContext()->IsActivatable(
DisplayLockActivationReason::kAny));
LockElement(*non_activatable, false);
EXPECT_EQ(
GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 2);
EXPECT_EQ(GetDocument()
.GetDisplayLockDocumentState()
.DisplayLockBlockingAllActivationCount(),
1);
EXPECT_FALSE(non_activatable->GetDisplayLockContext()->IsActivatable(
DisplayLockActivationReason::kAny));
// Now commit the lock for |non_activatable|.
CommitElement(*non_activatable);
EXPECT_EQ(
GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 1);
EXPECT_EQ(GetDocument()
.GetDisplayLockDocumentState()
.DisplayLockBlockingAllActivationCount(),
0);
// Re-acquire the lock for |activatable| again with the activatable flag.
LockElement(*activatable, true);
EXPECT_EQ(
GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 1);
EXPECT_EQ(GetDocument()
.GetDisplayLockDocumentState()
.DisplayLockBlockingAllActivationCount(),
0);
EXPECT_TRUE(activatable->GetDisplayLockContext()->IsActivatable(
DisplayLockActivationReason::kAny));
}
TEST_F(DisplayLockContextTest, ElementInTemplate) {
ResizeAndFocus();
SetHtmlInnerHTML(R"HTML(
<style>
#child {
width: 100px;
height: 100px;
contain: style layout paint;
}
#grandchild {
color: blue;
}
#container {
display: none;
}
</style>
<body>
<template id="template"><div id="child"><div id="grandchild">foo</div></div></template>
<div id="container"></div>
</body>
)HTML");
EXPECT_EQ(
GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 0);
EXPECT_EQ(GetDocument()
.GetDisplayLockDocumentState()
.DisplayLockBlockingAllActivationCount(),
0);
auto* template_el =
To<HTMLTemplateElement>(GetDocument().getElementById("template"));
auto* child = To<Element>(template_el->content()->firstChild());
EXPECT_FALSE(child->isConnected());
// Try to lock an element in a template.
LockElement(*child, false);
EXPECT_EQ(
GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 0);
EXPECT_EQ(GetDocument()
.GetDisplayLockDocumentState()
.DisplayLockBlockingAllActivationCount(),
0);
EXPECT_FALSE(child->GetDisplayLockContext());
// Commit also works, but does nothing.
CommitElement(*child);
EXPECT_FALSE(child->GetDisplayLockContext());
// Try to lock an element that was moved from a template to a document.
auto* document_child =
To<Element>(GetDocument().adoptNode(child, ASSERT_NO_EXCEPTION));
auto* container = GetDocument().getElementById("container");
container->appendChild(document_child);
LockElement(*document_child, false);
// These should be 0, since container is display: none, so locking its child
// is not visible to style.
EXPECT_EQ(
GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 0);
EXPECT_EQ(GetDocument()
.GetDisplayLockDocumentState()
.DisplayLockBlockingAllActivationCount(),
0);
ASSERT_FALSE(document_child->GetDisplayLockContext());
container->setAttribute(html_names::kStyleAttr, "display: block;");
EXPECT_TRUE(container->NeedsStyleRecalc());
UpdateAllLifecyclePhasesForTest();
EXPECT_EQ(
GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 1);
EXPECT_EQ(GetDocument()
.GetDisplayLockDocumentState()
.DisplayLockBlockingAllActivationCount(),
1);
ASSERT_TRUE(document_child->GetDisplayLockContext());
EXPECT_TRUE(document_child->GetDisplayLockContext()->IsLocked());
document_child->setAttribute(html_names::kStyleAttr,
"content-visibility: hidden; color: red;");
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(document_child->NeedsStyleRecalc());
// Commit will unlock the element and update the style.
document_child->setAttribute(html_names::kStyleAttr, "color: red;");
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(document_child->GetDisplayLockContext()->IsLocked());
EXPECT_EQ(
GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 0);
EXPECT_EQ(GetDocument()
.GetDisplayLockDocumentState()
.DisplayLockBlockingAllActivationCount(),
0);
EXPECT_FALSE(document_child->NeedsStyleRecalc());
EXPECT_FALSE(document_child->ChildNeedsStyleRecalc());
ASSERT_TRUE(document_child->GetComputedStyle());
EXPECT_EQ(document_child->GetComputedStyle()->VisitedDependentColor(
GetCSSPropertyColor()),
MakeRGB(255, 0, 0));
auto* grandchild = GetDocument().getElementById("grandchild");
EXPECT_FALSE(grandchild->NeedsStyleRecalc());
EXPECT_FALSE(grandchild->ChildNeedsStyleRecalc());
ASSERT_TRUE(grandchild->GetComputedStyle());
EXPECT_EQ(grandchild->GetComputedStyle()->VisitedDependentColor(
GetCSSPropertyColor()),
MakeRGB(0, 0, 255));
}
TEST_F(DisplayLockContextTest, AncestorAllowedTouchAction) {
SetHtmlInnerHTML(R"HTML(
<style>
#locked {
width: 100px;
height: 100px;
contain: style layout paint;
}
</style>
<div id="ancestor">
<div id="handler">
<div id="descendant">
<div id="locked">
<div id="lockedchild"></div>
</div>
</div>
</div>
</div>
)HTML");
auto* ancestor_element = GetDocument().getElementById("ancestor");
auto* handler_element = GetDocument().getElementById("handler");
auto* descendant_element = GetDocument().getElementById("descendant");
auto* locked_element = GetDocument().getElementById("locked");
auto* lockedchild_element = GetDocument().getElementById("lockedchild");
LockElement(*locked_element, false);
EXPECT_TRUE(locked_element->GetDisplayLockContext()->IsLocked());
auto* ancestor_object = ancestor_element->GetLayoutObject();
auto* handler_object = handler_element->GetLayoutObject();
auto* descendant_object = descendant_element->GetLayoutObject();
auto* locked_object = locked_element->GetLayoutObject();
auto* lockedchild_object = lockedchild_element->GetLayoutObject();
EXPECT_FALSE(ancestor_object->EffectiveAllowedTouchActionChanged());
EXPECT_FALSE(handler_object->EffectiveAllowedTouchActionChanged());
EXPECT_FALSE(descendant_object->EffectiveAllowedTouchActionChanged());
EXPECT_FALSE(locked_object->EffectiveAllowedTouchActionChanged());
EXPECT_FALSE(lockedchild_object->EffectiveAllowedTouchActionChanged());
EXPECT_FALSE(ancestor_object->DescendantEffectiveAllowedTouchActionChanged());
EXPECT_FALSE(handler_object->DescendantEffectiveAllowedTouchActionChanged());
EXPECT_FALSE(
descendant_object->DescendantEffectiveAllowedTouchActionChanged());
EXPECT_FALSE(locked_object->DescendantEffectiveAllowedTouchActionChanged());
EXPECT_FALSE(
lockedchild_object->DescendantEffectiveAllowedTouchActionChanged());
EXPECT_FALSE(ancestor_object->InsideBlockingTouchEventHandler());
EXPECT_FALSE(handler_object->InsideBlockingTouchEventHandler());
EXPECT_FALSE(descendant_object->InsideBlockingTouchEventHandler());
EXPECT_FALSE(locked_object->InsideBlockingTouchEventHandler());
EXPECT_FALSE(lockedchild_object->InsideBlockingTouchEventHandler());
auto* callback = MakeGarbageCollected<DisplayLockEmptyEventListener>();
handler_element->addEventListener(event_type_names::kTouchstart, callback);
EXPECT_FALSE(ancestor_object->EffectiveAllowedTouchActionChanged());
EXPECT_TRUE(handler_object->EffectiveAllowedTouchActionChanged());
EXPECT_FALSE(descendant_object->EffectiveAllowedTouchActionChanged());
EXPECT_FALSE(locked_object->EffectiveAllowedTouchActionChanged());
EXPECT_FALSE(lockedchild_object->EffectiveAllowedTouchActionChanged());
EXPECT_TRUE(ancestor_object->DescendantEffectiveAllowedTouchActionChanged());
EXPECT_FALSE(handler_object->DescendantEffectiveAllowedTouchActionChanged());
EXPECT_FALSE(
descendant_object->DescendantEffectiveAllowedTouchActionChanged());
EXPECT_FALSE(locked_object->DescendantEffectiveAllowedTouchActionChanged());
EXPECT_FALSE(
lockedchild_object->DescendantEffectiveAllowedTouchActionChanged());
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(ancestor_object->EffectiveAllowedTouchActionChanged());
EXPECT_FALSE(handler_object->EffectiveAllowedTouchActionChanged());
EXPECT_FALSE(descendant_object->EffectiveAllowedTouchActionChanged());
EXPECT_FALSE(locked_object->EffectiveAllowedTouchActionChanged());
EXPECT_FALSE(lockedchild_object->EffectiveAllowedTouchActionChanged());
EXPECT_FALSE(ancestor_object->DescendantEffectiveAllowedTouchActionChanged());
EXPECT_FALSE(handler_object->DescendantEffectiveAllowedTouchActionChanged());
EXPECT_FALSE(
descendant_object->DescendantEffectiveAllowedTouchActionChanged());
EXPECT_FALSE(locked_object->DescendantEffectiveAllowedTouchActionChanged());
EXPECT_FALSE(
lockedchild_object->DescendantEffectiveAllowedTouchActionChanged());
EXPECT_FALSE(ancestor_object->InsideBlockingTouchEventHandler());
EXPECT_TRUE(handler_object->InsideBlockingTouchEventHandler());
EXPECT_TRUE(descendant_object->InsideBlockingTouchEventHandler());
EXPECT_TRUE(locked_object->InsideBlockingTouchEventHandler());
EXPECT_FALSE(lockedchild_object->InsideBlockingTouchEventHandler());
// Manually commit the lock so that we can verify which dirty bits get
// propagated.
CommitElement(*locked_element, false);
UnlockImmediate(locked_element->GetDisplayLockContext());
EXPECT_FALSE(ancestor_object->EffectiveAllowedTouchActionChanged());
EXPECT_FALSE(handler_object->EffectiveAllowedTouchActionChanged());
EXPECT_FALSE(descendant_object->EffectiveAllowedTouchActionChanged());
EXPECT_TRUE(locked_object->EffectiveAllowedTouchActionChanged());
EXPECT_FALSE(lockedchild_object->EffectiveAllowedTouchActionChanged());
EXPECT_TRUE(ancestor_object->DescendantEffectiveAllowedTouchActionChanged());
EXPECT_TRUE(handler_object->DescendantEffectiveAllowedTouchActionChanged());
EXPECT_TRUE(
descendant_object->DescendantEffectiveAllowedTouchActionChanged());
EXPECT_FALSE(locked_object->DescendantEffectiveAllowedTouchActionChanged());
EXPECT_FALSE(
lockedchild_object->DescendantEffectiveAllowedTouchActionChanged());
EXPECT_FALSE(ancestor_object->InsideBlockingTouchEventHandler());
EXPECT_TRUE(handler_object->InsideBlockingTouchEventHandler());
EXPECT_TRUE(descendant_object->InsideBlockingTouchEventHandler());
EXPECT_TRUE(locked_object->InsideBlockingTouchEventHandler());
EXPECT_FALSE(lockedchild_object->InsideBlockingTouchEventHandler());
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(ancestor_object->EffectiveAllowedTouchActionChanged());
EXPECT_FALSE(handler_object->EffectiveAllowedTouchActionChanged());
EXPECT_FALSE(descendant_object->EffectiveAllowedTouchActionChanged());
EXPECT_FALSE(locked_object->EffectiveAllowedTouchActionChanged());
EXPECT_FALSE(lockedchild_object->EffectiveAllowedTouchActionChanged());
EXPECT_FALSE(ancestor_object->DescendantEffectiveAllowedTouchActionChanged());
EXPECT_FALSE(handler_object->DescendantEffectiveAllowedTouchActionChanged());
EXPECT_FALSE(
descendant_object->DescendantEffectiveAllowedTouchActionChanged());
EXPECT_FALSE(locked_object->DescendantEffectiveAllowedTouchActionChanged());
EXPECT_FALSE(
lockedchild_object->DescendantEffectiveAllowedTouchActionChanged());
EXPECT_FALSE(ancestor_object->InsideBlockingTouchEventHandler());
EXPECT_TRUE(handler_object->InsideBlockingTouchEventHandler());
EXPECT_TRUE(descendant_object->InsideBlockingTouchEventHandler());
EXPECT_TRUE(locked_object->InsideBlockingTouchEventHandler());
EXPECT_TRUE(lockedchild_object->InsideBlockingTouchEventHandler());
}
TEST_F(DisplayLockContextTest, DescendantAllowedTouchAction) {
SetHtmlInnerHTML(R"HTML(
<style>
#locked {
width: 100px;
height: 100px;
contain: style layout paint;
}
</style>
<div id="ancestor">
<div id="descendant">
<div id="locked">
<div id="handler"></div>
</div>
</div>
</div>
)HTML");
auto* ancestor_element = GetDocument().getElementById("ancestor");
auto* descendant_element = GetDocument().getElementById("descendant");
auto* locked_element = GetDocument().getElementById("locked");
auto* handler_element = GetDocument().getElementById("handler");
LockElement(*locked_element, false);
EXPECT_TRUE(locked_element->GetDisplayLockContext()->IsLocked());
auto* ancestor_object = ancestor_element->GetLayoutObject();
auto* descendant_object = descendant_element->GetLayoutObject();
auto* locked_object = locked_element->GetLayoutObject();
auto* handler_object = handler_element->GetLayoutObject();
EXPECT_FALSE(ancestor_object->EffectiveAllowedTouchActionChanged());
EXPECT_FALSE(descendant_object->EffectiveAllowedTouchActionChanged());
EXPECT_FALSE(locked_object->EffectiveAllowedTouchActionChanged());
EXPECT_FALSE(handler_object->EffectiveAllowedTouchActionChanged());
EXPECT_FALSE(ancestor_object->DescendantEffectiveAllowedTouchActionChanged());
EXPECT_FALSE(
descendant_object->DescendantEffectiveAllowedTouchActionChanged());
EXPECT_FALSE(locked_object->DescendantEffectiveAllowedTouchActionChanged());
EXPECT_FALSE(handler_object->DescendantEffectiveAllowedTouchActionChanged());
EXPECT_FALSE(ancestor_object->InsideBlockingTouchEventHandler());
EXPECT_FALSE(descendant_object->InsideBlockingTouchEventHandler());
EXPECT_FALSE(locked_object->InsideBlockingTouchEventHandler());
EXPECT_FALSE(handler_object->InsideBlockingTouchEventHandler());
auto* callback = MakeGarbageCollected<DisplayLockEmptyEventListener>();
handler_element->addEventListener(event_type_names::kTouchstart, callback);
EXPECT_FALSE(ancestor_object->EffectiveAllowedTouchActionChanged());
EXPECT_FALSE(descendant_object->EffectiveAllowedTouchActionChanged());
EXPECT_FALSE(locked_object->EffectiveAllowedTouchActionChanged());
EXPECT_TRUE(handler_object->EffectiveAllowedTouchActionChanged());
EXPECT_FALSE(ancestor_object->DescendantEffectiveAllowedTouchActionChanged());
EXPECT_FALSE(
descendant_object->DescendantEffectiveAllowedTouchActionChanged());
EXPECT_TRUE(locked_object->DescendantEffectiveAllowedTouchActionChanged());
EXPECT_FALSE(handler_object->DescendantEffectiveAllowedTouchActionChanged());
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(ancestor_object->EffectiveAllowedTouchActionChanged());
EXPECT_FALSE(descendant_object->EffectiveAllowedTouchActionChanged());
EXPECT_FALSE(locked_object->EffectiveAllowedTouchActionChanged());
EXPECT_TRUE(handler_object->EffectiveAllowedTouchActionChanged());
EXPECT_FALSE(ancestor_object->DescendantEffectiveAllowedTouchActionChanged());
EXPECT_FALSE(
descendant_object->DescendantEffectiveAllowedTouchActionChanged());
EXPECT_TRUE(locked_object->DescendantEffectiveAllowedTouchActionChanged());
EXPECT_FALSE(handler_object->DescendantEffectiveAllowedTouchActionChanged());
EXPECT_FALSE(ancestor_object->InsideBlockingTouchEventHandler());
EXPECT_FALSE(descendant_object->InsideBlockingTouchEventHandler());
EXPECT_FALSE(locked_object->InsideBlockingTouchEventHandler());
EXPECT_FALSE(handler_object->InsideBlockingTouchEventHandler());
// Do the same check again. For now, nothing is expected to change. However,
// when we separate self and child layout, then some flags would be different.
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(ancestor_object->EffectiveAllowedTouchActionChanged());
EXPECT_FALSE(descendant_object->EffectiveAllowedTouchActionChanged());
EXPECT_FALSE(locked_object->EffectiveAllowedTouchActionChanged());
EXPECT_TRUE(handler_object->EffectiveAllowedTouchActionChanged());
EXPECT_FALSE(ancestor_object->DescendantEffectiveAllowedTouchActionChanged());
EXPECT_FALSE(
descendant_object->DescendantEffectiveAllowedTouchActionChanged());
EXPECT_TRUE(locked_object->DescendantEffectiveAllowedTouchActionChanged());
EXPECT_FALSE(handler_object->DescendantEffectiveAllowedTouchActionChanged());
EXPECT_FALSE(ancestor_object->InsideBlockingTouchEventHandler());
EXPECT_FALSE(descendant_object->InsideBlockingTouchEventHandler());
EXPECT_FALSE(locked_object->InsideBlockingTouchEventHandler());
EXPECT_FALSE(handler_object->InsideBlockingTouchEventHandler());
// Manually commit the lock so that we can verify which dirty bits get
// propagated.
CommitElement(*locked_element, false);
UnlockImmediate(locked_element->GetDisplayLockContext());
EXPECT_FALSE(ancestor_object->EffectiveAllowedTouchActionChanged());
EXPECT_FALSE(descendant_object->EffectiveAllowedTouchActionChanged());
EXPECT_TRUE(locked_object->EffectiveAllowedTouchActionChanged());
EXPECT_TRUE(handler_object->EffectiveAllowedTouchActionChanged());
EXPECT_TRUE(ancestor_object->DescendantEffectiveAllowedTouchActionChanged());
EXPECT_TRUE(
descendant_object->DescendantEffectiveAllowedTouchActionChanged());
EXPECT_TRUE(locked_object->DescendantEffectiveAllowedTouchActionChanged());
EXPECT_FALSE(handler_object->DescendantEffectiveAllowedTouchActionChanged());
EXPECT_FALSE(ancestor_object->InsideBlockingTouchEventHandler());
EXPECT_FALSE(descendant_object->InsideBlockingTouchEventHandler());
EXPECT_FALSE(locked_object->InsideBlockingTouchEventHandler());
EXPECT_FALSE(handler_object->InsideBlockingTouchEventHandler());
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(ancestor_object->EffectiveAllowedTouchActionChanged());
EXPECT_FALSE(descendant_object->EffectiveAllowedTouchActionChanged());
EXPECT_FALSE(locked_object->EffectiveAllowedTouchActionChanged());
EXPECT_FALSE(handler_object->EffectiveAllowedTouchActionChanged());
EXPECT_FALSE(ancestor_object->DescendantEffectiveAllowedTouchActionChanged());
EXPECT_FALSE(
descendant_object->DescendantEffectiveAllowedTouchActionChanged());
EXPECT_FALSE(locked_object->DescendantEffectiveAllowedTouchActionChanged());
EXPECT_FALSE(handler_object->DescendantEffectiveAllowedTouchActionChanged());
EXPECT_FALSE(ancestor_object->InsideBlockingTouchEventHandler());
EXPECT_FALSE(descendant_object->InsideBlockingTouchEventHandler());
EXPECT_FALSE(locked_object->InsideBlockingTouchEventHandler());
EXPECT_TRUE(handler_object->InsideBlockingTouchEventHandler());
}
TEST_F(DisplayLockContextTest, AncestorWheelEventHandler) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(::features::kWheelEventRegions);
SetHtmlInnerHTML(R"HTML(
<style>
#locked {
width: 100px;
height: 100px;
contain: style layout paint;
}
</style>
<div id="ancestor">
<div id="handler">
<div id="descendant">
<div id="locked">
<div id="lockedchild"></div>
</div>
</div>
</div>
</div>
)HTML");
auto* ancestor_element = GetDocument().getElementById("ancestor");
auto* handler_element = GetDocument().getElementById("handler");
auto* descendant_element = GetDocument().getElementById("descendant");
auto* locked_element = GetDocument().getElementById("locked");
auto* lockedchild_element = GetDocument().getElementById("lockedchild");
LockElement(*locked_element, false);
EXPECT_TRUE(locked_element->GetDisplayLockContext()->IsLocked());
auto* ancestor_object = ancestor_element->GetLayoutObject();
auto* handler_object = handler_element->GetLayoutObject();
auto* descendant_object = descendant_element->GetLayoutObject();
auto* locked_object = locked_element->GetLayoutObject();
auto* lockedchild_object = lockedchild_element->GetLayoutObject();
EXPECT_FALSE(ancestor_object->BlockingWheelEventHandlerChanged());
EXPECT_FALSE(handler_object->BlockingWheelEventHandlerChanged());
EXPECT_FALSE(descendant_object->BlockingWheelEventHandlerChanged());
EXPECT_FALSE(locked_object->BlockingWheelEventHandlerChanged());
EXPECT_FALSE(lockedchild_object->BlockingWheelEventHandlerChanged());
EXPECT_FALSE(ancestor_object->DescendantBlockingWheelEventHandlerChanged());
EXPECT_FALSE(handler_object->DescendantBlockingWheelEventHandlerChanged());
EXPECT_FALSE(descendant_object->DescendantBlockingWheelEventHandlerChanged());
EXPECT_FALSE(locked_object->DescendantBlockingWheelEventHandlerChanged());
EXPECT_FALSE(
lockedchild_object->DescendantBlockingWheelEventHandlerChanged());
EXPECT_FALSE(ancestor_object->InsideBlockingWheelEventHandler());
EXPECT_FALSE(handler_object->InsideBlockingWheelEventHandler());
EXPECT_FALSE(descendant_object->InsideBlockingWheelEventHandler());
EXPECT_FALSE(locked_object->InsideBlockingWheelEventHandler());
EXPECT_FALSE(lockedchild_object->InsideBlockingWheelEventHandler());
auto* callback = MakeGarbageCollected<DisplayLockEmptyEventListener>();
handler_element->addEventListener(event_type_names::kWheel, callback);
EXPECT_FALSE(ancestor_object->BlockingWheelEventHandlerChanged());
EXPECT_TRUE(handler_object->BlockingWheelEventHandlerChanged());
EXPECT_FALSE(descendant_object->BlockingWheelEventHandlerChanged());
EXPECT_FALSE(locked_object->BlockingWheelEventHandlerChanged());
EXPECT_FALSE(lockedchild_object->BlockingWheelEventHandlerChanged());
EXPECT_TRUE(ancestor_object->DescendantBlockingWheelEventHandlerChanged());
EXPECT_FALSE(handler_object->DescendantBlockingWheelEventHandlerChanged());
EXPECT_FALSE(descendant_object->DescendantBlockingWheelEventHandlerChanged());
EXPECT_FALSE(locked_object->DescendantBlockingWheelEventHandlerChanged());
EXPECT_FALSE(
lockedchild_object->DescendantBlockingWheelEventHandlerChanged());
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(ancestor_object->BlockingWheelEventHandlerChanged());
EXPECT_FALSE(handler_object->BlockingWheelEventHandlerChanged());
EXPECT_FALSE(descendant_object->BlockingWheelEventHandlerChanged());
EXPECT_FALSE(locked_object->BlockingWheelEventHandlerChanged());
EXPECT_FALSE(lockedchild_object->BlockingWheelEventHandlerChanged());
EXPECT_FALSE(ancestor_object->DescendantBlockingWheelEventHandlerChanged());
EXPECT_FALSE(handler_object->DescendantBlockingWheelEventHandlerChanged());
EXPECT_FALSE(descendant_object->DescendantBlockingWheelEventHandlerChanged());
EXPECT_FALSE(locked_object->DescendantBlockingWheelEventHandlerChanged());
EXPECT_FALSE(
lockedchild_object->DescendantBlockingWheelEventHandlerChanged());
EXPECT_FALSE(ancestor_object->InsideBlockingWheelEventHandler());
EXPECT_TRUE(handler_object->InsideBlockingWheelEventHandler());
EXPECT_TRUE(descendant_object->InsideBlockingWheelEventHandler());
EXPECT_TRUE(locked_object->InsideBlockingWheelEventHandler());
EXPECT_FALSE(lockedchild_object->InsideBlockingWheelEventHandler());
// Manually commit the lock so that we can verify which dirty bits get
// propagated.
CommitElement(*locked_element, false);
UnlockImmediate(locked_element->GetDisplayLockContext());
EXPECT_FALSE(ancestor_object->BlockingWheelEventHandlerChanged());
EXPECT_FALSE(handler_object->BlockingWheelEventHandlerChanged());
EXPECT_FALSE(descendant_object->BlockingWheelEventHandlerChanged());
EXPECT_TRUE(locked_object->BlockingWheelEventHandlerChanged());
EXPECT_FALSE(lockedchild_object->BlockingWheelEventHandlerChanged());
EXPECT_TRUE(ancestor_object->DescendantBlockingWheelEventHandlerChanged());
EXPECT_TRUE(handler_object->DescendantBlockingWheelEventHandlerChanged());
EXPECT_TRUE(descendant_object->DescendantBlockingWheelEventHandlerChanged());
EXPECT_FALSE(locked_object->DescendantBlockingWheelEventHandlerChanged());
EXPECT_FALSE(
lockedchild_object->DescendantBlockingWheelEventHandlerChanged());
EXPECT_FALSE(ancestor_object->InsideBlockingWheelEventHandler());
EXPECT_TRUE(handler_object->InsideBlockingWheelEventHandler());
EXPECT_TRUE(descendant_object->InsideBlockingWheelEventHandler());
EXPECT_TRUE(locked_object->InsideBlockingWheelEventHandler());
EXPECT_FALSE(lockedchild_object->InsideBlockingWheelEventHandler());
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(ancestor_object->BlockingWheelEventHandlerChanged());
EXPECT_FALSE(handler_object->BlockingWheelEventHandlerChanged());
EXPECT_FALSE(descendant_object->BlockingWheelEventHandlerChanged());
EXPECT_FALSE(locked_object->BlockingWheelEventHandlerChanged());
EXPECT_FALSE(lockedchild_object->BlockingWheelEventHandlerChanged());
EXPECT_FALSE(ancestor_object->DescendantBlockingWheelEventHandlerChanged());
EXPECT_FALSE(handler_object->DescendantBlockingWheelEventHandlerChanged());
EXPECT_FALSE(descendant_object->DescendantBlockingWheelEventHandlerChanged());
EXPECT_FALSE(locked_object->DescendantBlockingWheelEventHandlerChanged());
EXPECT_FALSE(
lockedchild_object->DescendantBlockingWheelEventHandlerChanged());
EXPECT_FALSE(ancestor_object->InsideBlockingWheelEventHandler());
EXPECT_TRUE(handler_object->InsideBlockingWheelEventHandler());
EXPECT_TRUE(descendant_object->InsideBlockingWheelEventHandler());
EXPECT_TRUE(locked_object->InsideBlockingWheelEventHandler());
EXPECT_TRUE(lockedchild_object->InsideBlockingWheelEventHandler());
}
TEST_F(DisplayLockContextTest, DescendantWheelEventHandler) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(::features::kWheelEventRegions);
SetHtmlInnerHTML(R"HTML(
<style>
#locked {
width: 100px;
height: 100px;
contain: style layout paint;
}
</style>
<div id="ancestor">
<div id="descendant">
<div id="locked">
<div id="handler"></div>
</div>
</div>
</div>
)HTML");
auto* ancestor_element = GetDocument().getElementById("ancestor");
auto* descendant_element = GetDocument().getElementById("descendant");
auto* locked_element = GetDocument().getElementById("locked");
auto* handler_element = GetDocument().getElementById("handler");
LockElement(*locked_element, false);
EXPECT_TRUE(locked_element->GetDisplayLockContext()->IsLocked());
auto* ancestor_object = ancestor_element->GetLayoutObject();
auto* descendant_object = descendant_element->GetLayoutObject();
auto* locked_object = locked_element->GetLayoutObject();
auto* handler_object = handler_element->GetLayoutObject();
EXPECT_FALSE(ancestor_object->BlockingWheelEventHandlerChanged());
EXPECT_FALSE(descendant_object->BlockingWheelEventHandlerChanged());
EXPECT_FALSE(locked_object->BlockingWheelEventHandlerChanged());
EXPECT_FALSE(handler_object->BlockingWheelEventHandlerChanged());
EXPECT_FALSE(ancestor_object->DescendantBlockingWheelEventHandlerChanged());
EXPECT_FALSE(descendant_object->DescendantBlockingWheelEventHandlerChanged());
EXPECT_FALSE(locked_object->DescendantBlockingWheelEventHandlerChanged());
EXPECT_FALSE(handler_object->DescendantBlockingWheelEventHandlerChanged());
EXPECT_FALSE(ancestor_object->InsideBlockingWheelEventHandler());
EXPECT_FALSE(descendant_object->InsideBlockingWheelEventHandler());
EXPECT_FALSE(locked_object->InsideBlockingWheelEventHandler());
EXPECT_FALSE(handler_object->InsideBlockingWheelEventHandler());
auto* callback = MakeGarbageCollected<DisplayLockEmptyEventListener>();
handler_element->addEventListener(event_type_names::kWheel, callback);
EXPECT_FALSE(ancestor_object->BlockingWheelEventHandlerChanged());
EXPECT_FALSE(descendant_object->BlockingWheelEventHandlerChanged());
EXPECT_FALSE(locked_object->BlockingWheelEventHandlerChanged());
EXPECT_TRUE(handler_object->BlockingWheelEventHandlerChanged());
EXPECT_FALSE(ancestor_object->DescendantBlockingWheelEventHandlerChanged());
EXPECT_FALSE(descendant_object->DescendantBlockingWheelEventHandlerChanged());
EXPECT_TRUE(locked_object->DescendantBlockingWheelEventHandlerChanged());
EXPECT_FALSE(handler_object->DescendantBlockingWheelEventHandlerChanged());
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(ancestor_object->BlockingWheelEventHandlerChanged());
EXPECT_FALSE(descendant_object->BlockingWheelEventHandlerChanged());
EXPECT_FALSE(locked_object->BlockingWheelEventHandlerChanged());
EXPECT_TRUE(handler_object->BlockingWheelEventHandlerChanged());
EXPECT_FALSE(ancestor_object->DescendantBlockingWheelEventHandlerChanged());
EXPECT_FALSE(descendant_object->DescendantBlockingWheelEventHandlerChanged());
EXPECT_TRUE(locked_object->DescendantBlockingWheelEventHandlerChanged());
EXPECT_FALSE(handler_object->DescendantBlockingWheelEventHandlerChanged());
EXPECT_FALSE(ancestor_object->InsideBlockingWheelEventHandler());
EXPECT_FALSE(descendant_object->InsideBlockingWheelEventHandler());
EXPECT_FALSE(locked_object->InsideBlockingWheelEventHandler());
EXPECT_FALSE(handler_object->InsideBlockingWheelEventHandler());
// Do the same check again. For now, nothing is expected to change. However,
// when we separate self and child layout, then some flags would be different.
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(ancestor_object->BlockingWheelEventHandlerChanged());
EXPECT_FALSE(descendant_object->BlockingWheelEventHandlerChanged());
EXPECT_FALSE(locked_object->BlockingWheelEventHandlerChanged());
EXPECT_TRUE(handler_object->BlockingWheelEventHandlerChanged());
EXPECT_FALSE(ancestor_object->DescendantBlockingWheelEventHandlerChanged());
EXPECT_FALSE(descendant_object->DescendantBlockingWheelEventHandlerChanged());
EXPECT_TRUE(locked_object->DescendantBlockingWheelEventHandlerChanged());
EXPECT_FALSE(handler_object->DescendantBlockingWheelEventHandlerChanged());
EXPECT_FALSE(ancestor_object->InsideBlockingWheelEventHandler());
EXPECT_FALSE(descendant_object->InsideBlockingWheelEventHandler());
EXPECT_FALSE(locked_object->InsideBlockingWheelEventHandler());
EXPECT_FALSE(handler_object->InsideBlockingWheelEventHandler());
// Manually commit the lock so that we can verify which dirty bits get
// propagated.
CommitElement(*locked_element, false);
UnlockImmediate(locked_element->GetDisplayLockContext());
EXPECT_FALSE(ancestor_object->BlockingWheelEventHandlerChanged());
EXPECT_FALSE(descendant_object->BlockingWheelEventHandlerChanged());
EXPECT_TRUE(locked_object->BlockingWheelEventHandlerChanged());
EXPECT_TRUE(handler_object->BlockingWheelEventHandlerChanged());
EXPECT_TRUE(ancestor_object->DescendantBlockingWheelEventHandlerChanged());
EXPECT_TRUE(descendant_object->DescendantBlockingWheelEventHandlerChanged());
EXPECT_TRUE(locked_object->DescendantBlockingWheelEventHandlerChanged());
EXPECT_FALSE(handler_object->DescendantBlockingWheelEventHandlerChanged());
EXPECT_FALSE(ancestor_object->InsideBlockingWheelEventHandler());
EXPECT_FALSE(descendant_object->InsideBlockingWheelEventHandler());
EXPECT_FALSE(locked_object->InsideBlockingWheelEventHandler());
EXPECT_FALSE(handler_object->InsideBlockingWheelEventHandler());
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(ancestor_object->BlockingWheelEventHandlerChanged());
EXPECT_FALSE(descendant_object->BlockingWheelEventHandlerChanged());
EXPECT_FALSE(locked_object->BlockingWheelEventHandlerChanged());
EXPECT_FALSE(handler_object->BlockingWheelEventHandlerChanged());
EXPECT_FALSE(ancestor_object->DescendantBlockingWheelEventHandlerChanged());
EXPECT_FALSE(descendant_object->DescendantBlockingWheelEventHandlerChanged());
EXPECT_FALSE(locked_object->DescendantBlockingWheelEventHandlerChanged());
EXPECT_FALSE(handler_object->DescendantBlockingWheelEventHandlerChanged());
EXPECT_FALSE(ancestor_object->InsideBlockingWheelEventHandler());
EXPECT_FALSE(descendant_object->InsideBlockingWheelEventHandler());
EXPECT_FALSE(locked_object->InsideBlockingWheelEventHandler());
EXPECT_TRUE(handler_object->InsideBlockingWheelEventHandler());
}
TEST_F(DisplayLockContextTest, DescendantNeedsPaintPropertyUpdateBlocked) {
SetHtmlInnerHTML(R"HTML(
<style>
#locked {
width: 100px;
height: 100px;
contain: style layout paint;
}
</style>
<div id="ancestor">
<div id="descendant">
<div id="locked">
<div id="handler"></div>
</div>
</div>
</div>
)HTML");
auto* ancestor_element = GetDocument().getElementById("ancestor");
auto* descendant_element = GetDocument().getElementById("descendant");
auto* locked_element = GetDocument().getElementById("locked");
auto* handler_element = GetDocument().getElementById("handler");
LockElement(*locked_element, false);
EXPECT_TRUE(locked_element->GetDisplayLockContext()->IsLocked());
auto* ancestor_object = ancestor_element->GetLayoutObject();
auto* descendant_object = descendant_element->GetLayoutObject();
auto* locked_object = locked_element->GetLayoutObject();
auto* handler_object = handler_element->GetLayoutObject();
EXPECT_FALSE(ancestor_object->NeedsPaintPropertyUpdate());
EXPECT_FALSE(descendant_object->NeedsPaintPropertyUpdate());
EXPECT_FALSE(locked_object->NeedsPaintPropertyUpdate());
EXPECT_FALSE(handler_object->NeedsPaintPropertyUpdate());
EXPECT_FALSE(ancestor_object->DescendantNeedsPaintPropertyUpdate());
EXPECT_FALSE(descendant_object->DescendantNeedsPaintPropertyUpdate());
EXPECT_FALSE(locked_object->DescendantNeedsPaintPropertyUpdate());
EXPECT_FALSE(handler_object->DescendantNeedsPaintPropertyUpdate());
handler_object->SetNeedsPaintPropertyUpdate();
EXPECT_FALSE(ancestor_object->NeedsPaintPropertyUpdate());
EXPECT_FALSE(descendant_object->NeedsPaintPropertyUpdate());
EXPECT_FALSE(locked_object->NeedsPaintPropertyUpdate());
EXPECT_TRUE(handler_object->NeedsPaintPropertyUpdate());
EXPECT_TRUE(ancestor_object->DescendantNeedsPaintPropertyUpdate());
EXPECT_TRUE(descendant_object->DescendantNeedsPaintPropertyUpdate());
EXPECT_TRUE(locked_object->DescendantNeedsPaintPropertyUpdate());
EXPECT_FALSE(handler_object->DescendantNeedsPaintPropertyUpdate());
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(ancestor_object->NeedsPaintPropertyUpdate());
EXPECT_FALSE(descendant_object->NeedsPaintPropertyUpdate());
EXPECT_FALSE(locked_object->NeedsPaintPropertyUpdate());
EXPECT_TRUE(handler_object->NeedsPaintPropertyUpdate());
EXPECT_FALSE(ancestor_object->DescendantNeedsPaintPropertyUpdate());
EXPECT_FALSE(descendant_object->DescendantNeedsPaintPropertyUpdate());
EXPECT_TRUE(locked_object->DescendantNeedsPaintPropertyUpdate());
EXPECT_FALSE(handler_object->DescendantNeedsPaintPropertyUpdate());
locked_object->SetShouldCheckForPaintInvalidationWithoutGeometryChange();
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(ancestor_object->NeedsPaintPropertyUpdate());
EXPECT_FALSE(descendant_object->NeedsPaintPropertyUpdate());
EXPECT_FALSE(locked_object->NeedsPaintPropertyUpdate());
EXPECT_TRUE(handler_object->NeedsPaintPropertyUpdate());
EXPECT_FALSE(ancestor_object->DescendantNeedsPaintPropertyUpdate());
EXPECT_FALSE(descendant_object->DescendantNeedsPaintPropertyUpdate());
EXPECT_TRUE(locked_object->DescendantNeedsPaintPropertyUpdate());
EXPECT_FALSE(handler_object->DescendantNeedsPaintPropertyUpdate());
// Manually commit the lock so that we can verify which dirty bits get
// propagated.
CommitElement(*locked_element, false);
UnlockImmediate(locked_element->GetDisplayLockContext());
EXPECT_FALSE(ancestor_object->NeedsPaintPropertyUpdate());
EXPECT_FALSE(descendant_object->NeedsPaintPropertyUpdate());
EXPECT_TRUE(locked_object->NeedsPaintPropertyUpdate());
EXPECT_TRUE(handler_object->NeedsPaintPropertyUpdate());
EXPECT_TRUE(ancestor_object->DescendantNeedsPaintPropertyUpdate());
EXPECT_TRUE(descendant_object->DescendantNeedsPaintPropertyUpdate());
EXPECT_TRUE(locked_object->DescendantNeedsPaintPropertyUpdate());
EXPECT_FALSE(handler_object->DescendantNeedsPaintPropertyUpdate());
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(ancestor_object->NeedsPaintPropertyUpdate());
EXPECT_FALSE(descendant_object->NeedsPaintPropertyUpdate());
EXPECT_FALSE(locked_object->NeedsPaintPropertyUpdate());
EXPECT_FALSE(handler_object->NeedsPaintPropertyUpdate());
EXPECT_FALSE(ancestor_object->DescendantNeedsPaintPropertyUpdate());
EXPECT_FALSE(descendant_object->DescendantNeedsPaintPropertyUpdate());
EXPECT_FALSE(locked_object->DescendantNeedsPaintPropertyUpdate());
EXPECT_FALSE(handler_object->DescendantNeedsPaintPropertyUpdate());
}
class DisplayLockContextRenderingTest
: public RenderingTest,
private ScopedCSSContentVisibilityHiddenMatchableForTest {
public:
DisplayLockContextRenderingTest()
: RenderingTest(MakeGarbageCollected<SingleChildLocalFrameClient>()),
ScopedCSSContentVisibilityHiddenMatchableForTest(true) {}
void SetUp() override {
EnableCompositing();
RenderingTest::SetUp();
}
bool IsObservingLifecycle(DisplayLockContext* context) const {
return context->is_registered_for_lifecycle_notifications_;
}
bool DescendantDependentFlagUpdateWasBlocked(
DisplayLockContext* context) const {
return context->needs_compositing_dependent_flag_update_;
}
void LockImmediate(DisplayLockContext* context) {
context->SetRequestedState(EContentVisibility::kHidden);
}
void RunStartOfLifecycleTasks() {
auto start_of_lifecycle_tasks =
GetDocument().View()->TakeStartOfLifecycleTasksForTest();
for (auto& task : start_of_lifecycle_tasks)
std::move(task).Run();
}
DisplayLockUtilities::ScopedForcedUpdate GetScopedForcedUpdate(
const Node* node,
bool include_self = false) {
return DisplayLockUtilities::ScopedForcedUpdate(node, include_self);
}
};
TEST_F(DisplayLockContextRenderingTest, FrameDocumentRemovedWhileAcquire) {
SetHtmlInnerHTML(R"HTML(
<iframe id="frame"></iframe>
)HTML");
SetChildFrameHTML(R"HTML(
<style>
div {
contain: style layout;
}
</style>
<div id="target"></target>
)HTML");
auto* target = ChildDocument().getElementById("target");
GetDocument().getElementById("frame")->remove();
LockImmediate(&target->EnsureDisplayLockContext());
}
TEST_F(DisplayLockContextRenderingTest,
VisualOverflowCalculateOnChildPaintLayer) {
SetHtmlInnerHTML(R"HTML(
<style>
.hidden { content-visibility: hidden }
.paint_layer { contain: paint }
.composited { will-change: transform }
</style>
<div id=lockable class=paint_layer>
<div id=parent class=paint_layer>
<div id=child class=paint_layer>
<span>content</span>
<span>content</span>
<span>content</span>
</div>
</div>
</div>
)HTML");
auto* parent = GetDocument().getElementById("parent");
auto* parent_box = parent->GetLayoutBoxModelObject();
ASSERT_TRUE(parent_box);
EXPECT_TRUE(parent_box->Layer());
EXPECT_TRUE(parent_box->HasSelfPaintingLayer());
// Lock the container.
auto* lockable = GetDocument().getElementById("lockable");
lockable->classList().Add("hidden");
UpdateAllLifecyclePhasesForTest();
auto* child_layer = GetPaintLayerByElementId("child");
child_layer->SetNeedsVisualOverflowRecalc();
EXPECT_TRUE(child_layer->NeedsVisualOverflowRecalc());
// The following should not crash/DCHECK.
UpdateAllLifecyclePhasesForTest();
// Verify that the display lock knows that the descendant dependent flags
// update was blocked.
ASSERT_TRUE(lockable->GetDisplayLockContext());
EXPECT_TRUE(DescendantDependentFlagUpdateWasBlocked(
lockable->GetDisplayLockContext()));
EXPECT_TRUE(child_layer->NeedsVisualOverflowRecalc());
// After unlocking, we should process the pending visual overflow recalc.
lockable->classList().Remove("hidden");
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(child_layer->NeedsVisualOverflowRecalc());
}
TEST_F(DisplayLockContextRenderingTest, FloatChildLocked) {
SetHtmlInnerHTML(R"HTML(
<style>
.hidden { content-visibility: hidden }
#floating { float: left; width: 100px; height: 100px }
</style>
<div id=lockable style="width: 200px; height: 50px; position: absolute">
<div id=floating></div>
</div>
)HTML");
auto* lockable = GetDocument().getElementById("lockable");
auto* lockable_box = lockable->GetLayoutBox();
auto* floating = GetDocument().getElementById("floating");
EXPECT_EQ(LayoutRect(0, 0, 200, 100), lockable_box->VisualOverflowRect());
EXPECT_EQ(LayoutRect(0, 0, 200, 100), lockable_box->LayoutOverflowRect());
lockable->classList().Add("hidden");
UpdateAllLifecyclePhasesForTest();
// Verify that the display lock knows that the descendant dependent flags
// update was blocked.
ASSERT_TRUE(lockable->GetDisplayLockContext());
EXPECT_TRUE(DescendantDependentFlagUpdateWasBlocked(
lockable->GetDisplayLockContext()));
EXPECT_EQ(LayoutRect(0, 0, 200, 50), lockable_box->VisualOverflowRect());
EXPECT_EQ(LayoutRect(0, 0, 200, 50), lockable_box->LayoutOverflowRect());
floating->setAttribute(html_names::kStyleAttr, "height: 200px");
// The following should not crash/DCHECK.
UpdateAllLifecyclePhasesForTest();
ASSERT_TRUE(lockable->GetDisplayLockContext());
EXPECT_TRUE(DescendantDependentFlagUpdateWasBlocked(
lockable->GetDisplayLockContext()));
EXPECT_EQ(LayoutRect(0, 0, 200, 50), lockable_box->VisualOverflowRect());
EXPECT_EQ(LayoutRect(0, 0, 200, 50), lockable_box->LayoutOverflowRect());
// After unlocking, we should process the pending visual overflow recalc.
lockable->classList().Remove("hidden");
UpdateAllLifecyclePhasesForTest();
EXPECT_EQ(LayoutRect(0, 0, 200, 200), lockable_box->VisualOverflowRect());
EXPECT_EQ(LayoutRect(0, 0, 200, 200), lockable_box->LayoutOverflowRect());
}
TEST_F(DisplayLockContextRenderingTest,
VisualOverflowCalculateOnChildPaintLayerInForcedLock) {
SetHtmlInnerHTML(R"HTML(
<style>
.hidden { content-visibility: hidden }
.paint_layer { contain: paint }
.composited { will-change: transform }
</style>
<div id=lockable class=paint_layer>
<div id=parent class=paint_layer>
<div id=child class=paint_layer>
<span>content</span>
<span>content</span>
<span>content</span>
</div>
</div>
</div>
)HTML");
auto* parent = GetDocument().getElementById("parent");
auto* parent_box = parent->GetLayoutBoxModelObject();
ASSERT_TRUE(parent_box);
EXPECT_TRUE(parent_box->Layer());
EXPECT_TRUE(parent_box->HasSelfPaintingLayer());
// Lock the container.
auto* lockable = GetDocument().getElementById("lockable");
lockable->classList().Add("hidden");
UpdateAllLifecyclePhasesForTest();
auto* child_layer = GetPaintLayerByElementId("child");
child_layer->SetNeedsVisualOverflowRecalc();
EXPECT_TRUE(child_layer->NeedsVisualOverflowRecalc());
ASSERT_TRUE(lockable->GetDisplayLockContext());
{
auto scope = GetScopedForcedUpdate(lockable, true /* include self */);
// The following should not crash/DCHECK.
UpdateAllLifecyclePhasesForTest();
}
// Verify that the display lock doesn't keep extra state since the update was
// processed.
EXPECT_FALSE(DescendantDependentFlagUpdateWasBlocked(
lockable->GetDisplayLockContext()));
EXPECT_FALSE(child_layer->NeedsVisualOverflowRecalc());
// After unlocking, we should not need to do any extra work.
lockable->classList().Remove("hidden");
EXPECT_FALSE(child_layer->NeedsVisualOverflowRecalc());
UpdateAllLifecyclePhasesForTest();
}
TEST_F(DisplayLockContextRenderingTest,
SelectionOnAnonymousColumnSpannerDoesNotCrash) {
SetHtmlInnerHTML(R"HTML(
<style>
#columns {
column-count: 5;
}
#spanner {
column-span: all;
}
</style>
<div id="columns">
<div id="spanner"></div>
</div>
)HTML");
auto* columns_object =
GetDocument().getElementById("columns")->GetLayoutObject();
LayoutObject* spanner_placeholder_object = nullptr;
for (auto* candidate = columns_object->SlowFirstChild(); candidate;
candidate = candidate->NextSibling()) {
if (candidate->IsLayoutMultiColumnSpannerPlaceholder()) {
spanner_placeholder_object = candidate;
break;
}
}
ASSERT_TRUE(spanner_placeholder_object);
EXPECT_FALSE(spanner_placeholder_object->CanBeSelectionLeaf());
}
TEST_F(DisplayLockContextRenderingTest, ObjectsNeedingLayoutConsidersLocks) {
SetHtmlInnerHTML(R"HTML(
<div id=a>
<div id=b>
<div id=c></div>
<div id=d></div>
</div>
<div id=e>
<div id=f></div>
<div id=g></div>
</div>
</div>
)HTML");
// Dirty all of the leaf nodes.
auto dirty_all = [this]() {
GetDocument().getElementById("c")->GetLayoutObject()->SetNeedsLayout(
"test");
GetDocument().getElementById("d")->GetLayoutObject()->SetNeedsLayout(
"test");
GetDocument().getElementById("f")->GetLayoutObject()->SetNeedsLayout(
"test");
GetDocument().getElementById("g")->GetLayoutObject()->SetNeedsLayout(
"test");
};
unsigned dirty_count = 0;
unsigned total_count = 0;
bool is_subtree = false;
dirty_all();
GetDocument().View()->CountObjectsNeedingLayout(dirty_count, total_count,
is_subtree);
// 7 divs + body + html + layout view
EXPECT_EQ(dirty_count, 10u);
EXPECT_EQ(total_count, 10u);
GetDocument().getElementById("e")->setAttribute(html_names::kStyleAttr,
"content-visibility: hidden");
UpdateAllLifecyclePhasesForTest();
// Note that the dirty_all call propagate the dirty bit from the unlocked
// subtree all the way up to the layout view, so everything on the way up is
// dirtied.
dirty_all();
GetDocument().View()->CountObjectsNeedingLayout(dirty_count, total_count,
is_subtree);
// Element with 2 children is locked, and it itself isn't dirty (just the
// children are). So, 10 - 3 = 7
EXPECT_EQ(dirty_count, 7u);
// We still see the locked element, so the total is 8.
EXPECT_EQ(total_count, 8u);
GetDocument().getElementById("a")->setAttribute(html_names::kStyleAttr,
"content-visibility: hidden");
UpdateAllLifecyclePhasesForTest();
// Note that this dirty_all call is now not propagating the dirty bits at all,
// since they are stopped at the top level div.
dirty_all();
GetDocument().View()->CountObjectsNeedingLayout(dirty_count, total_count,
is_subtree);
// Top level element is locked and the dirty bits were not propagated, so we
// expect 0 dirty elements. The total should be 4 ('a' + body + html + layout
// view);
EXPECT_EQ(dirty_count, 0u);
EXPECT_EQ(total_count, 4u);
}
TEST_F(DisplayLockContextRenderingTest,
PaintDirtyBitsNotPropagatedAcrossBoundary) {
SetHtmlInnerHTML(R"HTML(
<style>
.locked { content-visibility: hidden; }
div { contain: paint; }
</style>
<div id=parent>
<div id=lockable>
<div id=child>
<div id=grandchild></div>
</div>
</div>
</div>
)HTML");
auto* parent = GetDocument().getElementById("parent");
auto* lockable = GetDocument().getElementById("lockable");
auto* child = GetDocument().getElementById("child");
auto* grandchild = GetDocument().getElementById("grandchild");
auto* parent_box = parent->GetLayoutBoxModelObject();
auto* lockable_box = lockable->GetLayoutBoxModelObject();
auto* child_box = child->GetLayoutBoxModelObject();
auto* grandchild_box = grandchild->GetLayoutBoxModelObject();
ASSERT_TRUE(parent_box);
ASSERT_TRUE(lockable_box);
ASSERT_TRUE(child_box);
ASSERT_TRUE(grandchild_box);
ASSERT_TRUE(parent_box->HasSelfPaintingLayer());
ASSERT_TRUE(lockable_box->HasSelfPaintingLayer());
ASSERT_TRUE(child_box->HasSelfPaintingLayer());
ASSERT_TRUE(grandchild_box->HasSelfPaintingLayer());
auto* parent_layer = parent_box->Layer();
auto* lockable_layer = lockable_box->Layer();
auto* child_layer = child_box->Layer();
auto* grandchild_layer = grandchild_box->Layer();
EXPECT_FALSE(parent_layer->SelfOrDescendantNeedsRepaint());
EXPECT_FALSE(lockable_layer->SelfOrDescendantNeedsRepaint());
EXPECT_FALSE(child_layer->SelfOrDescendantNeedsRepaint());
EXPECT_FALSE(grandchild_layer->SelfOrDescendantNeedsRepaint());
lockable->classList().Add("locked");
GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest);
// Lockable layer needs repainting after locking.
EXPECT_FALSE(parent_layer->SelfNeedsRepaint());
EXPECT_TRUE(lockable_layer->SelfNeedsRepaint());
EXPECT_FALSE(child_layer->SelfNeedsRepaint());
EXPECT_FALSE(grandchild_layer->SelfNeedsRepaint());
// Breadcrumbs are set from the lockable layer.
EXPECT_TRUE(parent_layer->DescendantNeedsRepaint());
EXPECT_FALSE(lockable_layer->DescendantNeedsRepaint());
EXPECT_FALSE(child_layer->DescendantNeedsRepaint());
EXPECT_FALSE(grandchild_layer->DescendantNeedsRepaint());
UpdateAllLifecyclePhasesForTest();
// Everything is clean.
EXPECT_FALSE(parent_layer->SelfNeedsRepaint());
EXPECT_FALSE(lockable_layer->SelfNeedsRepaint());
EXPECT_FALSE(child_layer->SelfNeedsRepaint());
EXPECT_FALSE(grandchild_layer->SelfNeedsRepaint());
// Breadcrumbs are clean as well.
EXPECT_FALSE(parent_layer->DescendantNeedsRepaint());
EXPECT_FALSE(lockable_layer->DescendantNeedsRepaint());
EXPECT_FALSE(child_layer->DescendantNeedsRepaint());
EXPECT_FALSE(grandchild_layer->DescendantNeedsRepaint());
grandchild_layer->SetNeedsRepaint();
// Grandchild needs repaint, so everything else should be clean.
EXPECT_FALSE(parent_layer->SelfNeedsRepaint());
EXPECT_FALSE(lockable_layer->SelfNeedsRepaint());
EXPECT_FALSE(child_layer->SelfNeedsRepaint());
EXPECT_TRUE(grandchild_layer->SelfNeedsRepaint());
// Breadcrumbs are set from the lockable layer but are stopped at the locked
// boundary.
EXPECT_FALSE(parent_layer->DescendantNeedsRepaint());
EXPECT_TRUE(lockable_layer->DescendantNeedsRepaint());
EXPECT_TRUE(child_layer->DescendantNeedsRepaint());
EXPECT_FALSE(grandchild_layer->DescendantNeedsRepaint());
// Updating the lifecycle does not clean the dirty bits.
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(parent_layer->SelfNeedsRepaint());
EXPECT_FALSE(lockable_layer->SelfNeedsRepaint());
EXPECT_FALSE(child_layer->SelfNeedsRepaint());
EXPECT_TRUE(grandchild_layer->SelfNeedsRepaint());
EXPECT_FALSE(parent_layer->DescendantNeedsRepaint());
EXPECT_TRUE(lockable_layer->DescendantNeedsRepaint());
EXPECT_TRUE(child_layer->DescendantNeedsRepaint());
EXPECT_FALSE(grandchild_layer->DescendantNeedsRepaint());
// Unlocking causes lockable to repaint itself.
lockable->classList().Remove("locked");
GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest);
EXPECT_FALSE(parent_layer->SelfNeedsRepaint());
EXPECT_TRUE(lockable_layer->SelfNeedsRepaint());
EXPECT_FALSE(child_layer->SelfNeedsRepaint());
EXPECT_TRUE(grandchild_layer->SelfNeedsRepaint());
EXPECT_TRUE(parent_layer->DescendantNeedsRepaint());
EXPECT_TRUE(lockable_layer->DescendantNeedsRepaint());
EXPECT_TRUE(child_layer->DescendantNeedsRepaint());
EXPECT_FALSE(grandchild_layer->DescendantNeedsRepaint());
UpdateAllLifecyclePhasesForTest();
// Everything should be clean.
EXPECT_FALSE(parent_layer->SelfNeedsRepaint());
EXPECT_FALSE(lockable_layer->SelfNeedsRepaint());
EXPECT_FALSE(child_layer->SelfNeedsRepaint());
EXPECT_FALSE(grandchild_layer->SelfNeedsRepaint());
// Breadcrumbs are clean as well.
EXPECT_FALSE(parent_layer->DescendantNeedsRepaint());
EXPECT_FALSE(lockable_layer->DescendantNeedsRepaint());
EXPECT_FALSE(child_layer->DescendantNeedsRepaint());
EXPECT_FALSE(grandchild_layer->DescendantNeedsRepaint());
}
TEST_F(DisplayLockContextRenderingTest,
NestedLockDoesNotInvalidateOnHideOrShow) {
SetHtmlInnerHTML(R"HTML(
<style>
.auto { content-visibility: auto; }
.hidden { content-visibility: hidden; }
.item { height: 10px; }
/* this is important to not invalidate layout when we hide the element! */
#outer { contain: style layout; }
</style>
<div id=outer>
<div id=unrelated>
<div id=inner class=auto>Content</div>
</div>
</div>
)HTML");
auto* inner_element = GetDocument().getElementById("inner");
auto* unrelated_element = GetDocument().getElementById("unrelated");
auto* outer_element = GetDocument().getElementById("outer");
// Ensure that the visibility switch happens. This would also clear the
// layout.
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(outer_element->GetLayoutObject()->NeedsLayout());
EXPECT_FALSE(outer_element->GetLayoutObject()->SelfNeedsLayout());
EXPECT_FALSE(unrelated_element->GetLayoutObject()->NeedsLayout());
EXPECT_FALSE(unrelated_element->GetLayoutObject()->SelfNeedsLayout());
EXPECT_FALSE(inner_element->GetLayoutObject()->NeedsLayout());
EXPECT_FALSE(inner_element->GetLayoutObject()->SelfNeedsLayout());
// Verify lock state.
auto* inner_context = inner_element->GetDisplayLockContext();
ASSERT_TRUE(inner_context);
EXPECT_FALSE(inner_context->IsLocked());
// Lock outer.
outer_element->setAttribute(html_names::kClassAttr, "hidden");
// Ensure the lock processes (but don't run intersection observation tasks
// yet).
UpdateAllLifecyclePhasesForTest();
// Verify the lock exists.
auto* outer_context = outer_element->GetDisplayLockContext();
ASSERT_TRUE(outer_context);
EXPECT_TRUE(outer_context->IsLocked());
// Everything should be layout clean.
EXPECT_FALSE(outer_element->GetLayoutObject()->NeedsLayout());
EXPECT_FALSE(outer_element->GetLayoutObject()->SelfNeedsLayout());
EXPECT_FALSE(unrelated_element->GetLayoutObject()->NeedsLayout());
EXPECT_FALSE(unrelated_element->GetLayoutObject()->SelfNeedsLayout());
EXPECT_FALSE(inner_element->GetLayoutObject()->NeedsLayout());
EXPECT_FALSE(inner_element->GetLayoutObject()->SelfNeedsLayout());
// Inner context should not be observing the lifecycle.
EXPECT_FALSE(IsObservingLifecycle(inner_context));
// Process any visibility changes.
RunStartOfLifecycleTasks();
// Run the following checks a few times since we should be observing
// lifecycle.
for (int i = 0; i < 3; ++i) {
// It shouldn't change the fact that we're layout clean.
EXPECT_FALSE(outer_element->GetLayoutObject()->NeedsLayout());
EXPECT_FALSE(outer_element->GetLayoutObject()->SelfNeedsLayout());
EXPECT_FALSE(unrelated_element->GetLayoutObject()->NeedsLayout());
EXPECT_FALSE(unrelated_element->GetLayoutObject()->SelfNeedsLayout());
EXPECT_FALSE(inner_element->GetLayoutObject()->NeedsLayout());
EXPECT_FALSE(inner_element->GetLayoutObject()->SelfNeedsLayout());
// Because we skipped hiding the element, inner_context should be observing
// lifecycle.
EXPECT_TRUE(IsObservingLifecycle(inner_context));
UpdateAllLifecyclePhasesForTest();
}
// Unlock outer.
outer_element->setAttribute(html_names::kClassAttr, "");
// Ensure the lock processes (but don't run intersection observation tasks
// yet).
UpdateAllLifecyclePhasesForTest();
// Note that although we're not nested, we're still observing the lifecycle
// because we don't yet know whether we should or should not hide and we only
// make this decision _before_ the lifecycle actually unlocked outer.
EXPECT_TRUE(IsObservingLifecycle(inner_context));
// Verify the lock is gone.
EXPECT_FALSE(outer_context->IsLocked());
// Everything should be layout clean.
EXPECT_FALSE(outer_element->GetLayoutObject()->NeedsLayout());
EXPECT_FALSE(outer_element->GetLayoutObject()->SelfNeedsLayout());
EXPECT_FALSE(unrelated_element->GetLayoutObject()->NeedsLayout());
EXPECT_FALSE(unrelated_element->GetLayoutObject()->SelfNeedsLayout());
EXPECT_FALSE(inner_element->GetLayoutObject()->NeedsLayout());
EXPECT_FALSE(inner_element->GetLayoutObject()->SelfNeedsLayout());
// Process visibility changes.
RunStartOfLifecycleTasks();
// We now should know we're visible and so we're not observing the lifecycle.
EXPECT_FALSE(IsObservingLifecycle(inner_context));
// Also we should still be activated and unlocked.
EXPECT_FALSE(inner_context->IsLocked());
// Everything should be layout clean.
EXPECT_FALSE(outer_element->GetLayoutObject()->NeedsLayout());
EXPECT_FALSE(outer_element->GetLayoutObject()->SelfNeedsLayout());
EXPECT_FALSE(unrelated_element->GetLayoutObject()->NeedsLayout());
EXPECT_FALSE(unrelated_element->GetLayoutObject()->SelfNeedsLayout());
EXPECT_FALSE(inner_element->GetLayoutObject()->NeedsLayout());
EXPECT_FALSE(inner_element->GetLayoutObject()->SelfNeedsLayout());
}
TEST_F(DisplayLockContextRenderingTest, NestedLockDoesHideWhenItIsOffscreen) {
SetHtmlInnerHTML(R"HTML(
<style>
.auto { content-visibility: auto; }
.hidden { content-visibility: hidden; }
.item { height: 10px; }
/* this is important to not invalidate layout when we hide the element! */
#outer { contain: style layout; }
.spacer { height: 10000px; }
</style>
<div id=future_spacer></div>
<div id=outer>
<div id=unrelated>
<div id=inner class=auto>Content</div>
</div>
</div>
)HTML");
auto* inner_element = GetDocument().getElementById("inner");
auto* unrelated_element = GetDocument().getElementById("unrelated");
auto* outer_element = GetDocument().getElementById("outer");
// Ensure that the visibility switch happens. This would also clear the
// layout.
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(outer_element->GetLayoutObject()->NeedsLayout());
EXPECT_FALSE(outer_element->GetLayoutObject()->SelfNeedsLayout());
EXPECT_FALSE(unrelated_element->GetLayoutObject()->NeedsLayout());
EXPECT_FALSE(unrelated_element->GetLayoutObject()->SelfNeedsLayout());
EXPECT_FALSE(inner_element->GetLayoutObject()->NeedsLayout());
EXPECT_FALSE(inner_element->GetLayoutObject()->SelfNeedsLayout());
// Verify lock state.
auto* inner_context = inner_element->GetDisplayLockContext();
ASSERT_TRUE(inner_context);
EXPECT_FALSE(inner_context->IsLocked());
// Lock outer.
outer_element->setAttribute(html_names::kClassAttr, "hidden");
// Ensure the lock processes (but don't run intersection observation tasks
// yet).
UpdateAllLifecyclePhasesForTest();
// Verify the lock exists.
auto* outer_context = outer_element->GetDisplayLockContext();
ASSERT_TRUE(outer_context);
EXPECT_TRUE(outer_context->IsLocked());
// Everything should be layout clean.
EXPECT_FALSE(outer_element->GetLayoutObject()->NeedsLayout());
EXPECT_FALSE(outer_element->GetLayoutObject()->SelfNeedsLayout());
EXPECT_FALSE(unrelated_element->GetLayoutObject()->NeedsLayout());
EXPECT_FALSE(unrelated_element->GetLayoutObject()->SelfNeedsLayout());
EXPECT_FALSE(inner_element->GetLayoutObject()->NeedsLayout());
EXPECT_FALSE(inner_element->GetLayoutObject()->SelfNeedsLayout());
// Inner context should not be observing the lifecycle.
EXPECT_FALSE(IsObservingLifecycle(inner_context));
// Process any visibility changes.
RunStartOfLifecycleTasks();
// It shouldn't change the fact that we're layout clean.
EXPECT_FALSE(outer_element->GetLayoutObject()->NeedsLayout());
EXPECT_FALSE(outer_element->GetLayoutObject()->SelfNeedsLayout());
EXPECT_FALSE(unrelated_element->GetLayoutObject()->NeedsLayout());
EXPECT_FALSE(unrelated_element->GetLayoutObject()->SelfNeedsLayout());
EXPECT_FALSE(inner_element->GetLayoutObject()->NeedsLayout());
EXPECT_FALSE(inner_element->GetLayoutObject()->SelfNeedsLayout());
// Let future spacer become a real spacer!
GetDocument()
.getElementById("future_spacer")
->setAttribute(html_names::kClassAttr, "spacer");
UpdateAllLifecyclePhasesForTest();
// Because we skipped hiding the element, inner_context should be observing
// lifecycle.
EXPECT_TRUE(IsObservingLifecycle(inner_context));
// Unlock outer.
outer_element->setAttribute(html_names::kClassAttr, "");
// Ensure the lock processes (but don't run intersection observation tasks
// yet).
UpdateAllLifecyclePhasesForTest();
// Note that although we're not nested, we're still observing the lifecycle
// because we don't yet know whether we should or should not hide and we only
// make this decision _before_ the lifecycle actually unlocked outer.
EXPECT_TRUE(IsObservingLifecycle(inner_context));
// Verify the lock is gone.
EXPECT_FALSE(outer_context->IsLocked());
// Everything should be layout clean.
EXPECT_FALSE(outer_element->GetLayoutObject()->NeedsLayout());
EXPECT_FALSE(outer_element->GetLayoutObject()->SelfNeedsLayout());
EXPECT_FALSE(unrelated_element->GetLayoutObject()->NeedsLayout());
EXPECT_FALSE(unrelated_element->GetLayoutObject()->SelfNeedsLayout());
EXPECT_FALSE(inner_element->GetLayoutObject()->NeedsLayout());
EXPECT_FALSE(inner_element->GetLayoutObject()->SelfNeedsLayout());
// Process any visibility changes.
RunStartOfLifecycleTasks();
// We're still invisible, and we don't know that we're not nested so we're
// still observing the lifecycle.
EXPECT_TRUE(IsObservingLifecycle(inner_context));
// We're unlocked for now.
EXPECT_FALSE(inner_context->IsLocked());
// Everything should be layout clean.
EXPECT_FALSE(outer_element->GetLayoutObject()->NeedsLayout());
EXPECT_FALSE(outer_element->GetLayoutObject()->SelfNeedsLayout());
EXPECT_FALSE(unrelated_element->GetLayoutObject()->NeedsLayout());
EXPECT_FALSE(unrelated_element->GetLayoutObject()->SelfNeedsLayout());
EXPECT_FALSE(inner_element->GetLayoutObject()->NeedsLayout());
EXPECT_FALSE(inner_element->GetLayoutObject()->SelfNeedsLayout());
UpdateAllLifecyclePhasesForTest();
// We figured out that we're actually invisible so no need to observe the
// lifecycle.
EXPECT_FALSE(IsObservingLifecycle(inner_context));
// We're locked.
EXPECT_TRUE(inner_context->IsLocked());
}
TEST_F(DisplayLockContextRenderingTest,
LockedCanvasWithFallbackHasFocusableStyle) {
SetHtmlInnerHTML(R"HTML(
<style>
.auto { content-visibility: auto; }
.spacer { height: 3000px; }
</style>
<div class=spacer></div>
<div class=auto>
<canvas>
<div id=target tabindex=0></div>
</canvas>
</div>
)HTML");
auto* target = GetDocument().getElementById("target");
EXPECT_TRUE(target->IsFocusable());
}
TEST_F(DisplayLockContextRenderingTest, ForcedUnlockBookkeeping) {
SetHtmlInnerHTML(R"HTML(
<style>
.hidden { content-visibility: hidden; }
.inline { display: inline; }
</style>
<div id=target class=hidden></div>
)HTML");
auto* target = GetDocument().getElementById("target");
auto* context = target->GetDisplayLockContext();
ASSERT_TRUE(context);
EXPECT_TRUE(context->IsLocked());
EXPECT_EQ(
GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 1);
target->classList().Add("inline");
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(context->IsLocked());
EXPECT_EQ(
GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 0);
}
TEST_F(DisplayLockContextRenderingTest, LayoutRootIsSkippedIfLocked) {
SetHtmlInnerHTML(R"HTML(
<style>
.hidden { content-visibility: hidden; }
.contained { contain: strict; }
.positioned { position: absolute; top: 0; left: 0; }
</style>
<div id=hide>
<div class=contained>
<div id=new_parent class="contained positioned">
<div>
<div id=target></div>
</div>
</div>
</div>
</div>
)HTML");
// Lock an ancestor.
auto* hide = GetDocument().getElementById("hide");
hide->classList().Add("hidden");
UpdateAllLifecyclePhasesForTest();
auto* target = GetDocument().getElementById("target");
auto* new_parent = GetDocument().getElementById("new_parent");
// Reparent elements which will invalidate layout without needing to process
// style (which is blocked by the display-lock).
new_parent->appendChild(target);
// Note that we don't check target here, since it doesn't have a layout object
// after being re-parented.
EXPECT_TRUE(new_parent->GetLayoutObject()->NeedsLayout());
// Updating the lifecycle should not update new_parent, since it is in a
// locked subtree.
UpdateAllLifecyclePhasesForTest();
EXPECT_TRUE(new_parent->GetLayoutObject()->NeedsLayout());
// Unlocking and updating should update everything.
hide->classList().Remove("hidden");
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(hide->GetLayoutObject()->NeedsLayout());
EXPECT_FALSE(target->GetLayoutObject()->NeedsLayout());
EXPECT_FALSE(new_parent->GetLayoutObject()->NeedsLayout());
}
TEST_F(DisplayLockContextRenderingTest,
LayoutRootIsProcessedIfLockedAndForced) {
SetHtmlInnerHTML(R"HTML(
<style>
.hidden { content-visibility: hidden; }
.contained { contain: strict; }
.positioned { position: absolute; top: 0; left: 0; }
</style>
<div id=hide>
<div class=contained>
<div id=new_parent class="contained positioned">
<div>
<div id=target></div>
</div>
</div>
</div>
</div>
)HTML");
// Lock an ancestor.
auto* hide = GetDocument().getElementById("hide");
hide->classList().Add("hidden");
UpdateAllLifecyclePhasesForTest();
auto* target = GetDocument().getElementById("target");
auto* new_parent = GetDocument().getElementById("new_parent");
// Reparent elements which will invalidate layout without needing to process
// style (which is blocked by the display-lock).
new_parent->appendChild(target);
// Note that we don't check target here, since it doesn't have a layout object
// after being re-parented.
EXPECT_TRUE(new_parent->GetLayoutObject()->NeedsLayout());
{
auto scope = GetScopedForcedUpdate(hide, true /* include self */);
// Updating the lifecycle should update target and new_parent, since it is
// in a locked but forced subtree.
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(target->GetLayoutObject()->NeedsLayout());
EXPECT_FALSE(new_parent->GetLayoutObject()->NeedsLayout());
}
// Unlocking and updating should update everything.
hide->classList().Remove("hidden");
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(hide->GetLayoutObject()->NeedsLayout());
EXPECT_FALSE(target->GetLayoutObject()->NeedsLayout());
EXPECT_FALSE(new_parent->GetLayoutObject()->NeedsLayout());
}
TEST_F(DisplayLockContextRenderingTest, ContainStrictChild) {
SetHtmlInnerHTML(R"HTML(
<style>
.hidden { content-visibility: hidden; }
.contained { contain: strict; }
#target { backface-visibility: hidden; }
</style>
<div id=hide>
<div id=container class=contained>
<div id=target></div>
</div>
</div>
)HTML");
// Lock an ancestor.
auto* hide = GetDocument().getElementById("hide");
hide->classList().Add("hidden");
// This should not DCHECK.
UpdateAllLifecyclePhasesForTest();
hide->classList().Remove("hidden");
UpdateAllLifecyclePhasesForTest();
}
TEST_F(DisplayLockContextRenderingTest, UseCounter) {
SetHtmlInnerHTML(R"HTML(
<style>
.auto { content-visibility: auto; }
.hidden { content-visibility: hidden; }
.matchable { content-visibility: hidden-matchable; }
</style>
<div id=e1></div>
<div id=e2></div>
<div id=e3></div>
)HTML");
EXPECT_FALSE(GetDocument().IsUseCounted(WebFeature::kContentVisibilityAuto));
EXPECT_FALSE(
GetDocument().IsUseCounted(WebFeature::kContentVisibilityHidden));
EXPECT_FALSE(GetDocument().IsUseCounted(
WebFeature::kContentVisibilityHiddenMatchable));
GetDocument().getElementById("e1")->classList().Add("auto");
UpdateAllLifecyclePhasesForTest();
EXPECT_TRUE(GetDocument().IsUseCounted(WebFeature::kContentVisibilityAuto));
EXPECT_FALSE(
GetDocument().IsUseCounted(WebFeature::kContentVisibilityHidden));
EXPECT_FALSE(GetDocument().IsUseCounted(
WebFeature::kContentVisibilityHiddenMatchable));
GetDocument().getElementById("e2")->classList().Add("hidden");
UpdateAllLifecyclePhasesForTest();
EXPECT_TRUE(GetDocument().IsUseCounted(WebFeature::kContentVisibilityAuto));
EXPECT_TRUE(GetDocument().IsUseCounted(WebFeature::kContentVisibilityHidden));
EXPECT_FALSE(GetDocument().IsUseCounted(
WebFeature::kContentVisibilityHiddenMatchable));
GetDocument().getElementById("e3")->classList().Add("matchable");
UpdateAllLifecyclePhasesForTest();
EXPECT_TRUE(GetDocument().IsUseCounted(WebFeature::kContentVisibilityAuto));
EXPECT_TRUE(GetDocument().IsUseCounted(WebFeature::kContentVisibilityHidden));
EXPECT_TRUE(GetDocument().IsUseCounted(
WebFeature::kContentVisibilityHiddenMatchable));
}
TEST_F(DisplayLockContextRenderingTest, CompositingRootIsSkippedIfLocked) {
SetHtmlInnerHTML(R"HTML(
<style>
.hidden { content-visibility: hidden; }
.contained { contain: strict; }
#target { backface-visibility: hidden; }
</style>
<div id=hide>
<div id=container class=contained>
<div id=target></div>
</div>
</div>
)HTML");
// Lock an ancestor.
auto* hide = GetDocument().getElementById("hide");
hide->classList().Add("hidden");
UpdateAllLifecyclePhasesForTest();
auto* target = GetDocument().getElementById("target");
ASSERT_TRUE(target->GetLayoutObject());
auto* target_box = target->GetLayoutBoxModelObject();
ASSERT_TRUE(target_box);
EXPECT_TRUE(target_box->Layer());
EXPECT_TRUE(target_box->HasSelfPaintingLayer());
auto* target_layer = target_box->Layer();
target_layer->SetNeedsCompositingInputsUpdate();
EXPECT_TRUE(target_layer->NeedsCompositingInputsUpdate());
auto* container = GetDocument().getElementById("container");
ASSERT_TRUE(container->GetLayoutObject());
auto* container_box = container->GetLayoutBoxModelObject();
ASSERT_TRUE(container_box);
EXPECT_TRUE(container_box->Layer());
EXPECT_TRUE(container_box->HasSelfPaintingLayer());
auto* container_layer = container_box->Layer();
auto* compositor = target_layer->Compositor();
ASSERT_TRUE(compositor);
EXPECT_EQ(compositor->GetCompositingInputsRoot(), container_layer);
UpdateAllLifecyclePhasesForTest();
EXPECT_EQ(compositor->GetCompositingInputsRoot(), container_layer);
EXPECT_TRUE(target_layer->NeedsCompositingInputsUpdate());
hide->classList().Remove("hidden");
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(compositor->GetCompositingInputsRoot());
EXPECT_FALSE(target_layer->NeedsCompositingInputsUpdate());
}
TEST_F(DisplayLockContextRenderingTest,
CompositingRootIsProcessedIfLockedButForced) {
SetHtmlInnerHTML(R"HTML(
<style>
.hidden { content-visibility: hidden; }
.contained { contain: strict; }
#target { backface-visibility: hidden; }
</style>
<div id=hide>
<div class=contained>
<div id=container class=contained>
<div id=target></div>
</div>
</div>
</div>
)HTML");
// Lock an ancestor.
auto* hide = GetDocument().getElementById("hide");
hide->classList().Add("hidden");
UpdateAllLifecyclePhasesForTest();
auto* target = GetDocument().getElementById("target");
ASSERT_TRUE(target->GetLayoutObject());
auto* target_box = To<LayoutBoxModelObject>(target->GetLayoutObject());
ASSERT_TRUE(target_box);
EXPECT_TRUE(target_box->Layer());
EXPECT_TRUE(target_box->HasSelfPaintingLayer());
auto* target_layer = target_box->Layer();
target_layer->SetNeedsCompositingInputsUpdate();
EXPECT_TRUE(target_layer->NeedsCompositingInputsUpdate());
auto* container = GetDocument().getElementById("container");
ASSERT_TRUE(container->GetLayoutObject());
auto* container_box = container->GetLayoutBoxModelObject();
ASSERT_TRUE(container_box);
EXPECT_TRUE(container_box->Layer());
EXPECT_TRUE(container_box->HasSelfPaintingLayer());
auto* container_layer = container_box->Layer();
auto* compositor = target_layer->Compositor();
ASSERT_TRUE(compositor);
EXPECT_EQ(compositor->GetCompositingInputsRoot(), container_layer);
{
auto scope = GetScopedForcedUpdate(hide, true /* include self */);
UpdateAllLifecyclePhasesForTest();
}
EXPECT_FALSE(compositor->GetCompositingInputsRoot());
EXPECT_FALSE(target_layer->NeedsCompositingInputsUpdate());
hide->classList().Remove("hidden");
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(compositor->GetCompositingInputsRoot());
EXPECT_FALSE(target_layer->NeedsCompositingInputsUpdate());
}
TEST_F(DisplayLockContextRenderingTest,
NeedsLayoutTreeUpdateForNodeRespectsForcedLocks) {
SetHtmlInnerHTML(R"HTML(
<style>
.hidden { content-visibility: hidden; }
.contained { contain: strict; }
.backface_hidden { backface-visibility: hidden; }
</style>
<div id=hide>
<div id=container class=contained>
<div id=target></div>
</div>
</div>
)HTML");
// Lock an ancestor.
auto* hide = GetDocument().getElementById("hide");
hide->classList().Add("hidden");
UpdateAllLifecyclePhasesForTest();
auto* target = GetDocument().getElementById("target");
target->classList().Add("backface_hidden");
auto scope = GetScopedForcedUpdate(hide, true /* include self */);
EXPECT_TRUE(GetDocument().NeedsLayoutTreeUpdateForNode(*target));
}
TEST_F(DisplayLockContextRenderingTest, InnerScrollerAutoVisibilityMargin) {
SetHtmlInnerHTML(R"HTML(
<style>
.auto { content-visibility: auto; }
#scroller { height: 300px; overflow: scroll }
#target { height: 10px; width: 10px; }
.spacer { height: 3000px }
</style>
<div id=scroller>
<div class=spacer></div>
<div id=target class=auto></div>
</div>
)HTML");
UpdateAllLifecyclePhasesForTest();
auto* target = GetDocument().getElementById("target");
ASSERT_TRUE(target->GetDisplayLockContext());
EXPECT_TRUE(target->GetDisplayLockContext()->IsLocked());
auto* scroller = GetDocument().getElementById("scroller");
// 2600 is spacer (3000) minus scroller height (300) minus 100 for some extra
// padding.
scroller->setScrollTop(2600);
UpdateAllLifecyclePhasesForTest();
// Since the intersection observation is delivered on the next frame, run
// another lifecycle.
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(target->GetDisplayLockContext()->IsLocked());
}
TEST_F(DisplayLockContextRenderingTest,
AutoReachesStableStateOnContentSmallerThanLockedSize) {
SetHtmlInnerHTML(R"HTML(
<style>
.spacer { height: 20000px; }
.auto {
content-visibility: auto;
contain-intrinsic-size: 1px 20000px;
}
.auto > div {
height: 3000px;
}
</style>
<div class=spacer></div>
<div id=e1 class=auto><div>content</div></div>
<div id=e2 class=auto><div>content</div></div>
<div class=spacer></div>
)HTML");
UpdateAllLifecyclePhasesForTest();
GetDocument().scrollingElement()->setScrollTop(29000);
Element* element = GetDocument().getElementById("e1");
// Note that this test also unlock/relocks #e2 but we only care about #e1
// settling into a steady state.
// Initially we start with locked in the viewport.
UpdateAllLifecyclePhasesForTest();
EXPECT_TRUE(element->GetDisplayLockContext()->IsLocked());
EXPECT_EQ(GetDocument().scrollingElement()->scrollTop(), 29000.);
// It gets unlocked because it's in the viewport.
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(element->GetDisplayLockContext()->IsLocked());
EXPECT_EQ(GetDocument().scrollingElement()->scrollTop(), 29000.);
// By unlocking it, it shrinks so next time it gets relocked.
UpdateAllLifecyclePhasesForTest();
EXPECT_TRUE(element->GetDisplayLockContext()->IsLocked());
EXPECT_EQ(GetDocument().scrollingElement()->scrollTop(), 29000.);
// It again gets unlocked and shrink. This time scroll anchoring puts it right
// off the edge of the screen.
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(element->GetDisplayLockContext()->IsLocked());
EXPECT_EQ(GetDocument().scrollingElement()->scrollTop(), 23008.);
// On the next update we select the following element as an anchor and the
// scroll offset doesn't change.
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(element->GetDisplayLockContext()->IsLocked());
EXPECT_EQ(GetDocument().scrollingElement()->scrollTop(), 23008.);
// Subsequent updates are in a stable state.
for (int i = 0; i < 5; ++i) {
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(element->GetDisplayLockContext()->IsLocked());
EXPECT_EQ(GetDocument().scrollingElement()->scrollTop(), 23008.);
}
}
TEST_F(DisplayLockContextRenderingTest,
AutoReachesStableStateOnContentSmallerThanLockedSizeInLtr) {
SetHtmlInnerHTML(R"HTML(
<style>
body { writing-mode: vertical-lr }
.spacer { block-size: 20000px; }
.auto {
content-visibility: auto;
contain-intrinsic-size: 20000px 1px;
}
.auto > div {
block-size: 3000px;
}
</style>
<div class=spacer></div>
<div id=e1 class=auto><div>content</div></div>
<div id=e2 class=auto><div>content</div></div>
<div class=spacer></div>
)HTML");
UpdateAllLifecyclePhasesForTest();
GetDocument().scrollingElement()->setScrollLeft(29000);
Element* element = GetDocument().getElementById("e1");
// Note that this test also unlock/relocks #e2 but we only care about #e1
// settling into a steady state.
// Initially we start with locked in the viewport.
UpdateAllLifecyclePhasesForTest();
EXPECT_TRUE(element->GetDisplayLockContext()->IsLocked());
EXPECT_EQ(GetDocument().scrollingElement()->scrollLeft(), 29000.);
// It gets unlocked because it's in the viewport.
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(element->GetDisplayLockContext()->IsLocked());
EXPECT_EQ(GetDocument().scrollingElement()->scrollLeft(), 29000.);
// By unlocking it, it shrinks so next time it gets relocked.
UpdateAllLifecyclePhasesForTest();
EXPECT_TRUE(element->GetDisplayLockContext()->IsLocked());
EXPECT_EQ(GetDocument().scrollingElement()->scrollLeft(), 29000.);
// It again gets unlocked and shrink. This time scroll anchoring puts it right
// off the edge of the screen.
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(element->GetDisplayLockContext()->IsLocked());
EXPECT_EQ(GetDocument().scrollingElement()->scrollLeft(), 23008.);
// On the next update we select the following element as an anchor and the
// scroll offset doesn't change.
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(element->GetDisplayLockContext()->IsLocked());
EXPECT_EQ(GetDocument().scrollingElement()->scrollLeft(), 23008.);
// Subsequent updates are in a stable state.
for (int i = 0; i < 5; ++i) {
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(element->GetDisplayLockContext()->IsLocked());
EXPECT_EQ(GetDocument().scrollingElement()->scrollLeft(), 23008.);
}
}
TEST_F(DisplayLockContextRenderingTest,
AutoReachesStableStateOnContentSmallerThanLockedSizeInRtl) {
SetHtmlInnerHTML(R"HTML(
<style>
body { writing-mode: vertical-rl }
.spacer { block-size: 20000px; }
.auto {
content-visibility: auto;
contain-intrinsic-size: 20000px 1px;
}
.auto > div {
block-size: 3000px;
}
</style>
<div class=spacer></div>
<div id=e1 class=auto><div>content</div></div>
<div id=e2 class=auto><div>content</div></div>
<div class=spacer></div>
)HTML");
UpdateAllLifecyclePhasesForTest();
GetDocument().scrollingElement()->setScrollLeft(-29000);
Element* element = GetDocument().getElementById("e1");
// Note that this test also unlock/relocks #e2 but we only care about #e1
// settling into a steady state.
// Initially we start with locked in the viewport.
UpdateAllLifecyclePhasesForTest();
EXPECT_TRUE(element->GetDisplayLockContext()->IsLocked());
EXPECT_EQ(GetDocument().scrollingElement()->scrollLeft(), -29000.);
// It gets unlocked because it's in the viewport.
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(element->GetDisplayLockContext()->IsLocked());
EXPECT_EQ(GetDocument().scrollingElement()->scrollLeft(), -29000.);
// By unlocking it, it shrinks so next time it gets relocked.
UpdateAllLifecyclePhasesForTest();
EXPECT_TRUE(element->GetDisplayLockContext()->IsLocked());
EXPECT_EQ(GetDocument().scrollingElement()->scrollLeft(), -29000.);
// It again gets unlocked and shrink. This time scroll anchoring puts it right
// off the edge of the screen.
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(element->GetDisplayLockContext()->IsLocked());
EXPECT_EQ(GetDocument().scrollingElement()->scrollLeft(), -23008.);
// On the next update we select the following element as an anchor and the
// scroll offset doesn't change.
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(element->GetDisplayLockContext()->IsLocked());
EXPECT_EQ(GetDocument().scrollingElement()->scrollLeft(), -23008.);
// Subsequent updates are in a stable state.
for (int i = 0; i < 5; ++i) {
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(element->GetDisplayLockContext()->IsLocked());
EXPECT_EQ(GetDocument().scrollingElement()->scrollLeft(), -23008.);
}
}
TEST_F(DisplayLockContextRenderingTest, FirstAutoFramePaintsInViewport) {
SetHtmlInnerHTML(R"HTML(
<style>
.spacer { height: 10000px }
.auto {
content-visibility: auto;
contain-intrinsic-size: 1px 200px;
}
.auto > div { height: 100px }
</style>
<div id=visible><div>content</div></div>
<div class=spacer></div>
<div id=hidden><div>content</div></div>
)HTML");
auto* visible = GetDocument().getElementById("visible");
auto* hidden = GetDocument().getElementById("hidden");
visible->classList().Add("auto");
hidden->classList().Add("auto");
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(visible->GetDisplayLockContext()->IsLocked());
EXPECT_TRUE(hidden->GetDisplayLockContext()->IsLocked());
EXPECT_FALSE(visible->GetLayoutObject()->SelfNeedsLayout());
EXPECT_FALSE(hidden->GetLayoutObject()->SelfNeedsLayout());
auto* visible_rect = visible->getBoundingClientRect();
auto* hidden_rect = hidden->getBoundingClientRect();
EXPECT_FLOAT_EQ(visible_rect->height(), 100);
EXPECT_FLOAT_EQ(hidden_rect->height(), 200);
}
class DisplayLockContextLegacyRenderingTest
: public RenderingTest,
private ScopedCSSContentVisibilityHiddenMatchableForTest,
private ScopedLayoutNGForTest {
public:
DisplayLockContextLegacyRenderingTest()
: RenderingTest(MakeGarbageCollected<SingleChildLocalFrameClient>()),
ScopedCSSContentVisibilityHiddenMatchableForTest(true),
ScopedLayoutNGForTest(false) {}
};
TEST_F(DisplayLockContextLegacyRenderingTest,
QuirksHiddenParentBlocksChildLayout) {
GetDocument().SetCompatibilityMode(Document::kQuirksMode);
SetHtmlInnerHTML(R"HTML(
<style>
.hidden { content-visibility: hidden; }
#grandparent { height: 100px; }
#parent { height: auto; }
#item { height: 10%; }
</style>
<div id=grandparent>
<div id=parent>
<div>
<div id=item></div>
</div>
</div>
</div>
)HTML");
auto* grandparent = GetDocument().getElementById("grandparent");
auto* parent = GetDocument().getElementById("parent");
auto* grandparent_box = To<LayoutBox>(grandparent->GetLayoutObject());
auto* item_box = GetLayoutBoxByElementId("item");
ASSERT_TRUE(grandparent_box);
ASSERT_TRUE(parent->GetLayoutObject());
ASSERT_TRUE(item_box);
EXPECT_EQ(item_box->PercentHeightContainer(), grandparent_box);
parent->classList().Add("hidden");
grandparent->setAttribute(html_names::kStyleAttr, "height: 150px");
// This shouldn't DCHECK. We are allowed to have dirty percent height
// descendants in quirks mode since they can cross a display-lock boundary.
UpdateAllLifecyclePhasesForTest();
}
TEST_F(DisplayLockContextTest, GraphicsLayerBitsNotCheckedInLockedSubtree) {
ResizeAndFocus();
GetDocument().GetSettings()->SetPreferCompositingToLCDTextEnabled(true);
SetHtmlInnerHTML(R"HTML(
<style>
div {
width: 100px;
height: 100px;
contain: style layout;
will-change: transform;
}
.locked {
content-visibility: hidden;
}
</style>
<div id=container>
<div>
<div id=target></div>
</div>
</div>
)HTML");
// Check if the result is correct if we update the contents.
auto* container = GetDocument().getElementById("container");
auto* target = GetDocument().getElementById("target");
auto* target_box = target->GetLayoutBoxModelObject();
ASSERT_TRUE(target_box);
EXPECT_TRUE(target_box->Layer());
EXPECT_TRUE(target_box->HasSelfPaintingLayer());
auto* target_layer = target_box->Layer();
ASSERT_TRUE(target_layer->HasCompositedLayerMapping());
container->classList().Add("locked");
target_layer->GetCompositedLayerMapping()->SetNeedsGraphicsLayerUpdate(
kGraphicsLayerUpdateLocal);
// This should not DCHECK.
UpdateAllLifecyclePhasesForTest();
EXPECT_TRUE(
target_layer->GetCompositedLayerMapping()->NeedsGraphicsLayerUpdate());
container->classList().Remove("locked");
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(
target_layer->GetCompositedLayerMapping()->NeedsGraphicsLayerUpdate());
}
TEST_F(DisplayLockContextTest, PrintingUnlocksAutoLocks) {
ResizeAndFocus();
SetHtmlInnerHTML(R"HTML(
<style>
.spacer { height: 30000px; }
.auto { content-visibility: auto; }
</style>
<div class=spacer></div>
<div id=target class=auto>
<div id=nested class=auto></div>
</div>
)HTML");
auto* target = GetDocument().getElementById("target");
auto* nested = GetDocument().getElementById("nested");
ASSERT_TRUE(target->GetDisplayLockContext());
EXPECT_TRUE(target->GetDisplayLockContext()->IsLocked());
// Nested should not have a display lock since we would have skipped style.
EXPECT_FALSE(nested->GetDisplayLockContext());
{
// Create a paint preview scope.
Document::PaintPreviewScope scope(GetDocument());
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(target->GetDisplayLockContext()->IsLocked());
// Nested should have created a context...
ASSERT_TRUE(nested->GetDisplayLockContext());
// ... but it should be unlocked.
EXPECT_FALSE(nested->GetDisplayLockContext()->IsLocked());
}
EXPECT_TRUE(target->GetDisplayLockContext()->IsLocked());
EXPECT_TRUE(nested->GetDisplayLockContext()->IsLocked());
}
TEST_F(DisplayLockContextTest, CullRectUpdate) {
ScopedCullRectUpdateForTest cull_rect_update(true);
ResizeAndFocus();
SetHtmlInnerHTML(R"HTML(
<style>
#clip {
width: 100px;
height: 100px;
overflow: hidden;
}
#container {
width: 300px;
height: 300px;
contain: paint layout;
}
.locked {
content-visibility: hidden;
}
</style>
<div id="clip">
<div id="container"
style="width: 300px; height: 300px; contain: paint layout">
<div id="target" style="position: relative"></div>
</div>
</div>
)HTML");
// Check if the result is correct if we update the contents.
auto* container = GetDocument().getElementById("container");
auto* target = GetDocument().getElementById("target")->GetLayoutBox();
EXPECT_EQ(IntRect(0, 0, 100, 100),
target->FirstFragment().GetCullRect().Rect());
container->classList().Add("locked");
UpdateAllLifecyclePhasesForTest();
EXPECT_EQ(IntRect(0, 0, 100, 100),
target->FirstFragment().GetCullRect().Rect());
GetDocument().getElementById("clip")->setAttribute(html_names::kStyleAttr,
"width: 200px");
UpdateAllLifecyclePhasesForTest();
EXPECT_EQ(IntRect(0, 0, 100, 100),
target->FirstFragment().GetCullRect().Rect());
container->classList().Remove("locked");
UpdateAllLifecyclePhasesForTest();
EXPECT_EQ(IntRect(0, 0, 200, 100),
target->FirstFragment().GetCullRect().Rect());
}
} // namespace blink