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