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 <string>
8 
9 #include "base/memory/ptr_util.h"
10 #include "base/metrics/histogram_macros.h"
11 #include "third_party/blink/renderer/core/accessibility/ax_object_cache.h"
12 #include "third_party/blink/renderer/core/css/style_change_reason.h"
13 #include "third_party/blink/renderer/core/css/style_engine.h"
14 #include "third_party/blink/renderer/core/css/style_recalc.h"
15 #include "third_party/blink/renderer/core/display_lock/display_lock_document_state.h"
16 #include "third_party/blink/renderer/core/display_lock/display_lock_utilities.h"
17 #include "third_party/blink/renderer/core/dom/document.h"
18 #include "third_party/blink/renderer/core/dom/dom_exception.h"
19 #include "third_party/blink/renderer/core/dom/element.h"
20 #include "third_party/blink/renderer/core/dom/node_computed_style.h"
21 #include "third_party/blink/renderer/core/editing/frame_selection.h"
22 #include "third_party/blink/renderer/core/editing/selection_template.h"
23 #include "third_party/blink/renderer/core/frame/local_frame.h"
24 #include "third_party/blink/renderer/core/frame/local_frame_view.h"
25 #include "third_party/blink/renderer/core/html_element_type_helpers.h"
26 #include "third_party/blink/renderer/core/inspector/inspector_trace_events.h"
27 #include "third_party/blink/renderer/core/layout/layout_box.h"
28 #include "third_party/blink/renderer/core/layout/layout_object.h"
29 #include "third_party/blink/renderer/core/page/page.h"
30 #include "third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.h"
31 #include "third_party/blink/renderer/core/paint/paint_layer.h"
32 #include "third_party/blink/renderer/core/paint/pre_paint_tree_walk.h"
33 #include "third_party/blink/renderer/platform/bindings/microtask.h"
34 #include "third_party/blink/renderer/platform/heap/heap.h"
35 #include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
36 #include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
37 
38 namespace blink {
39 
40 namespace {
41 namespace rejection_names {
42 const char* kContainmentNotSatisfied =
43     "Containment requirement is not satisfied.";
44 const char* kUnsupportedDisplay =
45     "Element has unsupported display type (display: contents).";
46 }  // namespace rejection_names
47 
RecordActivationReason(Document * document,DisplayLockActivationReason reason)48 void RecordActivationReason(Document* document,
49                             DisplayLockActivationReason reason) {
50   int ordered_reason = -1;
51 
52   // IMPORTANT: This number needs to be bumped up when adding
53   // new reasons.
54   static const int number_of_reasons = 9;
55 
56   switch (reason) {
57     case DisplayLockActivationReason::kAccessibility:
58       ordered_reason = 0;
59       break;
60     case DisplayLockActivationReason::kFindInPage:
61       ordered_reason = 1;
62       break;
63     case DisplayLockActivationReason::kFragmentNavigation:
64       ordered_reason = 2;
65       break;
66     case DisplayLockActivationReason::kScriptFocus:
67       ordered_reason = 3;
68       break;
69     case DisplayLockActivationReason::kScrollIntoView:
70       ordered_reason = 4;
71       break;
72     case DisplayLockActivationReason::kSelection:
73       ordered_reason = 5;
74       break;
75     case DisplayLockActivationReason::kSimulatedClick:
76       ordered_reason = 6;
77       break;
78     case DisplayLockActivationReason::kUserFocus:
79       ordered_reason = 7;
80       break;
81     case DisplayLockActivationReason::kViewportIntersection:
82       ordered_reason = 8;
83       break;
84     case DisplayLockActivationReason::kViewport:
85     case DisplayLockActivationReason::kAny:
86       NOTREACHED();
87       break;
88   }
89   UMA_HISTOGRAM_ENUMERATION("Blink.Render.DisplayLockActivationReason",
90                             ordered_reason, number_of_reasons);
91 
92   if (document && reason == DisplayLockActivationReason::kFindInPage)
93     document->MarkHasFindInPageContentVisibilityActiveMatch();
94 }
95 }  // namespace
96 
DisplayLockContext(Element * element)97 DisplayLockContext::DisplayLockContext(Element* element)
98     : element_(element), document_(&element_->GetDocument()) {
99   document_->GetDisplayLockDocumentState().AddDisplayLockContext(this);
100   DetermineIfSubtreeHasFocus();
101   DetermineIfSubtreeHasSelection();
102 }
103 
SetRequestedState(EContentVisibility state)104 void DisplayLockContext::SetRequestedState(EContentVisibility state) {
105   if (state_ == state)
106     return;
107   state_ = state;
108   switch (state_) {
109     case EContentVisibility::kVisible:
110       RequestUnlock();
111       break;
112     case EContentVisibility::kAuto:
113       UseCounter::Count(document_, WebFeature::kContentVisibilityAuto);
114       had_any_viewport_intersection_notifications_ = false;
115       RequestLock(static_cast<uint16_t>(DisplayLockActivationReason::kAny));
116       break;
117     case EContentVisibility::kHidden:
118       UseCounter::Count(document_, WebFeature::kContentVisibilityHidden);
119       RequestLock(0u);
120       break;
121     case EContentVisibility::kHiddenMatchable:
122       UseCounter::Count(document_,
123                         WebFeature::kContentVisibilityHiddenMatchable);
124       RequestLock(
125           static_cast<uint16_t>(DisplayLockActivationReason::kAny) &
126           ~static_cast<uint16_t>(DisplayLockActivationReason::kViewport));
127       break;
128   }
129   // In a new state, we might need to either start or stop observing viewport
130   // intersections.
131   UpdateActivationObservationIfNeeded();
132 
133   // If we needed a deferred not intersecting signal from 'auto' mode, we can
134   // set that to false, since the mode has switched to something else. If we're
135   // switching _to_ 'auto' mode, this should already be false and will be a
136   // no-op.
137   DCHECK(state_ != EContentVisibility::kAuto ||
138          !needs_deferred_not_intersecting_signal_);
139   needs_deferred_not_intersecting_signal_ = false;
140   UpdateLifecycleNotificationRegistration();
141 
142   // Note that we call this here since the |state_| change is a render affecting
143   // state, but is tracked independently.
144   NotifyRenderAffectingStateChanged();
145 
146   // Since our state changed, check if we need to create a scoped force update
147   // object.
148   // Note that creating this forced object may cause us to dirty style, which is
149   // fine since we are in a style update for this subtree anyway.
150   StyleEngine::AllowMarkStyleDirtyFromRecalcScope scope(
151       element_->GetDocument().GetStyleEngine());
152   element_->GetDocument().GetDisplayLockDocumentState().ForceLockIfNeeded(
153       element_.Get());
154 }
155 
AdjustElementStyle(ComputedStyle * style) const156 void DisplayLockContext::AdjustElementStyle(ComputedStyle* style) const {
157   if (state_ == EContentVisibility::kVisible)
158     return;
159   // If not visible, element gains style, layout, and paint containment. If
160   // skipped, it also gains size containment.
161   // https://wicg.github.io/display-locking/#content-visibility
162   auto contain =
163       style->Contain() | kContainsStyle | kContainsLayout | kContainsPaint;
164   if (IsLocked())
165     contain |= kContainsSize;
166   style->SetContain(contain);
167 }
168 
RequestLock(uint16_t activation_mask)169 void DisplayLockContext::RequestLock(uint16_t activation_mask) {
170   UpdateActivationMask(activation_mask);
171   SetRenderAffectingState(RenderAffectingState::kLockRequested, true);
172 }
173 
RequestUnlock()174 void DisplayLockContext::RequestUnlock() {
175   SetRenderAffectingState(RenderAffectingState::kLockRequested, false);
176 }
177 
UpdateActivationMask(uint16_t activatable_mask)178 void DisplayLockContext::UpdateActivationMask(uint16_t activatable_mask) {
179   if (activatable_mask == activatable_mask_)
180     return;
181 
182   bool all_activation_was_blocked = !activatable_mask_;
183   bool all_activation_is_blocked = !activatable_mask;
184   UpdateDocumentBookkeeping(IsLocked(), all_activation_was_blocked, IsLocked(),
185                             all_activation_is_blocked);
186 
187   activatable_mask_ = activatable_mask;
188 }
189 
UpdateDocumentBookkeeping(bool was_locked,bool all_activation_was_blocked,bool is_locked,bool all_activation_is_blocked)190 void DisplayLockContext::UpdateDocumentBookkeeping(
191     bool was_locked,
192     bool all_activation_was_blocked,
193     bool is_locked,
194     bool all_activation_is_blocked) {
195   if (!document_)
196     return;
197 
198   if (was_locked != is_locked) {
199     if (is_locked)
200       document_->GetDisplayLockDocumentState().AddLockedDisplayLock();
201     else
202       document_->GetDisplayLockDocumentState().RemoveLockedDisplayLock();
203   }
204 
205   bool was_locked_and_blocking = was_locked && all_activation_was_blocked;
206   bool is_locked_and_blocking = is_locked && all_activation_is_blocked;
207   if (was_locked_and_blocking != is_locked_and_blocking) {
208     if (is_locked_and_blocking) {
209       document_->GetDisplayLockDocumentState()
210           .IncrementDisplayLockBlockingAllActivation();
211     } else {
212       document_->GetDisplayLockDocumentState()
213           .DecrementDisplayLockBlockingAllActivation();
214     }
215   }
216 }
217 
UpdateActivationObservationIfNeeded()218 void DisplayLockContext::UpdateActivationObservationIfNeeded() {
219   // If we don't have a document, then we don't have an observer so just make
220   // sure we're marked as not observing anything and early out.
221   if (!document_) {
222     is_observed_ = false;
223     return;
224   }
225 
226   // We require observation if we are in 'auto' mode and we're connected to a
227   // view.
228   bool should_observe =
229       state_ == EContentVisibility::kAuto && ConnectedToView();
230   if (is_observed_ == should_observe)
231     return;
232   is_observed_ = should_observe;
233 
234   if (should_observe) {
235     document_->GetDisplayLockDocumentState()
236         .RegisterDisplayLockActivationObservation(element_);
237   } else {
238     document_->GetDisplayLockDocumentState()
239         .UnregisterDisplayLockActivationObservation(element_);
240     // If we're not listening to viewport intersections, then we can assume
241     // we're not intersecting:
242     // 1. We might not be connected, in which case we're not intersecting.
243     // 2. We might not be in 'auto' mode. which means that this doesn't affect
244     //    anything consequential but acts as a reset should we switch back to
245     //    the 'auto' mode.
246     SetRenderAffectingState(RenderAffectingState::kIntersectsViewport, false);
247   }
248 }
249 
NeedsLifecycleNotifications() const250 bool DisplayLockContext::NeedsLifecycleNotifications() const {
251   return needs_deferred_not_intersecting_signal_ ||
252          render_affecting_state_[static_cast<int>(
253              RenderAffectingState::kAutoStateUnlockedUntilLifecycle)];
254 }
255 
UpdateLifecycleNotificationRegistration()256 void DisplayLockContext::UpdateLifecycleNotificationRegistration() {
257   if (!document_ || !document_->View()) {
258     is_registered_for_lifecycle_notifications_ = false;
259     return;
260   }
261 
262   bool needs_notifications = NeedsLifecycleNotifications();
263   if (needs_notifications == is_registered_for_lifecycle_notifications_)
264     return;
265 
266   is_registered_for_lifecycle_notifications_ = needs_notifications;
267   if (needs_notifications) {
268     document_->View()->RegisterForLifecycleNotifications(this);
269   } else {
270     document_->View()->UnregisterFromLifecycleNotifications(this);
271   }
272 }
273 
Lock()274 void DisplayLockContext::Lock() {
275   DCHECK(!IsLocked());
276   is_locked_ = true;
277   UpdateDocumentBookkeeping(false, !activatable_mask_, true,
278                             !activatable_mask_);
279 
280   // If we're not connected, then we don't have to do anything else. Otherwise,
281   // we need to ensure that we update our style to check for containment later,
282   // layout size based on the options, and also clear the painted output.
283   if (!ConnectedToView())
284     return;
285 
286   // There are two ways we can get locked:
287   // 1. A new content-visibility property needs us to be locked.
288   // 2. We're in 'auto' mode and we are not intersecting the viewport.
289   // In the first case, we are already in style processing, so we don't need to
290   // invalidate style. However, in the second case we invalidate style so that
291   // `AdjustElementStyle()` can be called.
292   if (!document_->InStyleRecalc()) {
293     element_->SetNeedsStyleRecalc(
294         kLocalStyleChange,
295         StyleChangeReasonForTracing::Create(style_change_reason::kDisplayLock));
296 
297     // TODO(vmpstr): Note when an 'auto' context gets locked, we should clear
298     // the ancestor scroll anchors. This is a workaround for a behavior that
299     // happens when the user quickly scrolls (e.g. scrollbar scrolls) into an
300     // area that only has locked content. We can get into a loop that will
301     // keep unlocking an element, which may shrink it to be out of the viewport,
302     // and thus relocking it again. It is is also possible that we selected the
303     // scroller itself or one of the locked elements as the anchor, so we don't
304     // actually shift the scroll and the loop continues indefinitely. The user
305     // can easily get out of the loop by scrolling since that triggers a new
306     // scroll anchor selection. The work-around for us is also to pick a new
307     // scroll anchor for the scroller that has a newly-locked context. The
308     // reason it works is that it causes us to pick an anchor while the element
309     // is still unlocked, so when it gets relocked we shift the scroll to
310     // whatever visible content we had. The TODO here is to figure out if there
311     // is a better way to solve this. In either case, we have to select a new
312     // scroll anchor to get out of this behavior.
313     element_->NotifyPriorityScrollAnchorStatusChanged();
314   }
315 
316   // We need to notify the AX cache (if it exists) to update |element_|'s
317   // children in the AX cache.
318   if (AXObjectCache* cache = element_->GetDocument().ExistingAXObjectCache())
319     cache->ChildrenChanged(element_);
320 
321   if (!element_->GetLayoutObject())
322     return;
323 
324   MarkNeedsRepaintAndPaintArtifactCompositorUpdate();
325 }
326 
327 // Should* and Did* function for the lifecycle phases. These functions control
328 // whether or not to process the lifecycle for self or for children.
329 // =============================================================================
ShouldStyleChildren() const330 bool DisplayLockContext::ShouldStyleChildren() const {
331   return !is_locked_ || update_forced_ ||
332          (document_->GetDisplayLockDocumentState()
333               .ActivatableDisplayLocksForced() &&
334           IsActivatable(DisplayLockActivationReason::kAny));
335 }
336 
DidStyleSelf()337 void DisplayLockContext::DidStyleSelf() {
338   // TODO(vmpstr): This needs to be in the spec.
339   if (ForceUnlockIfNeeded())
340     return;
341 
342   if (!IsLocked() && state_ != EContentVisibility::kVisible) {
343     UpdateActivationObservationIfNeeded();
344     NotifyRenderAffectingStateChanged();
345   }
346 
347   if (blocked_style_traversal_type_ == kStyleUpdateSelf)
348     blocked_style_traversal_type_ = kStyleUpdateNotRequired;
349 }
350 
DidStyleChildren()351 void DisplayLockContext::DidStyleChildren() {
352   if (element_->ChildNeedsReattachLayoutTree())
353     element_->MarkAncestorsWithChildNeedsReattachLayoutTree();
354   blocked_style_traversal_type_ = kStyleUpdateNotRequired;
355   MarkElementsForWhitespaceReattachment();
356 }
357 
ShouldLayoutChildren() const358 bool DisplayLockContext::ShouldLayoutChildren() const {
359   return !is_locked_ || update_forced_ ||
360          (document_->GetDisplayLockDocumentState()
361               .ActivatableDisplayLocksForced() &&
362           IsActivatable(DisplayLockActivationReason::kAny));
363 }
364 
DidLayoutChildren()365 void DisplayLockContext::DidLayoutChildren() {
366   // Since we did layout on children already, we'll clear this.
367   child_layout_was_blocked_ = false;
368   had_lifecycle_update_since_last_unlock_ = true;
369 }
370 
ShouldPrePaintChildren() const371 bool DisplayLockContext::ShouldPrePaintChildren() const {
372   return !is_locked_ || update_forced_;
373 }
374 
ShouldPaintChildren() const375 bool DisplayLockContext::ShouldPaintChildren() const {
376   // Note that forced updates should never require us to paint, so we don't
377   // check |update_forced_| here. In other words, although |update_forced_|
378   // could be true here, we still should not paint.
379   return !is_locked_;
380 }
381 // End Should* and Did* functions ==============================================
382 
IsActivatable(DisplayLockActivationReason reason) const383 bool DisplayLockContext::IsActivatable(
384     DisplayLockActivationReason reason) const {
385   return activatable_mask_ & static_cast<uint16_t>(reason);
386 }
387 
CommitForActivationWithSignal(Element * activated_element,DisplayLockActivationReason reason)388 void DisplayLockContext::CommitForActivationWithSignal(
389     Element* activated_element,
390     DisplayLockActivationReason reason) {
391   DCHECK(activated_element);
392   DCHECK(element_);
393   DCHECK(ConnectedToView());
394   DCHECK(IsLocked());
395   DCHECK(ShouldCommitForActivation(DisplayLockActivationReason::kAny));
396 
397   // The following actions (can) scroll content into view. However, if the
398   // position of the target is outside of the bounds that would cause the
399   // auto-context to unlock, then we can scroll into wrong content while the
400   // context remains lock. To avoid this, unlock it until the next lifecycle.
401   // If the scroll is successful, then we will gain visibility anyway so the
402   // context will be unlocked for other reasons.
403   if (reason == DisplayLockActivationReason::kAccessibility ||
404       reason == DisplayLockActivationReason::kFindInPage ||
405       reason == DisplayLockActivationReason::kFragmentNavigation ||
406       reason == DisplayLockActivationReason::kScrollIntoView ||
407       reason == DisplayLockActivationReason::kSimulatedClick) {
408     // Note that because the visibility is only determined at the _end_ of the
409     // next frame, we need to ensure that we stay unlocked for two frames.
410     SetKeepUnlockedUntilLifecycleCount(2);
411   }
412 
413   RecordActivationReason(document_, reason);
414 }
415 
SetKeepUnlockedUntilLifecycleCount(int count)416 void DisplayLockContext::SetKeepUnlockedUntilLifecycleCount(int count) {
417   DCHECK_GT(count, 0);
418   keep_unlocked_count_ = std::max(keep_unlocked_count_, count);
419   SetRenderAffectingState(
420       RenderAffectingState::kAutoStateUnlockedUntilLifecycle, true);
421   UpdateLifecycleNotificationRegistration();
422   ScheduleAnimation();
423 }
424 
NotifyIsIntersectingViewport()425 void DisplayLockContext::NotifyIsIntersectingViewport() {
426   had_any_viewport_intersection_notifications_ = true;
427   // If we are now intersecting, then we are definitely not nested in a locked
428   // subtree and we don't need to lock as a result.
429   needs_deferred_not_intersecting_signal_ = false;
430   UpdateLifecycleNotificationRegistration();
431   // If we're not connected, then there is no need to change any state.
432   // This could be the case if we were disconnected while a viewport
433   // intersection notification was pending.
434   if (ConnectedToView())
435     SetRenderAffectingState(RenderAffectingState::kIntersectsViewport, true);
436 }
437 
NotifyIsNotIntersectingViewport()438 void DisplayLockContext::NotifyIsNotIntersectingViewport() {
439   had_any_viewport_intersection_notifications_ = true;
440 
441   if (IsLocked()) {
442     DCHECK(!needs_deferred_not_intersecting_signal_);
443     return;
444   }
445 
446   // We might have been disconnected while the intersection observation
447   // notification was pending. Ensure to unregister from lifecycle
448   // notifications if we're doing that, and early out.
449   if (!ConnectedToView()) {
450     needs_deferred_not_intersecting_signal_ = false;
451     UpdateLifecycleNotificationRegistration();
452     return;
453   }
454 
455   // There are two situations we need to consider here:
456   // 1. We are off-screen but not nested in any other lock. This means we should
457   //    re-lock (also verify that the reason we're in this state is that we're
458   //    activated).
459   // 2. We are in a nested locked context. This means we don't actually know
460   //    whether we should lock or not. In order to avoid needless dirty of the
461   //    layout and style trees up to the nested context, we remain unlocked.
462   //    However, we also need to ensure that we relock if we become unnested.
463   //    So, we simply delay this check to the next frame (via LocalFrameView),
464   //    which will call this function again and so we can perform the check
465   //    again.
466   auto* locked_ancestor =
467       DisplayLockUtilities::NearestLockedExclusiveAncestor(*element_);
468   if (locked_ancestor) {
469     needs_deferred_not_intersecting_signal_ = true;
470   } else {
471     needs_deferred_not_intersecting_signal_ = false;
472     SetRenderAffectingState(RenderAffectingState::kIntersectsViewport, false);
473   }
474   UpdateLifecycleNotificationRegistration();
475 }
476 
ShouldCommitForActivation(DisplayLockActivationReason reason) const477 bool DisplayLockContext::ShouldCommitForActivation(
478     DisplayLockActivationReason reason) const {
479   return IsActivatable(reason) && IsLocked();
480 }
481 
NotifyForcedUpdateScopeStarted()482 void DisplayLockContext::NotifyForcedUpdateScopeStarted() {
483   ++update_forced_;
484   if (update_forced_ == 1) {
485     TRACE_EVENT_NESTABLE_ASYNC_BEGIN0(
486         TRACE_DISABLED_BY_DEFAULT("blink.debug.display_lock"), "LockForced",
487         TRACE_ID_LOCAL(this));
488   }
489 
490   if (IsLocked()) {
491     // Now that the update is forced, we should ensure that style layout, and
492     // prepaint code can reach it via dirty bits. Note that paint isn't a part
493     // of this, since |update_forced_| doesn't force paint to happen. See
494     // ShouldPaint().
495     MarkForStyleRecalcIfNeeded();
496     MarkForLayoutIfNeeded();
497     MarkAncestorsForPrePaintIfNeeded();
498   }
499 }
500 
NotifyForcedUpdateScopeEnded()501 void DisplayLockContext::NotifyForcedUpdateScopeEnded() {
502   DCHECK(update_forced_);
503   --update_forced_;
504   if (update_forced_ == 0) {
505     TRACE_EVENT_NESTABLE_ASYNC_END0(
506         TRACE_DISABLED_BY_DEFAULT("blink.debug.display_lock"), "LockForced",
507         TRACE_ID_LOCAL(this));
508   }
509 }
510 
Unlock()511 void DisplayLockContext::Unlock() {
512   DCHECK(IsLocked());
513   is_locked_ = false;
514   had_lifecycle_update_since_last_unlock_ = false;
515   UpdateDocumentBookkeeping(true, !activatable_mask_, false,
516                             !activatable_mask_);
517 
518   if (!ConnectedToView())
519     return;
520 
521   // There are a few ways we can get unlocked:
522   // 1. A new content-visibility property needs us to be ulocked.
523   // 2. We're in 'auto' mode and we are intersecting the viewport.
524   // In the first case, we are already in style processing, so we don't need to
525   // invalidate style. However, in the second case we invalidate style so that
526   // `AdjustElementStyle()` can be called.
527   if (!document_->InStyleRecalc()) {
528     // Since size containment depends on the activatability state, we should
529     // invalidate the style for this element, so that the style adjuster can
530     // properly remove the containment.
531     element_->SetNeedsStyleRecalc(
532         kLocalStyleChange,
533         StyleChangeReasonForTracing::Create(style_change_reason::kDisplayLock));
534 
535     // Also propagate any dirty bits that we have previously blocked.
536     // If we're in style recalc, this will be handled by
537     // `AdjustStyleRecalcChangeForChildren()`.
538     MarkForStyleRecalcIfNeeded();
539   }
540 
541   // We also need to notify the AX cache (if it exists) to update the childrens
542   // of |element_| in the AX cache.
543   if (AXObjectCache* cache = element_->GetDocument().ExistingAXObjectCache())
544     cache->ChildrenChanged(element_);
545 
546   auto* layout_object = element_->GetLayoutObject();
547   // We might commit without connecting, so there is no layout object yet.
548   if (!layout_object)
549     return;
550 
551   // Now that we know we have a layout object, we should ensure that we can
552   // reach the rest of the phases as well.
553   MarkForLayoutIfNeeded();
554   MarkAncestorsForPrePaintIfNeeded();
555   MarkNeedsRepaintAndPaintArtifactCompositorUpdate();
556 }
557 
AddToWhitespaceReattachSet(Element & element)558 void DisplayLockContext::AddToWhitespaceReattachSet(Element& element) {
559   whitespace_reattach_set_.insert(&element);
560 }
561 
MarkElementsForWhitespaceReattachment()562 void DisplayLockContext::MarkElementsForWhitespaceReattachment() {
563   for (auto element : whitespace_reattach_set_) {
564     if (!element || element->NeedsReattachLayoutTree() ||
565         !element->GetLayoutObject())
566       continue;
567 
568     if (Node* first_child = LayoutTreeBuilderTraversal::FirstChild(*element))
569       first_child->MarkAncestorsWithChildNeedsReattachLayoutTree();
570   }
571   whitespace_reattach_set_.clear();
572 }
573 
AdjustStyleRecalcChangeForChildren(StyleRecalcChange change)574 StyleRecalcChange DisplayLockContext::AdjustStyleRecalcChangeForChildren(
575     StyleRecalcChange change) {
576   // This code is similar to MarkForStyleRecalcIfNeeded, except that it acts on
577   // |change| and not on |element_|. This is only called during style recalc.
578   // Note that since we're already in self style recalc, this code is shorter
579   // since it doesn't have to deal with dirtying self-style.
580   DCHECK(document_->InStyleRecalc());
581 
582   if (reattach_layout_tree_was_blocked_) {
583     change = change.ForceReattachLayoutTree();
584     reattach_layout_tree_was_blocked_ = false;
585   }
586 
587   if (blocked_style_traversal_type_ == kStyleUpdateDescendants)
588     change = change.ForceRecalcDescendants();
589   else if (blocked_style_traversal_type_ == kStyleUpdateChildren)
590     change = change.EnsureAtLeast(StyleRecalcChange::kRecalcChildren);
591   blocked_style_traversal_type_ = kStyleUpdateNotRequired;
592   return change;
593 }
594 
MarkForStyleRecalcIfNeeded()595 bool DisplayLockContext::MarkForStyleRecalcIfNeeded() {
596   if (reattach_layout_tree_was_blocked_) {
597     // We previously blocked a layout tree reattachment on |element_|'s
598     // descendants, so we should mark it for layout tree reattachment now.
599     element_->SetForceReattachLayoutTree();
600     reattach_layout_tree_was_blocked_ = false;
601   }
602   if (IsElementDirtyForStyleRecalc()) {
603     if (blocked_style_traversal_type_ > kStyleUpdateNotRequired) {
604       // We blocked a traversal going to the element previously.
605       // Make sure we will traverse this element and maybe its subtree if we
606       // previously blocked a style traversal that should've done that.
607       element_->SetNeedsStyleRecalc(
608           blocked_style_traversal_type_ == kStyleUpdateDescendants
609               ? kSubtreeStyleChange
610               : kLocalStyleChange,
611           StyleChangeReasonForTracing::Create(
612               style_change_reason::kDisplayLock));
613       if (blocked_style_traversal_type_ == kStyleUpdateChildren)
614         element_->SetChildNeedsStyleRecalc();
615       blocked_style_traversal_type_ = kStyleUpdateNotRequired;
616     } else if (element_->ChildNeedsReattachLayoutTree()) {
617       // Mark |element_| as style dirty, as we can't mark for child reattachment
618       // before style.
619       element_->SetNeedsStyleRecalc(kLocalStyleChange,
620                                     StyleChangeReasonForTracing::Create(
621                                         style_change_reason::kDisplayLock));
622     }
623     // Propagate to the ancestors, since the dirty bit in a locked subtree is
624     // stopped at the locked ancestor.
625     // See comment in IsElementDirtyForStyleRecalc.
626     element_->MarkAncestorsWithChildNeedsStyleRecalc();
627     return true;
628   }
629   return false;
630 }
631 
MarkForLayoutIfNeeded()632 bool DisplayLockContext::MarkForLayoutIfNeeded() {
633   if (IsElementDirtyForLayout()) {
634     // Forces the marking of ancestors to happen, even if
635     // |DisplayLockContext::ShouldLayout()| returns false.
636     base::AutoReset<int> scoped_force(&update_forced_, update_forced_ + 1);
637     if (child_layout_was_blocked_) {
638       // We've previously blocked a child traversal when doing self-layout for
639       // the locked element, so we're marking it with child-needs-layout so that
640       // it will traverse to the locked element and do the child traversal
641       // again. We don't need to mark it for self-layout (by calling
642       // |LayoutObject::SetNeedsLayout()|) because the locked element itself
643       // doesn't need to relayout.
644       element_->GetLayoutObject()->SetChildNeedsLayout();
645       child_layout_was_blocked_ = false;
646     } else {
647       // Since the dirty layout propagation stops at the locked element, we need
648       // to mark its ancestors as dirty here so that it will be traversed to on
649       // the next layout.
650       element_->GetLayoutObject()->MarkContainerChainForLayout();
651     }
652     return true;
653   }
654   return false;
655 }
656 
MarkAncestorsForPrePaintIfNeeded()657 bool DisplayLockContext::MarkAncestorsForPrePaintIfNeeded() {
658   // TODO(vmpstr): We should add a compositing phase for proper bookkeeping.
659   bool compositing_dirtied = MarkForCompositingUpdatesIfNeeded();
660 
661   if (IsElementDirtyForPrePaint()) {
662     auto* layout_object = element_->GetLayoutObject();
663     if (auto* parent = layout_object->Parent())
664       parent->SetSubtreeShouldCheckForPaintInvalidation();
665 
666     // Note that if either we or our descendants are marked as needing this
667     // update, then ensure to mark self as needing the update. This sets up the
668     // correct flags for PrePaint to recompute the necessary values and
669     // propagate the information into the subtree.
670     if (needs_effective_allowed_touch_action_update_ ||
671         layout_object->EffectiveAllowedTouchActionChanged() ||
672         layout_object->DescendantEffectiveAllowedTouchActionChanged()) {
673       // Note that although the object itself should have up to date value, in
674       // order to force recalc of the whole subtree, we mark it as needing an
675       // update.
676       layout_object->MarkEffectiveAllowedTouchActionChanged();
677     }
678     if (needs_blocking_wheel_event_handler_update_ ||
679         layout_object->BlockingWheelEventHandlerChanged() ||
680         layout_object->DescendantBlockingWheelEventHandlerChanged()) {
681       // Note that although the object itself should have up to date value, in
682       // order to force recalc of the whole subtree, we mark it as needing an
683       // update.
684       layout_object->MarkBlockingWheelEventHandlerChanged();
685     }
686     return true;
687   }
688   return compositing_dirtied;
689 }
690 
MarkNeedsRepaintAndPaintArtifactCompositorUpdate()691 bool DisplayLockContext::MarkNeedsRepaintAndPaintArtifactCompositorUpdate() {
692   DCHECK(ConnectedToView());
693   if (auto* layout_object = element_->GetLayoutObject()) {
694     layout_object->PaintingLayer()->SetNeedsRepaint();
695     document_->View()->SetPaintArtifactCompositorNeedsUpdate();
696     return true;
697   }
698   return false;
699 }
700 
MarkForCompositingUpdatesIfNeeded()701 bool DisplayLockContext::MarkForCompositingUpdatesIfNeeded() {
702   if (!ConnectedToView())
703     return false;
704 
705   auto* layout_object = element_->GetLayoutObject();
706   if (!layout_object)
707     return false;
708 
709   auto* layout_box = DynamicTo<LayoutBoxModelObject>(layout_object);
710   if (layout_box && layout_box->HasSelfPaintingLayer()) {
711     if (layout_box->Layer()->ChildNeedsCompositingInputsUpdate() &&
712         layout_box->Layer()->Parent()) {
713       // Note that if the layer's child needs compositing inputs update, then
714       // that layer itself also needs compositing inputs update. In order to
715       // propagate the dirty bit, we need to mark this layer's _parent_ as a
716       // needing an update.
717       layout_box->Layer()->Parent()->SetNeedsCompositingInputsUpdate();
718     }
719     if (needs_compositing_requirements_update_)
720       layout_box->Layer()->SetNeedsCompositingRequirementsUpdate();
721     needs_compositing_requirements_update_ = false;
722 
723     if (needs_compositing_dependent_flag_update_)
724       layout_box->Layer()->SetNeedsCompositingInputsUpdate();
725     needs_compositing_dependent_flag_update_ = false;
726 
727     if (needs_graphics_layer_rebuild_)
728       layout_box->Layer()->SetNeedsGraphicsLayerRebuild();
729     needs_graphics_layer_rebuild_ = false;
730 
731     if (forced_graphics_layer_update_blocked_) {
732       // We only add an extra dirty bit to the compositing state, which is safe
733       // since we do this before updating the compositing state.
734       DisableCompositingQueryAsserts disabler;
735 
736       auto* compositing_parent =
737           layout_box->Layer()->EnclosingLayerWithCompositedLayerMapping(
738               kIncludeSelf);
739       compositing_parent->GetCompositedLayerMapping()
740           ->SetNeedsGraphicsLayerUpdate(kGraphicsLayerUpdateSubtree);
741     }
742     forced_graphics_layer_update_blocked_ = false;
743 
744     return true;
745   }
746   return false;
747 }
748 
IsElementDirtyForStyleRecalc() const749 bool DisplayLockContext::IsElementDirtyForStyleRecalc() const {
750   // The |element_| checks could be true even if |blocked_style_traversal_type_|
751   // is not required. The reason for this is that the
752   // blocked_style_traversal_type_ is set during the style walk that this
753   // display lock blocked. However, we could dirty element style and commit
754   // before ever having gone through the style calc that would have been
755   // blocked, meaning we never blocked style during a walk. Instead we might
756   // have not propagated the dirty bits up the tree.
757   return element_->NeedsStyleRecalc() || element_->ChildNeedsStyleRecalc() ||
758          element_->ChildNeedsReattachLayoutTree() ||
759          blocked_style_traversal_type_ > kStyleUpdateNotRequired;
760 }
761 
IsElementDirtyForLayout() const762 bool DisplayLockContext::IsElementDirtyForLayout() const {
763   if (auto* layout_object = element_->GetLayoutObject())
764     return layout_object->NeedsLayout() || child_layout_was_blocked_;
765   return false;
766 }
767 
IsElementDirtyForPrePaint() const768 bool DisplayLockContext::IsElementDirtyForPrePaint() const {
769   if (auto* layout_object = element_->GetLayoutObject()) {
770     auto* layout_box = DynamicTo<LayoutBoxModelObject>(layout_object);
771     return PrePaintTreeWalk::ObjectRequiresPrePaint(*layout_object) ||
772            PrePaintTreeWalk::ObjectRequiresTreeBuilderContext(*layout_object) ||
773            needs_prepaint_subtree_walk_ ||
774            needs_effective_allowed_touch_action_update_ ||
775            needs_blocking_wheel_event_handler_update_ ||
776            needs_compositing_requirements_update_ ||
777            (layout_box && layout_box->HasSelfPaintingLayer() &&
778             layout_box->Layer()->ChildNeedsCompositingInputsUpdate());
779   }
780   return false;
781 }
782 
DidMoveToNewDocument(Document & old_document)783 void DisplayLockContext::DidMoveToNewDocument(Document& old_document) {
784   DCHECK(element_);
785   document_ = &element_->GetDocument();
786 
787   old_document.GetDisplayLockDocumentState().RemoveDisplayLockContext(this);
788   document_->GetDisplayLockDocumentState().AddDisplayLockContext(this);
789 
790   if (is_observed_) {
791     old_document.GetDisplayLockDocumentState()
792         .UnregisterDisplayLockActivationObservation(element_);
793     document_->GetDisplayLockDocumentState()
794         .RegisterDisplayLockActivationObservation(element_);
795   }
796 
797   // Since we're observing the lifecycle updates, ensure that we listen to the
798   // right document's view.
799   if (is_registered_for_lifecycle_notifications_) {
800     if (old_document.View())
801       old_document.View()->UnregisterFromLifecycleNotifications(this);
802 
803     if (document_->View())
804       document_->View()->RegisterForLifecycleNotifications(this);
805     else
806       is_registered_for_lifecycle_notifications_ = false;
807   }
808 
809   if (IsLocked()) {
810     old_document.GetDisplayLockDocumentState().RemoveLockedDisplayLock();
811     document_->GetDisplayLockDocumentState().AddLockedDisplayLock();
812     if (!IsActivatable(DisplayLockActivationReason::kAny)) {
813       old_document.GetDisplayLockDocumentState()
814           .DecrementDisplayLockBlockingAllActivation();
815       document_->GetDisplayLockDocumentState()
816           .IncrementDisplayLockBlockingAllActivation();
817     }
818   }
819 
820   DetermineIfSubtreeHasFocus();
821   DetermineIfSubtreeHasSelection();
822 }
823 
WillStartLifecycleUpdate(const LocalFrameView & view)824 void DisplayLockContext::WillStartLifecycleUpdate(const LocalFrameView& view) {
825   DCHECK(NeedsLifecycleNotifications());
826   // We might have delayed processing intersection observation update (signal
827   // that we were not intersecting) because this context was nested in another
828   // locked context. At the start of the lifecycle, we should check whether
829   // that is still true. In other words, this call will check if we're still
830   // nested. If we are, we won't do anything. If we're not, then we will lock
831   // this context.
832   //
833   // Note that when we are no longer nested and and we have not received any
834   // notifications from the intersection observer, it means that we are not
835   // visible.
836   if (needs_deferred_not_intersecting_signal_)
837     NotifyIsNotIntersectingViewport();
838 
839   // If we're keeping this context unlocked, update the values.
840   if (keep_unlocked_count_) {
841     if (--keep_unlocked_count_) {
842       ScheduleAnimation();
843     } else {
844       SetRenderAffectingState(
845           RenderAffectingState::kAutoStateUnlockedUntilLifecycle, false);
846       UpdateLifecycleNotificationRegistration();
847     }
848   } else {
849     DCHECK(!render_affecting_state_[static_cast<int>(
850         RenderAffectingState::kAutoStateUnlockedUntilLifecycle)]);
851   }
852 }
853 
NotifyWillDisconnect()854 void DisplayLockContext::NotifyWillDisconnect() {
855   if (!IsLocked() || !element_ || !element_->GetLayoutObject())
856     return;
857   // If we're locked while being disconnected, we need to layout the parent.
858   // The reason for this is that we might skip the layout if we're empty while
859   // locked, but it's important to update IsSelfCollapsingBlock property on
860   // the parent so that it's up to date. This property is updated during
861   // layout.
862   if (auto* parent = element_->GetLayoutObject()->Parent())
863     parent->SetNeedsLayout(layout_invalidation_reason::kDisplayLock);
864 }
865 
ElementDisconnected()866 void DisplayLockContext::ElementDisconnected() {
867   UpdateActivationObservationIfNeeded();
868 }
869 
ElementConnected()870 void DisplayLockContext::ElementConnected() {
871   UpdateActivationObservationIfNeeded();
872   DetermineIfSubtreeHasFocus();
873   DetermineIfSubtreeHasSelection();
874 }
875 
ScheduleAnimation()876 void DisplayLockContext::ScheduleAnimation() {
877   DCHECK(element_);
878   if (!ConnectedToView() || !document_ || !document_->GetPage())
879     return;
880 
881   // Schedule an animation to perform the lifecycle phases.
882   document_->GetPage()->Animator().ScheduleVisualUpdate(document_->GetFrame());
883 }
884 
ShouldForceUnlock() const885 const char* DisplayLockContext::ShouldForceUnlock() const {
886   DCHECK(element_);
887   // This function is only called after style, layout tree, or lifecycle
888   // updates, so the style should be up-to-date, except in the case of nested
889   // locks, where the style recalc will never actually get to |element_|.
890   // TODO(vmpstr): We need to figure out what to do here, since we don't know
891   // what the style is and whether this element has proper containment. However,
892   // forcing an update from the ancestor locks seems inefficient. For now, we
893   // just optimistically assume that we have all of the right containment in
894   // place. See crbug.com/926276 for more information.
895   if (element_->NeedsStyleRecalc()) {
896     DCHECK(DisplayLockUtilities::LockedAncestorPreventingStyle(*element_));
897     return nullptr;
898   }
899 
900   if (element_->HasDisplayContentsStyle())
901     return rejection_names::kUnsupportedDisplay;
902 
903   auto* style = element_->GetComputedStyle();
904   // Note that if for whatever reason we don't have computed style, then
905   // optimistically assume that we have containment.
906   if (!style)
907     return nullptr;
908   if (!style->ContainsStyle() || !style->ContainsLayout())
909     return rejection_names::kContainmentNotSatisfied;
910 
911   // We allow replaced elements to be locked. This check is similar to the check
912   // in DefinitelyNewFormattingContext() in element.cc, but in this case we
913   // allow object element to get locked.
914   if (IsA<HTMLObjectElement>(*element_) || IsA<HTMLImageElement>(*element_) ||
915       element_->IsFormControlElement() || element_->IsMediaElement() ||
916       element_->IsFrameOwnerElement() || element_->IsSVGElement()) {
917     return nullptr;
918   }
919 
920   // From https://www.w3.org/TR/css-contain-1/#containment-layout
921   // If the element does not generate a principal box (as is the case with
922   // display: contents or display: none), or if the element is an internal
923   // table element other than display: table-cell, if the element is an
924   // internal ruby element, or if the element’s principal box is a
925   // non-atomic inline-level box, layout containment has no effect.
926   // (Note we're allowing display:none for display locked elements, and a bit
927   // more restrictive on ruby - banning <ruby> elements entirely).
928   auto* html_element = DynamicTo<HTMLElement>(element_.Get());
929   if ((style->IsDisplayTableType() &&
930        style->Display() != EDisplay::kTableCell) ||
931       (!html_element || IsA<HTMLRubyElement>(html_element)) ||
932       (style->IsDisplayInlineType() && !style->IsDisplayReplacedType())) {
933     return rejection_names::kContainmentNotSatisfied;
934   }
935   return nullptr;
936 }
937 
ForceUnlockIfNeeded()938 bool DisplayLockContext::ForceUnlockIfNeeded() {
939   // We must have "contain: style layout", and disallow display:contents
940   // for display locking. Note that we should always guarantee this after
941   // every style or layout tree update. Otherwise, proceeding with layout may
942   // cause unexpected behavior. By rejecting the promise, the behavior can be
943   // detected by script.
944   // TODO(rakina): If this is after acquire's promise is resolved and update()
945   // commit() isn't in progress, the web author won't know that the element
946   // got unlocked. Figure out how to notify the author.
947   if (auto* reason = ShouldForceUnlock()) {
948     if (IsLocked())
949       Unlock();
950     return true;
951   }
952   return false;
953 }
954 
ConnectedToView() const955 bool DisplayLockContext::ConnectedToView() const {
956   return element_ && document_ && element_->isConnected() && document_->View();
957 }
958 
NotifySubtreeLostFocus()959 void DisplayLockContext::NotifySubtreeLostFocus() {
960   SetRenderAffectingState(RenderAffectingState::kSubtreeHasFocus, false);
961 }
962 
NotifySubtreeGainedFocus()963 void DisplayLockContext::NotifySubtreeGainedFocus() {
964   SetRenderAffectingState(RenderAffectingState::kSubtreeHasFocus, true);
965 }
966 
DetermineIfSubtreeHasFocus()967 void DisplayLockContext::DetermineIfSubtreeHasFocus() {
968   if (!ConnectedToView()) {
969     SetRenderAffectingState(RenderAffectingState::kSubtreeHasFocus, false);
970     return;
971   }
972 
973   bool subtree_has_focus = false;
974   // Iterate up the ancestor chain from the currently focused element. If at any
975   // time we find our element, then our subtree is focused.
976   for (auto* focused = document_->FocusedElement(); focused;
977        focused = FlatTreeTraversal::ParentElement(*focused)) {
978     if (focused == element_.Get()) {
979       subtree_has_focus = true;
980       break;
981     }
982   }
983   SetRenderAffectingState(RenderAffectingState::kSubtreeHasFocus,
984                           subtree_has_focus);
985 }
986 
NotifySubtreeGainedSelection()987 void DisplayLockContext::NotifySubtreeGainedSelection() {
988   SetRenderAffectingState(RenderAffectingState::kSubtreeHasSelection, true);
989 }
990 
NotifySubtreeLostSelection()991 void DisplayLockContext::NotifySubtreeLostSelection() {
992   SetRenderAffectingState(RenderAffectingState::kSubtreeHasSelection, false);
993 }
994 
DetermineIfSubtreeHasSelection()995 void DisplayLockContext::DetermineIfSubtreeHasSelection() {
996   if (!ConnectedToView() || !document_->GetFrame()) {
997     SetRenderAffectingState(RenderAffectingState::kSubtreeHasSelection, false);
998     return;
999   }
1000 
1001   auto range = ToEphemeralRangeInFlatTree(document_->GetFrame()
1002                                               ->Selection()
1003                                               .GetSelectionInDOMTree()
1004                                               .ComputeRange());
1005   bool subtree_has_selection = false;
1006   for (auto& node : range.Nodes()) {
1007     for (auto& ancestor : FlatTreeTraversal::InclusiveAncestorsOf(node)) {
1008       if (&ancestor == element_.Get()) {
1009         subtree_has_selection = true;
1010         break;
1011       }
1012     }
1013     if (subtree_has_selection)
1014       break;
1015   }
1016   SetRenderAffectingState(RenderAffectingState::kSubtreeHasSelection,
1017                           subtree_has_selection);
1018 }
1019 
SetRenderAffectingState(RenderAffectingState state,bool new_flag)1020 void DisplayLockContext::SetRenderAffectingState(RenderAffectingState state,
1021                                                  bool new_flag) {
1022   // If we have forced activatable locks, it is possible that we're within
1023   // find-in-page. We cannot lock an object while doing this, since it may
1024   // invalidate layout and in turn prevent find-in-page from properly finding
1025   // text (and DCHECK). Since layout is clean for this lock (we're unlocked),
1026   // keep the context unlocked until the next lifecycle starts.
1027   if (state == RenderAffectingState::kSubtreeHasSelection && !new_flag &&
1028       document_->GetDisplayLockDocumentState()
1029           .ActivatableDisplayLocksForced()) {
1030     SetKeepUnlockedUntilLifecycleCount(1);
1031   }
1032 
1033   render_affecting_state_[static_cast<int>(state)] = new_flag;
1034   NotifyRenderAffectingStateChanged();
1035 }
1036 
NotifyRenderAffectingStateChanged()1037 void DisplayLockContext::NotifyRenderAffectingStateChanged() {
1038   auto state = [this](RenderAffectingState state) {
1039     return render_affecting_state_[static_cast<int>(state)];
1040   };
1041 
1042   // Check that we're visible if and only if lock has not been requested.
1043   DCHECK(state_ == EContentVisibility::kVisible ||
1044          state(RenderAffectingState::kLockRequested));
1045   DCHECK(state_ != EContentVisibility::kVisible ||
1046          !state(RenderAffectingState::kLockRequested));
1047 
1048   // We should be locked if the lock has been requested (the above DCHECKs
1049   // verify that this means that we are not 'visible'), and any of the
1050   // following is true:
1051   // - We are not in 'auto' mode (meaning 'hidden') or
1052   // - We are in 'auto' mode and nothing blocks locking: viewport is
1053   //   not intersecting, subtree doesn't have focus, and subtree doesn't have
1054   //   selection.
1055   bool should_be_locked =
1056       state(RenderAffectingState::kLockRequested) &&
1057       (state_ != EContentVisibility::kAuto ||
1058        (!state(RenderAffectingState::kIntersectsViewport) &&
1059         !state(RenderAffectingState::kSubtreeHasFocus) &&
1060         !state(RenderAffectingState::kSubtreeHasSelection) &&
1061         !state(RenderAffectingState::kAutoStateUnlockedUntilLifecycle)));
1062 
1063   if (should_be_locked && !IsLocked())
1064     Lock();
1065   else if (!should_be_locked && IsLocked())
1066     Unlock();
1067 }
1068 
Trace(Visitor * visitor) const1069 void DisplayLockContext::Trace(Visitor* visitor) const {
1070   visitor->Trace(element_);
1071   visitor->Trace(document_);
1072   visitor->Trace(whitespace_reattach_set_);
1073 }
1074 
RenderAffectingStateName(int state) const1075 const char* DisplayLockContext::RenderAffectingStateName(int state) const {
1076   switch (static_cast<RenderAffectingState>(state)) {
1077     case RenderAffectingState::kLockRequested:
1078       return "LockRequested";
1079     case RenderAffectingState::kIntersectsViewport:
1080       return "IntersectsViewport";
1081     case RenderAffectingState::kSubtreeHasFocus:
1082       return "SubtreeHasFocus";
1083     case RenderAffectingState::kSubtreeHasSelection:
1084       return "SubtreeHasSelection";
1085     case RenderAffectingState::kAutoStateUnlockedUntilLifecycle:
1086       return "AutoStateUnlockedUntilLifecycle";
1087     case RenderAffectingState::kNumRenderAffectingStates:
1088       break;
1089   }
1090   return "<Invalid State>";
1091 }
1092 
RenderAffectingStateToString() const1093 String DisplayLockContext::RenderAffectingStateToString() const {
1094   StringBuilder builder;
1095   for (int i = 0;
1096        i < static_cast<int>(RenderAffectingState::kNumRenderAffectingStates);
1097        ++i) {
1098     builder.Append(RenderAffectingStateName(i));
1099     builder.Append(": ");
1100     builder.Append(render_affecting_state_[i] ? "true" : "false");
1101     builder.Append("\n");
1102   }
1103   return builder.ToString();
1104 }
1105 
1106 }  // namespace blink
1107