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