1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "third_party/blink/renderer/core/display_lock/display_lock_context.h"
6
7 #include <memory>
8 #include <utility>
9
10 #include "base/memory/ptr_util.h"
11 #include "base/test/scoped_feature_list.h"
12 #include "cc/base/features.h"
13 #include "mojo/public/cpp/bindings/pending_remote.h"
14 #include "mojo/public/cpp/bindings/receiver.h"
15 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
16 #include "third_party/blink/renderer/core/css/style_change_reason.h"
17 #include "third_party/blink/renderer/core/display_lock/display_lock_document_state.h"
18 #include "third_party/blink/renderer/core/display_lock/display_lock_utilities.h"
19 #include "third_party/blink/renderer/core/dom/dom_token_list.h"
20 #include "third_party/blink/renderer/core/dom/events/native_event_listener.h"
21 #include "third_party/blink/renderer/core/dom/node_computed_style.h"
22 #include "third_party/blink/renderer/core/editing/ephemeral_range.h"
23 #include "third_party/blink/renderer/core/editing/finder/text_finder.h"
24 #include "third_party/blink/renderer/core/editing/markers/document_marker_controller.h"
25 #include "third_party/blink/renderer/core/editing/visible_units.h"
26 #include "third_party/blink/renderer/core/frame/find_in_page.h"
27 #include "third_party/blink/renderer/core/frame/frame_test_helpers.h"
28 #include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
29 #include "third_party/blink/renderer/core/geometry/dom_rect.h"
30 #include "third_party/blink/renderer/core/html/html_template_element.h"
31 #include "third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.h"
32 #include "third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.h"
33 #include "third_party/blink/renderer/core/paint/paint_layer.h"
34 #include "third_party/blink/renderer/core/style/computed_style.h"
35 #include "third_party/blink/renderer/core/testing/core_unit_test_helper.h"
36 #include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
37 #include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
38
39 namespace blink {
40 namespace {
41 class DisplayLockTestFindInPageClient : public mojom::blink::FindInPageClient {
42 public:
DisplayLockTestFindInPageClient()43 DisplayLockTestFindInPageClient()
44 : find_results_are_ready_(false), active_index_(-1), count_(-1) {}
45
46 ~DisplayLockTestFindInPageClient() override = default;
47
SetFrame(WebLocalFrameImpl * frame)48 void SetFrame(WebLocalFrameImpl* frame) {
49 frame->GetFindInPage()->SetClient(receiver_.BindNewPipeAndPassRemote());
50 }
51
SetNumberOfMatches(int request_id,unsigned int current_number_of_matches,mojom::blink::FindMatchUpdateType final_update)52 void SetNumberOfMatches(
53 int request_id,
54 unsigned int current_number_of_matches,
55 mojom::blink::FindMatchUpdateType final_update) final {
56 count_ = current_number_of_matches;
57 find_results_are_ready_ =
58 (final_update == mojom::blink::FindMatchUpdateType::kFinalUpdate);
59 }
60
SetActiveMatch(int request_id,const gfx::Rect & active_match_rect,int active_match_ordinal,mojom::blink::FindMatchUpdateType final_update)61 void SetActiveMatch(int request_id,
62 const gfx::Rect& active_match_rect,
63 int active_match_ordinal,
64 mojom::blink::FindMatchUpdateType final_update) final {
65 active_match_rect_ = IntRect(active_match_rect);
66 active_index_ = active_match_ordinal;
67 find_results_are_ready_ =
68 (final_update == mojom::blink::FindMatchUpdateType::kFinalUpdate);
69 }
70
FindResultsAreReady() const71 bool FindResultsAreReady() const { return find_results_are_ready_; }
Count() const72 int Count() const { return count_; }
ActiveIndex() const73 int ActiveIndex() const { return active_index_; }
ActiveMatchRect() const74 IntRect ActiveMatchRect() const { return active_match_rect_; }
75
Reset()76 void Reset() {
77 find_results_are_ready_ = false;
78 count_ = -1;
79 active_index_ = -1;
80 active_match_rect_ = IntRect();
81 }
82
83 private:
84 IntRect active_match_rect_;
85 bool find_results_are_ready_;
86 int active_index_;
87
88 int count_;
89 mojo::Receiver<mojom::blink::FindInPageClient> receiver_{this};
90 };
91
92 class DisplayLockEmptyEventListener final : public NativeEventListener {
93 public:
Invoke(ExecutionContext *,Event *)94 void Invoke(ExecutionContext*, Event*) final {}
95 };
96 } // namespace
97
98 class DisplayLockContextTest
99 : public testing::Test,
100 private ScopedCSSContentVisibilityHiddenMatchableForTest {
101 public:
DisplayLockContextTest()102 DisplayLockContextTest()
103 : ScopedCSSContentVisibilityHiddenMatchableForTest(true) {}
104
SetUp()105 void SetUp() override { web_view_helper_.Initialize(); }
106
TearDown()107 void TearDown() override { web_view_helper_.Reset(); }
108
GetDocument()109 Document& GetDocument() {
110 return *static_cast<Document*>(
111 web_view_helper_.LocalMainFrame()->GetDocument());
112 }
GetFindInPage()113 FindInPage* GetFindInPage() {
114 return web_view_helper_.LocalMainFrame()->GetFindInPage();
115 }
LocalMainFrame()116 WebLocalFrameImpl* LocalMainFrame() {
117 return web_view_helper_.LocalMainFrame();
118 }
119
UpdateAllLifecyclePhasesForTest()120 void UpdateAllLifecyclePhasesForTest() {
121 GetDocument().View()->UpdateAllLifecyclePhasesForTest();
122 }
123
SetHtmlInnerHTML(const char * content)124 void SetHtmlInnerHTML(const char* content) {
125 GetDocument().documentElement()->setInnerHTML(String::FromUTF8(content));
126 UpdateAllLifecyclePhasesForTest();
127 }
128
ResizeAndFocus()129 void ResizeAndFocus() {
130 web_view_helper_.Resize(gfx::Size(640, 480));
131 web_view_helper_.GetWebView()->MainFrameWidget()->SetFocus(true);
132 test::RunPendingTasks();
133 }
134
LockElement(Element & element,bool activatable)135 void LockElement(Element& element, bool activatable) {
136 StringBuilder value;
137 value.Append("content-visibility: hidden");
138 if (activatable)
139 value.Append("-matchable");
140 element.setAttribute(html_names::kStyleAttr, value.ToAtomicString());
141 UpdateAllLifecyclePhasesForTest();
142 }
143
CommitElement(Element & element,bool update_lifecycle=true)144 void CommitElement(Element& element, bool update_lifecycle = true) {
145 element.setAttribute(html_names::kStyleAttr, "");
146 if (update_lifecycle)
147 UpdateAllLifecyclePhasesForTest();
148 }
149
UnlockImmediate(DisplayLockContext * context)150 void UnlockImmediate(DisplayLockContext* context) {
151 context->SetRequestedState(EContentVisibility::kVisible);
152 }
153
FindOptions(bool new_session=true)154 mojom::blink::FindOptionsPtr FindOptions(bool new_session = true) {
155 auto find_options = mojom::blink::FindOptions::New();
156 find_options->run_synchronously_for_testing = true;
157 find_options->new_session = new_session;
158 find_options->forward = true;
159 return find_options;
160 }
161
Find(String search_text,DisplayLockTestFindInPageClient & client,bool new_session=true)162 void Find(String search_text,
163 DisplayLockTestFindInPageClient& client,
164 bool new_session = true) {
165 client.Reset();
166 GetFindInPage()->Find(FAKE_FIND_ID, search_text, FindOptions(new_session));
167 test::RunPendingTasks();
168 }
169
ReattachWasBlocked(DisplayLockContext * context)170 bool ReattachWasBlocked(DisplayLockContext* context) {
171 return context->reattach_layout_tree_was_blocked_;
172 }
173
174 const int FAKE_FIND_ID = 1;
175
176 private:
177 frame_test_helpers::WebViewHelper web_view_helper_;
178 };
179
TEST_F(DisplayLockContextTest,LockAfterAppendStyleDirtyBits)180 TEST_F(DisplayLockContextTest, LockAfterAppendStyleDirtyBits) {
181 SetHtmlInnerHTML(R"HTML(
182 <style>
183 div {
184 width: 100px;
185 height: 100px;
186 contain: style layout paint;
187 }
188 </style>
189 <body><div id="container"><div id="child"></div></div></body>
190 )HTML");
191
192 auto* element = GetDocument().getElementById("container");
193 LockElement(*element, false);
194
195 // Finished acquiring the lock.
196 EXPECT_FALSE(element->GetDisplayLockContext()->ShouldStyleChildren());
197 EXPECT_FALSE(element->GetDisplayLockContext()->ShouldLayoutChildren());
198 EXPECT_FALSE(element->GetDisplayLockContext()->ShouldPaintChildren());
199 EXPECT_EQ(
200 GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 1);
201
202 // If the element is dirty, style recalc would handle it in the next recalc.
203 element->setAttribute(html_names::kStyleAttr,
204 "content-visibility: hidden; color: red;");
205 EXPECT_TRUE(GetDocument().body()->ChildNeedsStyleRecalc());
206 EXPECT_TRUE(element->NeedsStyleRecalc());
207 EXPECT_FALSE(element->ChildNeedsStyleRecalc());
208
209 UpdateAllLifecyclePhasesForTest();
210 EXPECT_FALSE(element->NeedsStyleRecalc());
211 EXPECT_FALSE(element->ChildNeedsStyleRecalc());
212 EXPECT_TRUE(element->GetComputedStyle());
213 EXPECT_EQ(
214 element->GetComputedStyle()->VisitedDependentColor(GetCSSPropertyColor()),
215 MakeRGB(255, 0, 0));
216 // Manually commit the lock so that we can verify which dirty bits get
217 // propagated.
218 UnlockImmediate(element->GetDisplayLockContext());
219 element->setAttribute(html_names::kStyleAttr, "color: red;");
220
221 auto* child = GetDocument().getElementById("child");
222 EXPECT_TRUE(GetDocument().body()->ChildNeedsStyleRecalc());
223 EXPECT_TRUE(element->NeedsStyleRecalc());
224 EXPECT_FALSE(element->ChildNeedsStyleRecalc());
225 EXPECT_FALSE(child->NeedsStyleRecalc());
226 UpdateAllLifecyclePhasesForTest();
227
228 EXPECT_FALSE(GetDocument().body()->ChildNeedsStyleRecalc());
229 EXPECT_FALSE(element->NeedsStyleRecalc());
230 EXPECT_FALSE(element->ChildNeedsStyleRecalc());
231 EXPECT_FALSE(child->NeedsStyleRecalc());
232
233 // Lock the child.
234 child->setAttribute(html_names::kStyleAttr,
235 "content-visibility: hidden; color: blue;");
236 UpdateAllLifecyclePhasesForTest();
237
238 EXPECT_FALSE(GetDocument().body()->ChildNeedsStyleRecalc());
239 EXPECT_FALSE(element->NeedsStyleRecalc());
240 EXPECT_FALSE(element->ChildNeedsStyleRecalc());
241 EXPECT_FALSE(child->NeedsStyleRecalc());
242 ASSERT_TRUE(child->GetComputedStyle());
243 EXPECT_EQ(
244 child->GetComputedStyle()->VisitedDependentColor(GetCSSPropertyColor()),
245 MakeRGB(0, 0, 255));
246
247 UnlockImmediate(child->GetDisplayLockContext());
248 child->setAttribute(html_names::kStyleAttr, "color: blue;");
249 EXPECT_TRUE(GetDocument().body()->ChildNeedsStyleRecalc());
250 EXPECT_FALSE(element->NeedsStyleRecalc());
251 EXPECT_TRUE(element->ChildNeedsStyleRecalc());
252 EXPECT_TRUE(child->NeedsStyleRecalc());
253 UpdateAllLifecyclePhasesForTest();
254
255 EXPECT_FALSE(GetDocument().body()->ChildNeedsStyleRecalc());
256 EXPECT_FALSE(element->NeedsStyleRecalc());
257 EXPECT_FALSE(element->ChildNeedsStyleRecalc());
258 EXPECT_FALSE(child->NeedsStyleRecalc());
259 ASSERT_TRUE(child->GetComputedStyle());
260 EXPECT_EQ(
261 child->GetComputedStyle()->VisitedDependentColor(GetCSSPropertyColor()),
262 MakeRGB(0, 0, 255));
263 }
264
TEST_F(DisplayLockContextTest,LockedElementIsNotSearchableViaFindInPage)265 TEST_F(DisplayLockContextTest, LockedElementIsNotSearchableViaFindInPage) {
266 ResizeAndFocus();
267 SetHtmlInnerHTML(R"HTML(
268 <style>
269 #container {
270 width: 100px;
271 height: 100px;
272 contain: style layout paint;
273 }
274 </style>
275 <body><div id="container">testing</div></body>
276 )HTML");
277
278 const String search_text = "testing";
279 DisplayLockTestFindInPageClient client;
280 client.SetFrame(LocalMainFrame());
281
282 auto* container = GetDocument().getElementById("container");
283 LockElement(*container, false /* activatable */);
284 Find(search_text, client);
285 EXPECT_EQ(0, client.Count());
286
287 // Check if we can find the result after we commit.
288 CommitElement(*container);
289 Find(search_text, client);
290 EXPECT_EQ(1, client.Count());
291 }
292
TEST_F(DisplayLockContextTest,ActivatableLockedElementIsSearchableViaFindInPage)293 TEST_F(DisplayLockContextTest,
294 ActivatableLockedElementIsSearchableViaFindInPage) {
295 ResizeAndFocus();
296 SetHtmlInnerHTML(R"HTML(
297 <style>
298 .spacer {
299 height: 10000px;
300 }
301 #container {
302 width: 100px;
303 height: 100px;
304 contain: style layout paint;
305 }
306 </style>
307 <body><div class=spacer></div><div id="container">testing</div></body>
308 )HTML");
309
310 const String search_text = "testing";
311 DisplayLockTestFindInPageClient client;
312 client.SetFrame(LocalMainFrame());
313
314 // Finds on a normal element.
315 Find(search_text, client);
316 EXPECT_EQ(1, client.Count());
317 // Clears selections since we're going to use the same query next time.
318 GetFindInPage()->StopFinding(
319 mojom::StopFindAction::kStopFindActionClearSelection);
320
321 auto* container = GetDocument().getElementById("container");
322 LockElement(*container, true /* activatable */);
323
324 EXPECT_TRUE(container->GetDisplayLockContext()->IsLocked());
325 // Check if we can still get the same result with the same query.
326 Find(search_text, client);
327 EXPECT_EQ(1, client.Count());
328 EXPECT_TRUE(container->GetDisplayLockContext()->IsLocked());
329 EXPECT_GT(GetDocument().scrollingElement()->scrollTop(), 1000);
330 }
331
TEST_F(DisplayLockContextTest,FindInPageContinuesAfterRelock)332 TEST_F(DisplayLockContextTest, FindInPageContinuesAfterRelock) {
333 ResizeAndFocus();
334 SetHtmlInnerHTML(R"HTML(
335 <style>
336 .spacer {
337 height: 10000px;
338 }
339 #container {
340 width: 100px;
341 height: 100px;
342 }
343 .auto { content-visibility: auto }
344 </style>
345 <body><div class=spacer></div><div id="container" class=auto>testing</div></body>
346 )HTML");
347
348 const String search_text = "testing";
349 DisplayLockTestFindInPageClient client;
350 client.SetFrame(LocalMainFrame());
351
352 // Finds on a normal element.
353 Find(search_text, client);
354 EXPECT_EQ(1, client.Count());
355
356 auto* container = GetDocument().getElementById("container");
357 GetDocument().scrollingElement()->setScrollTop(0);
358
359 UpdateAllLifecyclePhasesForTest();
360 UpdateAllLifecyclePhasesForTest();
361
362 EXPECT_TRUE(container->GetDisplayLockContext()->IsLocked());
363
364 // Clears selections since we're going to use the same query next time.
365 GetFindInPage()->StopFinding(
366 mojom::StopFindAction::kStopFindActionKeepSelection);
367
368 UpdateAllLifecyclePhasesForTest();
369
370 // This should not crash.
371 Find(search_text, client, false);
372
373 EXPECT_EQ(1, client.Count());
374 }
375
TEST_F(DisplayLockContextTest,FindInPageTargetBelowLockedSize)376 TEST_F(DisplayLockContextTest, FindInPageTargetBelowLockedSize) {
377 ResizeAndFocus();
378 SetHtmlInnerHTML(R"HTML(
379 <style>
380 .spacer { height: 1000px; }
381 #container { contain-intrinsic-size: 1px; }
382 .auto { content-visibility: auto }
383 </style>
384 <body>
385 <div class=spacer></div>
386 <div id=container class=auto>
387 <div class=spacer></div>
388 <div id=target>testing</div>
389 </div>
390 <div class=spacer></div>
391 <div class=spacer></div>
392 </body>
393 )HTML");
394
395 const String search_text = "testing";
396 DisplayLockTestFindInPageClient client;
397 client.SetFrame(LocalMainFrame());
398
399 Find(search_text, client);
400 EXPECT_EQ(1, client.Count());
401
402 auto* container = GetDocument().getElementById("container");
403 // The container should be unlocked.
404 EXPECT_FALSE(container->GetDisplayLockContext()->IsLocked());
405 UpdateAllLifecyclePhasesForTest();
406 EXPECT_FALSE(container->GetDisplayLockContext()->IsLocked());
407
408 EXPECT_FLOAT_EQ(GetDocument().scrollingElement()->scrollTop(), 1768);
409 }
410
TEST_F(DisplayLockContextTest,ActivatableLockedElementTickmarksAreAtLockedRoots)411 TEST_F(DisplayLockContextTest,
412 ActivatableLockedElementTickmarksAreAtLockedRoots) {
413 ResizeAndFocus();
414 SetHtmlInnerHTML(R"HTML(
415 <style>
416 body {
417 margin: 0;
418 padding: 0;
419 }
420 .small {
421 width: 100px;
422 height: 100px;
423 }
424 .medium {
425 width: 150px;
426 height: 150px;
427 }
428 .large {
429 width: 200px;
430 height: 200px;
431 }
432 </style>
433 <body>
434 testing
435 <div id="container1" class=small>testing</div>
436 <div id="container2" class=medium>testing</div>
437 <div id="container3" class=large>
438 <div id="container4" class=medium>testing</div>
439 </div>
440 <div id="container5" class=small>testing</div>
441 </body>
442 )HTML");
443
444 const String search_text = "testing";
445 DisplayLockTestFindInPageClient client;
446 client.SetFrame(LocalMainFrame());
447
448 auto* container1 = GetDocument().getElementById("container1");
449 auto* container2 = GetDocument().getElementById("container2");
450 auto* container3 = GetDocument().getElementById("container3");
451 auto* container4 = GetDocument().getElementById("container4");
452 auto* container5 = GetDocument().getElementById("container5");
453 LockElement(*container5, false /* activatable */);
454 LockElement(*container4, true /* activatable */);
455 LockElement(*container3, true /* activatable */);
456 LockElement(*container2, true /* activatable */);
457 LockElement(*container1, true /* activatable */);
458
459 EXPECT_TRUE(container1->GetDisplayLockContext()->IsLocked());
460 EXPECT_TRUE(container2->GetDisplayLockContext()->IsLocked());
461 EXPECT_TRUE(container3->GetDisplayLockContext()->IsLocked());
462 EXPECT_TRUE(container4->GetDisplayLockContext()->IsLocked());
463 EXPECT_TRUE(container5->GetDisplayLockContext()->IsLocked());
464
465 // Do a find-in-page.
466 Find(search_text, client);
467 // "testing" outside of the container divs, and 3 inside activatable divs.
468 EXPECT_EQ(4, client.Count());
469
470 auto tick_rects = GetDocument().Markers().LayoutRectsForTextMatchMarkers();
471 ASSERT_EQ(4u, tick_rects.size());
472
473 // Sort the layout rects by y coordinate for deterministic checks below.
474 std::sort(tick_rects.begin(), tick_rects.end(),
475 [](const IntRect& a, const IntRect& b) { return a.Y() < b.Y(); });
476
477 int y_offset = tick_rects[0].Height();
478
479 // The first tick rect will be based on the text itself, so we don't need to
480 // check that. The next three should be the small, medium and large rects,
481 // since those are the locked roots.
482 EXPECT_EQ(IntRect(0, y_offset, 100, 100), tick_rects[1]);
483 y_offset += tick_rects[1].Height();
484 EXPECT_EQ(IntRect(0, y_offset, 150, 150), tick_rects[2]);
485 y_offset += tick_rects[2].Height();
486 EXPECT_EQ(IntRect(0, y_offset, 200, 200), tick_rects[3]);
487 }
488
TEST_F(DisplayLockContextTest,FindInPageWhileLockedContentChangesDoesNotCrash)489 TEST_F(DisplayLockContextTest,
490 FindInPageWhileLockedContentChangesDoesNotCrash) {
491 ResizeAndFocus();
492 SetHtmlInnerHTML(R"HTML(
493 <style>
494 #container {
495 width: 100px;
496 height: 100px;
497 contain: style layout paint;
498 }
499 </style>
500 <body>testing<div id="container">testing</div></body>
501 )HTML");
502
503 const String search_text = "testing";
504 DisplayLockTestFindInPageClient client;
505 client.SetFrame(LocalMainFrame());
506
507 // Lock the container.
508 auto* container = GetDocument().getElementById("container");
509 LockElement(*container, true /* activatable */);
510 EXPECT_TRUE(container->GetDisplayLockContext()->IsLocked());
511
512 // Find the first "testing", container still locked since the match is outside
513 // the container.
514 Find(search_text, client);
515 EXPECT_EQ(2, client.Count());
516 EXPECT_TRUE(container->GetDisplayLockContext()->IsLocked());
517
518 // Change the inner text, this should not DCHECK.
519 container->setInnerHTML("please don't DCHECK");
520 UpdateAllLifecyclePhasesForTest();
521 }
522
TEST_F(DisplayLockContextTest,FindInPageWithChangedContent)523 TEST_F(DisplayLockContextTest, FindInPageWithChangedContent) {
524 if (!RuntimeEnabledFeatures::LayoutNGEnabled())
525 return;
526 ResizeAndFocus();
527 SetHtmlInnerHTML(R"HTML(
528 <style>
529 #container {
530 width: 100px;
531 height: 100px;
532 contain: style layout paint;
533 }
534 </style>
535 <body><div id="container">testing</div></body>
536 )HTML");
537
538 // Check if the result is correct if we update the contents.
539 auto* container = GetDocument().getElementById("container");
540 LockElement(*container, true /* activatable */);
541 EXPECT_TRUE(container->GetDisplayLockContext()->IsLocked());
542 container->setInnerHTML(
543 "testing"
544 "<div>testing</div>"
545 "tes<div style='display:none;'>x</div>ting");
546
547 DisplayLockTestFindInPageClient client;
548 client.SetFrame(LocalMainFrame());
549 Find("testing", client);
550 EXPECT_EQ(3, client.Count());
551 EXPECT_TRUE(container->GetDisplayLockContext()->IsLocked());
552 }
553
TEST_F(DisplayLockContextTest,FindInPageWithNoMatchesWontUnlock)554 TEST_F(DisplayLockContextTest, FindInPageWithNoMatchesWontUnlock) {
555 ResizeAndFocus();
556 SetHtmlInnerHTML(R"HTML(
557 <style>
558 #container {
559 width: 100px;
560 height: 100px;
561 contain: style layout paint;
562 }
563 </style>
564 <body><div id="container">tes<div>ting</div><div style='display:none;'>testing</div></div></body>
565 )HTML");
566
567 auto* container = GetDocument().getElementById("container");
568 LockElement(*container, true /* activatable */);
569 LockElement(*container, true /* activatable */);
570 EXPECT_TRUE(container->GetDisplayLockContext()->IsLocked());
571
572 DisplayLockTestFindInPageClient client;
573 client.SetFrame(LocalMainFrame());
574 Find("testing", client);
575 // No results found, container stays locked.
576 EXPECT_EQ(0, client.Count());
577 EXPECT_TRUE(container->GetDisplayLockContext()->IsLocked());
578 }
579
TEST_F(DisplayLockContextTest,NestedActivatableLockedElementIsSearchableViaFindInPage)580 TEST_F(DisplayLockContextTest,
581 NestedActivatableLockedElementIsSearchableViaFindInPage) {
582 ResizeAndFocus();
583 SetHtmlInnerHTML(R"HTML(
584 <body>
585 <style>
586 div {
587 width: 100px;
588 height: 100px;
589 contain: style layout;
590 }
591 </style>
592 <div id='container'>
593 <div>testing1</div>
594 <div id='activatable'>
595 testing2
596 <div id='nestedNonActivatable'>
597 testing3
598 </div>
599 </div>
600 <div id='nonActivatable'>testing4</div>
601 </div>
602 "</body>"
603 )HTML");
604
605 auto* container = GetDocument().getElementById("container");
606 auto* activatable = GetDocument().getElementById("activatable");
607 auto* non_activatable = GetDocument().getElementById("nonActivatable");
608 auto* nested_non_activatable =
609 GetDocument().getElementById("nestedNonActivatable");
610
611 LockElement(*non_activatable, false /* activatable */);
612 LockElement(*nested_non_activatable, false /* activatable */);
613 LockElement(*activatable, true /* activatable */);
614 LockElement(*container, true /* activatable */);
615
616 EXPECT_TRUE(container->GetDisplayLockContext()->IsLocked());
617 EXPECT_TRUE(activatable->GetDisplayLockContext()->IsLocked());
618 EXPECT_TRUE(non_activatable->GetDisplayLockContext()->IsLocked());
619 EXPECT_TRUE(nested_non_activatable->GetDisplayLockContext()->IsLocked());
620
621 // We can find testing1 and testing2.
622 DisplayLockTestFindInPageClient client;
623 client.SetFrame(LocalMainFrame());
624 Find("testing", client);
625 EXPECT_EQ(2, client.Count());
626 EXPECT_EQ(1, client.ActiveIndex());
627
628 EXPECT_TRUE(container->GetDisplayLockContext()->IsLocked());
629 EXPECT_TRUE(activatable->GetDisplayLockContext()->IsLocked());
630 EXPECT_TRUE(non_activatable->GetDisplayLockContext()->IsLocked());
631 EXPECT_TRUE(nested_non_activatable->GetDisplayLockContext()->IsLocked());
632 }
633
TEST_F(DisplayLockContextTest,NestedActivatableLockedElementIsNotUnlockedByFindInPage)634 TEST_F(DisplayLockContextTest,
635 NestedActivatableLockedElementIsNotUnlockedByFindInPage) {
636 ResizeAndFocus();
637 SetHtmlInnerHTML(R"HTML(
638 <body>
639 <style>
640 div {
641 width: 100px;
642 height: 100px;
643 contain: style layout;
644 }
645 </style>
646 <div id='container'>
647 <div id='child'>testing1</div>
648 </div>
649 )HTML");
650 auto* container = GetDocument().getElementById("container");
651 auto* child = GetDocument().getElementById("child");
652 LockElement(*child, true /* activatable */);
653 LockElement(*container, true /* activatable */);
654
655 EXPECT_TRUE(container->GetDisplayLockContext()->IsLocked());
656 EXPECT_TRUE(child->GetDisplayLockContext()->IsLocked());
657 // We can find testing1 and testing2.
658 DisplayLockTestFindInPageClient client;
659 client.SetFrame(LocalMainFrame());
660 Find("testing", client);
661 EXPECT_EQ(1, client.Count());
662 EXPECT_EQ(1, client.ActiveIndex());
663
664 EXPECT_TRUE(container->GetDisplayLockContext()->IsLocked());
665 EXPECT_TRUE(child->GetDisplayLockContext()->IsLocked());
666 }
667
TEST_F(DisplayLockContextTest,CallUpdateStyleAndLayoutAfterChange)668 TEST_F(DisplayLockContextTest, CallUpdateStyleAndLayoutAfterChange) {
669 ResizeAndFocus();
670 SetHtmlInnerHTML(R"HTML(
671 <style>
672 #container {
673 width: 100px;
674 height: 100px;
675 contain: style layout paint;
676 }
677 </style>
678 <body><div id="container"><b>t</b>esting</div></body>
679 )HTML");
680 auto* element = GetDocument().getElementById("container");
681 LockElement(*element, false);
682
683 // Sanity checks to ensure the element is locked.
684 EXPECT_TRUE(element->GetDisplayLockContext()->IsLocked());
685 EXPECT_FALSE(element->GetDisplayLockContext()->ShouldStyleChildren());
686 EXPECT_FALSE(element->GetDisplayLockContext()->ShouldLayoutChildren());
687 EXPECT_FALSE(element->GetDisplayLockContext()->ShouldPaintChildren());
688 EXPECT_EQ(
689 GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 1);
690 EXPECT_EQ(GetDocument()
691 .GetDisplayLockDocumentState()
692 .DisplayLockBlockingAllActivationCount(),
693 1);
694
695 EXPECT_FALSE(element->NeedsStyleRecalc());
696 EXPECT_FALSE(element->ChildNeedsStyleRecalc());
697 EXPECT_FALSE(element->NeedsReattachLayoutTree());
698 EXPECT_FALSE(element->ChildNeedsReattachLayoutTree());
699
700 // Testing whitespace reattachment, shouldn't mark for reattachment.
701 element->firstChild()->remove();
702
703 EXPECT_FALSE(element->NeedsStyleRecalc());
704 EXPECT_FALSE(element->ChildNeedsStyleRecalc());
705 EXPECT_FALSE(element->NeedsReattachLayoutTree());
706 EXPECT_FALSE(element->ChildNeedsReattachLayoutTree());
707
708 GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest);
709
710 EXPECT_FALSE(element->NeedsStyleRecalc());
711 EXPECT_FALSE(element->ChildNeedsStyleRecalc());
712 EXPECT_FALSE(element->NeedsReattachLayoutTree());
713 EXPECT_FALSE(element->ChildNeedsReattachLayoutTree());
714
715 // Testing whitespace reattachment + dirty style.
716 element->setInnerHTML("<div>something</div>");
717
718 EXPECT_FALSE(element->NeedsStyleRecalc());
719 EXPECT_TRUE(element->ChildNeedsStyleRecalc());
720 EXPECT_FALSE(element->NeedsReattachLayoutTree());
721 EXPECT_FALSE(element->ChildNeedsReattachLayoutTree());
722
723 GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest);
724
725 EXPECT_FALSE(element->NeedsStyleRecalc());
726 EXPECT_TRUE(element->ChildNeedsStyleRecalc());
727 EXPECT_FALSE(element->NeedsReattachLayoutTree());
728 EXPECT_FALSE(element->ChildNeedsReattachLayoutTree());
729
730 // Manually start commit, so that we can verify which dirty bits get
731 // propagated.
732 UnlockImmediate(element->GetDisplayLockContext());
733 EXPECT_TRUE(element->ChildNeedsStyleRecalc());
734 EXPECT_FALSE(element->NeedsReattachLayoutTree());
735 EXPECT_FALSE(element->ChildNeedsReattachLayoutTree());
736
737 // Simulating style recalc happening, will mark for reattachment.
738 element->ClearChildNeedsStyleRecalc();
739 element->firstChild()->ClearNeedsStyleRecalc();
740 element->GetDisplayLockContext()->DidStyleChildren();
741
742 EXPECT_FALSE(element->ChildNeedsStyleRecalc());
743 EXPECT_FALSE(element->NeedsReattachLayoutTree());
744 EXPECT_TRUE(element->ChildNeedsReattachLayoutTree());
745 }
746
TEST_F(DisplayLockContextTest,CallUpdateStyleAndLayoutAfterChangeCSS)747 TEST_F(DisplayLockContextTest, CallUpdateStyleAndLayoutAfterChangeCSS) {
748 ResizeAndFocus();
749 SetHtmlInnerHTML(R"HTML(
750 <style>
751 #container {
752 width: 100px;
753 height: 100px;
754 contain: style layout paint;
755 }
756 .bg {
757 background: blue;
758 }
759 .locked {
760 content-visibility: hidden;
761 }
762 </style>
763 <body><div class=locked id="container"><b>t</b>esting<div id=inner></div></div></body>
764 )HTML");
765 auto* element = GetDocument().getElementById("container");
766 auto* inner = GetDocument().getElementById("inner");
767
768 // Sanity checks to ensure the element is locked.
769 EXPECT_FALSE(element->GetDisplayLockContext()->ShouldStyleChildren());
770 EXPECT_FALSE(element->GetDisplayLockContext()->ShouldLayoutChildren());
771 EXPECT_FALSE(element->GetDisplayLockContext()->ShouldPaintChildren());
772 EXPECT_EQ(
773 GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 1);
774 EXPECT_EQ(GetDocument()
775 .GetDisplayLockDocumentState()
776 .DisplayLockBlockingAllActivationCount(),
777 1);
778
779 EXPECT_TRUE(ReattachWasBlocked(element->GetDisplayLockContext()));
780 // Note that we didn't create a layout object for inner, since the layout tree
781 // attachment was blocked.
782 EXPECT_FALSE(inner->GetLayoutObject());
783
784 EXPECT_FALSE(element->NeedsStyleRecalc());
785 EXPECT_FALSE(element->ChildNeedsStyleRecalc());
786 EXPECT_FALSE(element->NeedsReattachLayoutTree());
787 EXPECT_FALSE(element->ChildNeedsReattachLayoutTree());
788
789 element->classList().Remove("locked");
790
791 // Class list changed, so we should need self style change.
792 EXPECT_TRUE(element->NeedsStyleRecalc());
793 EXPECT_FALSE(element->ChildNeedsStyleRecalc());
794 EXPECT_FALSE(element->NeedsReattachLayoutTree());
795 EXPECT_FALSE(element->ChildNeedsReattachLayoutTree());
796
797 UpdateAllLifecyclePhasesForTest();
798
799 EXPECT_FALSE(element->NeedsStyleRecalc());
800 EXPECT_FALSE(element->ChildNeedsStyleRecalc());
801 EXPECT_FALSE(element->NeedsReattachLayoutTree());
802 EXPECT_FALSE(element->ChildNeedsReattachLayoutTree());
803 // Because we upgraded our style change, we created a layout object for inner.
804 EXPECT_TRUE(inner->GetLayoutObject());
805 }
806
TEST_F(DisplayLockContextTest,LockedElementAndDescendantsAreNotFocusable)807 TEST_F(DisplayLockContextTest, LockedElementAndDescendantsAreNotFocusable) {
808 ResizeAndFocus();
809 SetHtmlInnerHTML(R"HTML(
810 <style>
811 #container {
812 width: 100px;
813 height: 100px;
814 contain: style layout paint;
815 }
816 </style>
817 <body>
818 <div id="container">
819 <input id="textfield", type="text">
820 </div>
821 </body>
822 )HTML");
823
824 // We start off as being focusable.
825 ASSERT_TRUE(GetDocument().getElementById("textfield")->IsKeyboardFocusable());
826 ASSERT_TRUE(GetDocument().getElementById("textfield")->IsMouseFocusable());
827 ASSERT_TRUE(GetDocument().getElementById("textfield")->IsFocusable());
828 EXPECT_EQ(
829 GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 0);
830 EXPECT_EQ(GetDocument()
831 .GetDisplayLockDocumentState()
832 .DisplayLockBlockingAllActivationCount(),
833 0);
834
835 auto* element = GetDocument().getElementById("container");
836 LockElement(*element, false);
837
838 // Sanity checks to ensure the element is locked.
839 EXPECT_FALSE(element->GetDisplayLockContext()->ShouldStyleChildren());
840 EXPECT_FALSE(element->GetDisplayLockContext()->ShouldLayoutChildren());
841 EXPECT_FALSE(element->GetDisplayLockContext()->ShouldPaintChildren());
842 EXPECT_EQ(
843 GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 1);
844 EXPECT_EQ(GetDocument()
845 .GetDisplayLockDocumentState()
846 .DisplayLockBlockingAllActivationCount(),
847 1);
848
849 // The input should not be focusable now.
850 EXPECT_FALSE(
851 GetDocument().getElementById("textfield")->IsKeyboardFocusable());
852 EXPECT_FALSE(GetDocument().getElementById("textfield")->IsMouseFocusable());
853 EXPECT_FALSE(GetDocument().getElementById("textfield")->IsFocusable());
854
855 // Calling explicit focus() should also not focus the element.
856 GetDocument().getElementById("textfield")->focus();
857 EXPECT_FALSE(GetDocument().FocusedElement());
858
859 // Now commit the lock and ensure we can focus the input
860 CommitElement(*element);
861
862 EXPECT_TRUE(element->GetDisplayLockContext()->ShouldStyleChildren());
863 EXPECT_TRUE(element->GetDisplayLockContext()->ShouldLayoutChildren());
864 EXPECT_TRUE(element->GetDisplayLockContext()->ShouldPaintChildren());
865
866 UpdateAllLifecyclePhasesForTest();
867
868 EXPECT_EQ(
869 GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 0);
870 EXPECT_EQ(GetDocument()
871 .GetDisplayLockDocumentState()
872 .DisplayLockBlockingAllActivationCount(),
873 0);
874 EXPECT_TRUE(GetDocument().getElementById("textfield")->IsKeyboardFocusable());
875 EXPECT_TRUE(GetDocument().getElementById("textfield")->IsMouseFocusable());
876 EXPECT_TRUE(GetDocument().getElementById("textfield")->IsFocusable());
877
878 // Calling explicit focus() should focus the element
879 GetDocument().getElementById("textfield")->focus();
880 EXPECT_EQ(GetDocument().FocusedElement(),
881 GetDocument().getElementById("textfield"));
882 }
883
TEST_F(DisplayLockContextTest,DisplayLockPreventsActivation)884 TEST_F(DisplayLockContextTest, DisplayLockPreventsActivation) {
885 ResizeAndFocus();
886 SetHtmlInnerHTML(R"HTML(
887 <body>
888 <div id="shadowHost">
889 <div id="slotted"></div>
890 </div>
891 </body>
892 )HTML");
893
894 auto* host = GetDocument().getElementById("shadowHost");
895 auto* slotted = GetDocument().getElementById("slotted");
896
897 ASSERT_FALSE(
898 host->DisplayLockPreventsActivation(DisplayLockActivationReason::kAny));
899 ASSERT_FALSE(slotted->DisplayLockPreventsActivation(
900 DisplayLockActivationReason::kAny));
901
902 ShadowRoot& shadow_root =
903 host->AttachShadowRootInternal(ShadowRootType::kOpen);
904 shadow_root.setInnerHTML(
905 "<div id='container' style='contain:style layout "
906 "paint;'><slot></slot></div>");
907 UpdateAllLifecyclePhasesForTest();
908
909 auto* container = shadow_root.getElementById("container");
910 EXPECT_FALSE(
911 host->DisplayLockPreventsActivation(DisplayLockActivationReason::kAny));
912 EXPECT_FALSE(container->DisplayLockPreventsActivation(
913 DisplayLockActivationReason::kAny));
914 EXPECT_FALSE(slotted->DisplayLockPreventsActivation(
915 DisplayLockActivationReason::kAny));
916
917 LockElement(*container, false);
918
919 EXPECT_EQ(
920 GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 1);
921 EXPECT_EQ(GetDocument()
922 .GetDisplayLockDocumentState()
923 .DisplayLockBlockingAllActivationCount(),
924 1);
925 EXPECT_FALSE(
926 host->DisplayLockPreventsActivation(DisplayLockActivationReason::kAny));
927 EXPECT_TRUE(container->DisplayLockPreventsActivation(
928 DisplayLockActivationReason::kAny));
929 EXPECT_TRUE(slotted->DisplayLockPreventsActivation(
930 DisplayLockActivationReason::kAny));
931
932 // Ensure that we resolve the acquire callback, thus finishing the acquire
933 // step.
934 UpdateAllLifecyclePhasesForTest();
935
936 CommitElement(*container);
937
938 EXPECT_EQ(
939 GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 0);
940 EXPECT_EQ(GetDocument()
941 .GetDisplayLockDocumentState()
942 .DisplayLockBlockingAllActivationCount(),
943 0);
944 EXPECT_FALSE(
945 host->DisplayLockPreventsActivation(DisplayLockActivationReason::kAny));
946 EXPECT_FALSE(container->DisplayLockPreventsActivation(
947 DisplayLockActivationReason::kAny));
948 EXPECT_FALSE(slotted->DisplayLockPreventsActivation(
949 DisplayLockActivationReason::kAny));
950
951 UpdateAllLifecyclePhasesForTest();
952
953 EXPECT_EQ(
954 GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 0);
955 EXPECT_EQ(GetDocument()
956 .GetDisplayLockDocumentState()
957 .DisplayLockBlockingAllActivationCount(),
958 0);
959 EXPECT_FALSE(
960 host->DisplayLockPreventsActivation(DisplayLockActivationReason::kAny));
961 EXPECT_FALSE(container->DisplayLockPreventsActivation(
962 DisplayLockActivationReason::kAny));
963 EXPECT_FALSE(slotted->DisplayLockPreventsActivation(
964 DisplayLockActivationReason::kAny));
965
966 SetHtmlInnerHTML(R"HTML(
967 <body>
968 <div id="nonviewport" style="content-visibility: hidden-matchable">
969 </div>
970 </body>
971 )HTML");
972 auto* non_viewport = GetDocument().getElementById("nonviewport");
973
974 EXPECT_EQ(
975 GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 1);
976 EXPECT_EQ(GetDocument()
977 .GetDisplayLockDocumentState()
978 .DisplayLockBlockingAllActivationCount(),
979 0);
980
981 EXPECT_FALSE(non_viewport->DisplayLockPreventsActivation(
982 DisplayLockActivationReason::kAny));
983 EXPECT_FALSE(non_viewport->DisplayLockPreventsActivation(
984 DisplayLockActivationReason::kFindInPage));
985 EXPECT_TRUE(non_viewport->DisplayLockPreventsActivation(
986 DisplayLockActivationReason::kUserFocus));
987 }
988
TEST_F(DisplayLockContextTest,LockedElementAndFlatTreeDescendantsAreNotFocusable)989 TEST_F(DisplayLockContextTest,
990 LockedElementAndFlatTreeDescendantsAreNotFocusable) {
991 ResizeAndFocus();
992 SetHtmlInnerHTML(R"HTML(
993 <body>
994 <div id="shadowHost">
995 <input id="textfield" type="text">
996 </div>
997 </body>
998 )HTML");
999
1000 auto* host = GetDocument().getElementById("shadowHost");
1001 auto* text_field = GetDocument().getElementById("textfield");
1002 ShadowRoot& shadow_root =
1003 host->AttachShadowRootInternal(ShadowRootType::kOpen);
1004 shadow_root.setInnerHTML(
1005 "<div id='container' style='contain:style layout "
1006 "paint;'><slot></slot></div>");
1007
1008 UpdateAllLifecyclePhasesForTest();
1009 ASSERT_TRUE(text_field->IsKeyboardFocusable());
1010 ASSERT_TRUE(text_field->IsMouseFocusable());
1011 ASSERT_TRUE(text_field->IsFocusable());
1012
1013 auto* element = shadow_root.getElementById("container");
1014 LockElement(*element, false);
1015
1016 // Sanity checks to ensure the element is locked.
1017 EXPECT_FALSE(element->GetDisplayLockContext()->ShouldStyleChildren());
1018 EXPECT_FALSE(element->GetDisplayLockContext()->ShouldLayoutChildren());
1019 EXPECT_FALSE(element->GetDisplayLockContext()->ShouldPaintChildren());
1020 EXPECT_EQ(
1021 GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 1);
1022 EXPECT_EQ(GetDocument()
1023 .GetDisplayLockDocumentState()
1024 .DisplayLockBlockingAllActivationCount(),
1025 1);
1026
1027 // The input should not be focusable now.
1028 EXPECT_FALSE(text_field->IsKeyboardFocusable());
1029 EXPECT_FALSE(text_field->IsMouseFocusable());
1030 EXPECT_FALSE(text_field->IsFocusable());
1031
1032 // Calling explicit focus() should also not focus the element.
1033 text_field->focus();
1034 EXPECT_FALSE(GetDocument().FocusedElement());
1035 }
1036
TEST_F(DisplayLockContextTest,LockedCountsWithMultipleLocks)1037 TEST_F(DisplayLockContextTest, LockedCountsWithMultipleLocks) {
1038 ResizeAndFocus();
1039 SetHtmlInnerHTML(R"HTML(
1040 <style>
1041 .container {
1042 width: 100px;
1043 height: 100px;
1044 contain: style layout paint;
1045 }
1046 </style>
1047 <body>
1048 <div id="one" class="container">
1049 <div id="two" class="container"></div>
1050 </div>
1051 <div id="three" class="container"></div>
1052 </body>
1053 )HTML");
1054
1055 EXPECT_EQ(
1056 GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 0);
1057 EXPECT_EQ(GetDocument()
1058 .GetDisplayLockDocumentState()
1059 .DisplayLockBlockingAllActivationCount(),
1060 0);
1061
1062 auto* one = GetDocument().getElementById("one");
1063 auto* two = GetDocument().getElementById("two");
1064 auto* three = GetDocument().getElementById("three");
1065
1066 LockElement(*one, false);
1067
1068 EXPECT_EQ(
1069 GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 1);
1070 EXPECT_EQ(GetDocument()
1071 .GetDisplayLockDocumentState()
1072 .DisplayLockBlockingAllActivationCount(),
1073 1);
1074
1075 LockElement(*two, false);
1076
1077 // Because |two| is nested, the lock counts aren't updated since the lock
1078 // doesn't actually take effect until style can determine that we should lock.
1079 EXPECT_EQ(
1080 GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 1);
1081 EXPECT_EQ(GetDocument()
1082 .GetDisplayLockDocumentState()
1083 .DisplayLockBlockingAllActivationCount(),
1084 1);
1085
1086 LockElement(*three, false);
1087
1088 EXPECT_EQ(
1089 GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 2);
1090 EXPECT_EQ(GetDocument()
1091 .GetDisplayLockDocumentState()
1092 .DisplayLockBlockingAllActivationCount(),
1093 2);
1094
1095 // Now commit the outer lock.
1096 CommitElement(*one);
1097
1098 // The counts remain the same since now the inner lock is determined to be
1099 // locked.
1100 EXPECT_EQ(
1101 GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 2);
1102 EXPECT_EQ(GetDocument()
1103 .GetDisplayLockDocumentState()
1104 .DisplayLockBlockingAllActivationCount(),
1105 2);
1106
1107 // Commit the inner lock.
1108 CommitElement(*two);
1109
1110 // Both inner and outer locks should have committed.
1111 EXPECT_EQ(
1112 GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 1);
1113 EXPECT_EQ(GetDocument()
1114 .GetDisplayLockDocumentState()
1115 .DisplayLockBlockingAllActivationCount(),
1116 1);
1117
1118 // Commit the sibling lock.
1119 CommitElement(*three);
1120
1121 // Both inner and outer locks should have committed.
1122 EXPECT_EQ(
1123 GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 0);
1124 EXPECT_EQ(GetDocument()
1125 .GetDisplayLockDocumentState()
1126 .DisplayLockBlockingAllActivationCount(),
1127 0);
1128 }
1129
TEST_F(DisplayLockContextTest,ActivatableNotCountedAsBlocking)1130 TEST_F(DisplayLockContextTest, ActivatableNotCountedAsBlocking) {
1131 ResizeAndFocus();
1132 SetHtmlInnerHTML(R"HTML(
1133 <style>
1134 .container {
1135 width: 100px;
1136 height: 100px;
1137 contain: style layout paint;
1138 }
1139 </style>
1140 <body>
1141 <div id="activatable" class="container"></div>
1142 <div id="nonActivatable" class="container"></div>
1143 </body>
1144 )HTML");
1145
1146 EXPECT_EQ(
1147 GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 0);
1148 EXPECT_EQ(GetDocument()
1149 .GetDisplayLockDocumentState()
1150 .DisplayLockBlockingAllActivationCount(),
1151 0);
1152
1153 auto* activatable = GetDocument().getElementById("activatable");
1154 auto* non_activatable = GetDocument().getElementById("nonActivatable");
1155
1156 // Initial display lock context should be activatable, since nothing skipped
1157 // activation for it.
1158 EXPECT_TRUE(activatable->EnsureDisplayLockContext().IsActivatable(
1159 DisplayLockActivationReason::kAny));
1160
1161 LockElement(*activatable, true);
1162
1163 EXPECT_EQ(
1164 GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 1);
1165 EXPECT_EQ(GetDocument()
1166 .GetDisplayLockDocumentState()
1167 .DisplayLockBlockingAllActivationCount(),
1168 0);
1169 EXPECT_TRUE(activatable->GetDisplayLockContext()->IsActivatable(
1170 DisplayLockActivationReason::kAny));
1171
1172 LockElement(*non_activatable, false);
1173
1174 EXPECT_EQ(
1175 GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 2);
1176 EXPECT_EQ(GetDocument()
1177 .GetDisplayLockDocumentState()
1178 .DisplayLockBlockingAllActivationCount(),
1179 1);
1180 EXPECT_FALSE(non_activatable->GetDisplayLockContext()->IsActivatable(
1181 DisplayLockActivationReason::kAny));
1182
1183 // Now commit the lock for |non_activatable|.
1184 CommitElement(*non_activatable);
1185
1186 EXPECT_EQ(
1187 GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 1);
1188 EXPECT_EQ(GetDocument()
1189 .GetDisplayLockDocumentState()
1190 .DisplayLockBlockingAllActivationCount(),
1191 0);
1192
1193 // Re-acquire the lock for |activatable| again with the activatable flag.
1194 LockElement(*activatable, true);
1195
1196 EXPECT_EQ(
1197 GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 1);
1198 EXPECT_EQ(GetDocument()
1199 .GetDisplayLockDocumentState()
1200 .DisplayLockBlockingAllActivationCount(),
1201 0);
1202 EXPECT_TRUE(activatable->GetDisplayLockContext()->IsActivatable(
1203 DisplayLockActivationReason::kAny));
1204 }
1205
TEST_F(DisplayLockContextTest,ElementInTemplate)1206 TEST_F(DisplayLockContextTest, ElementInTemplate) {
1207 ResizeAndFocus();
1208 SetHtmlInnerHTML(R"HTML(
1209 <style>
1210 #child {
1211 width: 100px;
1212 height: 100px;
1213 contain: style layout paint;
1214 }
1215 #grandchild {
1216 color: blue;
1217 }
1218 #container {
1219 display: none;
1220 }
1221 </style>
1222 <body>
1223 <template id="template"><div id="child"><div id="grandchild">foo</div></div></template>
1224 <div id="container"></div>
1225 </body>
1226 )HTML");
1227
1228 EXPECT_EQ(
1229 GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 0);
1230 EXPECT_EQ(GetDocument()
1231 .GetDisplayLockDocumentState()
1232 .DisplayLockBlockingAllActivationCount(),
1233 0);
1234
1235 auto* template_el =
1236 To<HTMLTemplateElement>(GetDocument().getElementById("template"));
1237 auto* child = To<Element>(template_el->content()->firstChild());
1238 EXPECT_FALSE(child->isConnected());
1239
1240 // Try to lock an element in a template.
1241 LockElement(*child, false);
1242
1243 EXPECT_EQ(
1244 GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 0);
1245 EXPECT_EQ(GetDocument()
1246 .GetDisplayLockDocumentState()
1247 .DisplayLockBlockingAllActivationCount(),
1248 0);
1249 EXPECT_FALSE(child->GetDisplayLockContext());
1250
1251 // Commit also works, but does nothing.
1252 CommitElement(*child);
1253 EXPECT_FALSE(child->GetDisplayLockContext());
1254
1255 // Try to lock an element that was moved from a template to a document.
1256 auto* document_child =
1257 To<Element>(GetDocument().adoptNode(child, ASSERT_NO_EXCEPTION));
1258 auto* container = GetDocument().getElementById("container");
1259 container->appendChild(document_child);
1260
1261 LockElement(*document_child, false);
1262
1263 // These should be 0, since container is display: none, so locking its child
1264 // is not visible to style.
1265 EXPECT_EQ(
1266 GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 0);
1267 EXPECT_EQ(GetDocument()
1268 .GetDisplayLockDocumentState()
1269 .DisplayLockBlockingAllActivationCount(),
1270 0);
1271 ASSERT_FALSE(document_child->GetDisplayLockContext());
1272
1273 container->setAttribute(html_names::kStyleAttr, "display: block;");
1274 EXPECT_TRUE(container->NeedsStyleRecalc());
1275 UpdateAllLifecyclePhasesForTest();
1276
1277 EXPECT_EQ(
1278 GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 1);
1279 EXPECT_EQ(GetDocument()
1280 .GetDisplayLockDocumentState()
1281 .DisplayLockBlockingAllActivationCount(),
1282 1);
1283 ASSERT_TRUE(document_child->GetDisplayLockContext());
1284 EXPECT_TRUE(document_child->GetDisplayLockContext()->IsLocked());
1285
1286 document_child->setAttribute(html_names::kStyleAttr,
1287 "content-visibility: hidden; color: red;");
1288 UpdateAllLifecyclePhasesForTest();
1289
1290 EXPECT_FALSE(document_child->NeedsStyleRecalc());
1291
1292 // Commit will unlock the element and update the style.
1293 document_child->setAttribute(html_names::kStyleAttr, "color: red;");
1294 UpdateAllLifecyclePhasesForTest();
1295
1296 EXPECT_FALSE(document_child->GetDisplayLockContext()->IsLocked());
1297 EXPECT_EQ(
1298 GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 0);
1299 EXPECT_EQ(GetDocument()
1300 .GetDisplayLockDocumentState()
1301 .DisplayLockBlockingAllActivationCount(),
1302 0);
1303
1304 EXPECT_FALSE(document_child->NeedsStyleRecalc());
1305 EXPECT_FALSE(document_child->ChildNeedsStyleRecalc());
1306 ASSERT_TRUE(document_child->GetComputedStyle());
1307 EXPECT_EQ(document_child->GetComputedStyle()->VisitedDependentColor(
1308 GetCSSPropertyColor()),
1309 MakeRGB(255, 0, 0));
1310
1311 auto* grandchild = GetDocument().getElementById("grandchild");
1312 EXPECT_FALSE(grandchild->NeedsStyleRecalc());
1313 EXPECT_FALSE(grandchild->ChildNeedsStyleRecalc());
1314 ASSERT_TRUE(grandchild->GetComputedStyle());
1315 EXPECT_EQ(grandchild->GetComputedStyle()->VisitedDependentColor(
1316 GetCSSPropertyColor()),
1317 MakeRGB(0, 0, 255));
1318 }
1319
TEST_F(DisplayLockContextTest,AncestorAllowedTouchAction)1320 TEST_F(DisplayLockContextTest, AncestorAllowedTouchAction) {
1321 SetHtmlInnerHTML(R"HTML(
1322 <style>
1323 #locked {
1324 width: 100px;
1325 height: 100px;
1326 contain: style layout paint;
1327 }
1328 </style>
1329 <div id="ancestor">
1330 <div id="handler">
1331 <div id="descendant">
1332 <div id="locked">
1333 <div id="lockedchild"></div>
1334 </div>
1335 </div>
1336 </div>
1337 </div>
1338 )HTML");
1339
1340 auto* ancestor_element = GetDocument().getElementById("ancestor");
1341 auto* handler_element = GetDocument().getElementById("handler");
1342 auto* descendant_element = GetDocument().getElementById("descendant");
1343 auto* locked_element = GetDocument().getElementById("locked");
1344 auto* lockedchild_element = GetDocument().getElementById("lockedchild");
1345
1346 LockElement(*locked_element, false);
1347 EXPECT_TRUE(locked_element->GetDisplayLockContext()->IsLocked());
1348
1349 auto* ancestor_object = ancestor_element->GetLayoutObject();
1350 auto* handler_object = handler_element->GetLayoutObject();
1351 auto* descendant_object = descendant_element->GetLayoutObject();
1352 auto* locked_object = locked_element->GetLayoutObject();
1353 auto* lockedchild_object = lockedchild_element->GetLayoutObject();
1354
1355 EXPECT_FALSE(ancestor_object->EffectiveAllowedTouchActionChanged());
1356 EXPECT_FALSE(handler_object->EffectiveAllowedTouchActionChanged());
1357 EXPECT_FALSE(descendant_object->EffectiveAllowedTouchActionChanged());
1358 EXPECT_FALSE(locked_object->EffectiveAllowedTouchActionChanged());
1359 EXPECT_FALSE(lockedchild_object->EffectiveAllowedTouchActionChanged());
1360
1361 EXPECT_FALSE(ancestor_object->DescendantEffectiveAllowedTouchActionChanged());
1362 EXPECT_FALSE(handler_object->DescendantEffectiveAllowedTouchActionChanged());
1363 EXPECT_FALSE(
1364 descendant_object->DescendantEffectiveAllowedTouchActionChanged());
1365 EXPECT_FALSE(locked_object->DescendantEffectiveAllowedTouchActionChanged());
1366 EXPECT_FALSE(
1367 lockedchild_object->DescendantEffectiveAllowedTouchActionChanged());
1368
1369 EXPECT_FALSE(ancestor_object->InsideBlockingTouchEventHandler());
1370 EXPECT_FALSE(handler_object->InsideBlockingTouchEventHandler());
1371 EXPECT_FALSE(descendant_object->InsideBlockingTouchEventHandler());
1372 EXPECT_FALSE(locked_object->InsideBlockingTouchEventHandler());
1373 EXPECT_FALSE(lockedchild_object->InsideBlockingTouchEventHandler());
1374
1375 auto* callback = MakeGarbageCollected<DisplayLockEmptyEventListener>();
1376 handler_element->addEventListener(event_type_names::kTouchstart, callback);
1377
1378 EXPECT_FALSE(ancestor_object->EffectiveAllowedTouchActionChanged());
1379 EXPECT_TRUE(handler_object->EffectiveAllowedTouchActionChanged());
1380 EXPECT_FALSE(descendant_object->EffectiveAllowedTouchActionChanged());
1381 EXPECT_FALSE(locked_object->EffectiveAllowedTouchActionChanged());
1382 EXPECT_FALSE(lockedchild_object->EffectiveAllowedTouchActionChanged());
1383
1384 EXPECT_TRUE(ancestor_object->DescendantEffectiveAllowedTouchActionChanged());
1385 EXPECT_FALSE(handler_object->DescendantEffectiveAllowedTouchActionChanged());
1386 EXPECT_FALSE(
1387 descendant_object->DescendantEffectiveAllowedTouchActionChanged());
1388 EXPECT_FALSE(locked_object->DescendantEffectiveAllowedTouchActionChanged());
1389 EXPECT_FALSE(
1390 lockedchild_object->DescendantEffectiveAllowedTouchActionChanged());
1391
1392 UpdateAllLifecyclePhasesForTest();
1393 EXPECT_FALSE(ancestor_object->EffectiveAllowedTouchActionChanged());
1394 EXPECT_FALSE(handler_object->EffectiveAllowedTouchActionChanged());
1395 EXPECT_FALSE(descendant_object->EffectiveAllowedTouchActionChanged());
1396 EXPECT_FALSE(locked_object->EffectiveAllowedTouchActionChanged());
1397 EXPECT_FALSE(lockedchild_object->EffectiveAllowedTouchActionChanged());
1398
1399 EXPECT_FALSE(ancestor_object->DescendantEffectiveAllowedTouchActionChanged());
1400 EXPECT_FALSE(handler_object->DescendantEffectiveAllowedTouchActionChanged());
1401 EXPECT_FALSE(
1402 descendant_object->DescendantEffectiveAllowedTouchActionChanged());
1403 EXPECT_FALSE(locked_object->DescendantEffectiveAllowedTouchActionChanged());
1404 EXPECT_FALSE(
1405 lockedchild_object->DescendantEffectiveAllowedTouchActionChanged());
1406
1407 EXPECT_FALSE(ancestor_object->InsideBlockingTouchEventHandler());
1408 EXPECT_TRUE(handler_object->InsideBlockingTouchEventHandler());
1409 EXPECT_TRUE(descendant_object->InsideBlockingTouchEventHandler());
1410 EXPECT_TRUE(locked_object->InsideBlockingTouchEventHandler());
1411 EXPECT_FALSE(lockedchild_object->InsideBlockingTouchEventHandler());
1412
1413 // Manually commit the lock so that we can verify which dirty bits get
1414 // propagated.
1415 CommitElement(*locked_element, false);
1416 UnlockImmediate(locked_element->GetDisplayLockContext());
1417
1418 EXPECT_FALSE(ancestor_object->EffectiveAllowedTouchActionChanged());
1419 EXPECT_FALSE(handler_object->EffectiveAllowedTouchActionChanged());
1420 EXPECT_FALSE(descendant_object->EffectiveAllowedTouchActionChanged());
1421 EXPECT_TRUE(locked_object->EffectiveAllowedTouchActionChanged());
1422 EXPECT_FALSE(lockedchild_object->EffectiveAllowedTouchActionChanged());
1423
1424 EXPECT_TRUE(ancestor_object->DescendantEffectiveAllowedTouchActionChanged());
1425 EXPECT_TRUE(handler_object->DescendantEffectiveAllowedTouchActionChanged());
1426 EXPECT_TRUE(
1427 descendant_object->DescendantEffectiveAllowedTouchActionChanged());
1428 EXPECT_FALSE(locked_object->DescendantEffectiveAllowedTouchActionChanged());
1429 EXPECT_FALSE(
1430 lockedchild_object->DescendantEffectiveAllowedTouchActionChanged());
1431
1432 EXPECT_FALSE(ancestor_object->InsideBlockingTouchEventHandler());
1433 EXPECT_TRUE(handler_object->InsideBlockingTouchEventHandler());
1434 EXPECT_TRUE(descendant_object->InsideBlockingTouchEventHandler());
1435 EXPECT_TRUE(locked_object->InsideBlockingTouchEventHandler());
1436 EXPECT_FALSE(lockedchild_object->InsideBlockingTouchEventHandler());
1437
1438 UpdateAllLifecyclePhasesForTest();
1439 EXPECT_FALSE(ancestor_object->EffectiveAllowedTouchActionChanged());
1440 EXPECT_FALSE(handler_object->EffectiveAllowedTouchActionChanged());
1441 EXPECT_FALSE(descendant_object->EffectiveAllowedTouchActionChanged());
1442 EXPECT_FALSE(locked_object->EffectiveAllowedTouchActionChanged());
1443 EXPECT_FALSE(lockedchild_object->EffectiveAllowedTouchActionChanged());
1444
1445 EXPECT_FALSE(ancestor_object->DescendantEffectiveAllowedTouchActionChanged());
1446 EXPECT_FALSE(handler_object->DescendantEffectiveAllowedTouchActionChanged());
1447 EXPECT_FALSE(
1448 descendant_object->DescendantEffectiveAllowedTouchActionChanged());
1449 EXPECT_FALSE(locked_object->DescendantEffectiveAllowedTouchActionChanged());
1450 EXPECT_FALSE(
1451 lockedchild_object->DescendantEffectiveAllowedTouchActionChanged());
1452
1453 EXPECT_FALSE(ancestor_object->InsideBlockingTouchEventHandler());
1454 EXPECT_TRUE(handler_object->InsideBlockingTouchEventHandler());
1455 EXPECT_TRUE(descendant_object->InsideBlockingTouchEventHandler());
1456 EXPECT_TRUE(locked_object->InsideBlockingTouchEventHandler());
1457 EXPECT_TRUE(lockedchild_object->InsideBlockingTouchEventHandler());
1458 }
1459
TEST_F(DisplayLockContextTest,DescendantAllowedTouchAction)1460 TEST_F(DisplayLockContextTest, DescendantAllowedTouchAction) {
1461 SetHtmlInnerHTML(R"HTML(
1462 <style>
1463 #locked {
1464 width: 100px;
1465 height: 100px;
1466 contain: style layout paint;
1467 }
1468 </style>
1469 <div id="ancestor">
1470 <div id="descendant">
1471 <div id="locked">
1472 <div id="handler"></div>
1473 </div>
1474 </div>
1475 </div>
1476 )HTML");
1477
1478 auto* ancestor_element = GetDocument().getElementById("ancestor");
1479 auto* descendant_element = GetDocument().getElementById("descendant");
1480 auto* locked_element = GetDocument().getElementById("locked");
1481 auto* handler_element = GetDocument().getElementById("handler");
1482
1483 LockElement(*locked_element, false);
1484 EXPECT_TRUE(locked_element->GetDisplayLockContext()->IsLocked());
1485
1486 auto* ancestor_object = ancestor_element->GetLayoutObject();
1487 auto* descendant_object = descendant_element->GetLayoutObject();
1488 auto* locked_object = locked_element->GetLayoutObject();
1489 auto* handler_object = handler_element->GetLayoutObject();
1490
1491 EXPECT_FALSE(ancestor_object->EffectiveAllowedTouchActionChanged());
1492 EXPECT_FALSE(descendant_object->EffectiveAllowedTouchActionChanged());
1493 EXPECT_FALSE(locked_object->EffectiveAllowedTouchActionChanged());
1494 EXPECT_FALSE(handler_object->EffectiveAllowedTouchActionChanged());
1495
1496 EXPECT_FALSE(ancestor_object->DescendantEffectiveAllowedTouchActionChanged());
1497 EXPECT_FALSE(
1498 descendant_object->DescendantEffectiveAllowedTouchActionChanged());
1499 EXPECT_FALSE(locked_object->DescendantEffectiveAllowedTouchActionChanged());
1500 EXPECT_FALSE(handler_object->DescendantEffectiveAllowedTouchActionChanged());
1501
1502 EXPECT_FALSE(ancestor_object->InsideBlockingTouchEventHandler());
1503 EXPECT_FALSE(descendant_object->InsideBlockingTouchEventHandler());
1504 EXPECT_FALSE(locked_object->InsideBlockingTouchEventHandler());
1505 EXPECT_FALSE(handler_object->InsideBlockingTouchEventHandler());
1506
1507 auto* callback = MakeGarbageCollected<DisplayLockEmptyEventListener>();
1508 handler_element->addEventListener(event_type_names::kTouchstart, callback);
1509
1510 EXPECT_FALSE(ancestor_object->EffectiveAllowedTouchActionChanged());
1511 EXPECT_FALSE(descendant_object->EffectiveAllowedTouchActionChanged());
1512 EXPECT_FALSE(locked_object->EffectiveAllowedTouchActionChanged());
1513 EXPECT_TRUE(handler_object->EffectiveAllowedTouchActionChanged());
1514
1515 EXPECT_FALSE(ancestor_object->DescendantEffectiveAllowedTouchActionChanged());
1516 EXPECT_FALSE(
1517 descendant_object->DescendantEffectiveAllowedTouchActionChanged());
1518 EXPECT_TRUE(locked_object->DescendantEffectiveAllowedTouchActionChanged());
1519 EXPECT_FALSE(handler_object->DescendantEffectiveAllowedTouchActionChanged());
1520
1521 UpdateAllLifecyclePhasesForTest();
1522 EXPECT_FALSE(ancestor_object->EffectiveAllowedTouchActionChanged());
1523 EXPECT_FALSE(descendant_object->EffectiveAllowedTouchActionChanged());
1524 EXPECT_FALSE(locked_object->EffectiveAllowedTouchActionChanged());
1525 EXPECT_TRUE(handler_object->EffectiveAllowedTouchActionChanged());
1526
1527 EXPECT_FALSE(ancestor_object->DescendantEffectiveAllowedTouchActionChanged());
1528 EXPECT_FALSE(
1529 descendant_object->DescendantEffectiveAllowedTouchActionChanged());
1530 EXPECT_TRUE(locked_object->DescendantEffectiveAllowedTouchActionChanged());
1531 EXPECT_FALSE(handler_object->DescendantEffectiveAllowedTouchActionChanged());
1532
1533 EXPECT_FALSE(ancestor_object->InsideBlockingTouchEventHandler());
1534 EXPECT_FALSE(descendant_object->InsideBlockingTouchEventHandler());
1535 EXPECT_FALSE(locked_object->InsideBlockingTouchEventHandler());
1536 EXPECT_FALSE(handler_object->InsideBlockingTouchEventHandler());
1537
1538 // Do the same check again. For now, nothing is expected to change. However,
1539 // when we separate self and child layout, then some flags would be different.
1540 UpdateAllLifecyclePhasesForTest();
1541 EXPECT_FALSE(ancestor_object->EffectiveAllowedTouchActionChanged());
1542 EXPECT_FALSE(descendant_object->EffectiveAllowedTouchActionChanged());
1543 EXPECT_FALSE(locked_object->EffectiveAllowedTouchActionChanged());
1544 EXPECT_TRUE(handler_object->EffectiveAllowedTouchActionChanged());
1545
1546 EXPECT_FALSE(ancestor_object->DescendantEffectiveAllowedTouchActionChanged());
1547 EXPECT_FALSE(
1548 descendant_object->DescendantEffectiveAllowedTouchActionChanged());
1549 EXPECT_TRUE(locked_object->DescendantEffectiveAllowedTouchActionChanged());
1550 EXPECT_FALSE(handler_object->DescendantEffectiveAllowedTouchActionChanged());
1551
1552 EXPECT_FALSE(ancestor_object->InsideBlockingTouchEventHandler());
1553 EXPECT_FALSE(descendant_object->InsideBlockingTouchEventHandler());
1554 EXPECT_FALSE(locked_object->InsideBlockingTouchEventHandler());
1555 EXPECT_FALSE(handler_object->InsideBlockingTouchEventHandler());
1556
1557 // Manually commit the lock so that we can verify which dirty bits get
1558 // propagated.
1559 CommitElement(*locked_element, false);
1560 UnlockImmediate(locked_element->GetDisplayLockContext());
1561
1562 EXPECT_FALSE(ancestor_object->EffectiveAllowedTouchActionChanged());
1563 EXPECT_FALSE(descendant_object->EffectiveAllowedTouchActionChanged());
1564 EXPECT_TRUE(locked_object->EffectiveAllowedTouchActionChanged());
1565 EXPECT_TRUE(handler_object->EffectiveAllowedTouchActionChanged());
1566
1567 EXPECT_TRUE(ancestor_object->DescendantEffectiveAllowedTouchActionChanged());
1568 EXPECT_TRUE(
1569 descendant_object->DescendantEffectiveAllowedTouchActionChanged());
1570 EXPECT_TRUE(locked_object->DescendantEffectiveAllowedTouchActionChanged());
1571 EXPECT_FALSE(handler_object->DescendantEffectiveAllowedTouchActionChanged());
1572
1573 EXPECT_FALSE(ancestor_object->InsideBlockingTouchEventHandler());
1574 EXPECT_FALSE(descendant_object->InsideBlockingTouchEventHandler());
1575 EXPECT_FALSE(locked_object->InsideBlockingTouchEventHandler());
1576 EXPECT_FALSE(handler_object->InsideBlockingTouchEventHandler());
1577
1578 UpdateAllLifecyclePhasesForTest();
1579 EXPECT_FALSE(ancestor_object->EffectiveAllowedTouchActionChanged());
1580 EXPECT_FALSE(descendant_object->EffectiveAllowedTouchActionChanged());
1581 EXPECT_FALSE(locked_object->EffectiveAllowedTouchActionChanged());
1582 EXPECT_FALSE(handler_object->EffectiveAllowedTouchActionChanged());
1583
1584 EXPECT_FALSE(ancestor_object->DescendantEffectiveAllowedTouchActionChanged());
1585 EXPECT_FALSE(
1586 descendant_object->DescendantEffectiveAllowedTouchActionChanged());
1587 EXPECT_FALSE(locked_object->DescendantEffectiveAllowedTouchActionChanged());
1588 EXPECT_FALSE(handler_object->DescendantEffectiveAllowedTouchActionChanged());
1589
1590 EXPECT_FALSE(ancestor_object->InsideBlockingTouchEventHandler());
1591 EXPECT_FALSE(descendant_object->InsideBlockingTouchEventHandler());
1592 EXPECT_FALSE(locked_object->InsideBlockingTouchEventHandler());
1593 EXPECT_TRUE(handler_object->InsideBlockingTouchEventHandler());
1594 }
1595
TEST_F(DisplayLockContextTest,AncestorWheelEventHandler)1596 TEST_F(DisplayLockContextTest, AncestorWheelEventHandler) {
1597 base::test::ScopedFeatureList scoped_feature_list;
1598 scoped_feature_list.InitAndEnableFeature(::features::kWheelEventRegions);
1599 SetHtmlInnerHTML(R"HTML(
1600 <style>
1601 #locked {
1602 width: 100px;
1603 height: 100px;
1604 contain: style layout paint;
1605 }
1606 </style>
1607 <div id="ancestor">
1608 <div id="handler">
1609 <div id="descendant">
1610 <div id="locked">
1611 <div id="lockedchild"></div>
1612 </div>
1613 </div>
1614 </div>
1615 </div>
1616 )HTML");
1617
1618 auto* ancestor_element = GetDocument().getElementById("ancestor");
1619 auto* handler_element = GetDocument().getElementById("handler");
1620 auto* descendant_element = GetDocument().getElementById("descendant");
1621 auto* locked_element = GetDocument().getElementById("locked");
1622 auto* lockedchild_element = GetDocument().getElementById("lockedchild");
1623
1624 LockElement(*locked_element, false);
1625 EXPECT_TRUE(locked_element->GetDisplayLockContext()->IsLocked());
1626
1627 auto* ancestor_object = ancestor_element->GetLayoutObject();
1628 auto* handler_object = handler_element->GetLayoutObject();
1629 auto* descendant_object = descendant_element->GetLayoutObject();
1630 auto* locked_object = locked_element->GetLayoutObject();
1631 auto* lockedchild_object = lockedchild_element->GetLayoutObject();
1632
1633 EXPECT_FALSE(ancestor_object->BlockingWheelEventHandlerChanged());
1634 EXPECT_FALSE(handler_object->BlockingWheelEventHandlerChanged());
1635 EXPECT_FALSE(descendant_object->BlockingWheelEventHandlerChanged());
1636 EXPECT_FALSE(locked_object->BlockingWheelEventHandlerChanged());
1637 EXPECT_FALSE(lockedchild_object->BlockingWheelEventHandlerChanged());
1638
1639 EXPECT_FALSE(ancestor_object->DescendantBlockingWheelEventHandlerChanged());
1640 EXPECT_FALSE(handler_object->DescendantBlockingWheelEventHandlerChanged());
1641 EXPECT_FALSE(descendant_object->DescendantBlockingWheelEventHandlerChanged());
1642 EXPECT_FALSE(locked_object->DescendantBlockingWheelEventHandlerChanged());
1643 EXPECT_FALSE(
1644 lockedchild_object->DescendantBlockingWheelEventHandlerChanged());
1645
1646 EXPECT_FALSE(ancestor_object->InsideBlockingWheelEventHandler());
1647 EXPECT_FALSE(handler_object->InsideBlockingWheelEventHandler());
1648 EXPECT_FALSE(descendant_object->InsideBlockingWheelEventHandler());
1649 EXPECT_FALSE(locked_object->InsideBlockingWheelEventHandler());
1650 EXPECT_FALSE(lockedchild_object->InsideBlockingWheelEventHandler());
1651
1652 auto* callback = MakeGarbageCollected<DisplayLockEmptyEventListener>();
1653 handler_element->addEventListener(event_type_names::kWheel, callback);
1654
1655 EXPECT_FALSE(ancestor_object->BlockingWheelEventHandlerChanged());
1656 EXPECT_TRUE(handler_object->BlockingWheelEventHandlerChanged());
1657 EXPECT_FALSE(descendant_object->BlockingWheelEventHandlerChanged());
1658 EXPECT_FALSE(locked_object->BlockingWheelEventHandlerChanged());
1659 EXPECT_FALSE(lockedchild_object->BlockingWheelEventHandlerChanged());
1660
1661 EXPECT_TRUE(ancestor_object->DescendantBlockingWheelEventHandlerChanged());
1662 EXPECT_FALSE(handler_object->DescendantBlockingWheelEventHandlerChanged());
1663 EXPECT_FALSE(descendant_object->DescendantBlockingWheelEventHandlerChanged());
1664 EXPECT_FALSE(locked_object->DescendantBlockingWheelEventHandlerChanged());
1665 EXPECT_FALSE(
1666 lockedchild_object->DescendantBlockingWheelEventHandlerChanged());
1667
1668 UpdateAllLifecyclePhasesForTest();
1669 EXPECT_FALSE(ancestor_object->BlockingWheelEventHandlerChanged());
1670 EXPECT_FALSE(handler_object->BlockingWheelEventHandlerChanged());
1671 EXPECT_FALSE(descendant_object->BlockingWheelEventHandlerChanged());
1672 EXPECT_FALSE(locked_object->BlockingWheelEventHandlerChanged());
1673 EXPECT_FALSE(lockedchild_object->BlockingWheelEventHandlerChanged());
1674
1675 EXPECT_FALSE(ancestor_object->DescendantBlockingWheelEventHandlerChanged());
1676 EXPECT_FALSE(handler_object->DescendantBlockingWheelEventHandlerChanged());
1677 EXPECT_FALSE(descendant_object->DescendantBlockingWheelEventHandlerChanged());
1678 EXPECT_FALSE(locked_object->DescendantBlockingWheelEventHandlerChanged());
1679 EXPECT_FALSE(
1680 lockedchild_object->DescendantBlockingWheelEventHandlerChanged());
1681
1682 EXPECT_FALSE(ancestor_object->InsideBlockingWheelEventHandler());
1683 EXPECT_TRUE(handler_object->InsideBlockingWheelEventHandler());
1684 EXPECT_TRUE(descendant_object->InsideBlockingWheelEventHandler());
1685 EXPECT_TRUE(locked_object->InsideBlockingWheelEventHandler());
1686 EXPECT_FALSE(lockedchild_object->InsideBlockingWheelEventHandler());
1687
1688 // Manually commit the lock so that we can verify which dirty bits get
1689 // propagated.
1690 CommitElement(*locked_element, false);
1691 UnlockImmediate(locked_element->GetDisplayLockContext());
1692
1693 EXPECT_FALSE(ancestor_object->BlockingWheelEventHandlerChanged());
1694 EXPECT_FALSE(handler_object->BlockingWheelEventHandlerChanged());
1695 EXPECT_FALSE(descendant_object->BlockingWheelEventHandlerChanged());
1696 EXPECT_TRUE(locked_object->BlockingWheelEventHandlerChanged());
1697 EXPECT_FALSE(lockedchild_object->BlockingWheelEventHandlerChanged());
1698
1699 EXPECT_TRUE(ancestor_object->DescendantBlockingWheelEventHandlerChanged());
1700 EXPECT_TRUE(handler_object->DescendantBlockingWheelEventHandlerChanged());
1701 EXPECT_TRUE(descendant_object->DescendantBlockingWheelEventHandlerChanged());
1702 EXPECT_FALSE(locked_object->DescendantBlockingWheelEventHandlerChanged());
1703 EXPECT_FALSE(
1704 lockedchild_object->DescendantBlockingWheelEventHandlerChanged());
1705
1706 EXPECT_FALSE(ancestor_object->InsideBlockingWheelEventHandler());
1707 EXPECT_TRUE(handler_object->InsideBlockingWheelEventHandler());
1708 EXPECT_TRUE(descendant_object->InsideBlockingWheelEventHandler());
1709 EXPECT_TRUE(locked_object->InsideBlockingWheelEventHandler());
1710 EXPECT_FALSE(lockedchild_object->InsideBlockingWheelEventHandler());
1711
1712 UpdateAllLifecyclePhasesForTest();
1713 EXPECT_FALSE(ancestor_object->BlockingWheelEventHandlerChanged());
1714 EXPECT_FALSE(handler_object->BlockingWheelEventHandlerChanged());
1715 EXPECT_FALSE(descendant_object->BlockingWheelEventHandlerChanged());
1716 EXPECT_FALSE(locked_object->BlockingWheelEventHandlerChanged());
1717 EXPECT_FALSE(lockedchild_object->BlockingWheelEventHandlerChanged());
1718
1719 EXPECT_FALSE(ancestor_object->DescendantBlockingWheelEventHandlerChanged());
1720 EXPECT_FALSE(handler_object->DescendantBlockingWheelEventHandlerChanged());
1721 EXPECT_FALSE(descendant_object->DescendantBlockingWheelEventHandlerChanged());
1722 EXPECT_FALSE(locked_object->DescendantBlockingWheelEventHandlerChanged());
1723 EXPECT_FALSE(
1724 lockedchild_object->DescendantBlockingWheelEventHandlerChanged());
1725
1726 EXPECT_FALSE(ancestor_object->InsideBlockingWheelEventHandler());
1727 EXPECT_TRUE(handler_object->InsideBlockingWheelEventHandler());
1728 EXPECT_TRUE(descendant_object->InsideBlockingWheelEventHandler());
1729 EXPECT_TRUE(locked_object->InsideBlockingWheelEventHandler());
1730 EXPECT_TRUE(lockedchild_object->InsideBlockingWheelEventHandler());
1731 }
1732
TEST_F(DisplayLockContextTest,DescendantWheelEventHandler)1733 TEST_F(DisplayLockContextTest, DescendantWheelEventHandler) {
1734 base::test::ScopedFeatureList scoped_feature_list;
1735 scoped_feature_list.InitAndEnableFeature(::features::kWheelEventRegions);
1736 SetHtmlInnerHTML(R"HTML(
1737 <style>
1738 #locked {
1739 width: 100px;
1740 height: 100px;
1741 contain: style layout paint;
1742 }
1743 </style>
1744 <div id="ancestor">
1745 <div id="descendant">
1746 <div id="locked">
1747 <div id="handler"></div>
1748 </div>
1749 </div>
1750 </div>
1751 )HTML");
1752
1753 auto* ancestor_element = GetDocument().getElementById("ancestor");
1754 auto* descendant_element = GetDocument().getElementById("descendant");
1755 auto* locked_element = GetDocument().getElementById("locked");
1756 auto* handler_element = GetDocument().getElementById("handler");
1757
1758 LockElement(*locked_element, false);
1759 EXPECT_TRUE(locked_element->GetDisplayLockContext()->IsLocked());
1760
1761 auto* ancestor_object = ancestor_element->GetLayoutObject();
1762 auto* descendant_object = descendant_element->GetLayoutObject();
1763 auto* locked_object = locked_element->GetLayoutObject();
1764 auto* handler_object = handler_element->GetLayoutObject();
1765
1766 EXPECT_FALSE(ancestor_object->BlockingWheelEventHandlerChanged());
1767 EXPECT_FALSE(descendant_object->BlockingWheelEventHandlerChanged());
1768 EXPECT_FALSE(locked_object->BlockingWheelEventHandlerChanged());
1769 EXPECT_FALSE(handler_object->BlockingWheelEventHandlerChanged());
1770
1771 EXPECT_FALSE(ancestor_object->DescendantBlockingWheelEventHandlerChanged());
1772 EXPECT_FALSE(descendant_object->DescendantBlockingWheelEventHandlerChanged());
1773 EXPECT_FALSE(locked_object->DescendantBlockingWheelEventHandlerChanged());
1774 EXPECT_FALSE(handler_object->DescendantBlockingWheelEventHandlerChanged());
1775
1776 EXPECT_FALSE(ancestor_object->InsideBlockingWheelEventHandler());
1777 EXPECT_FALSE(descendant_object->InsideBlockingWheelEventHandler());
1778 EXPECT_FALSE(locked_object->InsideBlockingWheelEventHandler());
1779 EXPECT_FALSE(handler_object->InsideBlockingWheelEventHandler());
1780
1781 auto* callback = MakeGarbageCollected<DisplayLockEmptyEventListener>();
1782 handler_element->addEventListener(event_type_names::kWheel, callback);
1783
1784 EXPECT_FALSE(ancestor_object->BlockingWheelEventHandlerChanged());
1785 EXPECT_FALSE(descendant_object->BlockingWheelEventHandlerChanged());
1786 EXPECT_FALSE(locked_object->BlockingWheelEventHandlerChanged());
1787 EXPECT_TRUE(handler_object->BlockingWheelEventHandlerChanged());
1788
1789 EXPECT_FALSE(ancestor_object->DescendantBlockingWheelEventHandlerChanged());
1790 EXPECT_FALSE(descendant_object->DescendantBlockingWheelEventHandlerChanged());
1791 EXPECT_TRUE(locked_object->DescendantBlockingWheelEventHandlerChanged());
1792 EXPECT_FALSE(handler_object->DescendantBlockingWheelEventHandlerChanged());
1793
1794 UpdateAllLifecyclePhasesForTest();
1795 EXPECT_FALSE(ancestor_object->BlockingWheelEventHandlerChanged());
1796 EXPECT_FALSE(descendant_object->BlockingWheelEventHandlerChanged());
1797 EXPECT_FALSE(locked_object->BlockingWheelEventHandlerChanged());
1798 EXPECT_TRUE(handler_object->BlockingWheelEventHandlerChanged());
1799
1800 EXPECT_FALSE(ancestor_object->DescendantBlockingWheelEventHandlerChanged());
1801 EXPECT_FALSE(descendant_object->DescendantBlockingWheelEventHandlerChanged());
1802 EXPECT_TRUE(locked_object->DescendantBlockingWheelEventHandlerChanged());
1803 EXPECT_FALSE(handler_object->DescendantBlockingWheelEventHandlerChanged());
1804
1805 EXPECT_FALSE(ancestor_object->InsideBlockingWheelEventHandler());
1806 EXPECT_FALSE(descendant_object->InsideBlockingWheelEventHandler());
1807 EXPECT_FALSE(locked_object->InsideBlockingWheelEventHandler());
1808 EXPECT_FALSE(handler_object->InsideBlockingWheelEventHandler());
1809
1810 // Do the same check again. For now, nothing is expected to change. However,
1811 // when we separate self and child layout, then some flags would be different.
1812 UpdateAllLifecyclePhasesForTest();
1813 EXPECT_FALSE(ancestor_object->BlockingWheelEventHandlerChanged());
1814 EXPECT_FALSE(descendant_object->BlockingWheelEventHandlerChanged());
1815 EXPECT_FALSE(locked_object->BlockingWheelEventHandlerChanged());
1816 EXPECT_TRUE(handler_object->BlockingWheelEventHandlerChanged());
1817
1818 EXPECT_FALSE(ancestor_object->DescendantBlockingWheelEventHandlerChanged());
1819 EXPECT_FALSE(descendant_object->DescendantBlockingWheelEventHandlerChanged());
1820 EXPECT_TRUE(locked_object->DescendantBlockingWheelEventHandlerChanged());
1821 EXPECT_FALSE(handler_object->DescendantBlockingWheelEventHandlerChanged());
1822
1823 EXPECT_FALSE(ancestor_object->InsideBlockingWheelEventHandler());
1824 EXPECT_FALSE(descendant_object->InsideBlockingWheelEventHandler());
1825 EXPECT_FALSE(locked_object->InsideBlockingWheelEventHandler());
1826 EXPECT_FALSE(handler_object->InsideBlockingWheelEventHandler());
1827
1828 // Manually commit the lock so that we can verify which dirty bits get
1829 // propagated.
1830 CommitElement(*locked_element, false);
1831 UnlockImmediate(locked_element->GetDisplayLockContext());
1832
1833 EXPECT_FALSE(ancestor_object->BlockingWheelEventHandlerChanged());
1834 EXPECT_FALSE(descendant_object->BlockingWheelEventHandlerChanged());
1835 EXPECT_TRUE(locked_object->BlockingWheelEventHandlerChanged());
1836 EXPECT_TRUE(handler_object->BlockingWheelEventHandlerChanged());
1837
1838 EXPECT_TRUE(ancestor_object->DescendantBlockingWheelEventHandlerChanged());
1839 EXPECT_TRUE(descendant_object->DescendantBlockingWheelEventHandlerChanged());
1840 EXPECT_TRUE(locked_object->DescendantBlockingWheelEventHandlerChanged());
1841 EXPECT_FALSE(handler_object->DescendantBlockingWheelEventHandlerChanged());
1842
1843 EXPECT_FALSE(ancestor_object->InsideBlockingWheelEventHandler());
1844 EXPECT_FALSE(descendant_object->InsideBlockingWheelEventHandler());
1845 EXPECT_FALSE(locked_object->InsideBlockingWheelEventHandler());
1846 EXPECT_FALSE(handler_object->InsideBlockingWheelEventHandler());
1847
1848 UpdateAllLifecyclePhasesForTest();
1849 EXPECT_FALSE(ancestor_object->BlockingWheelEventHandlerChanged());
1850 EXPECT_FALSE(descendant_object->BlockingWheelEventHandlerChanged());
1851 EXPECT_FALSE(locked_object->BlockingWheelEventHandlerChanged());
1852 EXPECT_FALSE(handler_object->BlockingWheelEventHandlerChanged());
1853
1854 EXPECT_FALSE(ancestor_object->DescendantBlockingWheelEventHandlerChanged());
1855 EXPECT_FALSE(descendant_object->DescendantBlockingWheelEventHandlerChanged());
1856 EXPECT_FALSE(locked_object->DescendantBlockingWheelEventHandlerChanged());
1857 EXPECT_FALSE(handler_object->DescendantBlockingWheelEventHandlerChanged());
1858
1859 EXPECT_FALSE(ancestor_object->InsideBlockingWheelEventHandler());
1860 EXPECT_FALSE(descendant_object->InsideBlockingWheelEventHandler());
1861 EXPECT_FALSE(locked_object->InsideBlockingWheelEventHandler());
1862 EXPECT_TRUE(handler_object->InsideBlockingWheelEventHandler());
1863 }
1864
TEST_F(DisplayLockContextTest,DescendantNeedsPaintPropertyUpdateBlocked)1865 TEST_F(DisplayLockContextTest, DescendantNeedsPaintPropertyUpdateBlocked) {
1866 SetHtmlInnerHTML(R"HTML(
1867 <style>
1868 #locked {
1869 width: 100px;
1870 height: 100px;
1871 contain: style layout paint;
1872 }
1873 </style>
1874 <div id="ancestor">
1875 <div id="descendant">
1876 <div id="locked">
1877 <div id="handler"></div>
1878 </div>
1879 </div>
1880 </div>
1881 )HTML");
1882
1883 auto* ancestor_element = GetDocument().getElementById("ancestor");
1884 auto* descendant_element = GetDocument().getElementById("descendant");
1885 auto* locked_element = GetDocument().getElementById("locked");
1886 auto* handler_element = GetDocument().getElementById("handler");
1887
1888 LockElement(*locked_element, false);
1889 EXPECT_TRUE(locked_element->GetDisplayLockContext()->IsLocked());
1890
1891 auto* ancestor_object = ancestor_element->GetLayoutObject();
1892 auto* descendant_object = descendant_element->GetLayoutObject();
1893 auto* locked_object = locked_element->GetLayoutObject();
1894 auto* handler_object = handler_element->GetLayoutObject();
1895
1896 EXPECT_FALSE(ancestor_object->NeedsPaintPropertyUpdate());
1897 EXPECT_FALSE(descendant_object->NeedsPaintPropertyUpdate());
1898 EXPECT_FALSE(locked_object->NeedsPaintPropertyUpdate());
1899 EXPECT_FALSE(handler_object->NeedsPaintPropertyUpdate());
1900
1901 EXPECT_FALSE(ancestor_object->DescendantNeedsPaintPropertyUpdate());
1902 EXPECT_FALSE(descendant_object->DescendantNeedsPaintPropertyUpdate());
1903 EXPECT_FALSE(locked_object->DescendantNeedsPaintPropertyUpdate());
1904 EXPECT_FALSE(handler_object->DescendantNeedsPaintPropertyUpdate());
1905
1906 handler_object->SetNeedsPaintPropertyUpdate();
1907
1908 EXPECT_FALSE(ancestor_object->NeedsPaintPropertyUpdate());
1909 EXPECT_FALSE(descendant_object->NeedsPaintPropertyUpdate());
1910 EXPECT_FALSE(locked_object->NeedsPaintPropertyUpdate());
1911 EXPECT_TRUE(handler_object->NeedsPaintPropertyUpdate());
1912
1913 EXPECT_TRUE(ancestor_object->DescendantNeedsPaintPropertyUpdate());
1914 EXPECT_TRUE(descendant_object->DescendantNeedsPaintPropertyUpdate());
1915 EXPECT_TRUE(locked_object->DescendantNeedsPaintPropertyUpdate());
1916 EXPECT_FALSE(handler_object->DescendantNeedsPaintPropertyUpdate());
1917
1918 UpdateAllLifecyclePhasesForTest();
1919
1920 EXPECT_FALSE(ancestor_object->NeedsPaintPropertyUpdate());
1921 EXPECT_FALSE(descendant_object->NeedsPaintPropertyUpdate());
1922 EXPECT_FALSE(locked_object->NeedsPaintPropertyUpdate());
1923 EXPECT_TRUE(handler_object->NeedsPaintPropertyUpdate());
1924
1925 EXPECT_FALSE(ancestor_object->DescendantNeedsPaintPropertyUpdate());
1926 EXPECT_FALSE(descendant_object->DescendantNeedsPaintPropertyUpdate());
1927 EXPECT_TRUE(locked_object->DescendantNeedsPaintPropertyUpdate());
1928 EXPECT_FALSE(handler_object->DescendantNeedsPaintPropertyUpdate());
1929
1930 locked_object->SetShouldCheckForPaintInvalidationWithoutGeometryChange();
1931 UpdateAllLifecyclePhasesForTest();
1932
1933 EXPECT_FALSE(ancestor_object->NeedsPaintPropertyUpdate());
1934 EXPECT_FALSE(descendant_object->NeedsPaintPropertyUpdate());
1935 EXPECT_FALSE(locked_object->NeedsPaintPropertyUpdate());
1936 EXPECT_TRUE(handler_object->NeedsPaintPropertyUpdate());
1937
1938 EXPECT_FALSE(ancestor_object->DescendantNeedsPaintPropertyUpdate());
1939 EXPECT_FALSE(descendant_object->DescendantNeedsPaintPropertyUpdate());
1940 EXPECT_TRUE(locked_object->DescendantNeedsPaintPropertyUpdate());
1941 EXPECT_FALSE(handler_object->DescendantNeedsPaintPropertyUpdate());
1942
1943 // Manually commit the lock so that we can verify which dirty bits get
1944 // propagated.
1945 CommitElement(*locked_element, false);
1946 UnlockImmediate(locked_element->GetDisplayLockContext());
1947
1948 EXPECT_FALSE(ancestor_object->NeedsPaintPropertyUpdate());
1949 EXPECT_FALSE(descendant_object->NeedsPaintPropertyUpdate());
1950 EXPECT_TRUE(locked_object->NeedsPaintPropertyUpdate());
1951 EXPECT_TRUE(handler_object->NeedsPaintPropertyUpdate());
1952
1953 EXPECT_TRUE(ancestor_object->DescendantNeedsPaintPropertyUpdate());
1954 EXPECT_TRUE(descendant_object->DescendantNeedsPaintPropertyUpdate());
1955 EXPECT_TRUE(locked_object->DescendantNeedsPaintPropertyUpdate());
1956 EXPECT_FALSE(handler_object->DescendantNeedsPaintPropertyUpdate());
1957
1958 UpdateAllLifecyclePhasesForTest();
1959
1960 EXPECT_FALSE(ancestor_object->NeedsPaintPropertyUpdate());
1961 EXPECT_FALSE(descendant_object->NeedsPaintPropertyUpdate());
1962 EXPECT_FALSE(locked_object->NeedsPaintPropertyUpdate());
1963 EXPECT_FALSE(handler_object->NeedsPaintPropertyUpdate());
1964
1965 EXPECT_FALSE(ancestor_object->DescendantNeedsPaintPropertyUpdate());
1966 EXPECT_FALSE(descendant_object->DescendantNeedsPaintPropertyUpdate());
1967 EXPECT_FALSE(locked_object->DescendantNeedsPaintPropertyUpdate());
1968 EXPECT_FALSE(handler_object->DescendantNeedsPaintPropertyUpdate());
1969 }
1970
1971 class DisplayLockContextRenderingTest
1972 : public RenderingTest,
1973 private ScopedCSSContentVisibilityHiddenMatchableForTest {
1974 public:
DisplayLockContextRenderingTest()1975 DisplayLockContextRenderingTest()
1976 : RenderingTest(MakeGarbageCollected<SingleChildLocalFrameClient>()),
1977 ScopedCSSContentVisibilityHiddenMatchableForTest(true) {}
1978
SetUp()1979 void SetUp() override {
1980 EnableCompositing();
1981 RenderingTest::SetUp();
1982 }
1983
IsObservingLifecycle(DisplayLockContext * context) const1984 bool IsObservingLifecycle(DisplayLockContext* context) const {
1985 return context->is_registered_for_lifecycle_notifications_;
1986 }
DescendantDependentFlagUpdateWasBlocked(DisplayLockContext * context) const1987 bool DescendantDependentFlagUpdateWasBlocked(
1988 DisplayLockContext* context) const {
1989 return context->needs_compositing_dependent_flag_update_;
1990 }
LockImmediate(DisplayLockContext * context)1991 void LockImmediate(DisplayLockContext* context) {
1992 context->SetRequestedState(EContentVisibility::kHidden);
1993 }
RunStartOfLifecycleTasks()1994 void RunStartOfLifecycleTasks() {
1995 auto start_of_lifecycle_tasks =
1996 GetDocument().View()->TakeStartOfLifecycleTasksForTest();
1997 for (auto& task : start_of_lifecycle_tasks)
1998 std::move(task).Run();
1999 }
GetScopedForcedUpdate(const Node * node,bool include_self=false)2000 DisplayLockUtilities::ScopedForcedUpdate GetScopedForcedUpdate(
2001 const Node* node,
2002 bool include_self = false) {
2003 return DisplayLockUtilities::ScopedForcedUpdate(node, include_self);
2004 }
2005 };
2006
TEST_F(DisplayLockContextRenderingTest,FrameDocumentRemovedWhileAcquire)2007 TEST_F(DisplayLockContextRenderingTest, FrameDocumentRemovedWhileAcquire) {
2008 SetHtmlInnerHTML(R"HTML(
2009 <iframe id="frame"></iframe>
2010 )HTML");
2011 SetChildFrameHTML(R"HTML(
2012 <style>
2013 div {
2014 contain: style layout;
2015 }
2016 </style>
2017 <div id="target"></target>
2018 )HTML");
2019
2020 auto* target = ChildDocument().getElementById("target");
2021 GetDocument().getElementById("frame")->remove();
2022
2023 LockImmediate(&target->EnsureDisplayLockContext());
2024 }
2025
TEST_F(DisplayLockContextRenderingTest,VisualOverflowCalculateOnChildPaintLayer)2026 TEST_F(DisplayLockContextRenderingTest,
2027 VisualOverflowCalculateOnChildPaintLayer) {
2028 SetHtmlInnerHTML(R"HTML(
2029 <style>
2030 .hidden { content-visibility: hidden }
2031 .paint_layer { contain: paint }
2032 .composited { will-change: transform }
2033 </style>
2034 <div id=lockable class=paint_layer>
2035 <div id=parent class=paint_layer>
2036 <div id=child class=paint_layer>
2037 <span>content</span>
2038 <span>content</span>
2039 <span>content</span>
2040 </div>
2041 </div>
2042 </div>
2043 )HTML");
2044
2045 auto* parent = GetDocument().getElementById("parent");
2046 auto* parent_box = parent->GetLayoutBoxModelObject();
2047 ASSERT_TRUE(parent_box);
2048 EXPECT_TRUE(parent_box->Layer());
2049 EXPECT_TRUE(parent_box->HasSelfPaintingLayer());
2050
2051 // Lock the container.
2052 auto* lockable = GetDocument().getElementById("lockable");
2053 lockable->classList().Add("hidden");
2054 UpdateAllLifecyclePhasesForTest();
2055
2056 auto* child_layer = GetPaintLayerByElementId("child");
2057 child_layer->SetNeedsVisualOverflowRecalc();
2058 EXPECT_TRUE(child_layer->NeedsVisualOverflowRecalc());
2059
2060 // The following should not crash/DCHECK.
2061 UpdateAllLifecyclePhasesForTest();
2062
2063 // Verify that the display lock knows that the descendant dependent flags
2064 // update was blocked.
2065 ASSERT_TRUE(lockable->GetDisplayLockContext());
2066 EXPECT_TRUE(DescendantDependentFlagUpdateWasBlocked(
2067 lockable->GetDisplayLockContext()));
2068 EXPECT_TRUE(child_layer->NeedsVisualOverflowRecalc());
2069
2070 // After unlocking, we should process the pending visual overflow recalc.
2071 lockable->classList().Remove("hidden");
2072 UpdateAllLifecyclePhasesForTest();
2073
2074 EXPECT_FALSE(child_layer->NeedsVisualOverflowRecalc());
2075 }
2076
TEST_F(DisplayLockContextRenderingTest,FloatChildLocked)2077 TEST_F(DisplayLockContextRenderingTest, FloatChildLocked) {
2078 SetHtmlInnerHTML(R"HTML(
2079 <style>
2080 .hidden { content-visibility: hidden }
2081 #floating { float: left; width: 100px; height: 100px }
2082 </style>
2083 <div id=lockable style="width: 200px; height: 50px; position: absolute">
2084 <div id=floating></div>
2085 </div>
2086 )HTML");
2087
2088 auto* lockable = GetDocument().getElementById("lockable");
2089 auto* lockable_box = lockable->GetLayoutBox();
2090 auto* floating = GetDocument().getElementById("floating");
2091 EXPECT_EQ(LayoutRect(0, 0, 200, 100), lockable_box->VisualOverflowRect());
2092 EXPECT_EQ(LayoutRect(0, 0, 200, 100), lockable_box->LayoutOverflowRect());
2093
2094 lockable->classList().Add("hidden");
2095 UpdateAllLifecyclePhasesForTest();
2096
2097 // Verify that the display lock knows that the descendant dependent flags
2098 // update was blocked.
2099 ASSERT_TRUE(lockable->GetDisplayLockContext());
2100 EXPECT_TRUE(DescendantDependentFlagUpdateWasBlocked(
2101 lockable->GetDisplayLockContext()));
2102 EXPECT_EQ(LayoutRect(0, 0, 200, 50), lockable_box->VisualOverflowRect());
2103 EXPECT_EQ(LayoutRect(0, 0, 200, 50), lockable_box->LayoutOverflowRect());
2104
2105 floating->setAttribute(html_names::kStyleAttr, "height: 200px");
2106 // The following should not crash/DCHECK.
2107 UpdateAllLifecyclePhasesForTest();
2108
2109 ASSERT_TRUE(lockable->GetDisplayLockContext());
2110 EXPECT_TRUE(DescendantDependentFlagUpdateWasBlocked(
2111 lockable->GetDisplayLockContext()));
2112 EXPECT_EQ(LayoutRect(0, 0, 200, 50), lockable_box->VisualOverflowRect());
2113 EXPECT_EQ(LayoutRect(0, 0, 200, 50), lockable_box->LayoutOverflowRect());
2114
2115 // After unlocking, we should process the pending visual overflow recalc.
2116 lockable->classList().Remove("hidden");
2117 UpdateAllLifecyclePhasesForTest();
2118
2119 EXPECT_EQ(LayoutRect(0, 0, 200, 200), lockable_box->VisualOverflowRect());
2120 EXPECT_EQ(LayoutRect(0, 0, 200, 200), lockable_box->LayoutOverflowRect());
2121 }
2122
TEST_F(DisplayLockContextRenderingTest,VisualOverflowCalculateOnChildPaintLayerInForcedLock)2123 TEST_F(DisplayLockContextRenderingTest,
2124 VisualOverflowCalculateOnChildPaintLayerInForcedLock) {
2125 SetHtmlInnerHTML(R"HTML(
2126 <style>
2127 .hidden { content-visibility: hidden }
2128 .paint_layer { contain: paint }
2129 .composited { will-change: transform }
2130 </style>
2131 <div id=lockable class=paint_layer>
2132 <div id=parent class=paint_layer>
2133 <div id=child class=paint_layer>
2134 <span>content</span>
2135 <span>content</span>
2136 <span>content</span>
2137 </div>
2138 </div>
2139 </div>
2140 )HTML");
2141
2142 auto* parent = GetDocument().getElementById("parent");
2143 auto* parent_box = parent->GetLayoutBoxModelObject();
2144 ASSERT_TRUE(parent_box);
2145 EXPECT_TRUE(parent_box->Layer());
2146 EXPECT_TRUE(parent_box->HasSelfPaintingLayer());
2147
2148 // Lock the container.
2149 auto* lockable = GetDocument().getElementById("lockable");
2150 lockable->classList().Add("hidden");
2151 UpdateAllLifecyclePhasesForTest();
2152
2153 auto* child_layer = GetPaintLayerByElementId("child");
2154 child_layer->SetNeedsVisualOverflowRecalc();
2155 EXPECT_TRUE(child_layer->NeedsVisualOverflowRecalc());
2156
2157 ASSERT_TRUE(lockable->GetDisplayLockContext());
2158 {
2159 auto scope = GetScopedForcedUpdate(lockable, true /* include self */);
2160
2161 // The following should not crash/DCHECK.
2162 UpdateAllLifecyclePhasesForTest();
2163 }
2164
2165 // Verify that the display lock doesn't keep extra state since the update was
2166 // processed.
2167 EXPECT_FALSE(DescendantDependentFlagUpdateWasBlocked(
2168 lockable->GetDisplayLockContext()));
2169 EXPECT_FALSE(child_layer->NeedsVisualOverflowRecalc());
2170
2171 // After unlocking, we should not need to do any extra work.
2172 lockable->classList().Remove("hidden");
2173 EXPECT_FALSE(child_layer->NeedsVisualOverflowRecalc());
2174
2175 UpdateAllLifecyclePhasesForTest();
2176 }
TEST_F(DisplayLockContextRenderingTest,SelectionOnAnonymousColumnSpannerDoesNotCrash)2177 TEST_F(DisplayLockContextRenderingTest,
2178 SelectionOnAnonymousColumnSpannerDoesNotCrash) {
2179 SetHtmlInnerHTML(R"HTML(
2180 <style>
2181 #columns {
2182 column-count: 5;
2183 }
2184 #spanner {
2185 column-span: all;
2186 }
2187 </style>
2188 <div id="columns">
2189 <div id="spanner"></div>
2190 </div>
2191 )HTML");
2192
2193 auto* columns_object =
2194 GetDocument().getElementById("columns")->GetLayoutObject();
2195 LayoutObject* spanner_placeholder_object = nullptr;
2196 for (auto* candidate = columns_object->SlowFirstChild(); candidate;
2197 candidate = candidate->NextSibling()) {
2198 if (candidate->IsLayoutMultiColumnSpannerPlaceholder()) {
2199 spanner_placeholder_object = candidate;
2200 break;
2201 }
2202 }
2203
2204 ASSERT_TRUE(spanner_placeholder_object);
2205 EXPECT_FALSE(spanner_placeholder_object->CanBeSelectionLeaf());
2206 }
2207
TEST_F(DisplayLockContextRenderingTest,ObjectsNeedingLayoutConsidersLocks)2208 TEST_F(DisplayLockContextRenderingTest, ObjectsNeedingLayoutConsidersLocks) {
2209 SetHtmlInnerHTML(R"HTML(
2210 <div id=a>
2211 <div id=b>
2212 <div id=c></div>
2213 <div id=d></div>
2214 </div>
2215 <div id=e>
2216 <div id=f></div>
2217 <div id=g></div>
2218 </div>
2219 </div>
2220 )HTML");
2221
2222 // Dirty all of the leaf nodes.
2223 auto dirty_all = [this]() {
2224 GetDocument().getElementById("c")->GetLayoutObject()->SetNeedsLayout(
2225 "test");
2226 GetDocument().getElementById("d")->GetLayoutObject()->SetNeedsLayout(
2227 "test");
2228 GetDocument().getElementById("f")->GetLayoutObject()->SetNeedsLayout(
2229 "test");
2230 GetDocument().getElementById("g")->GetLayoutObject()->SetNeedsLayout(
2231 "test");
2232 };
2233
2234 unsigned dirty_count = 0;
2235 unsigned total_count = 0;
2236 bool is_subtree = false;
2237
2238 dirty_all();
2239 GetDocument().View()->CountObjectsNeedingLayout(dirty_count, total_count,
2240 is_subtree);
2241 // 7 divs + body + html + layout view
2242 EXPECT_EQ(dirty_count, 10u);
2243 EXPECT_EQ(total_count, 10u);
2244
2245 GetDocument().getElementById("e")->setAttribute(html_names::kStyleAttr,
2246 "content-visibility: hidden");
2247 UpdateAllLifecyclePhasesForTest();
2248
2249 // Note that the dirty_all call propagate the dirty bit from the unlocked
2250 // subtree all the way up to the layout view, so everything on the way up is
2251 // dirtied.
2252 dirty_all();
2253 GetDocument().View()->CountObjectsNeedingLayout(dirty_count, total_count,
2254 is_subtree);
2255 // Element with 2 children is locked, and it itself isn't dirty (just the
2256 // children are). So, 10 - 3 = 7
2257 EXPECT_EQ(dirty_count, 7u);
2258 // We still see the locked element, so the total is 8.
2259 EXPECT_EQ(total_count, 8u);
2260
2261 GetDocument().getElementById("a")->setAttribute(html_names::kStyleAttr,
2262 "content-visibility: hidden");
2263 UpdateAllLifecyclePhasesForTest();
2264
2265 // Note that this dirty_all call is now not propagating the dirty bits at all,
2266 // since they are stopped at the top level div.
2267 dirty_all();
2268 GetDocument().View()->CountObjectsNeedingLayout(dirty_count, total_count,
2269 is_subtree);
2270 // Top level element is locked and the dirty bits were not propagated, so we
2271 // expect 0 dirty elements. The total should be 4 ('a' + body + html + layout
2272 // view);
2273 EXPECT_EQ(dirty_count, 0u);
2274 EXPECT_EQ(total_count, 4u);
2275 }
2276
TEST_F(DisplayLockContextRenderingTest,PaintDirtyBitsNotPropagatedAcrossBoundary)2277 TEST_F(DisplayLockContextRenderingTest,
2278 PaintDirtyBitsNotPropagatedAcrossBoundary) {
2279 SetHtmlInnerHTML(R"HTML(
2280 <style>
2281 .locked { content-visibility: hidden; }
2282 div { contain: paint; }
2283 </style>
2284 <div id=parent>
2285 <div id=lockable>
2286 <div id=child>
2287 <div id=grandchild></div>
2288 </div>
2289 </div>
2290 </div>
2291 )HTML");
2292
2293 auto* parent = GetDocument().getElementById("parent");
2294 auto* lockable = GetDocument().getElementById("lockable");
2295 auto* child = GetDocument().getElementById("child");
2296 auto* grandchild = GetDocument().getElementById("grandchild");
2297
2298 auto* parent_box = parent->GetLayoutBoxModelObject();
2299 auto* lockable_box = lockable->GetLayoutBoxModelObject();
2300 auto* child_box = child->GetLayoutBoxModelObject();
2301 auto* grandchild_box = grandchild->GetLayoutBoxModelObject();
2302
2303 ASSERT_TRUE(parent_box);
2304 ASSERT_TRUE(lockable_box);
2305 ASSERT_TRUE(child_box);
2306 ASSERT_TRUE(grandchild_box);
2307
2308 ASSERT_TRUE(parent_box->HasSelfPaintingLayer());
2309 ASSERT_TRUE(lockable_box->HasSelfPaintingLayer());
2310 ASSERT_TRUE(child_box->HasSelfPaintingLayer());
2311 ASSERT_TRUE(grandchild_box->HasSelfPaintingLayer());
2312
2313 auto* parent_layer = parent_box->Layer();
2314 auto* lockable_layer = lockable_box->Layer();
2315 auto* child_layer = child_box->Layer();
2316 auto* grandchild_layer = grandchild_box->Layer();
2317
2318 EXPECT_FALSE(parent_layer->SelfOrDescendantNeedsRepaint());
2319 EXPECT_FALSE(lockable_layer->SelfOrDescendantNeedsRepaint());
2320 EXPECT_FALSE(child_layer->SelfOrDescendantNeedsRepaint());
2321 EXPECT_FALSE(grandchild_layer->SelfOrDescendantNeedsRepaint());
2322
2323 lockable->classList().Add("locked");
2324 GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest);
2325
2326 // Lockable layer needs repainting after locking.
2327 EXPECT_FALSE(parent_layer->SelfNeedsRepaint());
2328 EXPECT_TRUE(lockable_layer->SelfNeedsRepaint());
2329 EXPECT_FALSE(child_layer->SelfNeedsRepaint());
2330 EXPECT_FALSE(grandchild_layer->SelfNeedsRepaint());
2331
2332 // Breadcrumbs are set from the lockable layer.
2333 EXPECT_TRUE(parent_layer->DescendantNeedsRepaint());
2334 EXPECT_FALSE(lockable_layer->DescendantNeedsRepaint());
2335 EXPECT_FALSE(child_layer->DescendantNeedsRepaint());
2336 EXPECT_FALSE(grandchild_layer->DescendantNeedsRepaint());
2337
2338 UpdateAllLifecyclePhasesForTest();
2339
2340 // Everything is clean.
2341 EXPECT_FALSE(parent_layer->SelfNeedsRepaint());
2342 EXPECT_FALSE(lockable_layer->SelfNeedsRepaint());
2343 EXPECT_FALSE(child_layer->SelfNeedsRepaint());
2344 EXPECT_FALSE(grandchild_layer->SelfNeedsRepaint());
2345
2346 // Breadcrumbs are clean as well.
2347 EXPECT_FALSE(parent_layer->DescendantNeedsRepaint());
2348 EXPECT_FALSE(lockable_layer->DescendantNeedsRepaint());
2349 EXPECT_FALSE(child_layer->DescendantNeedsRepaint());
2350 EXPECT_FALSE(grandchild_layer->DescendantNeedsRepaint());
2351
2352 grandchild_layer->SetNeedsRepaint();
2353
2354 // Grandchild needs repaint, so everything else should be clean.
2355 EXPECT_FALSE(parent_layer->SelfNeedsRepaint());
2356 EXPECT_FALSE(lockable_layer->SelfNeedsRepaint());
2357 EXPECT_FALSE(child_layer->SelfNeedsRepaint());
2358 EXPECT_TRUE(grandchild_layer->SelfNeedsRepaint());
2359
2360 // Breadcrumbs are set from the lockable layer but are stopped at the locked
2361 // boundary.
2362 EXPECT_FALSE(parent_layer->DescendantNeedsRepaint());
2363 EXPECT_TRUE(lockable_layer->DescendantNeedsRepaint());
2364 EXPECT_TRUE(child_layer->DescendantNeedsRepaint());
2365 EXPECT_FALSE(grandchild_layer->DescendantNeedsRepaint());
2366
2367 // Updating the lifecycle does not clean the dirty bits.
2368 UpdateAllLifecyclePhasesForTest();
2369
2370 EXPECT_FALSE(parent_layer->SelfNeedsRepaint());
2371 EXPECT_FALSE(lockable_layer->SelfNeedsRepaint());
2372 EXPECT_FALSE(child_layer->SelfNeedsRepaint());
2373 EXPECT_TRUE(grandchild_layer->SelfNeedsRepaint());
2374
2375 EXPECT_FALSE(parent_layer->DescendantNeedsRepaint());
2376 EXPECT_TRUE(lockable_layer->DescendantNeedsRepaint());
2377 EXPECT_TRUE(child_layer->DescendantNeedsRepaint());
2378 EXPECT_FALSE(grandchild_layer->DescendantNeedsRepaint());
2379
2380 // Unlocking causes lockable to repaint itself.
2381 lockable->classList().Remove("locked");
2382 GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest);
2383
2384 EXPECT_FALSE(parent_layer->SelfNeedsRepaint());
2385 EXPECT_TRUE(lockable_layer->SelfNeedsRepaint());
2386 EXPECT_FALSE(child_layer->SelfNeedsRepaint());
2387 EXPECT_TRUE(grandchild_layer->SelfNeedsRepaint());
2388
2389 EXPECT_TRUE(parent_layer->DescendantNeedsRepaint());
2390 EXPECT_TRUE(lockable_layer->DescendantNeedsRepaint());
2391 EXPECT_TRUE(child_layer->DescendantNeedsRepaint());
2392 EXPECT_FALSE(grandchild_layer->DescendantNeedsRepaint());
2393
2394 UpdateAllLifecyclePhasesForTest();
2395
2396 // Everything should be clean.
2397 EXPECT_FALSE(parent_layer->SelfNeedsRepaint());
2398 EXPECT_FALSE(lockable_layer->SelfNeedsRepaint());
2399 EXPECT_FALSE(child_layer->SelfNeedsRepaint());
2400 EXPECT_FALSE(grandchild_layer->SelfNeedsRepaint());
2401
2402 // Breadcrumbs are clean as well.
2403 EXPECT_FALSE(parent_layer->DescendantNeedsRepaint());
2404 EXPECT_FALSE(lockable_layer->DescendantNeedsRepaint());
2405 EXPECT_FALSE(child_layer->DescendantNeedsRepaint());
2406 EXPECT_FALSE(grandchild_layer->DescendantNeedsRepaint());
2407 }
2408
TEST_F(DisplayLockContextRenderingTest,NestedLockDoesNotInvalidateOnHideOrShow)2409 TEST_F(DisplayLockContextRenderingTest,
2410 NestedLockDoesNotInvalidateOnHideOrShow) {
2411 SetHtmlInnerHTML(R"HTML(
2412 <style>
2413 .auto { content-visibility: auto; }
2414 .hidden { content-visibility: hidden; }
2415 .item { height: 10px; }
2416 /* this is important to not invalidate layout when we hide the element! */
2417 #outer { contain: style layout; }
2418 </style>
2419 <div id=outer>
2420 <div id=unrelated>
2421 <div id=inner class=auto>Content</div>
2422 </div>
2423 </div>
2424 )HTML");
2425
2426 auto* inner_element = GetDocument().getElementById("inner");
2427 auto* unrelated_element = GetDocument().getElementById("unrelated");
2428 auto* outer_element = GetDocument().getElementById("outer");
2429
2430 // Ensure that the visibility switch happens. This would also clear the
2431 // layout.
2432 UpdateAllLifecyclePhasesForTest();
2433 EXPECT_FALSE(outer_element->GetLayoutObject()->NeedsLayout());
2434 EXPECT_FALSE(outer_element->GetLayoutObject()->SelfNeedsLayout());
2435 EXPECT_FALSE(unrelated_element->GetLayoutObject()->NeedsLayout());
2436 EXPECT_FALSE(unrelated_element->GetLayoutObject()->SelfNeedsLayout());
2437 EXPECT_FALSE(inner_element->GetLayoutObject()->NeedsLayout());
2438 EXPECT_FALSE(inner_element->GetLayoutObject()->SelfNeedsLayout());
2439
2440 // Verify lock state.
2441 auto* inner_context = inner_element->GetDisplayLockContext();
2442 ASSERT_TRUE(inner_context);
2443 EXPECT_FALSE(inner_context->IsLocked());
2444
2445 // Lock outer.
2446 outer_element->setAttribute(html_names::kClassAttr, "hidden");
2447 // Ensure the lock processes (but don't run intersection observation tasks
2448 // yet).
2449 UpdateAllLifecyclePhasesForTest();
2450
2451 // Verify the lock exists.
2452 auto* outer_context = outer_element->GetDisplayLockContext();
2453 ASSERT_TRUE(outer_context);
2454 EXPECT_TRUE(outer_context->IsLocked());
2455
2456 // Everything should be layout clean.
2457 EXPECT_FALSE(outer_element->GetLayoutObject()->NeedsLayout());
2458 EXPECT_FALSE(outer_element->GetLayoutObject()->SelfNeedsLayout());
2459 EXPECT_FALSE(unrelated_element->GetLayoutObject()->NeedsLayout());
2460 EXPECT_FALSE(unrelated_element->GetLayoutObject()->SelfNeedsLayout());
2461 EXPECT_FALSE(inner_element->GetLayoutObject()->NeedsLayout());
2462 EXPECT_FALSE(inner_element->GetLayoutObject()->SelfNeedsLayout());
2463
2464 // Inner context should not be observing the lifecycle.
2465 EXPECT_FALSE(IsObservingLifecycle(inner_context));
2466
2467 // Process any visibility changes.
2468 RunStartOfLifecycleTasks();
2469
2470 // Run the following checks a few times since we should be observing
2471 // lifecycle.
2472 for (int i = 0; i < 3; ++i) {
2473 // It shouldn't change the fact that we're layout clean.
2474 EXPECT_FALSE(outer_element->GetLayoutObject()->NeedsLayout());
2475 EXPECT_FALSE(outer_element->GetLayoutObject()->SelfNeedsLayout());
2476 EXPECT_FALSE(unrelated_element->GetLayoutObject()->NeedsLayout());
2477 EXPECT_FALSE(unrelated_element->GetLayoutObject()->SelfNeedsLayout());
2478 EXPECT_FALSE(inner_element->GetLayoutObject()->NeedsLayout());
2479 EXPECT_FALSE(inner_element->GetLayoutObject()->SelfNeedsLayout());
2480
2481 // Because we skipped hiding the element, inner_context should be observing
2482 // lifecycle.
2483 EXPECT_TRUE(IsObservingLifecycle(inner_context));
2484
2485 UpdateAllLifecyclePhasesForTest();
2486 }
2487
2488 // Unlock outer.
2489 outer_element->setAttribute(html_names::kClassAttr, "");
2490 // Ensure the lock processes (but don't run intersection observation tasks
2491 // yet).
2492 UpdateAllLifecyclePhasesForTest();
2493
2494 // Note that although we're not nested, we're still observing the lifecycle
2495 // because we don't yet know whether we should or should not hide and we only
2496 // make this decision _before_ the lifecycle actually unlocked outer.
2497 EXPECT_TRUE(IsObservingLifecycle(inner_context));
2498
2499 // Verify the lock is gone.
2500 EXPECT_FALSE(outer_context->IsLocked());
2501
2502 // Everything should be layout clean.
2503 EXPECT_FALSE(outer_element->GetLayoutObject()->NeedsLayout());
2504 EXPECT_FALSE(outer_element->GetLayoutObject()->SelfNeedsLayout());
2505 EXPECT_FALSE(unrelated_element->GetLayoutObject()->NeedsLayout());
2506 EXPECT_FALSE(unrelated_element->GetLayoutObject()->SelfNeedsLayout());
2507 EXPECT_FALSE(inner_element->GetLayoutObject()->NeedsLayout());
2508 EXPECT_FALSE(inner_element->GetLayoutObject()->SelfNeedsLayout());
2509
2510 // Process visibility changes.
2511 RunStartOfLifecycleTasks();
2512
2513 // We now should know we're visible and so we're not observing the lifecycle.
2514 EXPECT_FALSE(IsObservingLifecycle(inner_context));
2515
2516 // Also we should still be activated and unlocked.
2517 EXPECT_FALSE(inner_context->IsLocked());
2518
2519 // Everything should be layout clean.
2520 EXPECT_FALSE(outer_element->GetLayoutObject()->NeedsLayout());
2521 EXPECT_FALSE(outer_element->GetLayoutObject()->SelfNeedsLayout());
2522 EXPECT_FALSE(unrelated_element->GetLayoutObject()->NeedsLayout());
2523 EXPECT_FALSE(unrelated_element->GetLayoutObject()->SelfNeedsLayout());
2524 EXPECT_FALSE(inner_element->GetLayoutObject()->NeedsLayout());
2525 EXPECT_FALSE(inner_element->GetLayoutObject()->SelfNeedsLayout());
2526 }
2527
TEST_F(DisplayLockContextRenderingTest,NestedLockDoesHideWhenItIsOffscreen)2528 TEST_F(DisplayLockContextRenderingTest, NestedLockDoesHideWhenItIsOffscreen) {
2529 SetHtmlInnerHTML(R"HTML(
2530 <style>
2531 .auto { content-visibility: auto; }
2532 .hidden { content-visibility: hidden; }
2533 .item { height: 10px; }
2534 /* this is important to not invalidate layout when we hide the element! */
2535 #outer { contain: style layout; }
2536 .spacer { height: 10000px; }
2537 </style>
2538 <div id=future_spacer></div>
2539 <div id=outer>
2540 <div id=unrelated>
2541 <div id=inner class=auto>Content</div>
2542 </div>
2543 </div>
2544 )HTML");
2545
2546 auto* inner_element = GetDocument().getElementById("inner");
2547 auto* unrelated_element = GetDocument().getElementById("unrelated");
2548 auto* outer_element = GetDocument().getElementById("outer");
2549
2550 // Ensure that the visibility switch happens. This would also clear the
2551 // layout.
2552 UpdateAllLifecyclePhasesForTest();
2553 EXPECT_FALSE(outer_element->GetLayoutObject()->NeedsLayout());
2554 EXPECT_FALSE(outer_element->GetLayoutObject()->SelfNeedsLayout());
2555 EXPECT_FALSE(unrelated_element->GetLayoutObject()->NeedsLayout());
2556 EXPECT_FALSE(unrelated_element->GetLayoutObject()->SelfNeedsLayout());
2557 EXPECT_FALSE(inner_element->GetLayoutObject()->NeedsLayout());
2558 EXPECT_FALSE(inner_element->GetLayoutObject()->SelfNeedsLayout());
2559
2560 // Verify lock state.
2561 auto* inner_context = inner_element->GetDisplayLockContext();
2562 ASSERT_TRUE(inner_context);
2563 EXPECT_FALSE(inner_context->IsLocked());
2564
2565 // Lock outer.
2566 outer_element->setAttribute(html_names::kClassAttr, "hidden");
2567 // Ensure the lock processes (but don't run intersection observation tasks
2568 // yet).
2569 UpdateAllLifecyclePhasesForTest();
2570
2571 // Verify the lock exists.
2572 auto* outer_context = outer_element->GetDisplayLockContext();
2573 ASSERT_TRUE(outer_context);
2574 EXPECT_TRUE(outer_context->IsLocked());
2575
2576 // Everything should be layout clean.
2577 EXPECT_FALSE(outer_element->GetLayoutObject()->NeedsLayout());
2578 EXPECT_FALSE(outer_element->GetLayoutObject()->SelfNeedsLayout());
2579 EXPECT_FALSE(unrelated_element->GetLayoutObject()->NeedsLayout());
2580 EXPECT_FALSE(unrelated_element->GetLayoutObject()->SelfNeedsLayout());
2581 EXPECT_FALSE(inner_element->GetLayoutObject()->NeedsLayout());
2582 EXPECT_FALSE(inner_element->GetLayoutObject()->SelfNeedsLayout());
2583
2584 // Inner context should not be observing the lifecycle.
2585 EXPECT_FALSE(IsObservingLifecycle(inner_context));
2586
2587 // Process any visibility changes.
2588 RunStartOfLifecycleTasks();
2589
2590 // It shouldn't change the fact that we're layout clean.
2591 EXPECT_FALSE(outer_element->GetLayoutObject()->NeedsLayout());
2592 EXPECT_FALSE(outer_element->GetLayoutObject()->SelfNeedsLayout());
2593 EXPECT_FALSE(unrelated_element->GetLayoutObject()->NeedsLayout());
2594 EXPECT_FALSE(unrelated_element->GetLayoutObject()->SelfNeedsLayout());
2595 EXPECT_FALSE(inner_element->GetLayoutObject()->NeedsLayout());
2596 EXPECT_FALSE(inner_element->GetLayoutObject()->SelfNeedsLayout());
2597
2598 // Let future spacer become a real spacer!
2599 GetDocument()
2600 .getElementById("future_spacer")
2601 ->setAttribute(html_names::kClassAttr, "spacer");
2602
2603 UpdateAllLifecyclePhasesForTest();
2604
2605 // Because we skipped hiding the element, inner_context should be observing
2606 // lifecycle.
2607 EXPECT_TRUE(IsObservingLifecycle(inner_context));
2608
2609 // Unlock outer.
2610 outer_element->setAttribute(html_names::kClassAttr, "");
2611 // Ensure the lock processes (but don't run intersection observation tasks
2612 // yet).
2613 UpdateAllLifecyclePhasesForTest();
2614
2615 // Note that although we're not nested, we're still observing the lifecycle
2616 // because we don't yet know whether we should or should not hide and we only
2617 // make this decision _before_ the lifecycle actually unlocked outer.
2618 EXPECT_TRUE(IsObservingLifecycle(inner_context));
2619
2620 // Verify the lock is gone.
2621 EXPECT_FALSE(outer_context->IsLocked());
2622
2623 // Everything should be layout clean.
2624 EXPECT_FALSE(outer_element->GetLayoutObject()->NeedsLayout());
2625 EXPECT_FALSE(outer_element->GetLayoutObject()->SelfNeedsLayout());
2626 EXPECT_FALSE(unrelated_element->GetLayoutObject()->NeedsLayout());
2627 EXPECT_FALSE(unrelated_element->GetLayoutObject()->SelfNeedsLayout());
2628 EXPECT_FALSE(inner_element->GetLayoutObject()->NeedsLayout());
2629 EXPECT_FALSE(inner_element->GetLayoutObject()->SelfNeedsLayout());
2630
2631 // Process any visibility changes.
2632 RunStartOfLifecycleTasks();
2633
2634 // We're still invisible, and we don't know that we're not nested so we're
2635 // still observing the lifecycle.
2636 EXPECT_TRUE(IsObservingLifecycle(inner_context));
2637
2638 // We're unlocked for now.
2639 EXPECT_FALSE(inner_context->IsLocked());
2640
2641 // Everything should be layout clean.
2642 EXPECT_FALSE(outer_element->GetLayoutObject()->NeedsLayout());
2643 EXPECT_FALSE(outer_element->GetLayoutObject()->SelfNeedsLayout());
2644 EXPECT_FALSE(unrelated_element->GetLayoutObject()->NeedsLayout());
2645 EXPECT_FALSE(unrelated_element->GetLayoutObject()->SelfNeedsLayout());
2646 EXPECT_FALSE(inner_element->GetLayoutObject()->NeedsLayout());
2647 EXPECT_FALSE(inner_element->GetLayoutObject()->SelfNeedsLayout());
2648
2649 UpdateAllLifecyclePhasesForTest();
2650
2651 // We figured out that we're actually invisible so no need to observe the
2652 // lifecycle.
2653 EXPECT_FALSE(IsObservingLifecycle(inner_context));
2654
2655 // We're locked.
2656 EXPECT_TRUE(inner_context->IsLocked());
2657 }
2658
TEST_F(DisplayLockContextRenderingTest,LockedCanvasWithFallbackHasFocusableStyle)2659 TEST_F(DisplayLockContextRenderingTest,
2660 LockedCanvasWithFallbackHasFocusableStyle) {
2661 SetHtmlInnerHTML(R"HTML(
2662 <style>
2663 .auto { content-visibility: auto; }
2664 .spacer { height: 3000px; }
2665 </style>
2666 <div class=spacer></div>
2667 <div class=auto>
2668 <canvas>
2669 <div id=target tabindex=0></div>
2670 </canvas>
2671 </div>
2672 )HTML");
2673
2674 auto* target = GetDocument().getElementById("target");
2675 EXPECT_TRUE(target->IsFocusable());
2676 }
2677
TEST_F(DisplayLockContextRenderingTest,ForcedUnlockBookkeeping)2678 TEST_F(DisplayLockContextRenderingTest, ForcedUnlockBookkeeping) {
2679 SetHtmlInnerHTML(R"HTML(
2680 <style>
2681 .hidden { content-visibility: hidden; }
2682 .inline { display: inline; }
2683 </style>
2684 <div id=target class=hidden></div>
2685 )HTML");
2686
2687 auto* target = GetDocument().getElementById("target");
2688 auto* context = target->GetDisplayLockContext();
2689
2690 ASSERT_TRUE(context);
2691 EXPECT_TRUE(context->IsLocked());
2692 EXPECT_EQ(
2693 GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 1);
2694
2695 target->classList().Add("inline");
2696 UpdateAllLifecyclePhasesForTest();
2697
2698 EXPECT_FALSE(context->IsLocked());
2699 EXPECT_EQ(
2700 GetDocument().GetDisplayLockDocumentState().LockedDisplayLockCount(), 0);
2701 }
2702
TEST_F(DisplayLockContextRenderingTest,LayoutRootIsSkippedIfLocked)2703 TEST_F(DisplayLockContextRenderingTest, LayoutRootIsSkippedIfLocked) {
2704 SetHtmlInnerHTML(R"HTML(
2705 <style>
2706 .hidden { content-visibility: hidden; }
2707 .contained { contain: strict; }
2708 .positioned { position: absolute; top: 0; left: 0; }
2709 </style>
2710 <div id=hide>
2711 <div class=contained>
2712 <div id=new_parent class="contained positioned">
2713 <div>
2714 <div id=target></div>
2715 </div>
2716 </div>
2717 </div>
2718 </div>
2719 )HTML");
2720
2721 // Lock an ancestor.
2722 auto* hide = GetDocument().getElementById("hide");
2723 hide->classList().Add("hidden");
2724 UpdateAllLifecyclePhasesForTest();
2725
2726 auto* target = GetDocument().getElementById("target");
2727 auto* new_parent = GetDocument().getElementById("new_parent");
2728
2729 // Reparent elements which will invalidate layout without needing to process
2730 // style (which is blocked by the display-lock).
2731 new_parent->appendChild(target);
2732
2733 // Note that we don't check target here, since it doesn't have a layout object
2734 // after being re-parented.
2735 EXPECT_TRUE(new_parent->GetLayoutObject()->NeedsLayout());
2736
2737 // Updating the lifecycle should not update new_parent, since it is in a
2738 // locked subtree.
2739 UpdateAllLifecyclePhasesForTest();
2740 EXPECT_TRUE(new_parent->GetLayoutObject()->NeedsLayout());
2741
2742 // Unlocking and updating should update everything.
2743 hide->classList().Remove("hidden");
2744 UpdateAllLifecyclePhasesForTest();
2745
2746 EXPECT_FALSE(hide->GetLayoutObject()->NeedsLayout());
2747 EXPECT_FALSE(target->GetLayoutObject()->NeedsLayout());
2748 EXPECT_FALSE(new_parent->GetLayoutObject()->NeedsLayout());
2749 }
2750
TEST_F(DisplayLockContextRenderingTest,LayoutRootIsProcessedIfLockedAndForced)2751 TEST_F(DisplayLockContextRenderingTest,
2752 LayoutRootIsProcessedIfLockedAndForced) {
2753 SetHtmlInnerHTML(R"HTML(
2754 <style>
2755 .hidden { content-visibility: hidden; }
2756 .contained { contain: strict; }
2757 .positioned { position: absolute; top: 0; left: 0; }
2758 </style>
2759 <div id=hide>
2760 <div class=contained>
2761 <div id=new_parent class="contained positioned">
2762 <div>
2763 <div id=target></div>
2764 </div>
2765 </div>
2766 </div>
2767 </div>
2768 )HTML");
2769
2770 // Lock an ancestor.
2771 auto* hide = GetDocument().getElementById("hide");
2772 hide->classList().Add("hidden");
2773 UpdateAllLifecyclePhasesForTest();
2774
2775 auto* target = GetDocument().getElementById("target");
2776 auto* new_parent = GetDocument().getElementById("new_parent");
2777
2778 // Reparent elements which will invalidate layout without needing to process
2779 // style (which is blocked by the display-lock).
2780 new_parent->appendChild(target);
2781
2782 // Note that we don't check target here, since it doesn't have a layout object
2783 // after being re-parented.
2784 EXPECT_TRUE(new_parent->GetLayoutObject()->NeedsLayout());
2785
2786 {
2787 auto scope = GetScopedForcedUpdate(hide, true /* include self */);
2788
2789 // Updating the lifecycle should update target and new_parent, since it is
2790 // in a locked but forced subtree.
2791 UpdateAllLifecyclePhasesForTest();
2792 EXPECT_FALSE(target->GetLayoutObject()->NeedsLayout());
2793 EXPECT_FALSE(new_parent->GetLayoutObject()->NeedsLayout());
2794 }
2795
2796 // Unlocking and updating should update everything.
2797 hide->classList().Remove("hidden");
2798 UpdateAllLifecyclePhasesForTest();
2799
2800 EXPECT_FALSE(hide->GetLayoutObject()->NeedsLayout());
2801 EXPECT_FALSE(target->GetLayoutObject()->NeedsLayout());
2802 EXPECT_FALSE(new_parent->GetLayoutObject()->NeedsLayout());
2803 }
2804
TEST_F(DisplayLockContextRenderingTest,ContainStrictChild)2805 TEST_F(DisplayLockContextRenderingTest, ContainStrictChild) {
2806 SetHtmlInnerHTML(R"HTML(
2807 <style>
2808 .hidden { content-visibility: hidden; }
2809 .contained { contain: strict; }
2810 #target { backface-visibility: hidden; }
2811 </style>
2812 <div id=hide>
2813 <div id=container class=contained>
2814 <div id=target></div>
2815 </div>
2816 </div>
2817 )HTML");
2818
2819 // Lock an ancestor.
2820 auto* hide = GetDocument().getElementById("hide");
2821 hide->classList().Add("hidden");
2822
2823 // This should not DCHECK.
2824 UpdateAllLifecyclePhasesForTest();
2825
2826 hide->classList().Remove("hidden");
2827 UpdateAllLifecyclePhasesForTest();
2828 }
2829
TEST_F(DisplayLockContextRenderingTest,UseCounter)2830 TEST_F(DisplayLockContextRenderingTest, UseCounter) {
2831 SetHtmlInnerHTML(R"HTML(
2832 <style>
2833 .auto { content-visibility: auto; }
2834 .hidden { content-visibility: hidden; }
2835 .matchable { content-visibility: hidden-matchable; }
2836 </style>
2837 <div id=e1></div>
2838 <div id=e2></div>
2839 <div id=e3></div>
2840 )HTML");
2841
2842 EXPECT_FALSE(GetDocument().IsUseCounted(WebFeature::kContentVisibilityAuto));
2843 EXPECT_FALSE(
2844 GetDocument().IsUseCounted(WebFeature::kContentVisibilityHidden));
2845 EXPECT_FALSE(GetDocument().IsUseCounted(
2846 WebFeature::kContentVisibilityHiddenMatchable));
2847
2848 GetDocument().getElementById("e1")->classList().Add("auto");
2849 UpdateAllLifecyclePhasesForTest();
2850
2851 EXPECT_TRUE(GetDocument().IsUseCounted(WebFeature::kContentVisibilityAuto));
2852 EXPECT_FALSE(
2853 GetDocument().IsUseCounted(WebFeature::kContentVisibilityHidden));
2854 EXPECT_FALSE(GetDocument().IsUseCounted(
2855 WebFeature::kContentVisibilityHiddenMatchable));
2856
2857 GetDocument().getElementById("e2")->classList().Add("hidden");
2858 UpdateAllLifecyclePhasesForTest();
2859
2860 EXPECT_TRUE(GetDocument().IsUseCounted(WebFeature::kContentVisibilityAuto));
2861 EXPECT_TRUE(GetDocument().IsUseCounted(WebFeature::kContentVisibilityHidden));
2862 EXPECT_FALSE(GetDocument().IsUseCounted(
2863 WebFeature::kContentVisibilityHiddenMatchable));
2864
2865 GetDocument().getElementById("e3")->classList().Add("matchable");
2866 UpdateAllLifecyclePhasesForTest();
2867
2868 EXPECT_TRUE(GetDocument().IsUseCounted(WebFeature::kContentVisibilityAuto));
2869 EXPECT_TRUE(GetDocument().IsUseCounted(WebFeature::kContentVisibilityHidden));
2870 EXPECT_TRUE(GetDocument().IsUseCounted(
2871 WebFeature::kContentVisibilityHiddenMatchable));
2872 }
2873
TEST_F(DisplayLockContextRenderingTest,CompositingRootIsSkippedIfLocked)2874 TEST_F(DisplayLockContextRenderingTest, CompositingRootIsSkippedIfLocked) {
2875 SetHtmlInnerHTML(R"HTML(
2876 <style>
2877 .hidden { content-visibility: hidden; }
2878 .contained { contain: strict; }
2879 #target { backface-visibility: hidden; }
2880 </style>
2881 <div id=hide>
2882 <div id=container class=contained>
2883 <div id=target></div>
2884 </div>
2885 </div>
2886 )HTML");
2887
2888 // Lock an ancestor.
2889 auto* hide = GetDocument().getElementById("hide");
2890 hide->classList().Add("hidden");
2891 UpdateAllLifecyclePhasesForTest();
2892
2893 auto* target = GetDocument().getElementById("target");
2894 ASSERT_TRUE(target->GetLayoutObject());
2895 auto* target_box = target->GetLayoutBoxModelObject();
2896 ASSERT_TRUE(target_box);
2897 EXPECT_TRUE(target_box->Layer());
2898 EXPECT_TRUE(target_box->HasSelfPaintingLayer());
2899 auto* target_layer = target_box->Layer();
2900
2901 target_layer->SetNeedsCompositingInputsUpdate();
2902 EXPECT_TRUE(target_layer->NeedsCompositingInputsUpdate());
2903
2904 auto* container = GetDocument().getElementById("container");
2905 ASSERT_TRUE(container->GetLayoutObject());
2906 auto* container_box = container->GetLayoutBoxModelObject();
2907 ASSERT_TRUE(container_box);
2908 EXPECT_TRUE(container_box->Layer());
2909 EXPECT_TRUE(container_box->HasSelfPaintingLayer());
2910 auto* container_layer = container_box->Layer();
2911
2912 auto* compositor = target_layer->Compositor();
2913 ASSERT_TRUE(compositor);
2914
2915 EXPECT_EQ(compositor->GetCompositingInputsRoot(), container_layer);
2916
2917 UpdateAllLifecyclePhasesForTest();
2918
2919 EXPECT_EQ(compositor->GetCompositingInputsRoot(), container_layer);
2920 EXPECT_TRUE(target_layer->NeedsCompositingInputsUpdate());
2921
2922 hide->classList().Remove("hidden");
2923
2924 UpdateAllLifecyclePhasesForTest();
2925
2926 EXPECT_FALSE(compositor->GetCompositingInputsRoot());
2927 EXPECT_FALSE(target_layer->NeedsCompositingInputsUpdate());
2928 }
2929
TEST_F(DisplayLockContextRenderingTest,CompositingRootIsProcessedIfLockedButForced)2930 TEST_F(DisplayLockContextRenderingTest,
2931 CompositingRootIsProcessedIfLockedButForced) {
2932 SetHtmlInnerHTML(R"HTML(
2933 <style>
2934 .hidden { content-visibility: hidden; }
2935 .contained { contain: strict; }
2936 #target { backface-visibility: hidden; }
2937 </style>
2938 <div id=hide>
2939 <div class=contained>
2940 <div id=container class=contained>
2941 <div id=target></div>
2942 </div>
2943 </div>
2944 </div>
2945 )HTML");
2946
2947 // Lock an ancestor.
2948 auto* hide = GetDocument().getElementById("hide");
2949 hide->classList().Add("hidden");
2950 UpdateAllLifecyclePhasesForTest();
2951
2952 auto* target = GetDocument().getElementById("target");
2953 ASSERT_TRUE(target->GetLayoutObject());
2954 auto* target_box = To<LayoutBoxModelObject>(target->GetLayoutObject());
2955 ASSERT_TRUE(target_box);
2956 EXPECT_TRUE(target_box->Layer());
2957 EXPECT_TRUE(target_box->HasSelfPaintingLayer());
2958 auto* target_layer = target_box->Layer();
2959
2960 target_layer->SetNeedsCompositingInputsUpdate();
2961 EXPECT_TRUE(target_layer->NeedsCompositingInputsUpdate());
2962
2963 auto* container = GetDocument().getElementById("container");
2964 ASSERT_TRUE(container->GetLayoutObject());
2965 auto* container_box = container->GetLayoutBoxModelObject();
2966 ASSERT_TRUE(container_box);
2967 EXPECT_TRUE(container_box->Layer());
2968 EXPECT_TRUE(container_box->HasSelfPaintingLayer());
2969 auto* container_layer = container_box->Layer();
2970
2971 auto* compositor = target_layer->Compositor();
2972 ASSERT_TRUE(compositor);
2973
2974 EXPECT_EQ(compositor->GetCompositingInputsRoot(), container_layer);
2975
2976 {
2977 auto scope = GetScopedForcedUpdate(hide, true /* include self */);
2978 UpdateAllLifecyclePhasesForTest();
2979 }
2980
2981 EXPECT_FALSE(compositor->GetCompositingInputsRoot());
2982 EXPECT_FALSE(target_layer->NeedsCompositingInputsUpdate());
2983
2984 hide->classList().Remove("hidden");
2985
2986 UpdateAllLifecyclePhasesForTest();
2987
2988 EXPECT_FALSE(compositor->GetCompositingInputsRoot());
2989 EXPECT_FALSE(target_layer->NeedsCompositingInputsUpdate());
2990 }
2991
TEST_F(DisplayLockContextRenderingTest,NeedsLayoutTreeUpdateForNodeRespectsForcedLocks)2992 TEST_F(DisplayLockContextRenderingTest,
2993 NeedsLayoutTreeUpdateForNodeRespectsForcedLocks) {
2994 SetHtmlInnerHTML(R"HTML(
2995 <style>
2996 .hidden { content-visibility: hidden; }
2997 .contained { contain: strict; }
2998 .backface_hidden { backface-visibility: hidden; }
2999 </style>
3000 <div id=hide>
3001 <div id=container class=contained>
3002 <div id=target></div>
3003 </div>
3004 </div>
3005 )HTML");
3006
3007 // Lock an ancestor.
3008 auto* hide = GetDocument().getElementById("hide");
3009 hide->classList().Add("hidden");
3010 UpdateAllLifecyclePhasesForTest();
3011
3012 auto* target = GetDocument().getElementById("target");
3013 target->classList().Add("backface_hidden");
3014
3015 auto scope = GetScopedForcedUpdate(hide, true /* include self */);
3016 EXPECT_TRUE(GetDocument().NeedsLayoutTreeUpdateForNode(*target));
3017 }
3018
TEST_F(DisplayLockContextRenderingTest,InnerScrollerAutoVisibilityMargin)3019 TEST_F(DisplayLockContextRenderingTest, InnerScrollerAutoVisibilityMargin) {
3020 SetHtmlInnerHTML(R"HTML(
3021 <style>
3022 .auto { content-visibility: auto; }
3023 #scroller { height: 300px; overflow: scroll }
3024 #target { height: 10px; width: 10px; }
3025 .spacer { height: 3000px }
3026 </style>
3027 <div id=scroller>
3028 <div class=spacer></div>
3029 <div id=target class=auto></div>
3030 </div>
3031 )HTML");
3032
3033 UpdateAllLifecyclePhasesForTest();
3034 auto* target = GetDocument().getElementById("target");
3035 ASSERT_TRUE(target->GetDisplayLockContext());
3036 EXPECT_TRUE(target->GetDisplayLockContext()->IsLocked());
3037
3038 auto* scroller = GetDocument().getElementById("scroller");
3039 // 2600 is spacer (3000) minus scroller height (300) minus 100 for some extra
3040 // padding.
3041 scroller->setScrollTop(2600);
3042 UpdateAllLifecyclePhasesForTest();
3043
3044 // Since the intersection observation is delivered on the next frame, run
3045 // another lifecycle.
3046 UpdateAllLifecyclePhasesForTest();
3047
3048 EXPECT_FALSE(target->GetDisplayLockContext()->IsLocked());
3049 }
3050
TEST_F(DisplayLockContextRenderingTest,AutoReachesStableStateOnContentSmallerThanLockedSize)3051 TEST_F(DisplayLockContextRenderingTest,
3052 AutoReachesStableStateOnContentSmallerThanLockedSize) {
3053 SetHtmlInnerHTML(R"HTML(
3054 <style>
3055 .spacer { height: 20000px; }
3056 .auto {
3057 content-visibility: auto;
3058 contain-intrinsic-size: 1px 20000px;
3059 }
3060 .auto > div {
3061 height: 3000px;
3062 }
3063 </style>
3064
3065 <div class=spacer></div>
3066 <div id=e1 class=auto><div>content</div></div>
3067 <div id=e2 class=auto><div>content</div></div>
3068 <div class=spacer></div>
3069 )HTML");
3070
3071 UpdateAllLifecyclePhasesForTest();
3072
3073 GetDocument().scrollingElement()->setScrollTop(29000);
3074
3075 Element* element = GetDocument().getElementById("e1");
3076
3077 // Note that this test also unlock/relocks #e2 but we only care about #e1
3078 // settling into a steady state.
3079
3080 // Initially we start with locked in the viewport.
3081 UpdateAllLifecyclePhasesForTest();
3082 EXPECT_TRUE(element->GetDisplayLockContext()->IsLocked());
3083 EXPECT_EQ(GetDocument().scrollingElement()->scrollTop(), 29000.);
3084
3085 // It gets unlocked because it's in the viewport.
3086 UpdateAllLifecyclePhasesForTest();
3087 EXPECT_FALSE(element->GetDisplayLockContext()->IsLocked());
3088 EXPECT_EQ(GetDocument().scrollingElement()->scrollTop(), 29000.);
3089
3090 // By unlocking it, it shrinks so next time it gets relocked.
3091 UpdateAllLifecyclePhasesForTest();
3092 EXPECT_TRUE(element->GetDisplayLockContext()->IsLocked());
3093 EXPECT_EQ(GetDocument().scrollingElement()->scrollTop(), 29000.);
3094
3095 // It again gets unlocked and shrink. This time scroll anchoring puts it right
3096 // off the edge of the screen.
3097 UpdateAllLifecyclePhasesForTest();
3098 EXPECT_FALSE(element->GetDisplayLockContext()->IsLocked());
3099 EXPECT_EQ(GetDocument().scrollingElement()->scrollTop(), 23008.);
3100
3101 // On the next update we select the following element as an anchor and the
3102 // scroll offset doesn't change.
3103 UpdateAllLifecyclePhasesForTest();
3104 EXPECT_FALSE(element->GetDisplayLockContext()->IsLocked());
3105 EXPECT_EQ(GetDocument().scrollingElement()->scrollTop(), 23008.);
3106
3107 // Subsequent updates are in a stable state.
3108 for (int i = 0; i < 5; ++i) {
3109 UpdateAllLifecyclePhasesForTest();
3110 EXPECT_FALSE(element->GetDisplayLockContext()->IsLocked());
3111 EXPECT_EQ(GetDocument().scrollingElement()->scrollTop(), 23008.);
3112 }
3113 }
3114
TEST_F(DisplayLockContextRenderingTest,AutoReachesStableStateOnContentSmallerThanLockedSizeInLtr)3115 TEST_F(DisplayLockContextRenderingTest,
3116 AutoReachesStableStateOnContentSmallerThanLockedSizeInLtr) {
3117 SetHtmlInnerHTML(R"HTML(
3118 <style>
3119 body { writing-mode: vertical-lr }
3120 .spacer { block-size: 20000px; }
3121 .auto {
3122 content-visibility: auto;
3123 contain-intrinsic-size: 20000px 1px;
3124 }
3125 .auto > div {
3126 block-size: 3000px;
3127 }
3128 </style>
3129
3130 <div class=spacer></div>
3131 <div id=e1 class=auto><div>content</div></div>
3132 <div id=e2 class=auto><div>content</div></div>
3133 <div class=spacer></div>
3134 )HTML");
3135
3136 UpdateAllLifecyclePhasesForTest();
3137
3138 GetDocument().scrollingElement()->setScrollLeft(29000);
3139
3140 Element* element = GetDocument().getElementById("e1");
3141
3142 // Note that this test also unlock/relocks #e2 but we only care about #e1
3143 // settling into a steady state.
3144
3145 // Initially we start with locked in the viewport.
3146 UpdateAllLifecyclePhasesForTest();
3147 EXPECT_TRUE(element->GetDisplayLockContext()->IsLocked());
3148 EXPECT_EQ(GetDocument().scrollingElement()->scrollLeft(), 29000.);
3149
3150 // It gets unlocked because it's in the viewport.
3151 UpdateAllLifecyclePhasesForTest();
3152 EXPECT_FALSE(element->GetDisplayLockContext()->IsLocked());
3153 EXPECT_EQ(GetDocument().scrollingElement()->scrollLeft(), 29000.);
3154
3155 // By unlocking it, it shrinks so next time it gets relocked.
3156 UpdateAllLifecyclePhasesForTest();
3157 EXPECT_TRUE(element->GetDisplayLockContext()->IsLocked());
3158 EXPECT_EQ(GetDocument().scrollingElement()->scrollLeft(), 29000.);
3159
3160 // It again gets unlocked and shrink. This time scroll anchoring puts it right
3161 // off the edge of the screen.
3162 UpdateAllLifecyclePhasesForTest();
3163 EXPECT_FALSE(element->GetDisplayLockContext()->IsLocked());
3164 EXPECT_EQ(GetDocument().scrollingElement()->scrollLeft(), 23008.);
3165
3166 // On the next update we select the following element as an anchor and the
3167 // scroll offset doesn't change.
3168 UpdateAllLifecyclePhasesForTest();
3169 EXPECT_FALSE(element->GetDisplayLockContext()->IsLocked());
3170 EXPECT_EQ(GetDocument().scrollingElement()->scrollLeft(), 23008.);
3171
3172 // Subsequent updates are in a stable state.
3173 for (int i = 0; i < 5; ++i) {
3174 UpdateAllLifecyclePhasesForTest();
3175 EXPECT_FALSE(element->GetDisplayLockContext()->IsLocked());
3176 EXPECT_EQ(GetDocument().scrollingElement()->scrollLeft(), 23008.);
3177 }
3178 }
3179
TEST_F(DisplayLockContextRenderingTest,AutoReachesStableStateOnContentSmallerThanLockedSizeInRtl)3180 TEST_F(DisplayLockContextRenderingTest,
3181 AutoReachesStableStateOnContentSmallerThanLockedSizeInRtl) {
3182 SetHtmlInnerHTML(R"HTML(
3183 <style>
3184 body { writing-mode: vertical-rl }
3185 .spacer { block-size: 20000px; }
3186 .auto {
3187 content-visibility: auto;
3188 contain-intrinsic-size: 20000px 1px;
3189 }
3190 .auto > div {
3191 block-size: 3000px;
3192 }
3193 </style>
3194
3195 <div class=spacer></div>
3196 <div id=e1 class=auto><div>content</div></div>
3197 <div id=e2 class=auto><div>content</div></div>
3198 <div class=spacer></div>
3199 )HTML");
3200
3201 UpdateAllLifecyclePhasesForTest();
3202
3203 GetDocument().scrollingElement()->setScrollLeft(-29000);
3204
3205 Element* element = GetDocument().getElementById("e1");
3206
3207 // Note that this test also unlock/relocks #e2 but we only care about #e1
3208 // settling into a steady state.
3209
3210 // Initially we start with locked in the viewport.
3211 UpdateAllLifecyclePhasesForTest();
3212 EXPECT_TRUE(element->GetDisplayLockContext()->IsLocked());
3213 EXPECT_EQ(GetDocument().scrollingElement()->scrollLeft(), -29000.);
3214
3215 // It gets unlocked because it's in the viewport.
3216 UpdateAllLifecyclePhasesForTest();
3217 EXPECT_FALSE(element->GetDisplayLockContext()->IsLocked());
3218 EXPECT_EQ(GetDocument().scrollingElement()->scrollLeft(), -29000.);
3219
3220 // By unlocking it, it shrinks so next time it gets relocked.
3221 UpdateAllLifecyclePhasesForTest();
3222 EXPECT_TRUE(element->GetDisplayLockContext()->IsLocked());
3223 EXPECT_EQ(GetDocument().scrollingElement()->scrollLeft(), -29000.);
3224
3225 // It again gets unlocked and shrink. This time scroll anchoring puts it right
3226 // off the edge of the screen.
3227 UpdateAllLifecyclePhasesForTest();
3228 EXPECT_FALSE(element->GetDisplayLockContext()->IsLocked());
3229 EXPECT_EQ(GetDocument().scrollingElement()->scrollLeft(), -23008.);
3230
3231 // On the next update we select the following element as an anchor and the
3232 // scroll offset doesn't change.
3233 UpdateAllLifecyclePhasesForTest();
3234 EXPECT_FALSE(element->GetDisplayLockContext()->IsLocked());
3235 EXPECT_EQ(GetDocument().scrollingElement()->scrollLeft(), -23008.);
3236
3237 // Subsequent updates are in a stable state.
3238 for (int i = 0; i < 5; ++i) {
3239 UpdateAllLifecyclePhasesForTest();
3240 EXPECT_FALSE(element->GetDisplayLockContext()->IsLocked());
3241 EXPECT_EQ(GetDocument().scrollingElement()->scrollLeft(), -23008.);
3242 }
3243 }
3244
TEST_F(DisplayLockContextRenderingTest,FirstAutoFramePaintsInViewport)3245 TEST_F(DisplayLockContextRenderingTest, FirstAutoFramePaintsInViewport) {
3246 SetHtmlInnerHTML(R"HTML(
3247 <style>
3248 .spacer { height: 10000px }
3249 .auto {
3250 content-visibility: auto;
3251 contain-intrinsic-size: 1px 200px;
3252 }
3253 .auto > div { height: 100px }
3254 </style>
3255
3256 <div id=visible><div>content</div></div>
3257 <div class=spacer></div>
3258 <div id=hidden><div>content</div></div>
3259 )HTML");
3260
3261 auto* visible = GetDocument().getElementById("visible");
3262 auto* hidden = GetDocument().getElementById("hidden");
3263
3264 visible->classList().Add("auto");
3265 hidden->classList().Add("auto");
3266
3267 UpdateAllLifecyclePhasesForTest();
3268
3269 EXPECT_FALSE(visible->GetDisplayLockContext()->IsLocked());
3270 EXPECT_TRUE(hidden->GetDisplayLockContext()->IsLocked());
3271
3272 EXPECT_FALSE(visible->GetLayoutObject()->SelfNeedsLayout());
3273 EXPECT_FALSE(hidden->GetLayoutObject()->SelfNeedsLayout());
3274
3275 auto* visible_rect = visible->getBoundingClientRect();
3276 auto* hidden_rect = hidden->getBoundingClientRect();
3277
3278 EXPECT_FLOAT_EQ(visible_rect->height(), 100);
3279 EXPECT_FLOAT_EQ(hidden_rect->height(), 200);
3280 }
3281
3282 class DisplayLockContextLegacyRenderingTest
3283 : public RenderingTest,
3284 private ScopedCSSContentVisibilityHiddenMatchableForTest,
3285 private ScopedLayoutNGForTest {
3286 public:
DisplayLockContextLegacyRenderingTest()3287 DisplayLockContextLegacyRenderingTest()
3288 : RenderingTest(MakeGarbageCollected<SingleChildLocalFrameClient>()),
3289 ScopedCSSContentVisibilityHiddenMatchableForTest(true),
3290 ScopedLayoutNGForTest(false) {}
3291 };
3292
TEST_F(DisplayLockContextLegacyRenderingTest,QuirksHiddenParentBlocksChildLayout)3293 TEST_F(DisplayLockContextLegacyRenderingTest,
3294 QuirksHiddenParentBlocksChildLayout) {
3295 GetDocument().SetCompatibilityMode(Document::kQuirksMode);
3296 SetHtmlInnerHTML(R"HTML(
3297 <style>
3298 .hidden { content-visibility: hidden; }
3299 #grandparent { height: 100px; }
3300 #parent { height: auto; }
3301 #item { height: 10%; }
3302 </style>
3303 <div id=grandparent>
3304 <div id=parent>
3305 <div>
3306 <div id=item></div>
3307 </div>
3308 </div>
3309 </div>
3310 )HTML");
3311
3312 auto* grandparent = GetDocument().getElementById("grandparent");
3313 auto* parent = GetDocument().getElementById("parent");
3314
3315 auto* grandparent_box = To<LayoutBox>(grandparent->GetLayoutObject());
3316 auto* item_box = GetLayoutBoxByElementId("item");
3317
3318 ASSERT_TRUE(grandparent_box);
3319 ASSERT_TRUE(parent->GetLayoutObject());
3320 ASSERT_TRUE(item_box);
3321
3322 EXPECT_EQ(item_box->PercentHeightContainer(), grandparent_box);
3323 parent->classList().Add("hidden");
3324 grandparent->setAttribute(html_names::kStyleAttr, "height: 150px");
3325
3326 // This shouldn't DCHECK. We are allowed to have dirty percent height
3327 // descendants in quirks mode since they can cross a display-lock boundary.
3328 UpdateAllLifecyclePhasesForTest();
3329 }
3330
TEST_F(DisplayLockContextTest,GraphicsLayerBitsNotCheckedInLockedSubtree)3331 TEST_F(DisplayLockContextTest, GraphicsLayerBitsNotCheckedInLockedSubtree) {
3332 ResizeAndFocus();
3333 GetDocument().GetSettings()->SetPreferCompositingToLCDTextEnabled(true);
3334
3335 SetHtmlInnerHTML(R"HTML(
3336 <style>
3337 div {
3338 width: 100px;
3339 height: 100px;
3340 contain: style layout;
3341 will-change: transform;
3342 }
3343 .locked {
3344 content-visibility: hidden;
3345 }
3346 </style>
3347 <div id=container>
3348 <div>
3349 <div id=target></div>
3350 </div>
3351 </div>
3352 )HTML");
3353
3354 // Check if the result is correct if we update the contents.
3355 auto* container = GetDocument().getElementById("container");
3356 auto* target = GetDocument().getElementById("target");
3357 auto* target_box = target->GetLayoutBoxModelObject();
3358 ASSERT_TRUE(target_box);
3359 EXPECT_TRUE(target_box->Layer());
3360 EXPECT_TRUE(target_box->HasSelfPaintingLayer());
3361 auto* target_layer = target_box->Layer();
3362 ASSERT_TRUE(target_layer->HasCompositedLayerMapping());
3363
3364 container->classList().Add("locked");
3365 target_layer->GetCompositedLayerMapping()->SetNeedsGraphicsLayerUpdate(
3366 kGraphicsLayerUpdateLocal);
3367 // This should not DCHECK.
3368 UpdateAllLifecyclePhasesForTest();
3369
3370 EXPECT_TRUE(
3371 target_layer->GetCompositedLayerMapping()->NeedsGraphicsLayerUpdate());
3372
3373 container->classList().Remove("locked");
3374 UpdateAllLifecyclePhasesForTest();
3375
3376 EXPECT_FALSE(
3377 target_layer->GetCompositedLayerMapping()->NeedsGraphicsLayerUpdate());
3378 }
3379 } // namespace blink
3380