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