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 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_DISPLAY_LOCK_DISPLAY_LOCK_CONTEXT_H_
6 #define THIRD_PARTY_BLINK_RENDERER_CORE_DISPLAY_LOCK_DISPLAY_LOCK_CONTEXT_H_
7 
8 #include "third_party/blink/renderer/core/core_export.h"
9 #include "third_party/blink/renderer/core/frame/local_frame_view.h"
10 #include "third_party/blink/renderer/core/style/computed_style_base_constants.h"
11 #include "third_party/blink/renderer/platform/scheduler/public/post_cancellable_task.h"
12 #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
13 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
14 
15 namespace blink {
16 
17 class Document;
18 class Element;
19 class StyleRecalcChange;
20 
21 enum class DisplayLockActivationReason {
22   // Accessibility driven activation
23   kAccessibility = 1 << 0,
24   // Activation as a result of find-in-page
25   kFindInPage = 1 << 1,
26   // Fragment link navigation
27   kFragmentNavigation = 1 << 2,
28   // Script invoked focus().
29   kScriptFocus = 1 << 3,
30   // scrollIntoView()
31   kScrollIntoView = 1 << 4,
32   // User / script selection
33   kSelection = 1 << 5,
34   // Simulated click (Node::DispatchSimulatedClick)
35   kSimulatedClick = 1 << 6,
36   // User focus (e.g. tab navigation)
37   kUserFocus = 1 << 7,
38   // Intersection observer activation
39   kViewportIntersection = 1 << 8,
40 
41   // Shorthands
42   kViewport = static_cast<uint16_t>(kSelection) |
43               static_cast<uint16_t>(kUserFocus) |
44               static_cast<uint16_t>(kViewportIntersection) |
45               static_cast<uint16_t>(kAccessibility),
46   kAny = static_cast<uint16_t>(kAccessibility) |
47          static_cast<uint16_t>(kFindInPage) |
48          static_cast<uint16_t>(kFragmentNavigation) |
49          static_cast<uint16_t>(kScriptFocus) |
50          static_cast<uint16_t>(kScrollIntoView) |
51          static_cast<uint16_t>(kSelection) |
52          static_cast<uint16_t>(kSimulatedClick) |
53          static_cast<uint16_t>(kUserFocus) |
54          static_cast<uint16_t>(kViewportIntersection)
55 };
56 
57 // Instead of specifying an underlying type, which would propagate throughout
58 // forward declarations, we static assert that the activation reasons enum is
59 // small-ish.
60 static_assert(static_cast<uint32_t>(DisplayLockActivationReason::kAny) <
61                   std::numeric_limits<uint16_t>::max(),
62               "DisplayLockActivationReason is too large");
63 
64 class CORE_EXPORT DisplayLockContext final
65     : public GarbageCollected<DisplayLockContext>,
66       public LocalFrameView::LifecycleNotificationObserver {
67  public:
68   // The type of style that was blocked by this display lock.
69   enum StyleType {
70     kStyleUpdateNotRequired,
71     kStyleUpdateSelf,
72     kStyleUpdatePseudoElements,
73     kStyleUpdateChildren,
74     kStyleUpdateDescendants
75   };
76 
77   explicit DisplayLockContext(Element*);
78   ~DisplayLockContext() = default;
79 
80   // Called by style to update the current state of content-visibility.
81   void SetRequestedState(EContentVisibility state);
82   // Called by style to adjust the element's style based on the current state.
83   void AdjustElementStyle(ComputedStyle* style) const;
84 
85   // Is called by the intersection observer callback to inform us of the
86   // intersection state.
87   void NotifyIsIntersectingViewport();
88   void NotifyIsNotIntersectingViewport();
89 
90   // Lifecycle state functions.
91   bool ShouldStyleChildren() const;
92   void DidStyleSelf();
93   void DidStyleChildren();
94   bool ShouldLayoutChildren() const;
95   void DidLayoutChildren();
96   bool ShouldPrePaintChildren() const;
97   bool ShouldPaintChildren() const;
98 
99   // Returns true if the last style recalc traversal was blocked at this
100   // element, either for itself, its children or its descendants.
StyleTraversalWasBlocked()101   bool StyleTraversalWasBlocked() {
102     return blocked_style_traversal_type_ != kStyleUpdateNotRequired;
103   }
104 
105   // Returns true if the contents of the associated element should be visible
106   // from and activatable by a specified reason. Note that passing
107   // kAny will return true if the lock is activatable for any
108   // reason.
109   bool IsActivatable(DisplayLockActivationReason reason) const;
110 
111   // Trigger commit because of activation from tab order, url fragment,
112   // find-in-page, scrolling, etc.
113   // This issues a before activate signal with the given element as the
114   // activated element.
115   // The reason is specified for metrics.
116   void CommitForActivationWithSignal(Element* activated_element,
117                                      DisplayLockActivationReason reason);
118 
119   bool ShouldCommitForActivation(DisplayLockActivationReason reason) const;
120 
121   // Returns true if this context is locked.
IsLocked()122   bool IsLocked() const { return is_locked_; }
123 
GetState()124   EContentVisibility GetState() { return state_; }
125 
UpdateForced()126   bool UpdateForced() const { return update_forced_; }
127 
128   // This is called when the element with which this context is associated is
129   // moved to a new document. Used to listen to the lifecycle update from the
130   // right document's view.
131   void DidMoveToNewDocument(Document& old_document);
132 
133   void AddToWhitespaceReattachSet(Element& element);
134 
135   // LifecycleNotificationObserver overrides.
136   void WillStartLifecycleUpdate(const LocalFrameView&) override;
137 
138   // Inform the display lock that it prevented a style change. This is used to
139   // invalidate style when we need to update it in the future.
NotifyStyleRecalcWasBlocked(StyleType type)140   void NotifyStyleRecalcWasBlocked(StyleType type) {
141     blocked_style_traversal_type_ =
142         std::max(blocked_style_traversal_type_, type);
143   }
144 
NotifyReattachLayoutTreeWasBlocked()145   void NotifyReattachLayoutTreeWasBlocked() {
146     reattach_layout_tree_was_blocked_ = true;
147   }
148 
NotifyChildLayoutWasBlocked()149   void NotifyChildLayoutWasBlocked() { child_layout_was_blocked_ = true; }
150 
NotifyCompositingRequirementsUpdateWasBlocked()151   void NotifyCompositingRequirementsUpdateWasBlocked() {
152     needs_compositing_requirements_update_ = true;
153   }
NotifyCompositingDescendantDependentFlagUpdateWasBlocked()154   void NotifyCompositingDescendantDependentFlagUpdateWasBlocked() {
155     needs_compositing_dependent_flag_update_ = true;
156   }
157 
NotifyGraphicsLayerRebuildBlocked()158   void NotifyGraphicsLayerRebuildBlocked() {
159     DCHECK(!RuntimeEnabledFeatures::CompositeAfterPaintEnabled());
160     needs_graphics_layer_rebuild_ = true;
161   }
162 
NotifyForcedGraphicsLayerUpdateBlocked()163   void NotifyForcedGraphicsLayerUpdateBlocked() {
164     forced_graphics_layer_update_blocked_ = true;
165   }
166 
167   // Notify this element will be disconnected.
168   void NotifyWillDisconnect();
169 
170   // Called when the element disconnects or connects.
171   void ElementDisconnected();
172   void ElementConnected();
173 
174   void NotifySubtreeLostFocus();
175   void NotifySubtreeGainedFocus();
176 
177   void NotifySubtreeLostSelection();
178   void NotifySubtreeGainedSelection();
179 
SetNeedsPrePaintSubtreeWalk(bool needs_effective_allowed_touch_action_update,bool needs_blocking_wheel_event_handler_update)180   void SetNeedsPrePaintSubtreeWalk(
181       bool needs_effective_allowed_touch_action_update,
182       bool needs_blocking_wheel_event_handler_update) {
183     needs_effective_allowed_touch_action_update_ =
184         needs_effective_allowed_touch_action_update;
185     needs_blocking_wheel_event_handler_update_ =
186         needs_blocking_wheel_event_handler_update;
187     needs_prepaint_subtree_walk_ = true;
188   }
189 
190   // This is called by the style recalc code in lieu of
191   // MarkForStyleRecalcIfNeeded() in order to adjust the child change if we need
192   // to recalc children nodes here.
193   StyleRecalcChange AdjustStyleRecalcChangeForChildren(
194       StyleRecalcChange change);
195 
DidForceActivatableDisplayLocks()196   void DidForceActivatableDisplayLocks() {
197     if (IsLocked() && IsActivatable(DisplayLockActivationReason::kAny)) {
198       MarkForStyleRecalcIfNeeded();
199       MarkForLayoutIfNeeded();
200     }
201   }
202 
HadAnyViewportIntersectionNotifications()203   bool HadAnyViewportIntersectionNotifications() const {
204     return had_any_viewport_intersection_notifications_;
205   }
206 
207   // GC functions.
208   void Trace(Visitor*) const override;
209 
210   // Debugging functions.
211   String RenderAffectingStateToString() const;
212 
IsAuto()213   bool IsAuto() const { return state_ == EContentVisibility::kAuto; }
HadLifecycleUpdateSinceLastUnlock()214   bool HadLifecycleUpdateSinceLastUnlock() const {
215     return had_lifecycle_update_since_last_unlock_;
216   }
217 
218  private:
219   // Give access to |NotifyForcedUpdateScopeStarted()| and
220   // |NotifyForcedUpdateScopeEnded()|.
221   friend class DisplayLockUtilities;
222 
223   // Test friends.
224   friend class DisplayLockContextRenderingTest;
225   friend class DisplayLockContextTest;
226 
227   // Request that this context be locked. Called when style determines that the
228   // subtree rooted at this element should be skipped, unless things like
229   // viewport intersection prevent it from doing so.
230   void RequestLock(uint16_t activation_mask);
231   // Request that this context be unlocked. Called when style determines that
232   // the subtree rooted at this element should be rendered.
233   void RequestUnlock();
234 
235   // Called in |DisplayLockUtilities| to notify the state of scope.
236   void NotifyForcedUpdateScopeStarted();
237   void NotifyForcedUpdateScopeEnded();
238 
239   // Records the locked context counts on the document as well as context that
240   // block all activation.
241   void UpdateDocumentBookkeeping(bool was_locked,
242                                  bool all_activation_was_blocked,
243                                  bool is_locked,
244                                  bool all_activation_is_blocked);
245 
246   // Set which reasons activate, as a mask of DisplayLockActivationReason enums.
247   void UpdateActivationMask(uint16_t activatable_mask);
248 
249   // Clear the activated flag.
250   void ResetActivation();
251 
252   // Marks ancestors of elements in |whitespace_reattach_set_| with
253   // ChildNeedsReattachLayoutTree and clears the set.
254   void MarkElementsForWhitespaceReattachment();
255 
256   // The following functions propagate dirty bits from the locked element up to
257   // the ancestors in order to be reached, and update dirty bits for the element
258   // as well if needed. They return true if the element or its subtree were
259   // dirty, and false otherwise.
260   bool MarkForStyleRecalcIfNeeded();
261   bool MarkForLayoutIfNeeded();
262   bool MarkAncestorsForPrePaintIfNeeded();
263   bool MarkNeedsRepaintAndPaintArtifactCompositorUpdate();
264   bool MarkForCompositingUpdatesIfNeeded();
265 
266   bool IsElementDirtyForStyleRecalc() const;
267   bool IsElementDirtyForLayout() const;
268   bool IsElementDirtyForPrePaint() const;
269 
270   // Helper to schedule an animation to delay lifecycle updates for the next
271   // frame.
272   void ScheduleAnimation();
273 
274   // Checks whether we should force unlock the lock (due to not meeting
275   // containment/display requirements), returns a string from rejection_names
276   // if we should, nullptr if not. Note that this can only be called if the
277   // style is clean. It checks the layout object if it exists. Otherwise,
278   // falls back to checking computed style.
279   const char* ShouldForceUnlock() const;
280 
281   // Unlocks the lock if the element doesn't meet requirements
282   // (containment/display type). Returns true if we did unlock.
283   bool ForceUnlockIfNeeded();
284 
285   // Returns true if the element is connected to a document that has a view.
286   // If we're not connected,  or if we're connected but the document doesn't
287   // have a view (e.g. templates) we shouldn't do style calculations etc and
288   // when acquiring this lock should immediately resolve the acquire promise.
289   bool ConnectedToView() const;
290 
291   // Registers or unregisters the element for intersection observations in the
292   // document. This is used to activate on visibily changes. This can be safely
293   // called even if changes are not required, since it will only act if a
294   // register/unregister is required.
295   void UpdateActivationObservationIfNeeded();
296 
297   // Determines whether or not we need lifecycle notifications.
298   bool NeedsLifecycleNotifications() const;
299   // Updates the lifecycle notification registration based on whether we need
300   // the notifications.
301   void UpdateLifecycleNotificationRegistration();
302 
303   // Locks the context.
304   void Lock();
305   // Unlocks the context.
306   void Unlock();
307 
308   // Determines if the subtree has focus. This is a linear walk from the focused
309   // element to its root element.
310   void DetermineIfSubtreeHasFocus();
311 
312   // Determines if the subtree has selection. This will walk from each of the
313   // selected notes up to its root looking for `element_`.
314   void DetermineIfSubtreeHasSelection();
315 
316   // Keep this context unlocked until the beginning of lifecycle. Effectively
317   // keeps this context unlocked for the next `count` frames. It also schedules
318   // a frame to ensure the lifecycle happens. Only affects locks with 'auto'
319   // setting.
320   void SetKeepUnlockedUntilLifecycleCount(int count);
321 
322   WeakMember<Element> element_;
323   WeakMember<Document> document_;
324   EContentVisibility state_ = EContentVisibility::kVisible;
325 
326   // See StyleEngine's |whitespace_reattach_set_|.
327   // Set of elements that had at least one rendered children removed
328   // since its last lifecycle update. For such elements that are located
329   // in a locked subtree, we save it here instead of the global set in
330   // StyleEngine because we don't want to accidentally mark elements
331   // in a locked subtree for layout tree reattachment before we did
332   // style recalc on them.
333   HeapHashSet<Member<Element>> whitespace_reattach_set_;
334 
335   // If non-zero, then the update has been forced.
336   int update_forced_ = 0;
337 
338   StyleType blocked_style_traversal_type_ = kStyleUpdateNotRequired;
339   // Signifies whether we've blocked a layout tree reattachment on |element_|'s
340   // descendants or not, so that we can mark |element_| for reattachment when
341   // needed.
342   bool reattach_layout_tree_was_blocked_ = false;
343 
344   bool needs_effective_allowed_touch_action_update_ = false;
345   bool needs_blocking_wheel_event_handler_update_ = false;
346   bool needs_prepaint_subtree_walk_ = false;
347   bool needs_compositing_requirements_update_ = false;
348   bool needs_compositing_dependent_flag_update_ = false;
349 
350   // Will be true if child traversal was blocked on a previous layout run on the
351   // locked element. We need to keep track of this to ensure that on the next
352   // layout run where the descendants of the locked element are allowed to be
353   // traversed into, we will traverse to the children of the locked element.
354   bool child_layout_was_blocked_ = false;
355 
356   // Tracks whether the element associated with this lock is being tracked by a
357   // document level intersection observer.
358   bool is_observed_ = false;
359 
360   uint16_t activatable_mask_ =
361       static_cast<uint16_t>(DisplayLockActivationReason::kAny);
362 
363   // Is set to true if we are registered for lifecycle notifications.
364   bool is_registered_for_lifecycle_notifications_ = false;
365 
366   // This is set to true when we have delayed locking ourselves due to viewport
367   // intersection (or lack thereof) because we were nested in a locked subtree.
368   // In that case, we register for lifecycle notifications and check every time
369   // if we are still nested.
370   bool needs_deferred_not_intersecting_signal_ = false;
371 
372   // Lock has been requested.
373   bool is_locked_ = false;
374 
375   // If true, this lock is kept unlocked at least until the beginning of the
376   // lifecycle. If nothing else is keeping it unlocked, then it will be locked
377   // again at the start of the lifecycle.
378   bool keep_unlocked_until_lifecycle_ = false;
379 
380   bool needs_graphics_layer_rebuild_ = false;
381 
382   bool forced_graphics_layer_update_blocked_ = false;
383 
384   // This is set to true if we're in the 'auto' mode and had our first
385   // intersection / non-intersection notification. This is reset to false if the
386   // 'auto' mode is added again (after being removed).
387   bool had_any_viewport_intersection_notifications_ = false;
388 
389   enum class RenderAffectingState : int {
390     kLockRequested,
391     kIntersectsViewport,
392     kSubtreeHasFocus,
393     kSubtreeHasSelection,
394     kAutoStateUnlockedUntilLifecycle,
395     kNumRenderAffectingStates
396   };
397   void SetRenderAffectingState(RenderAffectingState state, bool flag);
398   void NotifyRenderAffectingStateChanged();
399   const char* RenderAffectingStateName(int state) const;
400 
401   bool render_affecting_state_[static_cast<int>(
402       RenderAffectingState::kNumRenderAffectingStates)] = {false};
403   int keep_unlocked_count_ = 0;
404 
405   bool had_lifecycle_update_since_last_unlock_ = false;
406 };
407 
408 }  // namespace blink
409 
410 #endif  // THIRD_PARTY_BLINK_RENDERER_CORE_DISPLAY_LOCK_DISPLAY_LOCK_CONTEXT_H_
411