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