| /* |
| * Copyright (C) 1998, 1999 Torben Weis <weis@kde.org> |
| * 1999 Lars Knoll <knoll@kde.org> |
| * 1999 Antti Koivisto <koivisto@kde.org> |
| * 2000 Dirk Mueller <mueller@kde.org> |
| * Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. |
| * (C) 2006 Graham Dennis (graham.dennis@gmail.com) |
| * (C) 2006 Alexey Proskuryakov (ap@nypop.com) |
| * Copyright (C) 2009 Google Inc. All rights reserved. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public License |
| * along with this library; see the file COPYING.LIB. If not, write to |
| * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| #include "third_party/blink/renderer/core/frame/local_frame_view.h" |
| |
| #include <algorithm> |
| #include <memory> |
| #include <utility> |
| |
| #include "base/callback.h" |
| #include "base/feature_list.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/metrics/field_trial_params.h" |
| #include "base/numerics/safe_conversions.h" |
| #include "base/timer/lap_timer.h" |
| #include "cc/animation/animation_host.h" |
| #include "cc/input/main_thread_scrolling_reason.h" |
| #include "cc/layers/picture_layer.h" |
| #include "cc/tiles/frame_viewer_instrumentation.h" |
| #include "cc/trees/layer_tree_host.h" |
| #include "components/paint_preview/common/paint_preview_tracker.h" |
| #include "third_party/blink/public/common/features.h" |
| #include "third_party/blink/public/mojom/scroll/scroll_into_view_params.mojom-blink.h" |
| #include "third_party/blink/public/mojom/scroll/scrollbar_mode.mojom-blink.h" |
| #include "third_party/blink/public/platform/task_type.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_scroll_into_view_options.h" |
| #include "third_party/blink/renderer/core/accessibility/ax_object_cache.h" |
| #include "third_party/blink/renderer/core/animation/document_animations.h" |
| #include "third_party/blink/renderer/core/css/font_face_set_document.h" |
| #include "third_party/blink/renderer/core/css/style_change_reason.h" |
| #include "third_party/blink/renderer/core/display_lock/display_lock_utilities.h" |
| #include "third_party/blink/renderer/core/document_transition/document_transition_supplement.h" |
| #include "third_party/blink/renderer/core/dom/static_node_list.h" |
| #include "third_party/blink/renderer/core/editing/compute_layer_selection.h" |
| #include "third_party/blink/renderer/core/editing/drag_caret.h" |
| #include "third_party/blink/renderer/core/editing/frame_selection.h" |
| #include "third_party/blink/renderer/core/editing/markers/document_marker_controller.h" |
| #include "third_party/blink/renderer/core/events/error_event.h" |
| #include "third_party/blink/renderer/core/exported/web_plugin_container_impl.h" |
| #include "third_party/blink/renderer/core/frame/browser_controls.h" |
| #include "third_party/blink/renderer/core/frame/find_in_page.h" |
| #include "third_party/blink/renderer/core/frame/frame_overlay.h" |
| #include "third_party/blink/renderer/core/frame/frame_view_auto_size_info.h" |
| #include "third_party/blink/renderer/core/frame/local_dom_window.h" |
| #include "third_party/blink/renderer/core/frame/local_frame.h" |
| #include "third_party/blink/renderer/core/frame/local_frame_client.h" |
| #include "third_party/blink/renderer/core/frame/local_frame_ukm_aggregator.h" |
| #include "third_party/blink/renderer/core/frame/location.h" |
| #include "third_party/blink/renderer/core/frame/page_scale_constraints_set.h" |
| #include "third_party/blink/renderer/core/frame/remote_frame.h" |
| #include "third_party/blink/renderer/core/frame/remote_frame_view.h" |
| #include "third_party/blink/renderer/core/frame/root_frame_viewport.h" |
| #include "third_party/blink/renderer/core/frame/settings.h" |
| #include "third_party/blink/renderer/core/frame/visual_viewport.h" |
| #include "third_party/blink/renderer/core/frame/web_local_frame_impl.h" |
| #include "third_party/blink/renderer/core/fullscreen/fullscreen.h" |
| #include "third_party/blink/renderer/core/html/forms/text_control_element.h" |
| #include "third_party/blink/renderer/core/html/html_frame_element.h" |
| #include "third_party/blink/renderer/core/html/html_plugin_element.h" |
| #include "third_party/blink/renderer/core/html/media/html_video_element.h" |
| #include "third_party/blink/renderer/core/html/parser/text_resource_decoder.h" |
| #include "third_party/blink/renderer/core/html/portal/document_portals.h" |
| #include "third_party/blink/renderer/core/html/portal/html_portal_element.h" |
| #include "third_party/blink/renderer/core/html/portal/portal_contents.h" |
| #include "third_party/blink/renderer/core/html_names.h" |
| #include "third_party/blink/renderer/core/input/event_handler.h" |
| #include "third_party/blink/renderer/core/inspector/inspector_trace_events.h" |
| #include "third_party/blink/renderer/core/intersection_observer/intersection_observation.h" |
| #include "third_party/blink/renderer/core/intersection_observer/intersection_observer_controller.h" |
| #include "third_party/blink/renderer/core/layout/adjust_for_absolute_zoom.h" |
| #include "third_party/blink/renderer/core/layout/geometry/transform_state.h" |
| #include "third_party/blink/renderer/core/layout/layout_analyzer.h" |
| #include "third_party/blink/renderer/core/layout/layout_counter.h" |
| #include "third_party/blink/renderer/core/layout/layout_embedded_content.h" |
| #include "third_party/blink/renderer/core/layout/layout_embedded_object.h" |
| #include "third_party/blink/renderer/core/layout/layout_shift_tracker.h" |
| #include "third_party/blink/renderer/core/layout/layout_view.h" |
| #include "third_party/blink/renderer/core/layout/ng/legacy_layout_tree_walking.h" |
| #include "third_party/blink/renderer/core/layout/style_retain_scope.h" |
| #include "third_party/blink/renderer/core/layout/svg/layout_svg_root.h" |
| #include "third_party/blink/renderer/core/layout/text_autosizer.h" |
| #include "third_party/blink/renderer/core/layout/traced_layout_object.h" |
| #include "third_party/blink/renderer/core/loader/document_loader.h" |
| #include "third_party/blink/renderer/core/loader/frame_loader.h" |
| #include "third_party/blink/renderer/core/media_type_names.h" |
| #include "third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker.h" |
| #include "third_party/blink/renderer/core/page/autoscroll_controller.h" |
| #include "third_party/blink/renderer/core/page/chrome_client.h" |
| #include "third_party/blink/renderer/core/page/focus_controller.h" |
| #include "third_party/blink/renderer/core/page/frame_tree.h" |
| #include "third_party/blink/renderer/core/page/link_highlight.h" |
| #include "third_party/blink/renderer/core/page/page.h" |
| #include "third_party/blink/renderer/core/page/scrolling/fragment_anchor.h" |
| #include "third_party/blink/renderer/core/page/scrolling/scrolling_coordinator.h" |
| #include "third_party/blink/renderer/core/page/scrolling/scrolling_coordinator_context.h" |
| #include "third_party/blink/renderer/core/page/scrolling/snap_coordinator.h" |
| #include "third_party/blink/renderer/core/page/scrolling/top_document_root_scroller_controller.h" |
| #include "third_party/blink/renderer/core/page/spatial_navigation_controller.h" |
| #include "third_party/blink/renderer/core/page/validation_message_client.h" |
| #include "third_party/blink/renderer/core/paint/block_paint_invalidator.h" |
| #include "third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.h" |
| #include "third_party/blink/renderer/core/paint/compositing/compositing_inputs_updater.h" |
| #include "third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.h" |
| #include "third_party/blink/renderer/core/paint/cull_rect_updater.h" |
| #include "third_party/blink/renderer/core/paint/frame_painter.h" |
| #include "third_party/blink/renderer/core/paint/paint_layer.h" |
| #include "third_party/blink/renderer/core/paint/paint_layer_painter.h" |
| #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h" |
| #include "third_party/blink/renderer/core/paint/paint_timing.h" |
| #include "third_party/blink/renderer/core/paint/paint_timing_detector.h" |
| #include "third_party/blink/renderer/core/paint/pre_paint_tree_walk.h" |
| #include "third_party/blink/renderer/core/probe/core_probes.h" |
| #include "third_party/blink/renderer/core/resize_observer/resize_observer_controller.h" |
| #include "third_party/blink/renderer/core/scroll/scroll_alignment.h" |
| #include "third_party/blink/renderer/core/scroll/scroll_animator_base.h" |
| #include "third_party/blink/renderer/core/scroll/smooth_scroll_sequencer.h" |
| #include "third_party/blink/renderer/core/style/computed_style.h" |
| #include "third_party/blink/renderer/core/svg/svg_document_extensions.h" |
| #include "third_party/blink/renderer/core/svg/svg_svg_element.h" |
| #include "third_party/blink/renderer/platform/bindings/script_forbidden_scope.h" |
| #include "third_party/blink/renderer/platform/fonts/font_cache.h" |
| #include "third_party/blink/renderer/platform/geometry/double_rect.h" |
| #include "third_party/blink/renderer/platform/geometry/float_quad.h" |
| #include "third_party/blink/renderer/platform/geometry/float_rect.h" |
| #include "third_party/blink/renderer/platform/geometry/layout_rect.h" |
| #include "third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.h" |
| #include "third_party/blink/renderer/platform/graphics/graphics_context.h" |
| #include "third_party/blink/renderer/platform/graphics/graphics_layer.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/cull_rect.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/drawing_recorder.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/paint_chunk_subset_recorder.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/paint_controller.h" |
| #include "third_party/blink/renderer/platform/instrumentation/histogram.h" |
| #include "third_party/blink/renderer/platform/instrumentation/resource_coordinator/document_resource_coordinator.h" |
| #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h" |
| #include "third_party/blink/renderer/platform/instrumentation/tracing/traced_value.h" |
| #include "third_party/blink/renderer/platform/language.h" |
| #include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h" |
| #include "third_party/blink/renderer/platform/runtime_enabled_features.h" |
| #include "third_party/blink/renderer/platform/scheduler/public/frame_scheduler.h" |
| #include "third_party/blink/renderer/platform/web_test_support.h" |
| #include "third_party/blink/renderer/platform/wtf/std_lib_extras.h" |
| #include "third_party/skia/include/core/SkBitmap.h" |
| #include "ui/base/cursor/cursor.h" |
| #include "ui/base/cursor/mojom/cursor_type.mojom-blink.h" |
| |
| // Used to check for dirty layouts violating document lifecycle rules. |
| // If arg evaluates to true, the program will continue. If arg evaluates to |
| // false, program will crash if DCHECK_IS_ON() or return false from the current |
| // function. |
| #define CHECK_FOR_DIRTY_LAYOUT(arg) \ |
| do { \ |
| if (!(arg)) { \ |
| NOTREACHED(); \ |
| return false; \ |
| } \ |
| } while (false) |
| |
| namespace blink { |
| namespace { |
| |
| // Logs a UseCounter for the size of the cursor that will be set. This will be |
| // used for compatibility analysis to determine whether the maximum size can be |
| // reduced. |
| void LogCursorSizeCounter(LocalFrame* frame, const ui::Cursor& cursor) { |
| DCHECK(frame); |
| SkBitmap bitmap = cursor.custom_bitmap(); |
| if (cursor.type() != ui::mojom::blink::CursorType::kCustom || bitmap.isNull()) |
| return; |
| // Should not overflow, this calculation is done elsewhere when determining |
| // whether the cursor exceeds its maximum size (see event_handler.cc). |
| auto scaled_size = IntSize(bitmap.width(), bitmap.height()); |
| scaled_size.Scale(1 / cursor.image_scale_factor()); |
| if (scaled_size.Width() > 64 || scaled_size.Height() > 64) { |
| UseCounter::Count(frame->GetDocument(), WebFeature::kCursorImageGT64x64); |
| } else if (scaled_size.Width() > 32 || scaled_size.Height() > 32) { |
| UseCounter::Count(frame->GetDocument(), WebFeature::kCursorImageGT32x32); |
| } else { |
| UseCounter::Count(frame->GetDocument(), WebFeature::kCursorImageLE32x32); |
| } |
| } |
| |
| // Default value for how long we want to delay the |
| // compositor commit beyond the start of document lifecycle updates to avoid |
| // flash between navigations. The delay should be small enough so that it won't |
| // confuse users expecting a new page to appear after navigation and the omnibar |
| // has updated the url display. |
| constexpr int kCommitDelayDefaultInMs = 500; // 30 frames @ 60hz |
| |
| } // namespace |
| |
| // The maximum number of updatePlugins iterations that should be done before |
| // returning. |
| static const unsigned kMaxUpdatePluginsIterations = 2; |
| |
| LocalFrameView::LocalFrameView(LocalFrame& frame) |
| : LocalFrameView(frame, IntRect()) { |
| Show(); |
| } |
| |
| LocalFrameView::LocalFrameView(LocalFrame& frame, const IntSize& initial_size) |
| : LocalFrameView(frame, IntRect(IntPoint(), initial_size)) { |
| SetLayoutSizeInternal(initial_size); |
| Show(); |
| } |
| |
| LocalFrameView::LocalFrameView(LocalFrame& frame, IntRect frame_rect) |
| : FrameView(frame_rect), |
| frame_(frame), |
| can_have_scrollbars_(true), |
| has_pending_layout_(false), |
| layout_scheduling_enabled_(true), |
| layout_count_for_testing_(0), |
| lifecycle_update_count_for_testing_(0), |
| nested_layout_count_(0), |
| // We want plugin updates to happen in FIFO order with loading tasks. |
| update_plugins_timer_(frame.GetTaskRunner(TaskType::kInternalLoading), |
| this, |
| &LocalFrameView::UpdatePluginsTimerFired), |
| first_layout_(true), |
| base_background_color_(Color::kWhite), |
| last_zoom_factor_(1.0f), |
| media_type_(media_type_names::kScreen), |
| safe_to_propagate_scroll_to_parent_(true), |
| visually_non_empty_character_count_(0), |
| visually_non_empty_pixel_count_(0), |
| is_visually_non_empty_(false), |
| sticky_position_object_count_(0), |
| layout_size_fixed_to_frame_size_(true), |
| needs_update_geometries_(false), |
| root_layer_did_scroll_(false), |
| frame_timing_requests_dirty_(true), |
| // The compositor throttles the main frame using deferred begin main frame |
| // updates. We can't throttle it here or it seems the root compositor |
| // doesn't get setup properly. |
| lifecycle_updates_throttled_(!GetFrame().IsMainFrame()), |
| target_state_(DocumentLifecycle::kUninitialized), |
| suppress_adjust_view_size_(false), |
| intersection_observation_state_(kNotNeeded), |
| needs_forced_compositing_update_(false), |
| needs_focus_on_fragment_(false), |
| main_thread_scrolling_reasons_(0), |
| forced_layout_stack_depth_(0), |
| forced_layout_start_time_(base::TimeTicks()), |
| paint_frame_count_(0), |
| unique_id_(NewUniqueObjectId()), |
| layout_shift_tracker_(MakeGarbageCollected<LayoutShiftTracker>(this)), |
| paint_timing_detector_(MakeGarbageCollected<PaintTimingDetector>(this)), |
| mobile_friendliness_checker_( |
| MakeGarbageCollected<MobileFriendlinessChecker>(*this)) |
| #if DCHECK_IS_ON() |
| , |
| is_updating_descendant_dependent_flags_(false) |
| #endif |
| { |
| // Propagate the marginwidth/height and scrolling modes to the view. |
| if (frame_->Owner() && frame_->Owner()->ScrollbarMode() == |
| mojom::blink::ScrollbarMode::kAlwaysOff) |
| SetCanHaveScrollbars(false); |
| } |
| |
| LocalFrameView::~LocalFrameView() { |
| #if DCHECK_IS_ON() |
| DCHECK(has_been_disposed_); |
| #endif |
| } |
| |
| void LocalFrameView::Trace(Visitor* visitor) const { |
| visitor->Trace(frame_); |
| visitor->Trace(update_plugins_timer_); |
| visitor->Trace(fragment_anchor_); |
| visitor->Trace(scrollable_areas_); |
| visitor->Trace(animating_scrollable_areas_); |
| visitor->Trace(auto_size_info_); |
| visitor->Trace(plugins_); |
| visitor->Trace(scrollbars_); |
| visitor->Trace(viewport_scrollable_area_); |
| visitor->Trace(anchoring_adjustment_queue_); |
| visitor->Trace(scroll_event_queue_); |
| visitor->Trace(layout_shift_tracker_); |
| visitor->Trace(paint_timing_detector_); |
| visitor->Trace(mobile_friendliness_checker_); |
| visitor->Trace(lifecycle_observers_); |
| visitor->Trace(fullscreen_video_elements_); |
| } |
| |
| template <typename Function> |
| void LocalFrameView::ForAllChildViewsAndPlugins(const Function& function) { |
| for (Frame* child = frame_->Tree().FirstChild(); child; |
| child = child->Tree().NextSibling()) { |
| if (child->View()) |
| function(*child->View()); |
| } |
| |
| for (const auto& plugin : plugins_) { |
| function(*plugin); |
| } |
| |
| if (Document* document = frame_->GetDocument()) { |
| for (PortalContents* portal : |
| DocumentPortals::From(*document).GetPortals()) { |
| if (Frame* frame = portal->GetFrame()) |
| function(*frame->View()); |
| } |
| } |
| } |
| |
| template <typename Function> |
| void LocalFrameView::ForAllChildLocalFrameViews(const Function& function) { |
| for (Frame* child = frame_->Tree().FirstChild(); child; |
| child = child->Tree().NextSibling()) { |
| auto* child_local_frame = DynamicTo<LocalFrame>(child); |
| if (!child_local_frame) |
| continue; |
| if (LocalFrameView* child_view = child_local_frame->View()) |
| function(*child_view); |
| } |
| } |
| |
| // Call function for each non-throttled frame view in pre-order (by default) or |
| // post-order. If this logic is updated, consider updating |
| // |ForAllThrottledLocalFrameViews| too. |
| template <typename Function> |
| void LocalFrameView::ForAllNonThrottledLocalFrameViews(const Function& function, |
| TraversalOrder order) { |
| if (ShouldThrottleRendering()) |
| return; |
| |
| if (order == kPreOrder) |
| function(*this); |
| |
| ForAllChildLocalFrameViews([&function, order](LocalFrameView& child_view) { |
| child_view.ForAllNonThrottledLocalFrameViews(function, order); |
| }); |
| |
| if (order == kPostOrder) |
| function(*this); |
| } |
| |
| // Call function for each throttled frame view in pre-order. If this logic is |
| // updated, consider updating |ForAllNonThrottledLocalFrameViews| too. |
| template <typename Function> |
| void LocalFrameView::ForAllThrottledLocalFrameViews(const Function& function) { |
| if (ShouldThrottleRendering()) |
| function(*this); |
| |
| ForAllChildLocalFrameViews([&function](LocalFrameView& child_view) { |
| child_view.ForAllThrottledLocalFrameViews(function); |
| }); |
| } |
| |
| void LocalFrameView::ForAllThrottledLocalFrameViewsForTesting( |
| base::RepeatingCallback<void(LocalFrameView&)> callback) { |
| AllowThrottlingScope allow_throttling(*this); |
| ForAllThrottledLocalFrameViews( |
| [&callback](LocalFrameView& view) { callback.Run(view); }); |
| } |
| |
| template <typename Function> |
| void LocalFrameView::ForAllRemoteFrameViews(const Function& function) { |
| for (Frame* child = frame_->Tree().FirstChild(); child; |
| child = child->Tree().NextSibling()) { |
| if (child->IsLocalFrame()) { |
| To<LocalFrame>(child)->View()->ForAllRemoteFrameViews(function); |
| } else { |
| DCHECK(child->IsRemoteFrame()); |
| if (RemoteFrameView* view = To<RemoteFrame>(child)->View()) |
| function(*view); |
| } |
| } |
| if (Document* document = frame_->GetDocument()) { |
| for (PortalContents* portal : |
| DocumentPortals::From(*document).GetPortals()) { |
| if (RemoteFrame* frame = portal->GetFrame()) { |
| if (RemoteFrameView* view = frame->View()) |
| function(*view); |
| } |
| } |
| } |
| } |
| |
| void LocalFrameView::Dispose() { |
| CHECK(!IsInPerformLayout()); |
| |
| // TODO(dcheng): It's wrong that the frame can be detached before the |
| // LocalFrameView. Figure out what's going on and fix LocalFrameView to be |
| // disposed with the correct timing. |
| |
| // We need to clear the RootFrameViewport's animator since it gets called |
| // from non-GC'd objects and RootFrameViewport will still have a pointer to |
| // this class. |
| if (viewport_scrollable_area_) |
| viewport_scrollable_area_->ClearScrollableArea(); |
| |
| // If we have scheduled plugins to be updated, cancel it. They will still be |
| // notified before they are destroyed. |
| if (update_plugins_timer_.IsActive()) |
| update_plugins_timer_.Stop(); |
| part_update_set_.clear(); |
| |
| // These are LayoutObjects whose layout has been deferred to a subsequent |
| // lifecycle update. Not gonna happen. |
| layout_subtree_root_list_.Clear(); |
| |
| // TODO(szager): LayoutObjects are supposed to remove themselves from these |
| // tracking groups when they update style or are destroyed, but sometimes they |
| // are missed. It would be good to understand how/why that happens, but in the |
| // mean time, it's not safe to keep pointers around to defunct LayoutObjects. |
| orthogonal_writing_mode_root_list_.Clear(); |
| viewport_constrained_objects_.reset(); |
| background_attachment_fixed_objects_.clear(); |
| |
| // Destroy |m_autoSizeInfo| as early as possible, to avoid dereferencing |
| // partially destroyed |this| via |m_autoSizeInfo->m_frameView|. |
| auto_size_info_.Clear(); |
| |
| // FIXME: Do we need to do something here for OOPI? |
| HTMLFrameOwnerElement* owner_element = frame_->DeprecatedLocalOwner(); |
| // TODO(dcheng): It seems buggy that we can have an owner element that points |
| // to another EmbeddedContentView. This can happen when a plugin element loads |
| // a frame (EmbeddedContentView A of type LocalFrameView) and then loads a |
| // plugin (EmbeddedContentView B of type WebPluginContainerImpl). In this |
| // case, the frame's view is A and the frame element's |
| // OwnedEmbeddedContentView is B. See https://crbug.com/673170 for an example. |
| if (owner_element && owner_element->OwnedEmbeddedContentView() == this) |
| owner_element->SetEmbeddedContentView(nullptr); |
| |
| ukm_aggregator_.reset(); |
| layout_shift_tracker_->Dispose(); |
| |
| #if DCHECK_IS_ON() |
| has_been_disposed_ = true; |
| #endif |
| } |
| |
| void LocalFrameView::InvalidateAllCustomScrollbarsOnActiveChanged() { |
| bool uses_window_inactive_selector = |
| frame_->GetDocument()->GetStyleEngine().UsesWindowInactiveSelector(); |
| |
| ForAllChildLocalFrameViews([](LocalFrameView& frame_view) { |
| frame_view.InvalidateAllCustomScrollbarsOnActiveChanged(); |
| }); |
| |
| for (const auto& scrollbar : scrollbars_) { |
| if (uses_window_inactive_selector && scrollbar->IsCustomScrollbar()) |
| scrollbar->StyleChanged(); |
| } |
| } |
| |
| bool LocalFrameView::DidFirstLayout() const { |
| return !first_layout_; |
| } |
| |
| bool LocalFrameView::LifecycleUpdatesActive() const { |
| return !lifecycle_updates_throttled_; |
| } |
| |
| void LocalFrameView::SetLifecycleUpdatesThrottledForTesting(bool throttled) { |
| lifecycle_updates_throttled_ = throttled; |
| } |
| |
| void LocalFrameView::InvalidateRect(const IntRect& rect) { |
| auto* layout_object = GetLayoutEmbeddedContent(); |
| if (!layout_object) |
| return; |
| |
| IntRect paint_invalidation_rect = rect; |
| paint_invalidation_rect.Move( |
| (layout_object->BorderLeft() + layout_object->PaddingLeft()).ToInt(), |
| (layout_object->BorderTop() + layout_object->PaddingTop()).ToInt()); |
| layout_object->InvalidatePaintRectangle( |
| PhysicalRect(paint_invalidation_rect)); |
| } |
| |
| void LocalFrameView::FrameRectsChanged(const IntRect& old_rect) { |
| const bool width_changed = Size().Width() != old_rect.Width(); |
| const bool height_changed = Size().Height() != old_rect.Height(); |
| |
| PropagateFrameRects(); |
| |
| if (FrameRect() != old_rect) { |
| if (auto* layout_view = GetLayoutView()) |
| layout_view->SetShouldCheckForPaintInvalidation(); |
| } |
| |
| if (width_changed || height_changed) { |
| ViewportSizeChanged(width_changed, height_changed); |
| if (frame_->IsMainFrame()) |
| frame_->GetPage()->GetVisualViewport().MainFrameDidChangeSize(); |
| GetFrame().Loader().RestoreScrollPositionAndViewState(); |
| } |
| } |
| |
| Page* LocalFrameView::GetPage() const { |
| return GetFrame().GetPage(); |
| } |
| |
| LayoutView* LocalFrameView::GetLayoutView() const { |
| return GetFrame().ContentLayoutObject(); |
| } |
| |
| ScrollingCoordinator* LocalFrameView::GetScrollingCoordinator() const { |
| Page* p = GetPage(); |
| return p ? p->GetScrollingCoordinator() : nullptr; |
| } |
| |
| ScrollingCoordinatorContext* LocalFrameView::GetScrollingContext() const { |
| LocalFrame* root = &GetFrame().LocalFrameRoot(); |
| if (GetFrame() != root) |
| return root->View()->GetScrollingContext(); |
| |
| if (!scrolling_context_) |
| scrolling_context_.reset(new ScrollingCoordinatorContext()); |
| return scrolling_context_.get(); |
| } |
| |
| cc::AnimationHost* LocalFrameView::GetCompositorAnimationHost() const { |
| if (GetScrollingContext()->GetCompositorAnimationHost()) |
| return GetScrollingContext()->GetCompositorAnimationHost(); |
| |
| if (!GetFrame().LocalFrameRoot().IsMainFrame()) |
| return nullptr; |
| |
| // TODO(kenrb): Compositor animation host and timeline for the main frame |
| // still live on ScrollingCoordinator. https://crbug.com/680606. |
| ScrollingCoordinator* c = GetScrollingCoordinator(); |
| return c ? c->GetCompositorAnimationHost() : nullptr; |
| } |
| |
| CompositorAnimationTimeline* LocalFrameView::GetCompositorAnimationTimeline() |
| const { |
| if (GetScrollingContext()->GetCompositorAnimationTimeline()) |
| return GetScrollingContext()->GetCompositorAnimationTimeline(); |
| |
| if (!GetFrame().LocalFrameRoot().IsMainFrame()) |
| return nullptr; |
| |
| // TODO(kenrb): Compositor animation host and timeline for the main frame |
| // still live on ScrollingCoordinator. https://crbug.com/680606. |
| ScrollingCoordinator* c = GetScrollingCoordinator(); |
| return c ? c->GetCompositorAnimationTimeline() : nullptr; |
| } |
| |
| void LocalFrameView::SetLayoutOverflowSize(const IntSize& size) { |
| if (size == layout_overflow_size_) |
| return; |
| |
| layout_overflow_size_ = size; |
| |
| Page* page = GetFrame().GetPage(); |
| if (!page) |
| return; |
| page->GetChromeClient().ContentsSizeChanged(frame_.Get(), size); |
| } |
| |
| void LocalFrameView::AdjustViewSize() { |
| if (suppress_adjust_view_size_) |
| return; |
| |
| LayoutView* layout_view = GetLayoutView(); |
| if (!layout_view) |
| return; |
| |
| DCHECK_EQ(frame_->View(), this); |
| SetLayoutOverflowSize( |
| PixelSnappedIntRect(layout_view->DocumentRect()).Size()); |
| } |
| |
| void LocalFrameView::AdjustViewSizeAndLayout() { |
| AdjustViewSize(); |
| if (NeedsLayout()) { |
| base::AutoReset<bool> suppress_adjust_view_size(&suppress_adjust_view_size_, |
| true); |
| UpdateLayout(); |
| } |
| } |
| |
| void LocalFrameView::CountObjectsNeedingLayout(unsigned& needs_layout_objects, |
| unsigned& total_objects, |
| bool& is_subtree) { |
| needs_layout_objects = 0; |
| total_objects = 0; |
| is_subtree = IsSubtreeLayout(); |
| if (is_subtree) { |
| layout_subtree_root_list_.CountObjectsNeedingLayout(needs_layout_objects, |
| total_objects); |
| } else { |
| LayoutSubtreeRootList::CountObjectsNeedingLayoutInRoot( |
| GetLayoutView(), needs_layout_objects, total_objects); |
| } |
| } |
| |
| void LocalFrameView::PerformPreLayoutTasks() { |
| TRACE_EVENT0("blink,benchmark", "LocalFrameView::performPreLayoutTasks"); |
| Lifecycle().AdvanceTo(DocumentLifecycle::kInPreLayout); |
| |
| // Don't schedule more layouts, we're in one. |
| base::AutoReset<bool> change_scheduling_enabled(&layout_scheduling_enabled_, |
| false); |
| |
| bool was_resized = WasViewportResized(); |
| Document* document = frame_->GetDocument(); |
| if (was_resized) |
| document->SetResizedForViewportUnits(); |
| |
| // Viewport-dependent or device-dependent media queries may cause us to need |
| // completely different style information. |
| bool main_frame_rotation = |
| frame_->IsMainFrame() && frame_->GetSettings() && |
| frame_->GetSettings()->GetMainFrameResizesAreOrientationChanges(); |
| if ((was_resized && |
| document->GetStyleEngine().MediaQueryAffectedByViewportChange()) || |
| (was_resized && main_frame_rotation && |
| document->GetStyleEngine().MediaQueryAffectedByDeviceChange())) { |
| document->MediaQueryAffectingValueChanged(MediaValueChange::kSize); |
| } else if (was_resized) { |
| document->EvaluateMediaQueryList(); |
| } |
| |
| document->UpdateStyleAndLayoutTreeForThisDocument(); |
| |
| // Update style for all embedded SVG documents underneath this frame, so |
| // that intrinsic size computation for any embedded objects has up-to-date |
| // information before layout. |
| ForAllChildLocalFrameViews([](LocalFrameView& view) { |
| Document& document = *view.GetFrame().GetDocument(); |
| if (document.IsSVGDocument()) |
| document.UpdateStyleAndLayoutTreeForThisDocument(); |
| }); |
| |
| Lifecycle().AdvanceTo(DocumentLifecycle::kStyleClean); |
| |
| if (was_resized) { |
| document->ClearResizedForViewportUnits(); |
| |
| // Mark all of writing-mode roots for layout, as the ICB size has changed. |
| MarkOrthogonalWritingModeRootsForLayout(); |
| } |
| } |
| |
| bool LocalFrameView::LayoutFromRootObject(LayoutObject& root) { |
| if (!root.NeedsLayout()) |
| return false; |
| |
| if (auto* locked_ancestor = |
| DisplayLockUtilities::LockedAncestorPreventingLayout(root)) { |
| // Note that since we're preventing the layout on a layout root, we have to |
| // mark its ancestor chain for layout. The reason for this is that we will |
| // clear the layout roots whether or not we have finished laying them out, |
| // so the fact that this root still needs layout will be lost if we don't |
| // mark its container chain. |
| // |
| // Also, since we know that this root has a layout-blocking ancestor, the |
| // layout bit propagation will stop there. |
| // |
| // TODO(vmpstr): Note that an alternative to this approach is to keep `root` |
| // as a layout root in `layout_subtree_root_list_`. It would mean that we |
| // will keep it in the list while the display-lock prevents layout. We need |
| // to investigate which of these approaches is better. |
| root.MarkContainerChainForLayout(); |
| return false; |
| } |
| |
| LayoutState layout_state(root); |
| if (scrollable_areas_) { |
| for (auto& scrollable_area : *scrollable_areas_) { |
| if (scrollable_area->GetScrollAnchor() && |
| scrollable_area->ShouldPerformScrollAnchoring()) |
| scrollable_area->GetScrollAnchor()->NotifyBeforeLayout(); |
| } |
| } |
| |
| To<LayoutBox>(root).LayoutSubtreeRoot(); |
| return true; |
| } |
| |
| void LocalFrameView::PrepareLayoutAnalyzer() { |
| bool is_tracing = false; |
| TRACE_EVENT_CATEGORY_GROUP_ENABLED( |
| TRACE_DISABLED_BY_DEFAULT("blink.debug.layout"), &is_tracing); |
| if (!is_tracing) { |
| analyzer_.reset(); |
| return; |
| } |
| if (!analyzer_) |
| analyzer_ = std::make_unique<LayoutAnalyzer>(); |
| analyzer_->Reset(); |
| } |
| |
| std::unique_ptr<TracedValue> LocalFrameView::AnalyzerCounters() { |
| if (!analyzer_) |
| return std::make_unique<TracedValue>(); |
| std::unique_ptr<TracedValue> value = analyzer_->ToTracedValue(); |
| value->SetString("host", GetLayoutView()->GetDocument().location()->host()); |
| value->SetString( |
| "frame", |
| String::Format("0x%" PRIxPTR, reinterpret_cast<uintptr_t>(frame_.Get()))); |
| value->SetInteger( |
| "contentsHeightAfterLayout", |
| PixelSnappedIntRect(GetLayoutView()->DocumentRect()).Height()); |
| value->SetInteger("visibleHeight", Height()); |
| value->SetInteger("approximateBlankCharacterCount", |
| base::saturated_cast<int>( |
| FontFaceSetDocument::ApproximateBlankCharacterCount( |
| *frame_->GetDocument()))); |
| return value; |
| } |
| |
| #define PERFORM_LAYOUT_TRACE_CATEGORIES \ |
| "blink,benchmark,rail," TRACE_DISABLED_BY_DEFAULT("blink.debug.layout") |
| |
| void LocalFrameView::PerformLayout(bool in_subtree_layout) { |
| DCHECK(in_subtree_layout || layout_subtree_root_list_.IsEmpty()); |
| |
| double contents_height_before_layout = |
| GetLayoutView()->DocumentRect().Height(); |
| TRACE_EVENT_BEGIN1( |
| PERFORM_LAYOUT_TRACE_CATEGORIES, "LocalFrameView::performLayout", |
| "contentsHeightBeforeLayout", contents_height_before_layout); |
| PrepareLayoutAnalyzer(); |
| |
| ScriptForbiddenScope forbid_script; |
| |
| if (in_subtree_layout && HasOrthogonalWritingModeRoots()) { |
| // If we're going to lay out from each subtree root, rather than once from |
| // LayoutView, we need to merge the depth-ordered orthogonal writing mode |
| // root list into the depth-ordered list of subtrees scheduled for |
| // layout. Otherwise, during layout of one such subtree, we'd risk skipping |
| // over a subtree of objects needing layout. |
| DCHECK(!layout_subtree_root_list_.IsEmpty()); |
| ScheduleOrthogonalWritingModeRootsForLayout(); |
| } |
| |
| DCHECK(!IsInPerformLayout()); |
| Lifecycle().AdvanceTo(DocumentLifecycle::kInPerformLayout); |
| |
| // performLayout is the actual guts of layout(). |
| // FIXME: The 300 other lines in layout() probably belong in other helper |
| // functions so that a single human could understand what layout() is actually |
| // doing. |
| |
| { |
| // TODO(szager): Remove this after diagnosing crash. |
| DocumentLifecycle::CheckNoTransitionScope check_no_transition(Lifecycle()); |
| if (in_subtree_layout) { |
| if (analyzer_) { |
| analyzer_->Increment(LayoutAnalyzer::kPerformLayoutRootLayoutObjects, |
| layout_subtree_root_list_.size()); |
| } |
| for (auto& root : layout_subtree_root_list_.Ordered()) { |
| if (!LayoutFromRootObject(*root)) |
| continue; |
| |
| // We need to ensure that we mark up all layoutObjects up to the |
| // LayoutView for paint invalidation. This simplifies our code as we |
| // just always do a full tree walk. |
| if (LayoutObject* container = root->Container()) |
| container->SetShouldCheckForPaintInvalidation(); |
| } |
| layout_subtree_root_list_.Clear(); |
| } else { |
| if (HasOrthogonalWritingModeRoots()) |
| LayoutOrthogonalWritingModeRoots(); |
| GetLayoutView()->UpdateLayout(); |
| } |
| } |
| |
| frame_->GetDocument()->Fetcher()->UpdateAllImageResourcePriorities(); |
| |
| Lifecycle().AdvanceTo(DocumentLifecycle::kAfterPerformLayout); |
| |
| TRACE_EVENT_END1(PERFORM_LAYOUT_TRACE_CATEGORIES, |
| "LocalFrameView::performLayout", "counters", |
| AnalyzerCounters()); |
| FirstMeaningfulPaintDetector::From(*frame_->GetDocument()) |
| .MarkNextPaintAsMeaningfulIfNeeded( |
| layout_object_counter_, contents_height_before_layout, |
| GetLayoutView()->DocumentRect().Height(), Height()); |
| } |
| |
| void LocalFrameView::UpdateLayout() { |
| // We should never layout a Document which is not in a LocalFrame. |
| DCHECK(frame_); |
| DCHECK_EQ(frame_->View(), this); |
| DCHECK(frame_->GetPage()); |
| |
| ScriptForbiddenScope forbid_script; |
| |
| if (IsInPerformLayout() || ShouldThrottleRendering() || |
| !frame_->GetDocument()->IsActive() || frame_->IsProvisional()) |
| return; |
| |
| TRACE_EVENT0("blink,benchmark", "LocalFrameView::layout"); |
| |
| RUNTIME_CALL_TIMER_SCOPE(V8PerIsolateData::MainThreadIsolate(), |
| RuntimeCallStats::CounterId::kUpdateLayout); |
| |
| // The actual call to UpdateGeometries is in PerformPostLayoutTasks. |
| SetNeedsUpdateGeometries(); |
| |
| if (auto_size_info_) |
| auto_size_info_->AutoSizeIfNeeded(); |
| |
| has_pending_layout_ = false; |
| |
| Document* document = frame_->GetDocument(); |
| TRACE_EVENT_BEGIN1("devtools.timeline", "Layout", "beginData", |
| inspector_layout_event::BeginData(this)); |
| probe::UpdateLayout probe(document); |
| |
| PerformPreLayoutTasks(); |
| |
| VisualViewport& visual_viewport = frame_->GetPage()->GetVisualViewport(); |
| DoubleSize viewport_size(visual_viewport.VisibleWidthCSSPx(), |
| visual_viewport.VisibleHeightCSSPx()); |
| |
| // TODO(crbug.com/460956): The notion of a single root for layout is no |
| // longer applicable. Remove or update this code. |
| LayoutObject* root_for_this_layout = GetLayoutView(); |
| |
| FontCachePurgePreventer font_cache_purge_preventer; |
| StyleRetainScope style_retain_scope; |
| bool in_subtree_layout = false; |
| { |
| base::AutoReset<bool> change_scheduling_enabled(&layout_scheduling_enabled_, |
| false); |
| nested_layout_count_++; |
| |
| // If the layout view was marked as needing layout after we added items in |
| // the subtree roots we need to clear the roots and do the layout from the |
| // layoutView. |
| if (GetLayoutView()->NeedsLayout()) |
| ClearLayoutSubtreeRootsAndMarkContainingBlocks(); |
| GetLayoutView()->ClearHitTestCache(); |
| |
| in_subtree_layout = IsSubtreeLayout(); |
| |
| // TODO(crbug.com/460956): The notion of a single root for layout is no |
| // longer applicable. Remove or update this code. |
| if (in_subtree_layout) |
| root_for_this_layout = layout_subtree_root_list_.RandomRoot(); |
| |
| if (!root_for_this_layout) { |
| // FIXME: Do we need to set m_size here? |
| NOTREACHED(); |
| return; |
| } |
| |
| if (!in_subtree_layout) { |
| ClearLayoutSubtreeRootsAndMarkContainingBlocks(); |
| Node* body = document->body(); |
| if (body && body->GetLayoutObject()) { |
| if (IsA<HTMLFrameSetElement>(*body)) { |
| body->GetLayoutObject()->SetChildNeedsLayout(); |
| } else if (IsA<HTMLBodyElement>(*body)) { |
| if (!first_layout_ && size_.Height() != GetLayoutSize().Height() && |
| body->GetLayoutObject()->EnclosingBox()->StretchesToViewport()) |
| body->GetLayoutObject()->SetChildNeedsLayout(); |
| } |
| } |
| |
| if (first_layout_) { |
| first_layout_ = false; |
| last_viewport_size_ = GetLayoutSize(); |
| last_zoom_factor_ = GetLayoutView()->StyleRef().Zoom(); |
| |
| mojom::blink::ScrollbarMode h_mode; |
| mojom::blink::ScrollbarMode v_mode; |
| GetLayoutView()->CalculateScrollbarModes(h_mode, v_mode); |
| if (v_mode == mojom::blink::ScrollbarMode::kAuto) { |
| if (auto* scrollable_area = GetLayoutView()->GetScrollableArea()) |
| scrollable_area->ForceVerticalScrollbarForFirstLayout(); |
| } |
| } |
| |
| LayoutSize old_size = size_; |
| |
| size_ = LayoutSize(GetLayoutSize()); |
| |
| if (old_size != size_ && !first_layout_) { |
| LayoutBox* root_layout_object = |
| document->documentElement() |
| ? document->documentElement()->GetLayoutBox() |
| : nullptr; |
| LayoutBox* body_layout_object = root_layout_object && document->body() |
| ? document->body()->GetLayoutBox() |
| : nullptr; |
| if (body_layout_object && body_layout_object->StretchesToViewport()) |
| body_layout_object->SetChildNeedsLayout(); |
| else if (root_layout_object && |
| root_layout_object->StretchesToViewport()) |
| root_layout_object->SetChildNeedsLayout(); |
| } |
| } |
| |
| TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID( |
| TRACE_DISABLED_BY_DEFAULT("blink.debug.layout.trees"), "LayoutTree", |
| this, TracedLayoutObject::Create(*GetLayoutView(), false)); |
| |
| IntSize old_size(Size()); |
| |
| PerformLayout(in_subtree_layout); |
| |
| IntSize new_size(Size()); |
| if (old_size != new_size) { |
| MarkViewportConstrainedObjectsForLayout( |
| old_size.Width() != new_size.Width(), |
| old_size.Height() != new_size.Height()); |
| } |
| |
| if (frame_->IsMainFrame()) { |
| if (auto* text_autosizer = frame_->GetDocument()->GetTextAutosizer()) { |
| if (text_autosizer->HasLayoutInlineSizeChanged()) |
| text_autosizer->UpdatePageInfoInAllFrames(frame_); |
| } |
| } |
| |
| if (NeedsLayout()) { |
| base::AutoReset<bool> suppress(&suppress_adjust_view_size_, true); |
| UpdateLayout(); |
| } |
| |
| DCHECK(layout_subtree_root_list_.IsEmpty()); |
| } // Reset m_layoutSchedulingEnabled to its previous value. |
| CheckDoesNotNeedLayout(); |
| |
| DocumentLifecycle::Scope lifecycle_scope(Lifecycle(), |
| DocumentLifecycle::kLayoutClean); |
| |
| frame_timing_requests_dirty_ = true; |
| |
| TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID( |
| TRACE_DISABLED_BY_DEFAULT("blink.debug.layout.trees"), "LayoutTree", this, |
| TracedLayoutObject::Create(*GetLayoutView(), true)); |
| |
| layout_count_for_testing_++; |
| |
| if (AXObjectCache* cache = document->ExistingAXObjectCache()) { |
| const KURL& url = document->Url(); |
| if (url.IsValid() && !url.IsAboutBlankURL()) { |
| // TODO(kschmi) move HandleLayoutComplete to the accessibility lifecycle |
| // stage. crbug.com/1062122 |
| cache->HandleLayoutComplete(document); |
| } |
| } |
| |
| UpdateDocumentAnnotatedRegions(); |
| CheckDoesNotNeedLayout(); |
| |
| if (nested_layout_count_ == 1) { |
| PerformPostLayoutTasks(); |
| CheckDoesNotNeedLayout(); |
| } |
| |
| // FIXME: The notion of a single root for layout is no longer applicable. |
| // Remove or update this code. crbug.com/460596 |
| TRACE_EVENT_END1("devtools.timeline", "Layout", "endData", |
| inspector_layout_event::EndData(root_for_this_layout)); |
| probe::DidChangeViewport(frame_.Get()); |
| |
| nested_layout_count_--; |
| if (nested_layout_count_) |
| return; |
| |
| #if DCHECK_IS_ON() |
| // Post-layout assert that nobody was re-marked as needing layout during |
| // layout. |
| GetLayoutView()->AssertSubtreeIsLaidOut(); |
| #endif |
| |
| if (frame_->IsMainFrame()) { |
| // Scrollbars changing state can cause a visual viewport size change. |
| DoubleSize new_viewport_size(visual_viewport.VisibleWidthCSSPx(), |
| visual_viewport.VisibleHeightCSSPx()); |
| if (new_viewport_size != viewport_size) |
| frame_->GetDocument()->EnqueueVisualViewportResizeEvent(); |
| } |
| |
| GetFrame().GetDocument()->LayoutUpdated(); |
| CheckDoesNotNeedLayout(); |
| } |
| |
| void LocalFrameView::WillStartForcedLayout() { |
| // UpdateLayout is re-entrant for auto-sizing and plugins. So keep |
| // track of stack depth to include all the time in the top-level call. |
| forced_layout_stack_depth_++; |
| if (forced_layout_stack_depth_ > 1) |
| return; |
| forced_layout_start_time_ = base::TimeTicks::Now(); |
| } |
| |
| void LocalFrameView::DidFinishForcedLayout(DocumentUpdateReason reason) { |
| CHECK_GT(forced_layout_stack_depth_, (unsigned)0); |
| forced_layout_stack_depth_--; |
| if (!forced_layout_stack_depth_ && base::TimeTicks::IsHighResolution()) { |
| LocalFrameUkmAggregator& aggregator = EnsureUkmAggregator(); |
| aggregator.RecordForcedLayoutSample(reason, forced_layout_start_time_, |
| base::TimeTicks::Now()); |
| } |
| } |
| |
| void LocalFrameView::MarkFirstEligibleToPaint() { |
| if (frame_ && frame_->GetDocument()) { |
| PaintTiming& timing = PaintTiming::From(*frame_->GetDocument()); |
| timing.MarkFirstEligibleToPaint(); |
| } |
| } |
| |
| void LocalFrameView::MarkIneligibleToPaint() { |
| if (frame_ && frame_->GetDocument()) { |
| PaintTiming& timing = PaintTiming::From(*frame_->GetDocument()); |
| timing.MarkIneligibleToPaint(); |
| } |
| } |
| |
| void LocalFrameView::SetNeedsPaintPropertyUpdate() { |
| if (auto* layout_view = GetLayoutView()) |
| layout_view->SetNeedsPaintPropertyUpdate(); |
| } |
| |
| FloatSize LocalFrameView::ViewportSizeForViewportUnits() const { |
| float zoom = 1; |
| if (!frame_->GetDocument() || !frame_->GetDocument()->Printing()) |
| zoom = GetFrame().PageZoomFactor(); |
| |
| auto* layout_view = GetLayoutView(); |
| if (!layout_view) |
| return FloatSize(); |
| |
| FloatSize layout_size; |
| layout_size.SetWidth(layout_view->ViewWidth(kIncludeScrollbars) / zoom); |
| layout_size.SetHeight(layout_view->ViewHeight(kIncludeScrollbars) / zoom); |
| |
| BrowserControls& browser_controls = frame_->GetPage()->GetBrowserControls(); |
| if (browser_controls.PermittedState() != cc::BrowserControlsState::kHidden) { |
| // We use the layoutSize rather than frameRect to calculate viewport units |
| // so that we get correct results on mobile where the page is laid out into |
| // a rect that may be larger than the viewport (e.g. the 980px fallback |
| // width for desktop pages). Since the layout height is statically set to |
| // be the viewport with browser controls showing, we add the browser |
| // controls height, compensating for page scale as well, since we want to |
| // use the viewport with browser controls hidden for vh (to match Safari). |
| int viewport_width = frame_->GetPage()->GetVisualViewport().Size().Width(); |
| if (frame_->IsMainFrame() && layout_size.Width() && viewport_width) { |
| // TODO(bokan/eirage): BrowserControl height may need to account for the |
| // zoom factor when use-zoom-for-dsf is enabled on Android. Confirm this |
| // works correctly when that's turned on. https://crbug.com/737777. |
| float page_scale_at_layout_width = viewport_width / layout_size.Width(); |
| layout_size.Expand(0, (browser_controls.TotalHeight() - |
| browser_controls.TotalMinHeight()) / |
| page_scale_at_layout_width); |
| } |
| } |
| |
| return layout_size; |
| } |
| |
| FloatSize LocalFrameView::ViewportSizeForMediaQueries() const { |
| FloatSize viewport_size(GetLayoutSize()); |
| if (!frame_->GetDocument() || !frame_->GetDocument()->Printing()) |
| viewport_size.Scale(1 / GetFrame().PageZoomFactor()); |
| return viewport_size; |
| } |
| |
| DocumentLifecycle& LocalFrameView::Lifecycle() const { |
| DCHECK(frame_); |
| DCHECK(frame_->GetDocument()); |
| return frame_->GetDocument()->Lifecycle(); |
| } |
| |
| void LocalFrameView::RunPostLifecycleSteps() { |
| AllowThrottlingScope allow_throttling(*this); |
| RunIntersectionObserverSteps(); |
| ForAllRemoteFrameViews([](RemoteFrameView& frame_view) { |
| frame_view.UpdateCompositingScaleFactor(); |
| }); |
| } |
| |
| void LocalFrameView::RunIntersectionObserverSteps() { |
| #if DCHECK_IS_ON() |
| bool was_dirty = NeedsLayout(); |
| #endif |
| if ((intersection_observation_state_ < kRequired && |
| ShouldThrottleRendering()) || |
| Lifecycle().LifecyclePostponed() || !frame_->GetDocument()->IsActive()) { |
| return; |
| } |
| |
| if (frame_->IsMainFrame()) { |
| EnsureOverlayInterstitialAdDetector().MaybeFireDetection(frame_.Get()); |
| EnsureStickyAdDetector().MaybeFireDetection(frame_.Get()); |
| |
| // Report the main frame's document intersection with itself. |
| LayoutObject* layout_object = GetLayoutView(); |
| IntRect main_frame_dimensions = |
| To<LayoutBox>(layout_object)->PixelSnappedLayoutOverflowRect(); |
| GetFrame().Client()->OnMainFrameIntersectionChanged(IntRect( |
| 0, 0, main_frame_dimensions.Width(), main_frame_dimensions.Height())); |
| } |
| |
| TRACE_EVENT0("blink,benchmark", |
| "LocalFrameView::UpdateViewportIntersectionsForSubtree"); |
| SCOPED_UMA_AND_UKM_TIMER(EnsureUkmAggregator(), |
| LocalFrameUkmAggregator::kIntersectionObservation); |
| |
| bool needs_occlusion_tracking = UpdateViewportIntersectionsForSubtree(0); |
| if (FrameOwner* owner = frame_->Owner()) |
| owner->SetNeedsOcclusionTracking(needs_occlusion_tracking); |
| #if DCHECK_IS_ON() |
| DCHECK(was_dirty || !NeedsLayout()); |
| #endif |
| DeliverSynchronousIntersectionObservations(); |
| } |
| |
| void LocalFrameView::ForceUpdateViewportIntersections() { |
| // IntersectionObserver targets in this frame (and its frame tree) need to |
| // update; but we can't wait for a lifecycle update to run them, because a |
| // hidden frame won't run lifecycle updates. Force layout and run them now. |
| DisallowThrottlingScope disallow_throttling(*this); |
| UpdateLifecycleToPrePaintClean( |
| DocumentUpdateReason::kIntersectionObservation); |
| UpdateViewportIntersectionsForSubtree( |
| IntersectionObservation::kImplicitRootObserversNeedUpdate | |
| IntersectionObservation::kIgnoreDelay); |
| } |
| |
| LayoutSVGRoot* LocalFrameView::EmbeddedReplacedContent() const { |
| auto* layout_view = this->GetLayoutView(); |
| if (!layout_view) |
| return nullptr; |
| |
| LayoutObject* first_child = layout_view->FirstChild(); |
| if (!first_child || !first_child->IsBox()) |
| return nullptr; |
| |
| // Currently only embedded SVG documents participate in the size-negotiation |
| // logic. |
| return DynamicTo<LayoutSVGRoot>(first_child); |
| } |
| |
| bool LocalFrameView::GetIntrinsicSizingInfo( |
| IntrinsicSizingInfo& intrinsic_sizing_info) const { |
| if (LayoutSVGRoot* content_layout_object = EmbeddedReplacedContent()) { |
| content_layout_object->UnscaledIntrinsicSizingInfo(intrinsic_sizing_info); |
| return true; |
| } |
| return false; |
| } |
| |
| bool LocalFrameView::HasIntrinsicSizingInfo() const { |
| return EmbeddedReplacedContent(); |
| } |
| |
| void LocalFrameView::UpdateGeometry() { |
| LayoutEmbeddedContent* layout = GetLayoutEmbeddedContent(); |
| if (!layout) |
| return; |
| |
| bool did_need_layout = NeedsLayout(); |
| |
| PhysicalRect new_frame = layout->ReplacedContentRect(); |
| #if DCHECK_IS_ON() |
| if (new_frame.Width() != LayoutUnit::Max().RawValue() && |
| new_frame.Height() != LayoutUnit::Max().RawValue()) |
| DCHECK(!new_frame.size.HasFraction()); |
| #endif |
| bool bounds_will_change = PhysicalSize(Size()) != new_frame.size; |
| |
| // If frame bounds are changing mark the view for layout. Also check the |
| // frame's page to make sure that the frame isn't in the process of being |
| // destroyed. If iframe scrollbars needs reconstruction from native to custom |
| // scrollbar, then also we need to layout the frameview. |
| if (bounds_will_change) |
| SetNeedsLayout(); |
| |
| layout->UpdateGeometry(*this); |
| // If view needs layout, either because bounds have changed or possibly |
| // indicating content size is wrong, we have to do a layout to set the right |
| // LocalFrameView size. |
| if (NeedsLayout()) |
| UpdateLayout(); |
| |
| if (!did_need_layout && !ShouldThrottleRendering()) |
| CheckDoesNotNeedLayout(); |
| } |
| |
| void LocalFrameView::AddPartToUpdate(LayoutEmbeddedObject& object) { |
| // This is typically called during layout to ensure we update plugins. |
| // However, if layout is blocked (e.g. by content-visibility), we can add the |
| // part to update during layout tree attachment (which is a part of style |
| // recalc). |
| DCHECK(IsInPerformLayout() || |
| (DisplayLockUtilities::NearestLockedExclusiveAncestor(object) && |
| frame_->GetDocument()->InStyleRecalc())); |
| |
| // Tell the DOM element that it needs a Plugin update. |
| Node* node = object.GetNode(); |
| DCHECK(node); |
| if (IsA<HTMLObjectElement>(*node) || IsA<HTMLEmbedElement>(*node)) |
| To<HTMLPlugInElement>(node)->SetNeedsPluginUpdate(true); |
| |
| part_update_set_.insert(&object); |
| } |
| |
| void LocalFrameView::SetMediaType(const AtomicString& media_type) { |
| DCHECK(frame_->GetDocument()); |
| media_type_ = media_type; |
| frame_->GetDocument()->MediaQueryAffectingValueChanged( |
| MediaValueChange::kOther); |
| } |
| |
| AtomicString LocalFrameView::MediaType() const { |
| // See if we have an override type. |
| if (frame_->GetSettings() && |
| !frame_->GetSettings()->GetMediaTypeOverride().IsEmpty()) |
| return AtomicString(frame_->GetSettings()->GetMediaTypeOverride()); |
| return media_type_; |
| } |
| |
| void LocalFrameView::AdjustMediaTypeForPrinting(bool printing) { |
| if (printing) { |
| if (media_type_when_not_printing_.IsNull()) |
| media_type_when_not_printing_ = MediaType(); |
| SetMediaType(media_type_names::kPrint); |
| } else { |
| if (!media_type_when_not_printing_.IsNull()) |
| SetMediaType(media_type_when_not_printing_); |
| media_type_when_not_printing_ = g_null_atom; |
| } |
| } |
| |
| void LocalFrameView::AddBackgroundAttachmentFixedObject(LayoutObject* object) { |
| DCHECK(!background_attachment_fixed_objects_.Contains(object)); |
| background_attachment_fixed_objects_.insert(object); |
| |
| // Ensure main thread scrolling reasons of the ancestor scroll nodes are |
| // recomputed. The object's own scroll properties are not affected. |
| object->ForceAllAncestorsNeedPaintPropertyUpdate(); |
| } |
| |
| void LocalFrameView::RemoveBackgroundAttachmentFixedObject( |
| LayoutObject* object) { |
| background_attachment_fixed_objects_.erase(object); |
| |
| // Ensure main thread scrolling reasons of the ancestor scroll nodes are |
| // recomputed. The object's own scroll properties are not affected. |
| object->ForceAllAncestorsNeedPaintPropertyUpdate(); |
| } |
| |
| bool LocalFrameView::RequiresMainThreadScrollingForBackgroundAttachmentFixed() |
| const { |
| if (background_attachment_fixed_objects_.IsEmpty()) |
| return false; |
| if (background_attachment_fixed_objects_.size() > 1) |
| return true; |
| |
| const auto* object = |
| To<LayoutBoxModelObject>(*background_attachment_fixed_objects_.begin()); |
| // We should not add such object in the set. |
| DCHECK(!object->BackgroundTransfersToView()); |
| // If the background is viewport background and it paints onto the main |
| // graphics layer only, then it doesn't need main thread scrolling. |
| if (IsA<LayoutView>(object) && |
| object->GetBackgroundPaintLocation() == kBackgroundPaintInGraphicsLayer) |
| return false; |
| return true; |
| } |
| |
| void LocalFrameView::AddViewportConstrainedObject( |
| LayoutObject& object, |
| ViewportConstrainedType constrained_reason) { |
| if (!viewport_constrained_objects_) |
| viewport_constrained_objects_ = std::make_unique<ObjectSet>(); |
| |
| auto result = viewport_constrained_objects_->insert(&object); |
| if (constrained_reason == ViewportConstrainedType::kSticky) { |
| if (result.is_new_entry) { |
| sticky_position_object_count_++; |
| } |
| DCHECK_LE(sticky_position_object_count_, |
| viewport_constrained_objects_->size()); |
| } |
| } |
| |
| void LocalFrameView::RemoveViewportConstrainedObject( |
| LayoutObject& object, |
| ViewportConstrainedType constrained_reason) { |
| if (viewport_constrained_objects_) { |
| auto it = viewport_constrained_objects_->find(&object); |
| if (it != viewport_constrained_objects_->end()) { |
| viewport_constrained_objects_->erase(it); |
| if (constrained_reason == ViewportConstrainedType::kSticky) { |
| DCHECK_GT(sticky_position_object_count_, 0U); |
| sticky_position_object_count_--; |
| } |
| } |
| } |
| } |
| |
| void LocalFrameView::ViewportSizeChanged(bool width_changed, |
| bool height_changed) { |
| DCHECK(width_changed || height_changed); |
| DCHECK(frame_->GetPage()); |
| if (frame_->GetDocument() && |
| frame_->GetDocument()->Lifecycle().LifecyclePostponed()) |
| return; |
| |
| if (frame_->IsMainFrame()) |
| layout_shift_tracker_->NotifyViewportSizeChanged(); |
| |
| auto* layout_view = GetLayoutView(); |
| if (layout_view) { |
| // If this is the main frame, we might have got here by hiding/showing the |
| // top controls. In that case, layout won't be triggered, so we need to |
| // clamp the scroll offset here. |
| if (GetFrame().IsMainFrame()) { |
| layout_view->Layer()->UpdateSize(); |
| if (auto* scrollable_area = layout_view->GetScrollableArea()) |
| scrollable_area->ClampScrollOffsetAfterOverflowChange(); |
| } |
| |
| // TODO(pdr): |UsesCompositing()| will be false with CompositeAfterPaint but |
| // do we need to do these updates? |
| if (layout_view->UsesCompositing()) { |
| layout_view->Layer()->SetNeedsCompositingInputsUpdate(); |
| SetNeedsPaintPropertyUpdate(); |
| } |
| } |
| |
| if (GetFrame().GetDocument()) |
| GetFrame().GetDocument()->GetRootScrollerController().DidResizeFrameView(); |
| |
| // Change of viewport size after browser controls showing/hiding may affect |
| // painting of the background. |
| if (layout_view && frame_->IsMainFrame() && |
| frame_->GetPage()->GetBrowserControls().TotalHeight()) |
| layout_view->SetShouldCheckForPaintInvalidation(); |
| |
| if (GetFrame().GetDocument() && !IsInPerformLayout()) |
| MarkViewportConstrainedObjectsForLayout(width_changed, height_changed); |
| |
| if (GetPaintTimingDetector().Visualizer()) |
| GetPaintTimingDetector().Visualizer()->OnViewportChanged(); |
| } |
| |
| void LocalFrameView::MarkViewportConstrainedObjectsForLayout( |
| bool width_changed, |
| bool height_changed) { |
| if (!HasViewportConstrainedObjects() || !(width_changed || height_changed)) |
| return; |
| |
| for (auto* const viewport_constrained_object : |
| *viewport_constrained_objects_) { |
| LayoutObject* layout_object = viewport_constrained_object; |
| const ComputedStyle& style = layout_object->StyleRef(); |
| if (width_changed) { |
| if (style.Width().IsFixed() && |
| (style.Left().IsAuto() || style.Right().IsAuto())) { |
| layout_object->SetNeedsPositionedMovementLayout(); |
| } else { |
| layout_object->SetNeedsLayoutAndFullPaintInvalidation( |
| layout_invalidation_reason::kSizeChanged); |
| } |
| } |
| if (height_changed) { |
| if (style.Height().IsFixed() && |
| (style.Top().IsAuto() || style.Bottom().IsAuto())) { |
| layout_object->SetNeedsPositionedMovementLayout(); |
| } else { |
| layout_object->SetNeedsLayoutAndFullPaintInvalidation( |
| layout_invalidation_reason::kSizeChanged); |
| } |
| } |
| } |
| } |
| |
| bool LocalFrameView::ShouldSetCursor() const { |
| Page* page = GetFrame().GetPage(); |
| return page && page->IsPageVisible() && |
| !frame_->GetEventHandler().IsMousePositionUnknown() && |
| page->GetFocusController().IsActive(); |
| } |
| |
| void LocalFrameView::InvalidateBackgroundAttachmentFixedDescendantsOnScroll( |
| const LayoutObject& scrolled_object) { |
| for (auto* const layout_object : background_attachment_fixed_objects_) { |
| if (scrolled_object != GetLayoutView() && |
| !layout_object->IsDescendantOf(&scrolled_object)) |
| continue; |
| // An object needs to repaint the background on scroll when it has |
| // background-attachment:fixed unless the object is the LayoutView and the |
| // background is not painted on the scrolling contents. |
| if (layout_object == GetLayoutView() && |
| !(GetLayoutView()->GetBackgroundPaintLocation() & |
| kBackgroundPaintInScrollingContents)) |
| continue; |
| layout_object->SetBackgroundNeedsFullPaintInvalidation(); |
| } |
| } |
| |
| bool LocalFrameView::InvalidateViewportConstrainedObjects() { |
| bool fast_path_allowed = true; |
| for (auto* const viewport_constrained_object : |
| *viewport_constrained_objects_) { |
| LayoutObject* layout_object = viewport_constrained_object; |
| DCHECK(layout_object->StyleRef().HasViewportConstrainedPosition() || |
| layout_object->StyleRef().HasStickyConstrainedPosition()); |
| DCHECK(layout_object->HasLayer()); |
| PaintLayer* layer = To<LayoutBoxModelObject>(layout_object)->Layer(); |
| |
| if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { |
| DisableCompositingQueryAsserts disabler; |
| if (layer->IsPaintInvalidationContainer()) |
| continue; |
| } |
| |
| // If the layer has no visible content, then we shouldn't invalidate; but |
| // if we're not compositing-inputs-clean, then we can't query |
| // layer->SubtreeIsInvisible() here. |
| layout_object->SetSubtreeShouldCheckForPaintInvalidation(); |
| if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled() && |
| !layer->SelfOrDescendantNeedsRepaint()) { |
| DisableCompositingQueryAsserts disabler; |
| // Paint properties of the layer relative to its containing graphics |
| // layer may change if the paint properties escape the graphics layer's |
| // property state. Need to check raster invalidation for relative paint |
| // property changes. |
| if (auto* paint_invalidation_layer = |
| layer->EnclosingLayerForPaintInvalidation()) { |
| auto* mapping = paint_invalidation_layer->GetCompositedLayerMapping(); |
| if (!mapping) |
| mapping = paint_invalidation_layer->GroupedMapping(); |
| if (mapping) |
| mapping->SetNeedsCheckRasterInvalidation(); |
| } |
| } |
| |
| // If the fixed layer has a blur/drop-shadow filter applied on at least one |
| // of its parents, we cannot scroll using the fast path, otherwise the |
| // outsets of the filter will be moved around the page. |
| if (layer->HasAncestorWithFilterThatMovesPixels()) |
| fast_path_allowed = false; |
| } |
| return fast_path_allowed; |
| } |
| |
| HitTestResult LocalFrameView::HitTestWithThrottlingAllowed( |
| const HitTestLocation& location, |
| HitTestRequest::HitTestRequestType request_type) const { |
| AllowThrottlingScope allow_throttling(*this); |
| return GetFrame().GetEventHandler().HitTestResultAtLocation(location, |
| request_type); |
| } |
| |
| void LocalFrameView::ProcessUrlFragment(const KURL& url, |
| bool same_document_navigation, |
| bool should_scroll) { |
| // We want to create the anchor even if we don't need to scroll. This ensures |
| // all the side effects like setting CSS :target are correctly set. |
| FragmentAnchor* anchor = |
| FragmentAnchor::TryCreate(url, *frame_, should_scroll); |
| |
| if (anchor) { |
| fragment_anchor_ = anchor; |
| fragment_anchor_->Installed(); |
| // Post-load, same-document navigations need to schedule a frame in which |
| // the fragment anchor will be invoked. It will be done after layout as |
| // part of the lifecycle. |
| if (same_document_navigation) |
| ScheduleAnimation(); |
| } |
| } |
| |
| void LocalFrameView::SetLayoutSize(const IntSize& size) { |
| DCHECK(!LayoutSizeFixedToFrameSize()); |
| if (frame_->GetDocument() && |
| frame_->GetDocument()->Lifecycle().LifecyclePostponed()) |
| return; |
| |
| SetLayoutSizeInternal(size); |
| } |
| |
| void LocalFrameView::SetLayoutSizeFixedToFrameSize(bool is_fixed) { |
| if (layout_size_fixed_to_frame_size_ == is_fixed) |
| return; |
| |
| layout_size_fixed_to_frame_size_ = is_fixed; |
| if (is_fixed) |
| SetLayoutSizeInternal(Size()); |
| } |
| |
| static cc::LayerSelection ComputeLayerSelection(LocalFrame& frame) { |
| if (!frame.View() || frame.View()->ShouldThrottleRendering()) |
| return {}; |
| |
| return ComputeLayerSelection(frame.Selection()); |
| } |
| |
| void LocalFrameView::UpdateCompositedSelectionIfNeeded() { |
| if (!RuntimeEnabledFeatures::CompositedSelectionUpdateEnabled()) |
| return; |
| |
| if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) |
| return; |
| |
| TRACE_EVENT0("blink", "LocalFrameView::updateCompositedSelectionIfNeeded"); |
| |
| Page* page = GetFrame().GetPage(); |
| DCHECK(page); |
| |
| LocalFrame* focused_frame = page->GetFocusController().FocusedFrame(); |
| LocalFrame* local_frame = |
| (focused_frame && |
| (focused_frame->LocalFrameRoot() == frame_->LocalFrameRoot())) |
| ? focused_frame |
| : nullptr; |
| |
| if (local_frame) { |
| const cc::LayerSelection& selection = ComputeLayerSelection(*local_frame); |
| if (selection != cc::LayerSelection()) { |
| page->GetChromeClient().UpdateLayerSelection(local_frame, selection); |
| return; |
| } |
| } |
| |
| if (!local_frame) { |
| // Clearing the mainframe when there is no focused frame (and hence |
| // no localFrame) is legacy behaviour, and implemented here to |
| // satisfy WebFrameTest.CompositedSelectionBoundsCleared's |
| // first check that the composited selection has been cleared even |
| // though no frame has focus yet. If this is not desired, then the |
| // expectation needs to be removed from the test. |
| local_frame = &frame_->LocalFrameRoot(); |
| } |
| DCHECK(local_frame); |
| page->GetChromeClient().ClearLayerSelection(local_frame); |
| } |
| |
| void LocalFrameView::SetNeedsCompositingUpdate( |
| CompositingUpdateType update_type) { |
| if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) |
| return; |
| if (auto* layout_view = GetLayoutView()) { |
| if (frame_->GetDocument()->IsActive()) { |
| auto* compositor = layout_view->Compositor(); |
| compositor->SetNeedsCompositingUpdate(update_type); |
| // Even if the frame is throttlable, we may still need to decomposite it |
| // in response to a visibility change. |
| if (compositor->StaleInCompositingMode()) |
| needs_forced_compositing_update_ = true; |
| } |
| } |
| } |
| |
| ChromeClient* LocalFrameView::GetChromeClient() const { |
| Page* page = GetFrame().GetPage(); |
| if (!page) |
| return nullptr; |
| return &page->GetChromeClient(); |
| } |
| |
| void LocalFrameView::HandleLoadCompleted() { |
| // Once loading has completed, allow autoSize one last opportunity to |
| // reduce the size of the frame. |
| if (auto_size_info_) |
| auto_size_info_->AutoSizeIfNeeded(); |
| } |
| |
| void LocalFrameView::ClearLayoutSubtreeRoot(const LayoutObject& root) { |
| layout_subtree_root_list_.Remove(const_cast<LayoutObject&>(root)); |
| } |
| |
| void LocalFrameView::ClearLayoutSubtreeRootsAndMarkContainingBlocks() { |
| layout_subtree_root_list_.ClearAndMarkContainingBlocksForLayout(); |
| } |
| |
| void LocalFrameView::AddOrthogonalWritingModeRoot(LayoutBox& root) { |
| DCHECK(!root.IsLayoutCustomScrollbarPart()); |
| orthogonal_writing_mode_root_list_.Add(root); |
| } |
| |
| void LocalFrameView::RemoveOrthogonalWritingModeRoot(LayoutBox& root) { |
| orthogonal_writing_mode_root_list_.Remove(root); |
| } |
| |
| bool LocalFrameView::HasOrthogonalWritingModeRoots() const { |
| return !orthogonal_writing_mode_root_list_.IsEmpty(); |
| } |
| |
| static inline void RemoveFloatingObjectsForSubtreeRoot(LayoutObject& root) { |
| // TODO(kojii): Under certain conditions, moveChildTo() defers |
| // removeFloatingObjects() until the containing block layouts. For |
| // instance, when descendants of the moving child is floating, |
| // removeChildNode() does not clear them. In such cases, at this |
| // point, FloatingObjects may contain old or even deleted objects. |
| // Dealing this in markAllDescendantsWithFloatsForLayout() could |
| // solve, but since that is likely to suffer the performance and |
| // since the containing block of orthogonal writing mode roots |
| // having floats is very rare, prefer to re-create |
| // FloatingObjects. |
| if (LayoutBlock* cb = root.ContainingBlock()) { |
| auto* child_block_flow = DynamicTo<LayoutBlockFlow>(cb); |
| if ((cb->NormalChildNeedsLayout() || cb->SelfNeedsLayout()) && |
| child_block_flow) { |
| child_block_flow->RemoveFloatingObjectsFromDescendants(); |
| } |
| } |
| } |
| |
| static bool PrepareOrthogonalWritingModeRootForLayout(LayoutObject& root) { |
| DCHECK(To<LayoutBox>(root).IsOrthogonalWritingModeRoot()); |
| if (!root.NeedsLayout() || root.IsOutOfFlowPositioned() || |
| root.IsColumnSpanAll() || root.StyleRef().LogicalHeight().IsSpecified() || |
| To<LayoutBox>(root).IsGridItem() || root.IsTablePart()) |
| return false; |
| |
| if (RuntimeEnabledFeatures::LayoutNGEnabled()) { |
| // Do not pre-layout objects that are fully managed by LayoutNG; it is not |
| // necessary and may lead to double layouts. We do need to pre-layout |
| // objects whose containing block is a legacy object so that it can |
| // properly compute its intrinsic size. |
| if (IsManagedByLayoutNG(root)) |
| return false; |
| |
| // If the root is legacy but has |CachedLayoutResult|, its parent is NG, |
| // which called |RunLegacyLayout()|. This parent not only needs to run |
| // pre-layout, but also clearing |NeedsLayout()| without updating |
| // |CachedLayoutResult| is harmful. |
| if (const auto* box = DynamicTo<LayoutBox>(root)) { |
| if (box->GetCachedLayoutResult()) |
| return false; |
| } |
| } |
| |
| RemoveFloatingObjectsForSubtreeRoot(root); |
| return true; |
| } |
| |
| void LocalFrameView::LayoutOrthogonalWritingModeRoots() { |
| for (auto& root : orthogonal_writing_mode_root_list_.Ordered()) { |
| if (PrepareOrthogonalWritingModeRootForLayout(*root)) |
| LayoutFromRootObject(*root); |
| } |
| } |
| |
| void LocalFrameView::ScheduleOrthogonalWritingModeRootsForLayout() { |
| for (auto& root : orthogonal_writing_mode_root_list_.Ordered()) { |
| if (PrepareOrthogonalWritingModeRootForLayout(*root)) |
| layout_subtree_root_list_.Add(*root); |
| } |
| } |
| |
| void LocalFrameView::MarkOrthogonalWritingModeRootsForLayout() { |
| for (auto& root : orthogonal_writing_mode_root_list_.Ordered()) { |
| // OOF-positioned objects don't depend on the ICB size. |
| if (root->NeedsLayout() || root->IsOutOfFlowPositioned()) |
| continue; |
| |
| root->SetNeedsLayoutAndIntrinsicWidthsRecalc( |
| layout_invalidation_reason::kSizeChanged); |
| } |
| } |
| |
| bool LocalFrameView::CheckLayoutInvalidationIsAllowed() const { |
| #if DCHECK_IS_ON() |
| if (allows_layout_invalidation_after_layout_clean_) |
| return true; |
| |
| // If we are updating all lifecycle phases beyond LayoutClean, we don't expect |
| // dirty layout after LayoutClean. |
| CHECK_FOR_DIRTY_LAYOUT(Lifecycle().GetState() < |
| DocumentLifecycle::kLayoutClean); |
| |
| #endif |
| return true; |
| } |
| |
| bool LocalFrameView::RunPostLayoutIntersectionObserverSteps() { |
| DCHECK(frame_->IsLocalRoot()); |
| DCHECK(Lifecycle().GetState() >= DocumentLifecycle::kPrePaintClean); |
| |
| ComputePostLayoutIntersections(0); |
| |
| bool needs_more_lifecycle_steps = false; |
| ForAllNonThrottledLocalFrameViews( |
| [&needs_more_lifecycle_steps](LocalFrameView& frame_view) { |
| if (auto* controller = frame_view.GetFrame() |
| .GetDocument() |
| ->GetIntersectionObserverController()) { |
| controller->DeliverNotifications( |
| IntersectionObserver::kDeliverDuringPostLayoutSteps); |
| } |
| // If the lifecycle state changed as a result of the notifications, we |
| // should run the lifecycle again. |
| needs_more_lifecycle_steps |= frame_view.Lifecycle().GetState() < |
| DocumentLifecycle::kPrePaintClean; |
| }); |
| |
| return needs_more_lifecycle_steps; |
| } |
| |
| void LocalFrameView::ComputePostLayoutIntersections(unsigned parent_flags) { |
| if (ShouldThrottleRendering()) |
| return; |
| |
| unsigned flags = GetIntersectionObservationFlags(parent_flags) | |
| IntersectionObservation::kPostLayoutDeliveryOnly; |
| |
| if (auto* controller = |
| GetFrame().GetDocument()->GetIntersectionObserverController()) { |
| controller->ComputeIntersections(flags, EnsureUkmAggregator()); |
| } |
| |
| for (Frame* child = frame_->Tree().FirstChild(); child; |
| child = child->Tree().NextSibling()) { |
| auto* child_local_frame = DynamicTo<LocalFrame>(child); |
| if (!child_local_frame) |
| continue; |
| if (LocalFrameView* child_view = child_local_frame->View()) |
| child_view->ComputePostLayoutIntersections(flags); |
| } |
| } |
| |
| void LocalFrameView::ScheduleRelayout() { |
| DCHECK(frame_->View() == this); |
| |
| if (!layout_scheduling_enabled_) |
| return; |
| // TODO(crbug.com/590856): It's still broken when we choose not to crash when |
| // the check fails. |
| if (!CheckLayoutInvalidationIsAllowed()) |
| return; |
| if (!NeedsLayout()) |
| return; |
| if (!frame_->GetDocument()->ShouldScheduleLayout()) |
| return; |
| TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), |
| "InvalidateLayout", TRACE_EVENT_SCOPE_THREAD, "data", |
| inspector_invalidate_layout_event::Data(frame_.Get())); |
| |
| ClearLayoutSubtreeRootsAndMarkContainingBlocks(); |
| |
| if (has_pending_layout_) |
| return; |
| has_pending_layout_ = true; |
| |
| if (!ShouldThrottleRendering()) |
| GetPage()->Animator().ScheduleVisualUpdate(frame_.Get()); |
| } |
| |
| void LocalFrameView::ScheduleRelayoutOfSubtree(LayoutObject* relayout_root) { |
| DCHECK(frame_->View() == this); |
| DCHECK(relayout_root->IsBox()); |
| |
| // TODO(crbug.com/590856): It's still broken when we choose not to crash when |
| // the check fails. |
| if (!CheckLayoutInvalidationIsAllowed()) |
| return; |
| |
| // FIXME: Should this call shouldScheduleLayout instead? |
| if (!frame_->GetDocument()->IsActive()) |
| return; |
| |
| LayoutView* layout_view = this->GetLayoutView(); |
| if (layout_view && layout_view->NeedsLayout()) { |
| if (relayout_root) |
| relayout_root->MarkContainerChainForLayout(false); |
| return; |
| } |
| |
| if (relayout_root == layout_view) |
| layout_subtree_root_list_.ClearAndMarkContainingBlocksForLayout(); |
| else |
| layout_subtree_root_list_.Add(*relayout_root); |
| |
| if (layout_scheduling_enabled_) { |
| has_pending_layout_ = true; |
| |
| if (!ShouldThrottleRendering()) |
| GetPage()->Animator().ScheduleVisualUpdate(frame_.Get()); |
| |
| if (GetPage()->Animator().IsServicingAnimations()) |
| Lifecycle().EnsureStateAtMost(DocumentLifecycle::kStyleClean); |
| } |
| TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), |
| "InvalidateLayout", TRACE_EVENT_SCOPE_THREAD, "data", |
| inspector_invalidate_layout_event::Data(frame_.Get())); |
| } |
| |
| bool LocalFrameView::LayoutPending() const { |
| // FIXME: This should check Document::lifecycle instead. |
| return has_pending_layout_; |
| } |
| |
| bool LocalFrameView::IsInPerformLayout() const { |
| return Lifecycle().GetState() == DocumentLifecycle::kInPerformLayout; |
| } |
| |
| bool LocalFrameView::NeedsLayout() const { |
| // This can return true in cases where the document does not have a body yet. |
| // Document::shouldScheduleLayout takes care of preventing us from scheduling |
| // layout in that case. |
| |
| auto* layout_view = GetLayoutView(); |
| return LayoutPending() || (layout_view && layout_view->NeedsLayout()) || |
| IsSubtreeLayout(); |
| } |
| |
| NOINLINE bool LocalFrameView::CheckDoesNotNeedLayout() const { |
| CHECK_FOR_DIRTY_LAYOUT(!LayoutPending()); |
| CHECK_FOR_DIRTY_LAYOUT(!GetLayoutView() || !GetLayoutView()->NeedsLayout()); |
| CHECK_FOR_DIRTY_LAYOUT(!IsSubtreeLayout()); |
| return true; |
| } |
| |
| void LocalFrameView::SetNeedsLayout() { |
| auto* layout_view = GetLayoutView(); |
| if (!layout_view) |
| return; |
| // TODO(crbug.com/590856): It's still broken if we choose not to crash when |
| // the check fails. |
| if (!CheckLayoutInvalidationIsAllowed()) |
| return; |
| layout_view->SetNeedsLayout(layout_invalidation_reason::kUnknown); |
| } |
| |
| bool LocalFrameView::ShouldUseColorAdjustBackground() const { |
| return use_color_adjust_background_ == UseColorAdjustBackground::kYes || |
| (use_color_adjust_background_ == |
| UseColorAdjustBackground::kIfBaseNotTransparent && |
| base_background_color_ != Color::kTransparent); |
| } |
| |
| Color LocalFrameView::BaseBackgroundColor() const { |
| if (ShouldUseColorAdjustBackground()) { |
| DCHECK(frame_->GetDocument()); |
| return frame_->GetDocument()->GetStyleEngine().ColorAdjustBackgroundColor(); |
| } |
| return base_background_color_; |
| } |
| |
| void LocalFrameView::SetBaseBackgroundColor(const Color& background_color) { |
| if (base_background_color_ == background_color) |
| return; |
| |
| base_background_color_ = background_color; |
| |
| if (auto* layout_view = GetLayoutView()) { |
| if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { |
| if (auto* mapping = layout_view->Layer()->GetCompositedLayerMapping()) |
| mapping->UpdateContentsOpaque(); |
| } |
| layout_view->SetBackgroundNeedsFullPaintInvalidation(); |
| } |
| |
| if (!ShouldThrottleRendering()) |
| GetPage()->Animator().ScheduleVisualUpdate(frame_.Get()); |
| } |
| |
| void LocalFrameView::SetUseColorAdjustBackground(UseColorAdjustBackground use, |
| bool color_scheme_changed) { |
| if (use_color_adjust_background_ == use && !color_scheme_changed) |
| return; |
| |
| use_color_adjust_background_ = use; |
| |
| if (GetFrame().IsMainFrame() && ShouldUseColorAdjustBackground()) { |
| // Pass the dark color-scheme background to the browser process to paint a |
| // dark background in the browser tab while rendering is blocked in order to |
| // avoid flashing the white background in between loading documents. If we |
| // perform a navigation within the same renderer process, we keep the |
| // content background from the previous page while rendering is blocked in |
| // the new page, but for cross process navigations we would paint the |
| // default background (typically white) while the rendering is blocked. |
| GetFrame().DidChangeBackgroundColor(SkColor(BaseBackgroundColor()), |
| true /* color_adjust */); |
| } |
| |
| if (auto* layout_view = GetLayoutView()) |
| layout_view->SetBackgroundNeedsFullPaintInvalidation(); |
| } |
| |
| bool LocalFrameView::ShouldPaintBaseBackgroundColor() const { |
| return ShouldUseColorAdjustBackground() || |
| frame_->GetDocument()->IsInMainFrame(); |
| } |
| |
| void LocalFrameView::UpdateBaseBackgroundColorRecursively( |
| const Color& base_background_color) { |
| ForAllNonThrottledLocalFrameViews( |
| [base_background_color](LocalFrameView& frame_view) { |
| frame_view.SetBaseBackgroundColor(base_background_color); |
| }); |
| } |
| |
| void LocalFrameView::InvokeFragmentAnchor() { |
| if (!fragment_anchor_) |
| return; |
| |
| if (!fragment_anchor_->Invoke()) |
| fragment_anchor_ = nullptr; |
| } |
| |
| void LocalFrameView::DismissFragmentAnchor() { |
| if (!fragment_anchor_) |
| return; |
| |
| if (fragment_anchor_->Dismiss()) |
| fragment_anchor_ = nullptr; |
| } |
| |
| bool LocalFrameView::UpdatePlugins() { |
| // This is always called from UpdatePluginsTimerFired. |
| // update_plugins_timer should only be scheduled if we have FrameViews to |
| // update. Thus I believe we can stop checking isEmpty here, and just ASSERT |
| // isEmpty: |
| // FIXME: This assert has been temporarily removed due to |
| // https://crbug.com/430344 |
| if (nested_layout_count_ > 1 || part_update_set_.IsEmpty()) |
| return true; |
| |
| // Need to swap because script will run inside the below loop and invalidate |
| // the iterator. |
| EmbeddedObjectSet objects; |
| objects.swap(part_update_set_); |
| |
| for (const auto& embedded_object : objects) { |
| LayoutEmbeddedObject& object = *embedded_object; |
| auto* element = To<HTMLPlugInElement>(object.GetNode()); |
| |
| // The object may have already been destroyed (thus node cleared), |
| // but LocalFrameView holds a manual ref, so it won't have been deleted. |
| if (!element) |
| continue; |
| |
| // No need to update if it's already crashed or known to be missing. |
| if (object.ShowsUnavailablePluginIndicator()) |
| continue; |
| |
| if (element->NeedsPluginUpdate()) |
| element->UpdatePlugin(); |
| if (EmbeddedContentView* view = element->OwnedEmbeddedContentView()) |
| view->UpdateGeometry(); |
| |
| // Prevent plugins from causing infinite updates of themselves. |
| // FIXME: Do we really need to prevent this? |
| part_update_set_.erase(&object); |
| } |
| |
| return part_update_set_.IsEmpty(); |
| } |
| |
| void LocalFrameView::UpdatePluginsTimerFired(TimerBase*) { |
| DCHECK(!IsInPerformLayout()); |
| for (unsigned i = 0; i < kMaxUpdatePluginsIterations; ++i) { |
| if (UpdatePlugins()) |
| return; |
| } |
| } |
| |
| void LocalFrameView::FlushAnyPendingPostLayoutTasks() { |
| DCHECK(!IsInPerformLayout()); |
| if (update_plugins_timer_.IsActive()) { |
| update_plugins_timer_.Stop(); |
| UpdatePluginsTimerFired(nullptr); |
| } |
| } |
| |
| void LocalFrameView::ScheduleUpdatePluginsIfNecessary() { |
| DCHECK(!IsInPerformLayout()); |
| if (update_plugins_timer_.IsActive() || part_update_set_.IsEmpty()) |
| return; |
| update_plugins_timer_.StartOneShot(base::TimeDelta(), FROM_HERE); |
| } |
| |
| void LocalFrameView::PerformPostLayoutTasks() { |
| // FIXME: We can reach here, even when the page is not active! |
| // http/tests/inspector/elements/html-link-import.html and many other |
| // tests hit that case. |
| // We should DCHECK(isActive()); or at least return early if we can! |
| |
| // Always called before or after performLayout(), part of the highest-level |
| // layout() call. |
| DCHECK(!IsInPerformLayout()); |
| TRACE_EVENT0("blink,benchmark", "LocalFrameView::performPostLayoutTasks"); |
| |
| GetLayoutView()->EnclosingLayer()->UpdateLayerPositionsAfterLayout(); |
| frame_->Selection().DidLayout(); |
| |
| DCHECK(frame_->GetDocument()); |
| |
| FontFaceSetDocument::DidLayout(*frame_->GetDocument()); |
| // Fire a fake a mouse move event to update hover state and mouse cursor, and |
| // send the right mouse out/over events. |
| // TODO(lanwei): we should check whether the mouse is inside the frame before |
| // dirtying the hover state. |
| frame_->LocalFrameRoot().GetEventHandler().MarkHoverStateDirty(); |
| |
| UpdateGeometriesIfNeeded(); |
| |
| // Plugins could have torn down the page inside updateGeometries(). |
| if (!GetLayoutView()) |
| return; |
| |
| ScheduleUpdatePluginsIfNecessary(); |
| SendResizeEventIfNeeded(); |
| } |
| |
| bool LocalFrameView::WasViewportResized() { |
| DCHECK(frame_); |
| auto* layout_view = GetLayoutView(); |
| if (!layout_view) |
| return false; |
| return (GetLayoutSize() != last_viewport_size_ || |
| layout_view->StyleRef().Zoom() != last_zoom_factor_); |
| } |
| |
| void LocalFrameView::SendResizeEventIfNeeded() { |
| DCHECK(frame_); |
| |
| auto* layout_view = GetLayoutView(); |
| if (!layout_view || layout_view->GetDocument().Printing()) |
| return; |
| |
| if (!WasViewportResized()) |
| return; |
| |
| last_viewport_size_ = GetLayoutSize(); |
| last_zoom_factor_ = layout_view->StyleRef().Zoom(); |
| |
| frame_->GetDocument()->EnqueueVisualViewportResizeEvent(); |
| |
| frame_->GetDocument()->EnqueueResizeEvent(); |
| |
| if (frame_->IsMainFrame()) |
| probe::DidResizeMainFrame(frame_.Get()); |
| } |
| |
| float LocalFrameView::InputEventsScaleFactor() const { |
| float page_scale = frame_->GetPage()->GetVisualViewport().Scale(); |
| return page_scale * |
| frame_->GetPage()->GetChromeClient().InputEventsScaleForEmulation(); |
| } |
| |
| void LocalFrameView::NotifyPageThatContentAreaWillPaint() const { |
| Page* page = frame_->GetPage(); |
| if (!page) |
| return; |
| |
| if (!scrollable_areas_) |
| return; |
| |
| for (const auto& scrollable_area : *scrollable_areas_) { |
| if (!scrollable_area->ScrollbarsCanBeActive()) |
| continue; |
| |
| scrollable_area->ContentAreaWillPaint(); |
| } |
| } |
| |
| void LocalFrameView::UpdateDocumentAnnotatedRegions() const { |
| Document* document = frame_->GetDocument(); |
| if (!document->HasAnnotatedRegions()) |
| return; |
| Vector<AnnotatedRegionValue> new_regions; |
| CollectAnnotatedRegions(*(document->GetLayoutBox()), new_regions); |
| if (new_regions == document->AnnotatedRegions()) |
| return; |
| document->SetAnnotatedRegions(new_regions); |
| |
| DCHECK(frame_->Client()); |
| frame_->Client()->AnnotatedRegionsChanged(); |
| } |
| |
| void LocalFrameView::DidAttachDocument() { |
| Page* page = frame_->GetPage(); |
| DCHECK(page); |
| |
| DCHECK(frame_->GetDocument()); |
| |
| if (frame_->IsMainFrame()) { |
| ScrollableArea& visual_viewport = frame_->GetPage()->GetVisualViewport(); |
| ScrollableArea* layout_viewport = LayoutViewport(); |
| DCHECK(layout_viewport); |
| |
| auto* root_frame_viewport = MakeGarbageCollected<RootFrameViewport>( |
| visual_viewport, *layout_viewport); |
| viewport_scrollable_area_ = root_frame_viewport; |
| |
| page->GlobalRootScrollerController().InitializeViewportScrollCallback( |
| *root_frame_viewport, *frame_->GetDocument()); |
| |
| // Allow for commits to be deferred because this is a new document. |
| have_deferred_commits_ = false; |
| } |
| } |
| |
| Color LocalFrameView::DocumentBackgroundColor() const { |
| // The LayoutView's background color is set in |
| // Document::inheritHtmlAndBodyElementStyles. Blend this with the base |
| // background color of the LocalFrameView. This should match the color drawn |
| // by ViewPainter::paintBoxDecorationBackground. |
| Color result = BaseBackgroundColor(); |
| |
| // If we have a fullscreen element grab the fullscreen color from the |
| // backdrop. |
| if (Document* doc = frame_->GetDocument()) { |
| if (Element* element = Fullscreen::FullscreenElementFrom(*doc)) { |
| if (doc->IsXrOverlay()) { |
| // Use the fullscreened element's background directly. Don't bother |
| // blending with the backdrop since that's transparent. |
| if (LayoutObject* layout_object = element->GetLayoutObject()) { |
| return layout_object->ResolveColor(GetCSSPropertyBackgroundColor()); |
| } |
| if (LayoutObject* layout_object = |
| element->PseudoElementLayoutObject(kPseudoIdBackdrop)) { |
| return layout_object->ResolveColor(GetCSSPropertyBackgroundColor()); |
| } |
| } |
| if (LayoutObject* layout_object = |
| element->PseudoElementLayoutObject(kPseudoIdBackdrop)) { |
| return result.Blend( |
| layout_object->ResolveColor(GetCSSPropertyBackgroundColor())); |
| } |
| } |
| } |
| auto* layout_view = GetLayoutView(); |
| if (layout_view) { |
| result = result.Blend( |
| layout_view->ResolveColor(GetCSSPropertyBackgroundColor())); |
| } |
| return result; |
| } |
| |
| void LocalFrameView::WillBeRemovedFromFrame() { |
| if (paint_artifact_compositor_) |
| paint_artifact_compositor_->WillBeRemovedFromFrame(); |
| |
| if (Settings* settings = frame_->GetSettings()) { |
| DCHECK(frame_->GetPage()); |
| if (settings->GetSpatialNavigationEnabled()) { |
| frame_->GetPage()->GetSpatialNavigationController().DidDetachFrameView( |
| *this); |
| } |
| } |
| } |
| |
| bool LocalFrameView::IsUpdatingLifecycle() const { |
| LocalFrameView* root_view = GetFrame().LocalFrameRoot().View(); |
| DCHECK(root_view); |
| return root_view->target_state_ != DocumentLifecycle::kUninitialized; |
| } |
| |
| LocalFrameView* LocalFrameView::ParentFrameView() const { |
| if (!IsAttached()) |
| return nullptr; |
| |
| Frame* parent_frame = frame_->Tree().Parent(); |
| if (auto* parent_local_frame = DynamicTo<LocalFrame>(parent_frame)) |
| return parent_local_frame->View(); |
| |
| return nullptr; |
| } |
| |
| LayoutEmbeddedContent* LocalFrameView::GetLayoutEmbeddedContent() const { |
| return frame_->OwnerLayoutObject(); |
| } |
| |
| void LocalFrameView::VisualViewportScrollbarsChanged() { |
| SetVisualViewportNeedsRepaint(); |
| if (LayoutView* layout_view = GetLayoutView()) |
| layout_view->Layer()->ClearClipRects(); |
| } |
| |
| void LocalFrameView::UpdateGeometriesIfNeeded() { |
| if (!needs_update_geometries_) |
| return; |
| needs_update_geometries_ = false; |
| HeapVector<Member<EmbeddedContentView>> views; |
| ForAllChildViewsAndPlugins( |
| [&](EmbeddedContentView& view) { views.push_back(view); }); |
| |
| for (const auto& view : views) { |
| // Script or plugins could detach the frame so abort processing if that |
| // happens. |
| if (!GetLayoutView()) |
| break; |
| |
| view->UpdateGeometry(); |
| } |
| // Explicitly free the backing store to avoid memory regressions. |
| // TODO(bikineev): Revisit after young generation is there. |
| views.clear(); |
| } |
| |
| bool LocalFrameView::UpdateAllLifecyclePhases(DocumentUpdateReason reason) { |
| AllowThrottlingScope allow_throttling(*this); |
| bool updated = GetFrame().LocalFrameRoot().View()->UpdateLifecyclePhases( |
| DocumentLifecycle::kPaintClean, reason); |
| |
| #if DCHECK_IS_ON() |
| if (updated) { |
| // This function should return true iff all non-throttled frames are in the |
| // kPaintClean lifecycle state. |
| ForAllNonThrottledLocalFrameViews([](LocalFrameView& frame_view) { |
| DCHECK_EQ(frame_view.Lifecycle().GetState(), |
| DocumentLifecycle::kPaintClean); |
| }); |
| |
| // A required intersection observation should run throttled frames to |
| // kLayoutClean. |
| ForAllThrottledLocalFrameViews([](LocalFrameView& frame_view) { |
| DCHECK(frame_view.intersection_observation_state_ != kRequired || |
| frame_view.IsDisplayLocked() || |
| frame_view.Lifecycle().GetState() >= |
| DocumentLifecycle::kLayoutClean); |
| }); |
| } |
| #endif |
| |
| return updated; |
| } |
| |
| bool LocalFrameView::UpdateAllLifecyclePhasesForTest() { |
| bool result = UpdateAllLifecyclePhases(DocumentUpdateReason::kTest); |
| RunPostLifecycleSteps(); |
| return result; |
| } |
| |
| // TODO(schenney): add a scrolling update lifecycle phase. |
| bool LocalFrameView::UpdateLifecycleToCompositingCleanPlusScrolling( |
| DocumentUpdateReason reason) { |
| if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) |
| return UpdateAllLifecyclePhasesExceptPaint(reason); |
| return GetFrame().LocalFrameRoot().View()->UpdateLifecyclePhases( |
| DocumentLifecycle::kCompositingAssignmentsClean, reason); |
| } |
| |
| bool LocalFrameView::UpdateLifecycleToPrePaintClean( |
| DocumentUpdateReason reason) { |
| return GetFrame().LocalFrameRoot().View()->UpdateLifecyclePhases( |
| DocumentLifecycle::kPrePaintClean, reason); |
| } |
| |
| bool LocalFrameView::UpdateLifecycleToCompositingInputsClean( |
| DocumentUpdateReason reason) { |
| if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) |
| return UpdateAllLifecyclePhasesExceptPaint(reason); |
| return GetFrame().LocalFrameRoot().View()->UpdateLifecyclePhases( |
| DocumentLifecycle::kCompositingInputsClean, reason); |
| } |
| |
| bool LocalFrameView::UpdateAllLifecyclePhasesExceptPaint( |
| DocumentUpdateReason reason) { |
| return GetFrame().LocalFrameRoot().View()->UpdateLifecyclePhases( |
| DocumentLifecycle::kCompositingAssignmentsClean, reason); |
| } |
| |
| void LocalFrameView::UpdateLifecyclePhasesForPrinting() { |
| auto* local_frame_view_root = GetFrame().LocalFrameRoot().View(); |
| local_frame_view_root->UpdateLifecyclePhases( |
| DocumentLifecycle::kCompositingAssignmentsClean, |
| DocumentUpdateReason::kPrinting); |
| |
| auto* detached_frame_view = this; |
| while (detached_frame_view->IsAttached() && |
| detached_frame_view != local_frame_view_root) { |
| detached_frame_view = detached_frame_view->ParentFrameView(); |
| CHECK(detached_frame_view); |
| } |
| |
| if (detached_frame_view == local_frame_view_root) |
| return; |
| DCHECK(!detached_frame_view->IsAttached()); |
| |
| // We are printing a detached frame or a descendant of a detached frame which |
| // was not reached in some phases during during |local_frame_view_root-> |
| // UpdateLifecyclePhasesnormal()|. We need the subtree to be ready for |
| // painting. |
| detached_frame_view->UpdateLifecyclePhases( |
| DocumentLifecycle::kCompositingAssignmentsClean, |
| DocumentUpdateReason::kPrinting); |
| } |
| |
| bool LocalFrameView::UpdateLifecycleToLayoutClean(DocumentUpdateReason reason) { |
| return GetFrame().LocalFrameRoot().View()->UpdateLifecyclePhases( |
| DocumentLifecycle::kLayoutClean, reason); |
| } |
| |
| void LocalFrameView::ScheduleVisualUpdateForPaintInvalidationIfNeeded() { |
| LocalFrame& local_frame_root = GetFrame().LocalFrameRoot(); |
| // We need a full lifecycle update to clear pending paint invalidations. |
| if (local_frame_root.View()->target_state_ < DocumentLifecycle::kPaintClean || |
| Lifecycle().GetState() >= DocumentLifecycle::kPrePaintClean) { |
| // Schedule visual update to process the paint invalidation in the next |
| // cycle. |
| local_frame_root.ScheduleVisualUpdateUnlessThrottled(); |
| } |
| // Otherwise the paint invalidation will be handled in the pre-paint and paint |
| // phase of this full lifecycle update. |
| } |
| |
| bool LocalFrameView::NotifyResizeObservers( |
| DocumentLifecycle::LifecycleState target_state) { |
| // Return true if lifecycles need to be re-run |
| TRACE_EVENT0("blink,benchmark", "LocalFrameView::NotifyResizeObservers"); |
| |
| if (target_state < DocumentLifecycle::kPaintClean) |
| return false; |
| |
| // Controller exists only if ResizeObserver was created. |
| ResizeObserverController* resize_controller = |
| ResizeObserverController::FromIfExists(*GetFrame().DomWindow()); |
| if (!resize_controller) |
| return false; |
| |
| DCHECK(Lifecycle().GetState() >= DocumentLifecycle::kPrePaintClean); |
| |
| size_t min_depth = resize_controller->GatherObservations(); |
| |
| if (min_depth != ResizeObserverController::kDepthBottom) { |
| resize_controller->DeliverObservations(); |
| } else { |
| // Observation depth limit reached |
| if (resize_controller->SkippedObservations() && |
| !resize_controller->IsLoopLimitErrorDispatched()) { |
| resize_controller->ClearObservations(); |
| ErrorEvent* error = ErrorEvent::Create( |
| "ResizeObserver loop limit exceeded", |
| SourceLocation::Capture(frame_->DomWindow()), nullptr); |
| // We're using |SanitizeScriptErrors::kDoNotSanitize| as the error is made |
| // by blink itself. |
| // TODO(yhirano): Reconsider this. |
| frame_->DomWindow()->DispatchErrorEvent( |
| error, SanitizeScriptErrors::kDoNotSanitize); |
| // Ensure notifications will get delivered in next cycle. |
| ScheduleAnimation(); |
| resize_controller->SetLoopLimitErrorDispatched(true); |
| } |
| if (Lifecycle().GetState() >= DocumentLifecycle::kPrePaintClean) |
| return false; |
| } |
| |
| // Lifecycle needs to be run again because Resize Observer affected layout |
| return true; |
| } |
| |
| bool LocalFrameView::LocalFrameTreeAllowsThrottling() const { |
| if (LocalFrameView* root_view = GetFrame().LocalFrameRoot().View()) |
| return root_view->allow_throttling_; |
| return false; |
| } |
| |
| void LocalFrameView::PrepareForLifecycleUpdateRecursive() { |
| // We will run lifecycle phases for LocalFrameViews that are unthrottled; or |
| // are throttled but require IntersectionObserver steps to run. |
| if (!ShouldThrottleRendering() || |
| intersection_observation_state_ == kRequired) { |
| Lifecycle().EnsureStateAtMost(DocumentLifecycle::kVisualUpdatePending); |
| ForAllChildLocalFrameViews([](LocalFrameView& child) { |
| child.PrepareForLifecycleUpdateRecursive(); |
| }); |
| } |
| } |
| |
| // TODO(leviw): We don't assert lifecycle information from documents in child |
| // WebPluginContainerImpls. |
| bool LocalFrameView::UpdateLifecyclePhases( |
| DocumentLifecycle::LifecycleState target_state, |
| DocumentUpdateReason reason) { |
| // If the lifecycle is postponed, which can happen if the inspector requests |
| // it, then we shouldn't update any lifecycle phases. |
| if (UNLIKELY(frame_->GetDocument() && |
| frame_->GetDocument()->Lifecycle().LifecyclePostponed())) { |
| return false; |
| } |
| |
| // Prevent reentrance. |
| // TODO(vmpstr): Should we just have a DCHECK instead here? |
| if (UNLIKELY(IsUpdatingLifecycle())) { |
| NOTREACHED() |
| << "LocalFrameView::updateLifecyclePhasesInternal() reentrance"; |
| return false; |
| } |
| |
| // This must be called from the root frame, or a detached frame for printing, |
| // since it recurses down, not up. Otherwise the lifecycles of the frames |
| // might be out of sync. |
| DCHECK(frame_->IsLocalRoot() || !IsAttached()); |
| |
| DCHECK(LocalFrameTreeAllowsThrottling() || |
| (target_state < DocumentLifecycle::kPaintClean)); |
| |
| // Only the following target states are supported. |
| DCHECK(target_state == DocumentLifecycle::kLayoutClean || |
| target_state == DocumentLifecycle::kAccessibilityClean || |
| target_state == DocumentLifecycle::kCompositingInputsClean || |
| target_state == DocumentLifecycle::kCompositingAssignmentsClean || |
| target_state == DocumentLifecycle::kPrePaintClean || |
| target_state == DocumentLifecycle::kPaintClean); |
| lifecycle_update_count_for_testing_++; |
| |
| // If the document is not active then it is either not yet initialized, or it |
| // is stopping. In either case, we can't reach one of the supported target |
| // states. |
| if (!frame_->GetDocument()->IsActive()) |
| return false; |
| |
| if (frame_->IsLocalRoot()) |
| UpdateLayerDebugInfoEnabled(); |
| |
| // If we're throttling and we aren't required to run the IntersectionObserver |
| // steps, then we don't need to update lifecycle phases. The throttling status |
| // will get updated in RunPostLifecycleSteps(). |
| if (ShouldThrottleRendering() && |
| intersection_observation_state_ < kRequired) { |
| return Lifecycle().GetState() == target_state; |
| } |
| |
| PrepareForLifecycleUpdateRecursive(); |
| |
| // This is used to guard against reentrance. It is also used in conjunction |
| // with the current lifecycle state to determine which phases are yet to run |
| // in this cycle. Note that this may change the return value of |
| // ShouldThrottleRendering(), hence it cannot be moved before the preceeding |
| // code, which relies on the prior value of ShouldThrottleRendering(). |
| base::AutoReset<DocumentLifecycle::LifecycleState> target_state_scope( |
| &target_state_, target_state); |
| |
| lifecycle_data_.start_time = base::TimeTicks::Now(); |
| ++lifecycle_data_.count; |
| |
| if (target_state == DocumentLifecycle::kPaintClean) { |
| { |
| TRACE_EVENT0("blink", "LocalFrameView::WillStartLifecycleUpdate"); |
| |
| ForAllNonThrottledLocalFrameViews([](LocalFrameView& frame_view) { |
| auto lifecycle_observers = frame_view.lifecycle_observers_; |
| for (auto& observer : lifecycle_observers) |
| observer->WillStartLifecycleUpdate(frame_view); |
| }); |
| } |
| |
| { |
| TRACE_EVENT0( |
| "blink", |
| "LocalFrameView::UpdateLifecyclePhases - start of lifecycle tasks"); |
| ForAllNonThrottledLocalFrameViews([](LocalFrameView& frame_view) { |
| WTF::Vector<base::OnceClosure> tasks; |
| frame_view.start_of_lifecycle_tasks_.swap(tasks); |
| for (auto& task : tasks) |
| std::move(task).Run(); |
| }); |
| } |
| } |
| |
| // Run the lifecycle updates. |
| UpdateLifecyclePhasesInternal(target_state); |
| |
| if (target_state == DocumentLifecycle::kPaintClean) { |
| TRACE_EVENT0("blink", "LocalFrameView::DidFinishLifecycleUpdate"); |
| |
| ForAllNonThrottledLocalFrameViews([](LocalFrameView& frame_view) { |
| auto lifecycle_observers = frame_view.lifecycle_observers_; |
| for (auto& observer : lifecycle_observers) |
| observer->DidFinishLifecycleUpdate(frame_view); |
| }); |
| } |
| |
| // Hit testing metrics include the entire time processing a document update |
| // in preparation for a hit test. |
| if (reason == DocumentUpdateReason::kHitTest) { |
| LocalFrameUkmAggregator& aggregator = EnsureUkmAggregator(); |
| aggregator.RecordSample( |
| static_cast<size_t>(LocalFrameUkmAggregator::kHitTestDocumentUpdate), |
| lifecycle_data_.start_time, base::TimeTicks::Now()); |
| } |
| |
| return Lifecycle().GetState() == target_state; |
| } |
| |
| void LocalFrameView::UpdateLifecyclePhasesInternal( |
| DocumentLifecycle::LifecycleState target_state) { |
| // RunScrollTimelineSteps must not run more than once. |
| bool should_run_scroll_timeline_steps = true; |
| |
| // Run style, layout, compositing and prepaint lifecycle phases and deliver |
| // resize observations if required. Resize observer callbacks/delegates have |
| // the potential to dirty layout (until loop limit is reached) and therefore |
| // the above lifecycle phases need to be re-run until the limit is reached |
| // or no layout is pending. |
| // Note that after ResizeObserver has settled, we also run intersection |
| // observations that need to be delievered in post-layout. This process can |
| // also dirty layout, which will run this loop again. |
| |
| // A LocalFrameView can be unthrottled at this point, but become throttled as |
| // it advances through lifecycle stages. If that happens, it will prevent |
| // subsequent passes through the loop from updating the newly-throttled views. |
| // To avoid that, we lock in the set of unthrottled views before entering the |
| // loop. |
| HeapVector<Member<LocalFrameView>> unthrottled_frame_views; |
| ForAllNonThrottledLocalFrameViews( |
| [&unthrottled_frame_views](LocalFrameView& frame_view) { |
| unthrottled_frame_views.push_back(&frame_view); |
| }); |
| |
| while (true) { |
| for (LocalFrameView* frame_view : unthrottled_frame_views) { |
| frame_view->Lifecycle().EnsureStateAtMost( |
| DocumentLifecycle::kVisualUpdatePending); |
| } |
| bool run_more_lifecycle_phases = |
| RunStyleAndLayoutLifecyclePhases(target_state); |
| if (!run_more_lifecycle_phases) |
| return; |
| DCHECK(Lifecycle().GetState() >= DocumentLifecycle::kLayoutClean); |
| |
| if (!GetLayoutView()) |
| return; |
| |
| { |
| // We need scoping braces here because this |
| // DisallowLayoutInvalidationScope is meant to be in effect during |
| // pre-paint, but not during ResizeObserver. |
| #if DCHECK_IS_ON() |
| DisallowLayoutInvalidationScope disallow_layout_invalidation(this); |
| #endif |
| |
| DCHECK_GE(target_state, DocumentLifecycle::kAccessibilityClean); |
| run_more_lifecycle_phases = RunAccessibilityLifecyclePhase(target_state); |
| DCHECK(ShouldThrottleRendering() || !ExistingAXObjectCache() || |
| Lifecycle().GetState() == DocumentLifecycle::kAccessibilityClean); |
| if (!run_more_lifecycle_phases) |
| return; |
| |
| TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), |
| "SetLayerTreeId", TRACE_EVENT_SCOPE_THREAD, "data", |
| inspector_set_layer_tree_id::Data(frame_.Get())); |
| TRACE_EVENT1("devtools.timeline", "UpdateLayerTree", "data", |
| inspector_update_layer_tree_event::Data(frame_.Get())); |
| |
| run_more_lifecycle_phases = |
| RunCompositingInputsLifecyclePhase(target_state); |
| if (!run_more_lifecycle_phases) |
| return; |
| |
| // TODO(pdr): PrePaint should be under the "Paint" devtools timeline step |
| // when CompositeAfterPaint is enabled. |
| run_more_lifecycle_phases = RunPrePaintLifecyclePhase(target_state); |
| DCHECK(ShouldThrottleRendering() || |
| Lifecycle().GetState() >= DocumentLifecycle::kPrePaintClean); |
| if (ShouldThrottleRendering() || !run_more_lifecycle_phases) |
| return; |
| |
| run_more_lifecycle_phases = |
| RunCompositingAssignmentsLifecyclePhase(target_state); |
| if (!run_more_lifecycle_phases) { |
| return; |
| } |
| } |
| |
| // Some features may require several passes over style and layout |
| // within the same lifecycle update. |
| bool needs_to_repeat_lifecycle = false; |
| |
| // ScrollTimelines may be associated with a source that never had a |
| // a chance to get a layout box at the time style was calculated; when |
| // this situation happens, RunScrollTimelineSteps will re-snapshot all |
| // affected timelines and dirty style for associated effect targets. |
| // |
| // https://github.com/w3c/csswg-drafts/issues/5261 |
| if (RuntimeEnabledFeatures::CSSScrollTimelineEnabled() && |
| should_run_scroll_timeline_steps) { |
| should_run_scroll_timeline_steps = false; |
| needs_to_repeat_lifecycle = RunScrollTimelineSteps(); |
| if (needs_to_repeat_lifecycle) |
| continue; |
| } |
| |
| // ResizeObserver and post-layout IntersectionObserver observation |
| // deliveries may dirty style and layout. RunResizeObserverSteps will return |
| // true if any observer ran that may have dirtied style or layout; |
| // RunPostLayoutIntersectionObserverSteps will return true if any |
| // observations led to content-visibility intersection changing visibility |
| // state synchronously (which happens on the first intersection |
| // observeration of a context). |
| needs_to_repeat_lifecycle = RunResizeObserverSteps(target_state); |
| // Only run the rest of the steps here if resize observer is done. |
| if (needs_to_repeat_lifecycle) |
| continue; |
| |
| needs_to_repeat_lifecycle = RunPostLayoutIntersectionObserverSteps(); |
| if (!needs_to_repeat_lifecycle) |
| break; |
| } |
| |
| // Once we exit the ResizeObserver / IntersectionObserver loop above, we need |
| // to clear the resize observer limits so that next time we run this, we can |
| // deliver more observations. |
| ClearResizeObserverLimit(); |
| |
| // Layout invalidation scope was disabled for resize observer |
| // re-enable it for subsequent steps |
| #if DCHECK_IS_ON() |
| DisallowLayoutInvalidationScope disallow_layout_invalidation(this); |
| #endif |
| |
| DCHECK_EQ(target_state, DocumentLifecycle::kPaintClean); |
| RunPaintLifecyclePhase(); |
| DCHECK(ShouldThrottleRendering() || AnyFrameIsPrintingOrPaintingPreview() || |
| Lifecycle().GetState() == DocumentLifecycle::kPaintClean); |
| |
| ForAllRemoteFrameViews( |
| [](RemoteFrameView& frame_view) { frame_view.UpdateCompositingRect(); }); |
| } |
| |
| bool LocalFrameView::RunScrollTimelineSteps() { |
| DCHECK_GE(Lifecycle().GetState(), |
| DocumentLifecycle::kCompositingAssignmentsClean); |
| bool re_run_lifecycles = false; |
| ForAllNonThrottledLocalFrameViews( |
| [&re_run_lifecycles](LocalFrameView& frame_view) { |
| frame_view.GetFrame() |
| .GetDocument() |
| ->GetDocumentAnimations() |
| .ValidateTimelines(); |
| re_run_lifecycles |= (frame_view.Lifecycle().GetState() < |
| DocumentLifecycle::kCompositingAssignmentsClean); |
| }); |
| return re_run_lifecycles; |
| } |
| |
| bool LocalFrameView::RunResizeObserverSteps( |
| DocumentLifecycle::LifecycleState target_state) { |
| bool re_run_lifecycles = false; |
| if (target_state == DocumentLifecycle::kPaintClean) { |
| ForAllNonThrottledLocalFrameViews( |
| [&re_run_lifecycles](LocalFrameView& frame_view) { |
| bool result = |
| frame_view.NotifyResizeObservers(DocumentLifecycle::kPaintClean); |
| re_run_lifecycles = re_run_lifecycles || result; |
| }); |
| } |
| return re_run_lifecycles; |
| } |
| |
| void LocalFrameView::ClearResizeObserverLimit() { |
| ForAllNonThrottledLocalFrameViews([](LocalFrameView& frame_view) { |
| ResizeObserverController* resize_controller = |
| ResizeObserverController::From(*frame_view.frame_->DomWindow()); |
| resize_controller->ClearMinDepth(); |
| resize_controller->SetLoopLimitErrorDispatched(false); |
| }); |
| } |
| |
| bool LocalFrameView::RunStyleAndLayoutLifecyclePhases( |
| DocumentLifecycle::LifecycleState target_state) { |
| TRACE_EVENT0("blink,benchmark", |
| "LocalFrameView::RunStyleAndLayoutLifecyclePhases"); |
| UpdateStyleAndLayoutIfNeededRecursive(); |
| DCHECK(ShouldThrottleRendering() || |
| Lifecycle().GetState() >= DocumentLifecycle::kLayoutClean); |
| if (Lifecycle().GetState() < DocumentLifecycle::kLayoutClean) |
| return false; |
| |
| frame_->GetDocument() |
| ->GetRootScrollerController() |
| .PerformRootScrollerSelection(); |
| |
| // PerformRootScrollerSelection can dirty layout if an effective root |
| // scroller is changed so make sure we get back to LayoutClean. |
| if (RuntimeEnabledFeatures::ImplicitRootScrollerEnabled()) { |
| ForAllNonThrottledLocalFrameViews([](LocalFrameView& frame_view) { |
| if (frame_view.NeedsLayout()) |
| frame_view.UpdateLayout(); |
| |
| DCHECK(frame_view.Lifecycle().GetState() >= |
| DocumentLifecycle::kLayoutClean); |
| }); |
| } |
| |
| if (target_state == DocumentLifecycle::kLayoutClean) |
| return false; |
| |
| // Now we can run post layout steps in preparation for further phases. |
| ForAllNonThrottledLocalFrameViews([](LocalFrameView& frame_view) { |
| frame_view.PerformScrollAnchoringAdjustments(); |
| }); |
| |
| frame_->GetDocument()->PerformScrollSnappingTasks(); |
| |
| EnqueueScrollEvents(); |
| |
| frame_->GetPage()->GetValidationMessageClient().LayoutOverlay(); |
| |
| if (target_state == DocumentLifecycle::kPaintClean) { |
| ForAllNonThrottledLocalFrameViews([](LocalFrameView& frame_view) { |
| frame_view.NotifyFrameRectsChangedIfNeeded(); |
| }); |
| } |
| |
| return Lifecycle().GetState() >= DocumentLifecycle::kLayoutClean; |
| } |
| |
| bool LocalFrameView::RunCompositingInputsLifecyclePhase( |
| DocumentLifecycle::LifecycleState target_state) { |
| TRACE_EVENT0("blink,benchmark", |
| "LocalFrameView::RunCompositingInputsLifecyclePhase"); |
| auto* layout_view = GetLayoutView(); |
| DCHECK(layout_view); |
| |
| if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { |
| SCOPED_UMA_AND_UKM_TIMER(EnsureUkmAggregator(), |
| LocalFrameUkmAggregator::kCompositingInputs); |
| layout_view->Compositor()->UpdateInputsIfNeededRecursive(target_state); |
| } else { |
| ForAllNonThrottledLocalFrameViews([](LocalFrameView& frame_view) { |
| frame_view.Lifecycle().AdvanceTo( |
| DocumentLifecycle::kCompositingInputsClean); |
| }); |
| } |
| |
| return target_state > DocumentLifecycle::kCompositingInputsClean; |
| } |
| |
| bool LocalFrameView::RunCompositingAssignmentsLifecyclePhase( |
| DocumentLifecycle::LifecycleState target_state) { |
| TRACE_EVENT0("blink,benchmark", |
| "LocalFrameView::RunCompositingAssignmentsLifecyclePhase"); |
| auto* layout_view = GetLayoutView(); |
| DCHECK(layout_view); |
| |
| if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { |
| SCOPED_UMA_AND_UKM_TIMER(EnsureUkmAggregator(), |
| LocalFrameUkmAggregator::kCompositingAssignments); |
| layout_view->Compositor()->UpdateAssignmentsIfNeededRecursive(target_state); |
| } else { |
| ForAllNonThrottledLocalFrameViews([](LocalFrameView& frame_view) { |
| frame_view.Lifecycle().AdvanceTo( |
| DocumentLifecycle::kCompositingAssignmentsClean); |
| }); |
| } |
| |
| UpdateCompositedSelectionIfNeeded(); |
| |
| frame_->GetPage()->GetValidationMessageClient().UpdatePrePaint(); |
| ForAllNonThrottledLocalFrameViews([](LocalFrameView& view) { |
| view.frame_->UpdateFrameColorOverlayPrePaint(); |
| }); |
| if (auto* web_local_frame_impl = WebLocalFrameImpl::FromFrame(frame_)) |
| web_local_frame_impl->UpdateDevToolsOverlaysPrePaint(); |
| |
| return target_state > DocumentLifecycle::kCompositingAssignmentsClean; |
| } |
| |
| bool LocalFrameView::RunPrePaintLifecyclePhase( |
| DocumentLifecycle::LifecycleState target_state) { |
| TRACE_EVENT0("blink,benchmark", "LocalFrameView::RunPrePaintLifecyclePhase"); |
| |
| if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { |
| // TODO(pdr): This descendant dependent treewalk should be integrated into |
| // the prepaint tree walk. |
| #if DCHECK_IS_ON() |
| SetIsUpdatingDescendantDependentFlags(true); |
| #endif |
| ForAllNonThrottledLocalFrameViews([](LocalFrameView& frame_view) { |
| frame_view.GetLayoutView()->Layer()->UpdateDescendantDependentFlags(); |
| frame_view.GetLayoutView()->CommitPendingSelection(); |
| }); |
| #if DCHECK_IS_ON() |
| SetIsUpdatingDescendantDependentFlags(false); |
| #endif |
| } |
| |
| ForAllNonThrottledLocalFrameViews( |
| [](LocalFrameView& frame_view) { |
| frame_view.Lifecycle().AdvanceTo(DocumentLifecycle::kInPrePaint); |
| // We skipped pre-paint for this frame while it was throttled, or we |
| // have never run pre-paint for this frame. Either way, we're |
| // unthrottled now, so we must propagate our dirty bits into our |
| // parent frame so that pre-paint reaches into this frame. |
| if (LayoutView* layout_view = frame_view.GetLayoutView()) { |
| if (auto* owner = frame_view.GetFrame().OwnerLayoutObject()) { |
| if (layout_view->NeedsPaintPropertyUpdate() || |
| layout_view->DescendantNeedsPaintPropertyUpdate()) { |
| owner->SetDescendantNeedsPaintPropertyUpdate(); |
| } |
| if (layout_view->ShouldCheckForPaintInvalidation()) { |
| owner->SetShouldCheckForPaintInvalidation(); |
| } |
| if (layout_view->EffectiveAllowedTouchActionChanged() || |
| layout_view->DescendantEffectiveAllowedTouchActionChanged()) { |
| owner->MarkDescendantEffectiveAllowedTouchActionChanged(); |
| } |
| if (layout_view->BlockingWheelEventHandlerChanged() || |
| layout_view->DescendantBlockingWheelEventHandlerChanged()) { |
| owner->MarkDescendantBlockingWheelEventHandlerChanged(); |
| } |
| if (RuntimeEnabledFeatures::CullRectUpdateEnabled() && |
| (layout_view->Layer()->NeedsCullRectUpdate() || |
| layout_view->Layer()->DescendantNeedsCullRectUpdate())) { |
| layout_view->Layer() |
| ->MarkCompositingContainerChainForNeedsCullRectUpdate(); |
| } |
| } |
| } |
| }, |
| // Use post-order to ensure correct flag propagation for nested frames. |
| kPostOrder); |
| |
| { |
| SCOPED_UMA_AND_UKM_TIMER(EnsureUkmAggregator(), |
| LocalFrameUkmAggregator::kPrePaint); |
| |
| GetPage()->GetLinkHighlight().UpdateBeforePrePaint(); |
| PrePaintTreeWalk().WalkTree(*this); |
| GetPage()->GetLinkHighlight().UpdateAfterPrePaint(); |
| } |
| |
| ForAllNonThrottledLocalFrameViews([](LocalFrameView& frame_view) { |
| frame_view.Lifecycle().AdvanceTo(DocumentLifecycle::kPrePaintClean); |
| }); |
| |
| return target_state > DocumentLifecycle::kPrePaintClean; |
| } |
| |
| bool LocalFrameView::AnyFrameIsPrintingOrPaintingPreview() { |
| bool any_frame_is_printing_or_painting_preview = false; |
| ForAllNonThrottledLocalFrameViews( |
| [&any_frame_is_printing_or_painting_preview](LocalFrameView& frame_view) { |
| if (frame_view.GetFrame().GetDocument()->IsPrintingOrPaintingPreview()) |
| any_frame_is_printing_or_painting_preview = true; |
| }); |
| return any_frame_is_printing_or_painting_preview; |
| } |
| |
| void LocalFrameView::RunPaintLifecyclePhase(PaintBenchmarkMode benchmark_mode) { |
| TRACE_EVENT0("blink,benchmark", "LocalFrameView::RunPaintLifecyclePhase"); |
| // While printing or capturing a paint preview of a document, the paint walk |
| // is done into a special canvas. There is no point doing a normal paint step |
| // (or animations update) when in this mode. |
| if (AnyFrameIsPrintingOrPaintingPreview()) |
| return; |
| |
| bool repainted = PaintTree(benchmark_mode); |
| |
| if (paint_artifact_compositor_ && |
| (benchmark_mode == |
| PaintBenchmarkMode::kForcePaintArtifactCompositorUpdate || |
| // TODO(paint-dev): Separate requirement for update for repaint and full |
| // PaintArtifactCompositor update. |
| (RuntimeEnabledFeatures::CompositeAfterPaintEnabled() && |
| benchmark_mode != PaintBenchmarkMode::kNormal))) { |
| paint_artifact_compositor_->SetNeedsUpdate(); |
| } |
| |
| bool needed_update = |
| !paint_artifact_compositor_ || paint_artifact_compositor_->NeedsUpdate(); |
| PushPaintArtifactToCompositor(repainted); |
| size_t total_animations_count = 0; |
| bool current_frame_had_raf = false; |
| bool next_frame_has_pending_raf = false; |
| ForAllNonThrottledLocalFrameViews( |
| [this, &total_animations_count, ¤t_frame_had_raf, |
| &next_frame_has_pending_raf](LocalFrameView& frame_view) { |
| if (auto* scrollable_area = frame_view.GetScrollableArea()) |
| scrollable_area->UpdateCompositorScrollAnimations(); |
| if (const auto* animating_scrollable_areas = |
| frame_view.AnimatingScrollableAreas()) { |
| for (PaintLayerScrollableArea* area : *animating_scrollable_areas) |
| area->UpdateCompositorScrollAnimations(); |
| } |
| { |
| // Updating animations can notify ready promises which could mutate |
| // the DOM. We should delay these until we have finished the lifecycle |
| // update. https://crbug.com/1196781 |
| ScriptForbiddenScope forbid_script; |
| frame_view.GetLayoutView() |
| ->GetDocument() |
| .GetDocumentAnimations() |
| .UpdateAnimations(DocumentLifecycle::kPaintClean, |
| paint_artifact_compositor_.get()); |
| } |
| Document& document = frame_view.GetLayoutView()->GetDocument(); |
| total_animations_count += |
| document.GetDocumentAnimations().GetAnimationsCount(); |
| current_frame_had_raf |= document.CurrentFrameHadRAF(); |
| next_frame_has_pending_raf |= document.NextFrameHasPendingRAF(); |
| }); |
| |
| if (GetLayoutView()->GetDocument().View() && |
| GetLayoutView()->GetDocument().View()->GetCompositorAnimationHost()) { |
| GetLayoutView() |
| ->GetDocument() |
| .View() |
| ->GetCompositorAnimationHost() |
| ->SetAnimationCounts(total_animations_count, current_frame_had_raf, |
| next_frame_has_pending_raf); |
| } |
| |
| // Initialize animation properties in the newly created paint property |
| // nodes according to the current animation state. This is mainly for |
| // the running composited animations which didn't change state during |
| // above UpdateAnimations() but associated with new paint property nodes. |
| if (needed_update) { |
| auto* root_layer = RootCcLayer(); |
| if (root_layer && root_layer->layer_tree_host()) { |
| root_layer->layer_tree_host()->mutator_host()->InitClientAnimationState(); |
| } |
| } |
| |
| // Notify the controller that the artifact has been pushed and some |
| // lifecycle state can be freed (such as raster invalidations). |
| if (paint_controller_) |
| paint_controller_->FinishCycle(); |
| |
| if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { |
| auto* root = GetLayoutView()->Compositor()->PaintRootGraphicsLayer(); |
| if (root) { |
| ForAllPaintingGraphicsLayers(*root, [](GraphicsLayer& layer) { |
| // Notify the paint controller that the artifact has been pushed and |
| // some lifecycle state can be freed (such as raster invalidations). |
| layer.GetPaintController().FinishCycle(); |
| }); |
| } |
| } |
| |
| if (paint_artifact_compositor_) |
| paint_artifact_compositor_->ClearPropertyTreeChangedState(); |
| |
| if (GetPage()) |
| GetPage()->Animator().ReportFrameAnimations(GetCompositorAnimationHost()); |
| } |
| |
| bool LocalFrameView::RunAccessibilityLifecyclePhase( |
| DocumentLifecycle::LifecycleState target_state) { |
| TRACE_EVENT0("blink,benchmark", |
| "LocalFrameView::RunAccessibilityLifecyclePhase"); |
| ForAllNonThrottledLocalFrameViews([](LocalFrameView& frame_view) { |
| if (AXObjectCache* cache = frame_view.ExistingAXObjectCache()) { |
| frame_view.Lifecycle().AdvanceTo(DocumentLifecycle::kInAccessibility); |
| cache->ProcessDeferredAccessibilityEvents( |
| *frame_view.GetFrame().GetDocument()); |
| frame_view.Lifecycle().AdvanceTo(DocumentLifecycle::kAccessibilityClean); |
| } |
| }); |
| |
| return target_state > DocumentLifecycle::kAccessibilityClean; |
| } |
| |
| void LocalFrameView::EnqueueScrollAnchoringAdjustment( |
| ScrollableArea* scrollable_area) { |
| anchoring_adjustment_queue_.insert(scrollable_area); |
| } |
| |
| void LocalFrameView::DequeueScrollAnchoringAdjustment( |
| ScrollableArea* scrollable_area) { |
| anchoring_adjustment_queue_.erase(scrollable_area); |
| } |
| |
| void LocalFrameView::SetNeedsEnqueueScrollEvent( |
| PaintLayerScrollableArea* scrollable_area) { |
| scroll_event_queue_.insert(scrollable_area); |
| GetPage()->Animator().ScheduleVisualUpdate(frame_.Get()); |
| } |
| |
| void LocalFrameView::PerformScrollAnchoringAdjustments() { |
| // Adjust() will cause a scroll which could end up causing a layout and |
| // reentering this method. Copy and clear the queue so we don't modify it |
| // during iteration. |
| AnchoringAdjustmentQueue queue_copy = anchoring_adjustment_queue_; |
| anchoring_adjustment_queue_.clear(); |
| |
| for (const WeakMember<ScrollableArea>& scroller : queue_copy) { |
| if (scroller) { |
| DCHECK(scroller->GetScrollAnchor()); |
| scroller->GetScrollAnchor()->Adjust(); |
| } |
| } |
| } |
| |
| void LocalFrameView::EnqueueScrollEvents() { |
| ForAllNonThrottledLocalFrameViews([](LocalFrameView& frame_view) { |
| for (const WeakMember<PaintLayerScrollableArea>& scroller : |
| frame_view.scroll_event_queue_) { |
| if (scroller) |
| scroller->EnqueueScrollEventIfNeeded(); |
| } |
| frame_view.scroll_event_queue_.clear(); |
| }); |
| } |
| |
| bool LocalFrameView::PaintTree(PaintBenchmarkMode benchmark_mode) { |
| SCOPED_UMA_AND_UKM_TIMER(EnsureUkmAggregator(), |
| LocalFrameUkmAggregator::kPaint); |
| |
| DCHECK(GetFrame().IsLocalRoot()); |
| |
| auto* layout_view = GetLayoutView(); |
| DCHECK(layout_view); |
| paint_frame_count_++; |
| ForAllNonThrottledLocalFrameViews( |
| [](LocalFrameView& frame_view) { |
| frame_view.MarkFirstEligibleToPaint(); |
| frame_view.Lifecycle().AdvanceTo(DocumentLifecycle::kInPaint); |
| // Propagate child frame PaintLayer NeedsRepaint flag into the owner |
| // frame. |
| if (auto* frame_layout_view = frame_view.GetLayoutView()) { |
| if (auto* owner = frame_view.GetFrame().OwnerLayoutObject()) { |
| PaintLayer* frame_root_layer = frame_layout_view->Layer(); |
| DCHECK(frame_root_layer); |
| DCHECK(owner->Layer()); |
| // In pre-CompositeAfterPaint the root layer's SelfNeedsRepaint() |
| // means it's compositing state has changed, so propagate the flag |
| // to owner. Or propagate DescendantNeedsRepaint only if it is not |
| // composited. In CompositeAfterPaint, the whole condition can be |
| // changed to |if |
| // (frame_root_layer->SelfOrDescendantNeedsRepaint())|. |
| if (frame_root_layer->SelfNeedsRepaint() || |
| (frame_root_layer->DescendantNeedsRepaint() && |
| frame_root_layer->GetCompositingState() != |
| kPaintsIntoOwnBacking)) |
| owner->Layer()->SetDescendantNeedsRepaint(); |
| } |
| } |
| }, |
| // Use post-order to ensure correct flag propagation for nested frames. |
| kPostOrder); |
| |
| ForAllThrottledLocalFrameViews( |
| [](LocalFrameView& frame_view) { frame_view.MarkIneligibleToPaint(); }); |
| |
| bool repainted = false; |
| bool needs_clear_repaint_flags = false; |
| |
| if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { |
| EnsurePaintController(); |
| |
| PaintController::ScopedBenchmarkMode scoped_benchmark(*paint_controller_, |
| benchmark_mode); |
| |
| // TODO(crbug.com/917911): Painting of overlays should not force repainting |
| // of the frame contents. |
| auto* web_local_frame_impl = WebLocalFrameImpl::FromFrame(frame_); |
| bool has_dev_tools_overlays = |
| web_local_frame_impl && web_local_frame_impl->HasDevToolsOverlays(); |
| if (!paint_controller_->ShouldForcePaintForBenchmark() && |
| !GetLayoutView()->Layer()->SelfOrDescendantNeedsRepaint() && |
| !visual_viewport_needs_repaint_ && !has_dev_tools_overlays) { |
| paint_controller_->UpdateUMACountsOnFullyCached(); |
| } else { |
| GraphicsContext graphics_context(*paint_controller_); |
| if (Settings* settings = frame_->GetSettings()) { |
| graphics_context.SetDarkModeEnabled( |
| settings->GetForceDarkModeEnabled() && |
| !GetLayoutView()->StyleRef().DarkColorScheme()); |
| } |
| |
| bool painted_full_screen_overlay = false; |
| if (frame_->IsMainFrame()) { |
| PaintLayer* full_screen_layer = GetFullScreenOverlayLayer(); |
| if (full_screen_layer) { |
| PaintLayerPainter(*full_screen_layer) |
| .Paint(graphics_context, CullRect::Infinite(), |
| kGlobalPaintNormalPhase, 0); |
| painted_full_screen_overlay = true; |
| } |
| } |
| |
| if (!painted_full_screen_overlay) { |
| PaintInternal(graphics_context, kGlobalPaintNormalPhase, |
| CullRect::Infinite()); |
| |
| GetPage()->GetValidationMessageClient().PaintOverlay(graphics_context); |
| ForAllNonThrottledLocalFrameViews( |
| [&graphics_context](LocalFrameView& view) { |
| view.frame_->PaintFrameColorOverlay(graphics_context); |
| }); |
| |
| // Devtools overlays query the inspected page's paint data so this |
| // update needs to be after other paintings. |
| if (has_dev_tools_overlays) |
| web_local_frame_impl->PaintDevToolsOverlays(graphics_context); |
| |
| if (frame_->IsMainFrame()) |
| GetPage()->GetVisualViewport().Paint(graphics_context); |
| } |
| |
| // Link highlights paint after all other paintings. |
| GetPage()->GetLinkHighlight().Paint(graphics_context); |
| |
| paint_controller_->CommitNewDisplayItems(); |
| repainted = true; |
| // TODO(paint-dev): Implement repaint-only update for CompositeAfterPaint. |
| SetPaintArtifactCompositorNeedsUpdate(); |
| |
| // As if we created a root layer containing all paintings which needs full |
| // layerization. |
| pre_composited_layers_ = { |
| {PaintChunkSubset(paint_controller_->GetPaintArtifactShared())}}; |
| } |
| } else { |
| // A null graphics layer can occur for painting of SVG images that are not |
| // parented into the main frame tree, or when the LocalFrameView is the main |
| // frame view of a page overlay. The page overlay is in the layer tree of |
| // the host page and will be painted during painting of the host page. |
| paint_controller_ = |
| std::make_unique<PaintController>(PaintController::kTransient); |
| pre_composited_layers_.clear(); |
| GraphicsContext graphics_context(*paint_controller_); |
| |
| if (GraphicsLayer* root = |
| layout_view->Compositor()->PaintRootGraphicsLayer()) { |
| repainted = root->PaintRecursively( |
| graphics_context, pre_composited_layers_, benchmark_mode); |
| if (visual_viewport_needs_repaint_ && paint_artifact_compositor_) |
| paint_artifact_compositor_->SetNeedsUpdate(); |
| |
| { |
| PaintChunkSubsetRecorder subset_recorder(*paint_controller_); |
| if (root == GetLayoutView()->Compositor()->RootGraphicsLayer() && |
| frame_->IsMainFrame()) { |
| GetPage()->GetVisualViewport().Paint(graphics_context); |
| } |
| // Link highlights paint after all other layers. |
| GetPage()->GetLinkHighlight().Paint(graphics_context); |
| pre_composited_layers_.push_back( |
| PreCompositedLayerInfo{subset_recorder.Get()}); |
| } |
| |
| paint_controller_->CommitNewDisplayItems(); |
| paint_controller_ = nullptr; |
| } else { |
| needs_clear_repaint_flags = true; |
| } |
| } |
| |
| visual_viewport_needs_repaint_ = false; |
| |
| needs_clear_repaint_flags |= repainted; |
| ForAllNonThrottledLocalFrameViews( |
| [needs_clear_repaint_flags](LocalFrameView& frame_view) { |
| frame_view.Lifecycle().AdvanceTo(DocumentLifecycle::kPaintClean); |
| if (needs_clear_repaint_flags) { |
| if (auto* layout_view = frame_view.GetLayoutView()) |
| layout_view->Layer()->ClearNeedsRepaintRecursively(); |
| } |
| frame_view.GetPaintTimingDetector().NotifyPaintFinished(); |
| }); |
| |
| PaintController::ReportUMACounts(); |
| return repainted; |
| } |
| |
| const cc::Layer* LocalFrameView::RootCcLayer() const { |
| return paint_artifact_compositor_ ? paint_artifact_compositor_->RootLayer() |
| : nullptr; |
| } |
| |
| void LocalFrameView::PushPaintArtifactToCompositor(bool repainted) { |
| TRACE_EVENT0("blink", "LocalFrameView::pushPaintArtifactToCompositor"); |
| if (!frame_->GetSettings()->GetAcceleratedCompositingEnabled()) { |
| if (paint_artifact_compositor_) { |
| paint_artifact_compositor_->WillBeRemovedFromFrame(); |
| paint_artifact_compositor_ = nullptr; |
| } |
| return; |
| } |
| |
| Page* page = GetFrame().GetPage(); |
| if (!page) |
| return; |
| |
| if (!paint_artifact_compositor_) { |
| paint_artifact_compositor_ = std::make_unique<PaintArtifactCompositor>( |
| page->GetScrollingCoordinator()->GetWeakPtr()); |
| page->GetChromeClient().AttachRootLayer( |
| paint_artifact_compositor_->RootLayer(), &GetFrame()); |
| } |
| |
| SCOPED_UMA_AND_UKM_TIMER(EnsureUkmAggregator(), |
| LocalFrameUkmAggregator::kCompositingCommit); |
| |
| // Skip updating property trees, pushing cc::Layers, and issuing raster |
| // invalidations if possible. |
| // TODO(paint-dev): In CompositeAfterPaint mode, we always set |
| // PaintArtifactCompositor::NeedsUpdate() when anything needs repaint. |
| // We need an equivalent signal to indicate that PAC doesn't need to run the |
| // layerization algorithm, but it does need to update properties on layers |
| // that depend on painted output. |
| if (!paint_artifact_compositor_->NeedsUpdate()) { |
| if (repainted) |
| paint_artifact_compositor_->UpdateRepaintedLayerProperties(); |
| return; |
| } |
| |
| paint_artifact_compositor_->SetLayerDebugInfoEnabled( |
| layer_debug_info_enabled_); |
| |
| PaintArtifactCompositor::ViewportProperties viewport_properties; |
| if (GetFrame().IsMainFrame()) { |
| const auto& viewport = page->GetVisualViewport(); |
| viewport_properties.overscroll_elasticity_transform = |
| viewport.GetOverscrollElasticityTransformNode(); |
| viewport_properties.page_scale = viewport.GetPageScaleNode(); |
| |
| if (const auto* root_scroller = |
| GetPage()->GlobalRootScrollerController().GlobalRootScroller()) { |
| if (const auto* layout_object = root_scroller->GetLayoutObject()) { |
| if (const auto* paint_properties = |
| layout_object->FirstFragment().PaintProperties()) { |
| if (paint_properties->Scroll()) { |
| viewport_properties.outer_clip = paint_properties->OverflowClip(); |
| viewport_properties.outer_scroll_translation = |
| paint_properties->ScrollTranslation(); |
| viewport_properties.inner_scroll_translation = |
| viewport.GetScrollTranslationNode(); |
| } |
| } |
| } |
| } |
| } |
| |
| WTF::Vector<const TransformPaintPropertyNode*> scroll_translation_nodes; |
| if (RuntimeEnabledFeatures::ScrollUnificationEnabled()) { |
| ForAllNonThrottledLocalFrameViews( |
| [&scroll_translation_nodes](LocalFrameView& frame_view) { |
| scroll_translation_nodes.AppendVector( |
| frame_view.GetScrollTranslationNodes()); |
| }); |
| } |
| |
| WTF::Vector<std::unique_ptr<DocumentTransition::Request>> |
| document_transition_requests; |
| // TODO(vmpstr): We should make this work for subframes as well. |
| AppendDocumentTransitionRequests(document_transition_requests); |
| |
| paint_artifact_compositor_->Update( |
| pre_composited_layers_, viewport_properties, scroll_translation_nodes, |
| std::move(document_transition_requests)); |
| |
| probe::LayerTreePainted(&GetFrame()); |
| } |
| |
| void LocalFrameView::AppendDocumentTransitionRequests( |
| WTF::Vector<std::unique_ptr<DocumentTransition::Request>>& requests) { |
| DCHECK(frame_ && frame_->GetDocument()); |
| auto* document_transition_supplement = |
| DocumentTransitionSupplement::FromIfExists(*frame_->GetDocument()); |
| if (!document_transition_supplement) |
| return; |
| auto* document_transition = document_transition_supplement->GetTransition(); |
| auto pending_request = document_transition->TakePendingRequest(); |
| if (pending_request) |
| requests.push_back(std::move(pending_request)); |
| } |
| |
| std::unique_ptr<JSONObject> LocalFrameView::CompositedLayersAsJSON( |
| LayerTreeFlags flags) { |
| auto* root_frame_view = GetFrame().LocalFrameRoot().View(); |
| if (root_frame_view->paint_artifact_compositor_) |
| return root_frame_view->paint_artifact_compositor_->GetLayersAsJSON(flags); |
| return std::make_unique<JSONObject>(); |
| } |
| |
| void LocalFrameView::UpdateStyleAndLayoutIfNeededRecursive() { |
| if (ShouldThrottleRendering() || !frame_->GetDocument()->IsActive()) |
| return; |
| |
| ScopedFrameBlamer frame_blamer(frame_); |
| TRACE_EVENT0("blink,benchmark", |
| "LocalFrameView::updateStyleAndLayoutIfNeededRecursive"); |
| |
| // We have to crawl our entire subtree looking for any FrameViews that need |
| // layout and make sure they are up to date. |
| // Mac actually tests for intersection with the dirty region and tries not to |
| // update layout for frames that are outside the dirty region. Not only does |
| // this seem pointless (since those frames will have set a zero timer to |
| // layout anyway), but it is also incorrect, since if two frames overlap, the |
| // first could be excluded from the dirty region but then become included |
| // later by the second frame adding rects to the dirty region when it lays |
| // out. |
| |
| { |
| SCOPED_UMA_AND_UKM_TIMER(EnsureUkmAggregator(), |
| LocalFrameUkmAggregator::kStyle); |
| frame_->GetDocument()->UpdateStyleAndLayoutTreeForThisDocument(); |
| |
| // Update style for all embedded SVG documents underneath this frame, so |
| // that intrinsic size computation for any embedded objects has up-to-date |
| // information before layout. |
| ForAllChildLocalFrameViews([](LocalFrameView& view) { |
| Document& document = *view.GetFrame().GetDocument(); |
| if (document.IsSVGDocument()) |
| document.UpdateStyleAndLayoutTreeForThisDocument(); |
| }); |
| } |
| |
| CHECK(!ShouldThrottleRendering()); |
| CHECK(frame_->GetDocument()->IsActive()); |
| CHECK(!nested_layout_count_); |
| |
| if (NeedsLayout()) { |
| SCOPED_UMA_AND_UKM_TIMER(EnsureUkmAggregator(), |
| LocalFrameUkmAggregator::kLayout); |
| UpdateLayout(); |
| } else { |
| UpdateGeometriesIfNeeded(); |
| } |
| |
| CheckDoesNotNeedLayout(); |
| |
| // WebView plugins need to update regardless of whether the |
| // LayoutEmbeddedObject that owns them needed layout. |
| // TODO(schenney): This currently runs the entire lifecycle on plugin |
| // WebViews. We should have a way to only run these other Documents to the |
| // same lifecycle stage as this frame. |
| for (const auto& plugin : plugins_) { |
| plugin->UpdateAllLifecyclePhases(); |
| } |
| CheckDoesNotNeedLayout(); |
| |
| // FIXME: Calling layout() shouldn't trigger script execution or have any |
| // observable effects on the frame tree but we're not quite there yet. |
| HeapVector<Member<LocalFrameView>> frame_views; |
| for (Frame* child = frame_->Tree().FirstChild(); child; |
| child = child->Tree().NextSibling()) { |
| auto* child_local_frame = DynamicTo<LocalFrame>(child); |
| if (!child_local_frame) |
| continue; |
| if (LocalFrameView* view = child_local_frame->View()) |
| frame_views.push_back(view); |
| } |
| |
| for (const auto& frame_view : frame_views) |
| frame_view->UpdateStyleAndLayoutIfNeededRecursive(); |
| |
| // These asserts ensure that parent frames are clean, when child frames |
| // finished updating layout and style. |
| // TODO(szager): this is the last call to CheckDoesNotNeedLayout during the |
| // lifecycle code, but it can happen that NeedsLayout() becomes true after |
| // this point, even while the document lifecycle proceeds to kLayoutClean |
| // and beyond. Figure out how this happens, and do something sensible. |
| CheckDoesNotNeedLayout(); |
| #if DCHECK_IS_ON() |
| frame_->GetDocument()->GetLayoutView()->AssertLaidOut(); |
| #endif |
| |
| if (Lifecycle().GetState() < DocumentLifecycle::kLayoutClean) |
| Lifecycle().AdvanceTo(DocumentLifecycle::kLayoutClean); |
| |
| // If we're restoring a scroll position from history, that takes precedence |
| // over scrolling to the anchor in the URL. |
| frame_->GetDocument()->ApplyScrollRestorationLogic(); |
| |
| // Ensure that we become visually non-empty eventually. |
| // TODO(esprehn): This should check isRenderingReady() instead. |
| if (GetFrame().GetDocument()->HasFinishedParsing() && |
| !GetFrame().GetDocument()->IsInitialEmptyDocument()) |
| is_visually_non_empty_ = true; |
| |
| GetFrame().Selection().UpdateStyleAndLayoutIfNeeded(); |
| GetFrame().GetPage()->GetDragCaret().UpdateStyleAndLayoutIfNeeded(); |
| } |
| |
| void LocalFrameView::EnableAutoSizeMode(const IntSize& min_size, |
| const IntSize& max_size) { |
| if (!auto_size_info_) |
| auto_size_info_ = MakeGarbageCollected<FrameViewAutoSizeInfo>(this); |
| |
| auto_size_info_->ConfigureAutoSizeMode(min_size, max_size); |
| SetLayoutSizeFixedToFrameSize(true); |
| SetNeedsLayout(); |
| ScheduleRelayout(); |
| } |
| |
| void LocalFrameView::DisableAutoSizeMode() { |
| if (!auto_size_info_) |
| return; |
| |
| SetLayoutSizeFixedToFrameSize(false); |
| SetNeedsLayout(); |
| ScheduleRelayout(); |
| |
| // Since autosize mode forces the scrollbar mode, change them to being auto. |
| GetLayoutView()->SetAutosizeScrollbarModes( |
| mojom::blink::ScrollbarMode::kAuto, mojom::blink::ScrollbarMode::kAuto); |
| auto_size_info_.Clear(); |
| } |
| |
| void LocalFrameView::ForceLayoutForPagination( |
| const FloatSize& page_size, |
| const FloatSize& original_page_size, |
| float maximum_shrink_factor) { |
| // Dumping externalRepresentation(m_frame->layoutObject()).ascii() is a good |
| // trick to see the state of things before and after the layout |
| if (LayoutView* layout_view = this->GetLayoutView()) { |
| float page_logical_width = layout_view->StyleRef().IsHorizontalWritingMode() |
| ? page_size.Width() |
| : page_size.Height(); |
| float page_logical_height = |
| layout_view->StyleRef().IsHorizontalWritingMode() ? page_size.Height() |
| : page_size.Width(); |
| |
| LayoutUnit floored_page_logical_width = |
| static_cast<LayoutUnit>(page_logical_width); |
| LayoutUnit floored_page_logical_height = |
| static_cast<LayoutUnit>(page_logical_height); |
| layout_view->SetLogicalWidth(floored_page_logical_width); |
| layout_view->SetPageLogicalHeight(floored_page_logical_height); |
| layout_view->SetNeedsLayoutAndIntrinsicWidthsRecalcAndFullPaintInvalidation( |
| layout_invalidation_reason::kPrintingChanged); |
| UpdateLayout(); |
| |
| // If we don't fit in the given page width, we'll lay out again. If we don't |
| // fit in the page width when shrunk, we will lay out at maximum shrink and |
| // clip extra content. |
| // FIXME: We are assuming a shrink-to-fit printing implementation. A |
| // cropping implementation should not do this! |
| bool horizontal_writing_mode = |
| layout_view->StyleRef().IsHorizontalWritingMode(); |
| PhysicalRect document_rect(layout_view->DocumentRect()); |
| LayoutUnit doc_logical_width = horizontal_writing_mode |
| ? document_rect.Width() |
| : document_rect.Height(); |
| if (doc_logical_width > page_logical_width) { |
| // ResizePageRectsKeepingRatio would truncate the expected page size, |
| // while we want it rounded -- so make sure it's rounded here. |
| FloatSize expected_page_size( |
| std::min<float>(document_rect.Width().Round(), |
| page_size.Width() * maximum_shrink_factor), |
| std::min<float>(document_rect.Height().Round(), |
| page_size.Height() * maximum_shrink_factor)); |
| FloatSize max_page_size = frame_->ResizePageRectsKeepingRatio( |
| FloatSize(original_page_size.Width(), original_page_size.Height()), |
| expected_page_size); |
| page_logical_width = horizontal_writing_mode ? max_page_size.Width() |
| : max_page_size.Height(); |
| page_logical_height = horizontal_writing_mode ? max_page_size.Height() |
| : max_page_size.Width(); |
| |
| floored_page_logical_width = static_cast<LayoutUnit>(page_logical_width); |
| floored_page_logical_height = |
| static_cast<LayoutUnit>(page_logical_height); |
| layout_view->SetLogicalWidth(floored_page_logical_width); |
| layout_view->SetPageLogicalHeight(floored_page_logical_height); |
| layout_view |
| ->SetNeedsLayoutAndIntrinsicWidthsRecalcAndFullPaintInvalidation( |
| layout_invalidation_reason::kPrintingChanged); |
| UpdateLayout(); |
| |
| PhysicalRect updated_document_rect(layout_view->DocumentRect()); |
| LayoutUnit doc_logical_height = horizontal_writing_mode |
| ? updated_document_rect.Height() |
| : updated_document_rect.Width(); |
| LayoutUnit doc_logical_top = horizontal_writing_mode |
| ? updated_document_rect.Y() |
| : updated_document_rect.X(); |
| LayoutUnit doc_logical_right = horizontal_writing_mode |
| ? updated_document_rect.Right() |
| : updated_document_rect.Bottom(); |
| LayoutUnit clipped_logical_left; |
| if (!layout_view->StyleRef().IsLeftToRightDirection()) { |
| clipped_logical_left = |
| LayoutUnit(doc_logical_right - page_logical_width); |
| } |
| LayoutRect overflow(clipped_logical_left, doc_logical_top, |
| LayoutUnit(page_logical_width), doc_logical_height); |
| |
| if (!horizontal_writing_mode) |
| overflow = overflow.TransposedRect(); |
| AdjustViewSizeAndLayout(); |
| // This is how we clip in case we overflow again. |
| layout_view->ClearLayoutOverflow(); |
| layout_view->AddLayoutOverflow(overflow); |
| return; |
| } |
| } |
| |
| if (TextAutosizer* text_autosizer = frame_->GetDocument()->GetTextAutosizer()) |
| text_autosizer->UpdatePageInfo(); |
| AdjustViewSizeAndLayout(); |
| } |
| |
| IntRect LocalFrameView::RootFrameToDocument(const IntRect& rect_in_root_frame) { |
| IntPoint offset = RootFrameToDocument(rect_in_root_frame.Location()); |
| IntRect local_rect = rect_in_root_frame; |
| local_rect.SetLocation(offset); |
| return local_rect; |
| } |
| |
| IntPoint LocalFrameView::RootFrameToDocument( |
| const IntPoint& point_in_root_frame) { |
| return FlooredIntPoint(RootFrameToDocument(FloatPoint(point_in_root_frame))); |
| } |
| |
| FloatPoint LocalFrameView::RootFrameToDocument( |
| const FloatPoint& point_in_root_frame) { |
| ScrollableArea* layout_viewport = LayoutViewport(); |
| if (!layout_viewport) |
| return point_in_root_frame; |
| |
| FloatPoint local_frame = ConvertFromRootFrame(point_in_root_frame); |
| return local_frame + layout_viewport->GetScrollOffset(); |
| } |
| |
| IntRect LocalFrameView::DocumentToFrame(const IntRect& rect_in_document) const { |
| IntRect rect_in_frame = rect_in_document; |
| rect_in_frame.SetLocation(DocumentToFrame(rect_in_document.Location())); |
| return rect_in_frame; |
| } |
| |
| DoublePoint LocalFrameView::DocumentToFrame( |
| const DoublePoint& point_in_document) const { |
| ScrollableArea* layout_viewport = LayoutViewport(); |
| if (!layout_viewport) |
| return point_in_document; |
| |
| return point_in_document - layout_viewport->GetScrollOffset(); |
| } |
| |
| IntPoint LocalFrameView::DocumentToFrame( |
| const IntPoint& point_in_document) const { |
| return FlooredIntPoint(DocumentToFrame(DoublePoint(point_in_document))); |
| } |
| |
| FloatPoint LocalFrameView::DocumentToFrame( |
| const FloatPoint& point_in_document) const { |
| return FloatPoint(DocumentToFrame(DoublePoint(point_in_document))); |
| } |
| |
| PhysicalOffset LocalFrameView::DocumentToFrame( |
| const PhysicalOffset& offset_in_document) const { |
| ScrollableArea* layout_viewport = LayoutViewport(); |
| if (!layout_viewport) |
| return offset_in_document; |
| |
| return offset_in_document - |
| PhysicalOffset::FromFloatSizeRound(layout_viewport->GetScrollOffset()); |
| } |
| |
| PhysicalRect LocalFrameView::DocumentToFrame( |
| const PhysicalRect& rect_in_document) const { |
| return PhysicalRect(DocumentToFrame(rect_in_document.offset), |
| rect_in_document.size); |
| } |
| |
| IntPoint LocalFrameView::FrameToDocument(const IntPoint& point_in_frame) const { |
| return FlooredIntPoint(FrameToDocument(PhysicalOffset(point_in_frame))); |
| } |
| |
| PhysicalOffset LocalFrameView::FrameToDocument( |
| const PhysicalOffset& offset_in_frame) const { |
| ScrollableArea* layout_viewport = LayoutViewport(); |
| if (!layout_viewport) |
| return offset_in_frame; |
| |
| return offset_in_frame + |
| PhysicalOffset::FromFloatSizeRound(layout_viewport->GetScrollOffset()); |
| } |
| |
| IntRect LocalFrameView::FrameToDocument(const IntRect& rect_in_frame) const { |
| return IntRect(FrameToDocument(rect_in_frame.Location()), |
| rect_in_frame.Size()); |
| } |
| |
| PhysicalRect LocalFrameView::FrameToDocument( |
| const PhysicalRect& rect_in_frame) const { |
| return PhysicalRect(FrameToDocument(rect_in_frame.offset), |
| rect_in_frame.size); |
| } |
| |
| IntRect LocalFrameView::ConvertToContainingEmbeddedContentView( |
| const IntRect& local_rect) const { |
| if (ParentFrameView()) { |
| auto* layout_object = GetLayoutEmbeddedContent(); |
| if (!layout_object) |
| return local_rect; |
| |
| IntRect rect(local_rect); |
| // Add borders and padding |
| rect.Move( |
| (layout_object->BorderLeft() + layout_object->PaddingLeft()).ToInt(), |
| (layout_object->BorderTop() + layout_object->PaddingTop()).ToInt()); |
| return PixelSnappedIntRect( |
| layout_object->LocalToAbsoluteRect(PhysicalRect(rect))); |
| } |
| |
| return local_rect; |
| } |
| |
| IntRect LocalFrameView::ConvertFromContainingEmbeddedContentView( |
| const IntRect& parent_rect) const { |
| if (ParentFrameView()) { |
| IntRect local_rect = parent_rect; |
| local_rect.MoveBy(-Location()); |
| return local_rect; |
| } |
| return parent_rect; |
| } |
| |
| PhysicalOffset LocalFrameView::ConvertToContainingEmbeddedContentView( |
| const PhysicalOffset& local_offset) const { |
| if (ParentFrameView()) { |
| auto* layout_object = GetLayoutEmbeddedContent(); |
| if (!layout_object) |
| return local_offset; |
| |
| PhysicalOffset point(local_offset); |
| |
| // Add borders and padding |
| point += PhysicalOffset( |
| layout_object->BorderLeft() + layout_object->PaddingLeft(), |
| layout_object->BorderTop() + layout_object->PaddingTop()); |
| return layout_object->LocalToAbsolutePoint(point); |
| } |
| |
| return local_offset; |
| } |
| |
| FloatPoint LocalFrameView::ConvertToContainingEmbeddedContentView( |
| const FloatPoint& local_point) const { |
| if (ParentFrameView()) { |
| auto* layout_object = GetLayoutEmbeddedContent(); |
| if (!layout_object) |
| return local_point; |
| |
| PhysicalOffset point = PhysicalOffset::FromFloatPointRound(local_point); |
| |
| // Add borders and padding |
| point.left += layout_object->BorderLeft() + layout_object->PaddingLeft(); |
| point.top += layout_object->BorderTop() + layout_object->PaddingTop(); |
| return FloatPoint(layout_object->LocalToAbsolutePoint(point)); |
| } |
| |
| return local_point; |
| } |
| |
| PhysicalOffset LocalFrameView::ConvertFromContainingEmbeddedContentView( |
| const PhysicalOffset& parent_offset) const { |
| return PhysicalOffset::FromFloatPointRound( |
| ConvertFromContainingEmbeddedContentView(FloatPoint(parent_offset))); |
| } |
| |
| FloatPoint LocalFrameView::ConvertFromContainingEmbeddedContentView( |
| const FloatPoint& parent_point) const { |
| return FloatPoint( |
| ConvertFromContainingEmbeddedContentView(DoublePoint(parent_point))); |
| } |
| |
| DoublePoint LocalFrameView::ConvertFromContainingEmbeddedContentView( |
| const DoublePoint& parent_point) const { |
| if (ParentFrameView()) { |
| // Get our layoutObject in the parent view |
| auto* layout_object = GetLayoutEmbeddedContent(); |
| if (!layout_object) |
| return parent_point; |
| |
| DoublePoint point( |
| layout_object->AbsoluteToLocalFloatPoint(FloatPoint(parent_point))); |
| // Subtract borders and padding |
| point.Move( |
| (-layout_object->BorderLeft() - layout_object->PaddingLeft()) |
| .ToDouble(), |
| (-layout_object->BorderTop() - layout_object->PaddingTop()).ToDouble()); |
| return point; |
| } |
| |
| return parent_point; |
| } |
| |
| IntPoint LocalFrameView::ConvertToContainingEmbeddedContentView( |
| const IntPoint& local_point) const { |
| return RoundedIntPoint( |
| ConvertToContainingEmbeddedContentView(PhysicalOffset(local_point))); |
| } |
| |
| void LocalFrameView::SetTracksRasterInvalidations( |
| bool track_raster_invalidations) { |
| if (track_raster_invalidations == is_tracking_raster_invalidations_) |
| return; |
| |
| // Ensure the document is up-to-date before tracking invalidations. |
| UpdateAllLifecyclePhasesForTest(); |
| |
| for (Frame* frame = &frame_->Tree().Top(); frame; |
| frame = frame->Tree().TraverseNext()) { |
| auto* local_frame = DynamicTo<LocalFrame>(frame); |
| if (!local_frame) |
| continue; |
| if (auto* layout_view = local_frame->ContentLayoutObject()) { |
| is_tracking_raster_invalidations_ = track_raster_invalidations; |
| if (paint_artifact_compositor_) { |
| paint_artifact_compositor_->SetTracksRasterInvalidations( |
| track_raster_invalidations); |
| } |
| if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) |
| layout_view->Compositor()->UpdateTrackingRasterInvalidations(); |
| } |
| } |
| |
| TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("blink.invalidation"), |
| "LocalFrameView::setTracksPaintInvalidations", |
| TRACE_EVENT_SCOPE_GLOBAL, "enabled", |
| track_raster_invalidations); |
| } |
| |
| void LocalFrameView::ServiceScriptedAnimations(base::TimeTicks start_time) { |
| // Disallow throttling in case any script needs to do a synchronous |
| // lifecycle update in other frames which are throttled. |
| DisallowThrottlingScope disallow_throttling(*this); |
| if (ScrollableArea* scrollable_area = GetScrollableArea()) { |
| scrollable_area->ServiceScrollAnimations( |
| start_time.since_origin().InSecondsF()); |
| } |
| if (const ScrollableAreaSet* animating_scrollable_areas = |
| AnimatingScrollableAreas()) { |
| // Iterate over a copy, since ScrollableAreas may deregister |
| // themselves during the iteration. |
| HeapVector<Member<PaintLayerScrollableArea>> |
| animating_scrollable_areas_copy; |
| CopyToVector(*animating_scrollable_areas, animating_scrollable_areas_copy); |
| for (PaintLayerScrollableArea* scrollable_area : |
| animating_scrollable_areas_copy) { |
| scrollable_area->ServiceScrollAnimations( |
| start_time.since_origin().InSecondsF()); |
| } |
| } |
| GetFrame().AnimateSnapFling(start_time); |
| Document* document = GetFrame().GetDocument(); |
| DCHECK(document); |
| SVGDocumentExtensions::ServiceOnAnimationFrame(*document); |
| document->GetDocumentAnimations().UpdateAnimationTimingForAnimationFrame(); |
| document->ServiceScriptedAnimations(start_time); |
| } |
| |
| void LocalFrameView::ScheduleAnimation(base::TimeDelta delay) { |
| if (auto* client = GetChromeClient()) |
| client->ScheduleAnimation(this, delay); |
| } |
| |
| void LocalFrameView::AddScrollableArea( |
| PaintLayerScrollableArea* scrollable_area) { |
| DCHECK(scrollable_area); |
| if (!scrollable_areas_) |
| scrollable_areas_ = MakeGarbageCollected<ScrollableAreaSet>(); |
| scrollable_areas_->insert(scrollable_area); |
| } |
| |
| void LocalFrameView::RemoveScrollableArea( |
| PaintLayerScrollableArea* scrollable_area) { |
| if (!scrollable_areas_) |
| return; |
| scrollable_areas_->erase(scrollable_area); |
| } |
| |
| void LocalFrameView::AddAnimatingScrollableArea( |
| PaintLayerScrollableArea* scrollable_area) { |
| DCHECK(scrollable_area); |
| if (!animating_scrollable_areas_) |
| animating_scrollable_areas_ = MakeGarbageCollected<ScrollableAreaSet>(); |
| animating_scrollable_areas_->insert(scrollable_area); |
| } |
| |
| void LocalFrameView::RemoveAnimatingScrollableArea( |
| PaintLayerScrollableArea* scrollable_area) { |
| if (!animating_scrollable_areas_) |
| return; |
| animating_scrollable_areas_->erase(scrollable_area); |
| } |
| |
| void LocalFrameView::AttachToLayout() { |
| CHECK(!IsAttached()); |
| if (frame_->GetDocument()) |
| CHECK_NE(Lifecycle().GetState(), DocumentLifecycle::kStopping); |
| SetAttached(true); |
| LocalFrameView* parent_view = ParentFrameView(); |
| CHECK(parent_view); |
| if (parent_view->IsVisible()) |
| SetParentVisible(true); |
| UpdateRenderThrottlingStatus(IsHiddenForThrottling(), |
| parent_view->CanThrottleRendering(), |
| IsDisplayLocked()); |
| |
| // We may have updated paint properties in detached frame subtree for |
| // printing (see UpdateLifecyclePhasesForPrinting()). The paint properties |
| // may change after the frame is attached. |
| if (auto* layout_view = GetLayoutView()) { |
| layout_view->AddSubtreePaintPropertyUpdateReason( |
| SubtreePaintPropertyUpdateReason::kPrinting); |
| } |
| } |
| |
| void LocalFrameView::DetachFromLayout() { |
| CHECK(IsAttached()); |
| SetParentVisible(false); |
| SetAttached(false); |
| |
| // We may need update paint properties in detached frame subtree for printing. |
| // See UpdateLifecyclePhasesForPrinting(). |
| if (auto* layout_view = GetLayoutView()) { |
| layout_view->AddSubtreePaintPropertyUpdateReason( |
| SubtreePaintPropertyUpdateReason::kPrinting); |
| } |
| } |
| |
| void LocalFrameView::AddPlugin(WebPluginContainerImpl* plugin) { |
| DCHECK(!plugins_.Contains(plugin)); |
| plugins_.insert(plugin); |
| } |
| |
| void LocalFrameView::RemovePlugin(WebPluginContainerImpl* plugin) { |
| DCHECK(plugins_.Contains(plugin)); |
| plugins_.erase(plugin); |
| } |
| |
| void LocalFrameView::RemoveScrollbar(Scrollbar* scrollbar) { |
| DCHECK(scrollbars_.Contains(scrollbar)); |
| scrollbars_.erase(scrollbar); |
| } |
| |
| void LocalFrameView::AddScrollbar(Scrollbar* scrollbar) { |
| DCHECK(!scrollbars_.Contains(scrollbar)); |
| scrollbars_.insert(scrollbar); |
| } |
| |
| bool LocalFrameView::VisualViewportSuppliesScrollbars() { |
| // On desktop, we always use the layout viewport's scrollbars. |
| if (!frame_->GetSettings() || !frame_->GetSettings()->GetViewportEnabled() || |
| !frame_->GetDocument() || !frame_->GetPage()) |
| return false; |
| |
| if (!LayoutViewport()) |
| return false; |
| |
| const TopDocumentRootScrollerController& controller = |
| frame_->GetPage()->GlobalRootScrollerController(); |
| return controller.RootScrollerArea() == LayoutViewport(); |
| } |
| |
| AXObjectCache* LocalFrameView::ExistingAXObjectCache() const { |
| if (GetFrame().GetDocument()) |
| return GetFrame().GetDocument()->ExistingAXObjectCache(); |
| return nullptr; |
| } |
| |
| void LocalFrameView::SetCursor(const ui::Cursor& cursor) { |
| Page* page = GetFrame().GetPage(); |
| if (!page || frame_->GetEventHandler().IsMousePositionUnknown()) |
| return; |
| LogCursorSizeCounter(&GetFrame(), cursor); |
| page->GetChromeClient().SetCursor(cursor, frame_); |
| } |
| |
| void LocalFrameView::PropagateFrameRects() { |
| TRACE_EVENT0("blink", "LocalFrameView::PropagateFrameRects"); |
| if (LayoutSizeFixedToFrameSize()) |
| SetLayoutSizeInternal(Size()); |
| |
| ForAllChildViewsAndPlugins([](EmbeddedContentView& view) { |
| auto* local_frame_view = DynamicTo<LocalFrameView>(view); |
| if (!local_frame_view || !local_frame_view->ShouldThrottleRendering()) { |
| view.PropagateFrameRects(); |
| } |
| }); |
| |
| // To limit the number of Mojo communications, only notify the browser when |
| // the rect's size changes, not when the position changes. The size needs to |
| // be replicated if the iframe goes out-of-process. |
| IntSize frame_size = FrameRect().Size(); |
| if (!frame_size_ || *frame_size_ != frame_size) { |
| frame_size_ = frame_size; |
| GetFrame().GetLocalFrameHostRemote().FrameSizeChanged( |
| gfx::Size(frame_size)); |
| } |
| |
| // It's possible for changing the frame rect to not generate a layout |
| // or any other event tracked by accessibility, we've seen this with |
| // Android WebView. Ensure that the root of the accessibility tree is |
| // invalidated so that it gets the right bounding rect. |
| if (AXObjectCache* cache = ExistingAXObjectCache()) |
| cache->HandleFrameRectsChanged(*GetFrame().GetDocument()); |
| } |
| |
| void LocalFrameView::SetLayoutSizeInternal(const IntSize& size) { |
| if (layout_size_ == size) |
| return; |
| layout_size_ = size; |
| |
| if (frame_->IsMainFrame() && frame_->GetDocument()) |
| TextAutosizer::UpdatePageInfoInAllFrames(frame_); |
| |
| SetNeedsLayout(); |
| } |
| |
| void LocalFrameView::DidChangeScrollOffset() { |
| GetFrame().Client()->DidChangeScrollOffset(); |
| if (GetFrame().IsMainFrame()) { |
| GetFrame().GetPage()->GetChromeClient().MainFrameScrollOffsetChanged( |
| GetFrame()); |
| } |
| } |
| |
| ScrollableArea* LocalFrameView::ScrollableAreaWithElementId( |
| const CompositorElementId& id) { |
| // Check for the layout viewport, which may not be in scrollable_areas_ if it |
| // is styled overflow: hidden. (Other overflow: hidden elements won't have |
| // composited scrolling layers per crbug.com/784053, so we don't have to worry |
| // about them.) |
| ScrollableArea* viewport = LayoutViewport(); |
| if (id == viewport->GetScrollElementId()) |
| return viewport; |
| |
| if (scrollable_areas_) { |
| // This requires iterating over all scrollable areas. We may want to store a |
| // map of ElementId to ScrollableArea if this is an issue for performance. |
| for (ScrollableArea* scrollable_area : *scrollable_areas_) { |
| if (id == scrollable_area->GetScrollElementId()) |
| return scrollable_area; |
| } |
| } |
| return nullptr; |
| } |
| |
| void LocalFrameView::ScrollRectToVisibleInRemoteParent( |
| const PhysicalRect& rect_to_scroll, |
| mojom::blink::ScrollIntoViewParamsPtr params) { |
| DCHECK(GetFrame().IsLocalRoot() && !GetFrame().IsMainFrame() && |
| safe_to_propagate_scroll_to_parent_); |
| PhysicalRect new_rect = ConvertToRootFrame(rect_to_scroll); |
| frame_->GetLocalFrameHostRemote().ScrollRectToVisibleInParentFrame( |
| gfx::Rect(new_rect.X().ToInt(), new_rect.Y().ToInt(), |
| new_rect.Width().ToInt(), new_rect.Height().ToInt()), |
| std::move(params)); |
| } |
| |
| void LocalFrameView::NotifyFrameRectsChangedIfNeeded() { |
| if (root_layer_did_scroll_) { |
| root_layer_did_scroll_ = false; |
| PropagateFrameRects(); |
| } |
| } |
| |
| void LocalFrameView::SetViewportIntersection( |
| const mojom::blink::ViewportIntersectionState& intersection_state) { |
| // The viewport intersection of the main frame is not tracked. |
| DCHECK(!GetFrame().IsMainFrame()); |
| |
| if (auto* document_resource_coordinator = |
| frame_->GetDocument()->GetResourceCoordinator()) { |
| gfx::RectF transform_rect = |
| gfx::RectF(gfx::Rect(intersection_state.viewport_intersection)); |
| |
| // The viewport intersection in the child frame's coordinate system is |
| // transformed into the intersection in the viewport's coordinate system. |
| intersection_state.main_frame_transform.TransformRect(&transform_rect); |
| |
| // Get rid of the possible floating point values for x and y of the |
| // resulting rectangle. |
| IntRect rect = EnclosingIntRect( |
| FloatRect(transform_rect.x(), transform_rect.y(), |
| transform_rect.width(), transform_rect.height())); |
| |
| // Return <0, 0, 0, 0> if there is no area. |
| if (rect.IsEmpty()) |
| rect.SetLocation(IntPoint(0, 0)); |
| |
| document_resource_coordinator->SetViewportIntersection(rect); |
| } |
| } |
| |
| PhysicalOffset LocalFrameView::ViewportToFrame( |
| const PhysicalOffset& point_in_viewport) const { |
| PhysicalOffset point_in_root_frame = PhysicalOffset::FromFloatPointRound( |
| frame_->GetPage()->GetVisualViewport().ViewportToRootFrame( |
| FloatPoint(point_in_viewport))); |
| return ConvertFromRootFrame(point_in_root_frame); |
| } |
| |
| FloatPoint LocalFrameView::ViewportToFrame( |
| const FloatPoint& point_in_viewport) const { |
| FloatPoint point_in_root_frame( |
| frame_->GetPage()->GetVisualViewport().ViewportToRootFrame( |
| point_in_viewport)); |
| return ConvertFromRootFrame(point_in_root_frame); |
| } |
| |
| IntRect LocalFrameView::ViewportToFrame(const IntRect& rect_in_viewport) const { |
| IntRect rect_in_root_frame = |
| frame_->GetPage()->GetVisualViewport().ViewportToRootFrame( |
| rect_in_viewport); |
| return ConvertFromRootFrame(rect_in_root_frame); |
| } |
| |
| IntPoint LocalFrameView::ViewportToFrame( |
| const IntPoint& point_in_viewport) const { |
| return RoundedIntPoint(ViewportToFrame(PhysicalOffset(point_in_viewport))); |
| } |
| |
| IntRect LocalFrameView::FrameToViewport(const IntRect& rect_in_frame) const { |
| IntRect rect_in_root_frame = ConvertToRootFrame(rect_in_frame); |
| return frame_->GetPage()->GetVisualViewport().RootFrameToViewport( |
| rect_in_root_frame); |
| } |
| |
| IntPoint LocalFrameView::FrameToViewport(const IntPoint& point_in_frame) const { |
| IntPoint point_in_root_frame = ConvertToRootFrame(point_in_frame); |
| return frame_->GetPage()->GetVisualViewport().RootFrameToViewport( |
| point_in_root_frame); |
| } |
| |
| IntRect LocalFrameView::FrameToScreen(const IntRect& rect) const { |
| if (auto* client = GetChromeClient()) |
| return client->ViewportToScreen(FrameToViewport(rect), this); |
| return IntRect(); |
| } |
| |
| IntPoint LocalFrameView::SoonToBeRemovedUnscaledViewportToContents( |
| const IntPoint& point_in_viewport) const { |
| IntPoint point_in_root_frame = FlooredIntPoint( |
| frame_->GetPage()->GetVisualViewport().ViewportCSSPixelsToRootFrame( |
| FloatPoint(point_in_viewport))); |
| return ConvertFromRootFrame(point_in_root_frame); |
| } |
| |
| LocalFrameView::AllowThrottlingScope::AllowThrottlingScope( |
| const LocalFrameView& frame_view) |
| : value_(&frame_view.GetFrame().LocalFrameRoot().View()->allow_throttling_, |
| true) {} |
| |
| LocalFrameView::DisallowThrottlingScope::DisallowThrottlingScope( |
| const LocalFrameView& frame_view) |
| : value_(&frame_view.GetFrame().LocalFrameRoot().View()->allow_throttling_, |
| false) {} |
| |
| bool LocalFrameView::CapturePaintPreview(GraphicsContext& context, |
| const IntSize& paint_offset) const { |
| base::Optional<base::UnguessableToken> maybe_embedding_token = |
| GetFrame().GetEmbeddingToken(); |
| |
| // Avoid crashing if a local frame doesn't have an embedding token. |
| // e.g. it was unloaded or hasn't finished loading (crbug/1103157). |
| if (!maybe_embedding_token.has_value()) |
| return false; |
| |
| // Ensure a recording canvas is properly created. |
| DrawingRecorder recorder(context, *GetFrame().OwnerLayoutObject(), |
| DisplayItem::kDocumentBackground); |
| context.Save(); |
| context.Translate(paint_offset.Width(), paint_offset.Height()); |
| DCHECK(context.Canvas()); |
| |
| auto* tracker = context.Canvas()->GetPaintPreviewTracker(); |
| DCHECK(tracker); // |tracker| must exist or there is a bug upstream. |
| |
| // Create a placeholder ID that maps to an embedding token. |
| context.Canvas()->recordCustomData(tracker->CreateContentForRemoteFrame( |
| FrameRect(), maybe_embedding_token.value())); |
| context.Restore(); |
| |
| // Send a request to the browser to trigger a capture of the frame. |
| GetFrame().GetLocalFrameHostRemote().CapturePaintPreviewOfSubframe( |
| FrameRect(), tracker->Guid()); |
| return true; |
| } |
| |
| void LocalFrameView::Paint(GraphicsContext& context, |
| const GlobalPaintFlags global_paint_flags, |
| const CullRect& cull_rect, |
| const IntSize& paint_offset) const { |
| const auto* owner_layout_object = GetFrame().OwnerLayoutObject(); |
| base::Optional<Document::PaintPreviewScope> paint_preview; |
| if (owner_layout_object && |
| owner_layout_object->GetDocument().IsPaintingPreview()) { |
| paint_preview.emplace(*GetFrame().GetDocument()); |
| // When capturing a Paint Preview we want to capture scrollable embedded |
| // content separately. Paint should stop here and ask the browser to |
| // coordinate painting such frames as a separate task. |
| if (LayoutViewport()->ScrollsOverflow()) { |
| // If capture fails we should fallback to capturing inline if possible. |
| if (CapturePaintPreview(context, paint_offset)) |
| return; |
| } |
| } |
| |
| // |paint_offset| is not used because paint properties of the contents will |
| // ensure the correct location. |
| PaintInternal(context, global_paint_flags, cull_rect); |
| } |
| |
| void LocalFrameView::PaintInternal(GraphicsContext& context, |
| const GlobalPaintFlags global_paint_flags, |
| const CullRect& cull_rect) const { |
| FramePainter(*this).Paint(context, global_paint_flags, cull_rect); |
| } |
| |
| static bool PaintOutsideOfLifecycleIsAllowed(GraphicsContext& context, |
| const LocalFrameView& frame_view) { |
| // A paint outside of lifecycle should not conflict about paint controller |
| // caching with the default painting executed during lifecycle update, |
| // otherwise the caller should either use a transient paint controller or |
| // explicitly skip cache. |
| if (context.GetPaintController().IsSkippingCache()) |
| return true; |
| // For CompositeAfterPaint, they always conflict because we always paint into |
| // paint_controller_ during lifecycle update. |
| if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) |
| return false; |
| // For pre-CompositeAfterPaint, they conflict if the local frame root has a |
| // a root graphics layer. |
| return !frame_view.GetFrame() |
| .LocalFrameRoot() |
| .View() |
| ->GetLayoutView() |
| ->Compositor() |
| ->PaintRootGraphicsLayer(); |
| } |
| |
| void LocalFrameView::PaintOutsideOfLifecycle( |
| GraphicsContext& context, |
| const GlobalPaintFlags global_paint_flags, |
| const CullRect& cull_rect) { |
| DCHECK(PaintOutsideOfLifecycleIsAllowed(context, *this)); |
| |
| AllowThrottlingScope allow_throttling(*this); |
| ForAllNonThrottledLocalFrameViews([](LocalFrameView& frame_view) { |
| frame_view.Lifecycle().AdvanceTo(DocumentLifecycle::kInPaint); |
| }); |
| |
| { |
| OverriddenCullRectScope force_cull_rect(*this, cull_rect); |
| PaintInternal(context, global_paint_flags, cull_rect); |
| } |
| |
| ForAllNonThrottledLocalFrameViews([](LocalFrameView& frame_view) { |
| frame_view.Lifecycle().AdvanceTo(DocumentLifecycle::kPaintClean); |
| }); |
| } |
| |
| void LocalFrameView::PaintContentsOutsideOfLifecycle( |
| GraphicsContext& context, |
| const GlobalPaintFlags global_paint_flags, |
| const CullRect& cull_rect) { |
| DCHECK(PaintOutsideOfLifecycleIsAllowed(context, *this)); |
| |
| ForAllNonThrottledLocalFrameViews([](LocalFrameView& frame_view) { |
| frame_view.Lifecycle().AdvanceTo(DocumentLifecycle::kInPaint); |
| }); |
| |
| { |
| OverriddenCullRectScope force_cull_rect(*this, cull_rect); |
| FramePainter(*this).PaintContents(context, global_paint_flags, cull_rect); |
| } |
| |
| ForAllNonThrottledLocalFrameViews([](LocalFrameView& frame_view) { |
| frame_view.Lifecycle().AdvanceTo(DocumentLifecycle::kPaintClean); |
| }); |
| } |
| |
| void LocalFrameView::PaintContentsForTest(const CullRect& cull_rect) { |
| AllowThrottlingScope allow_throttling(*this); |
| Lifecycle().AdvanceTo(DocumentLifecycle::kInPaint); |
| OverriddenCullRectScope force_cull_rect(*this, cull_rect); |
| if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { |
| PaintController& paint_controller = EnsurePaintController(); |
| if (GetLayoutView()->Layer()->SelfOrDescendantNeedsRepaint()) { |
| GraphicsContext graphics_context(paint_controller); |
| Paint(graphics_context, kGlobalPaintNormalPhase, cull_rect); |
| paint_controller.CommitNewDisplayItems(); |
| } |
| paint_controller.FinishCycle(); |
| } else { |
| GraphicsLayer* graphics_layer = |
| GetLayoutView()->Layer()->GraphicsLayerBacking(); |
| graphics_layer->PaintForTesting(cull_rect.Rect()); |
| graphics_layer->GetPaintController().FinishCycle(); |
| } |
| Lifecycle().AdvanceTo(DocumentLifecycle::kPaintClean); |
| } |
| |
| sk_sp<PaintRecord> LocalFrameView::GetPaintRecord() const { |
| DCHECK(RuntimeEnabledFeatures::CompositeAfterPaintEnabled()); |
| DCHECK_EQ(DocumentLifecycle::kPaintClean, Lifecycle().GetState()); |
| DCHECK(frame_->IsLocalRoot()); |
| DCHECK(paint_controller_); |
| return paint_controller_->GetPaintArtifact().GetPaintRecord( |
| PropertyTreeState::Root()); |
| } |
| |
| IntRect LocalFrameView::ConvertToRootFrame(const IntRect& local_rect) const { |
| if (LocalFrameView* parent = ParentFrameView()) { |
| IntRect parent_rect = ConvertToContainingEmbeddedContentView(local_rect); |
| return parent->ConvertToRootFrame(parent_rect); |
| } |
| return local_rect; |
| } |
| |
| IntPoint LocalFrameView::ConvertToRootFrame(const IntPoint& local_point) const { |
| return RoundedIntPoint(ConvertToRootFrame(PhysicalOffset(local_point))); |
| } |
| |
| PhysicalOffset LocalFrameView::ConvertToRootFrame( |
| const PhysicalOffset& local_offset) const { |
| if (LocalFrameView* parent = ParentFrameView()) { |
| PhysicalOffset parent_offset = |
| ConvertToContainingEmbeddedContentView(local_offset); |
| return parent->ConvertToRootFrame(parent_offset); |
| } |
| return local_offset; |
| } |
| |
| FloatPoint LocalFrameView::ConvertToRootFrame( |
| const FloatPoint& local_point) const { |
| if (LocalFrameView* parent = ParentFrameView()) { |
| FloatPoint parent_point = |
| ConvertToContainingEmbeddedContentView(local_point); |
| return parent->ConvertToRootFrame(parent_point); |
| } |
| return local_point; |
| } |
| |
| PhysicalRect LocalFrameView::ConvertToRootFrame( |
| const PhysicalRect& local_rect) const { |
| if (LocalFrameView* parent = ParentFrameView()) { |
| PhysicalOffset parent_offset = |
| ConvertToContainingEmbeddedContentView(local_rect.offset); |
| PhysicalRect parent_rect(parent_offset, local_rect.size); |
| return parent->ConvertToRootFrame(parent_rect); |
| } |
| return local_rect; |
| } |
| |
| IntRect LocalFrameView::ConvertFromRootFrame( |
| const IntRect& rect_in_root_frame) const { |
| if (LocalFrameView* parent = ParentFrameView()) { |
| IntRect parent_rect = parent->ConvertFromRootFrame(rect_in_root_frame); |
| return ConvertFromContainingEmbeddedContentView(parent_rect); |
| } |
| return rect_in_root_frame; |
| } |
| |
| IntPoint LocalFrameView::ConvertFromRootFrame( |
| const IntPoint& point_in_root_frame) const { |
| return RoundedIntPoint( |
| ConvertFromRootFrame(PhysicalOffset(point_in_root_frame))); |
| } |
| |
| PhysicalOffset LocalFrameView::ConvertFromRootFrame( |
| const PhysicalOffset& offset_in_root_frame) const { |
| if (LocalFrameView* parent = ParentFrameView()) { |
| PhysicalOffset parent_point = |
| parent->ConvertFromRootFrame(offset_in_root_frame); |
| return ConvertFromContainingEmbeddedContentView(parent_point); |
| } |
| return offset_in_root_frame; |
| } |
| |
| FloatPoint LocalFrameView::ConvertFromRootFrame( |
| const FloatPoint& point_in_root_frame) const { |
| if (LocalFrameView* parent = ParentFrameView()) { |
| FloatPoint parent_point = parent->ConvertFromRootFrame(point_in_root_frame); |
| return ConvertFromContainingEmbeddedContentView(parent_point); |
| } |
| return point_in_root_frame; |
| } |
| |
| void LocalFrameView::ParentVisibleChanged() { |
| // As parent visibility changes, we may need to recomposite this frame view |
| // and potentially child frame views. |
| SetNeedsCompositingUpdate(kCompositingUpdateRebuildTree); |
| |
| if (!IsSelfVisible()) |
| return; |
| |
| bool visible = IsParentVisible(); |
| ForAllChildViewsAndPlugins( |
| [visible](EmbeddedContentView& embedded_content_view) { |
| embedded_content_view.SetParentVisible(visible); |
| }); |
| } |
| |
| void LocalFrameView::SelfVisibleChanged() { |
| // FrameView visibility affects PLC::CanBeComposited, which in turn affects |
| // compositing inputs. |
| if (LayoutView* view = GetLayoutView()) |
| view->Layer()->SetNeedsCompositingInputsUpdate(); |
| } |
| |
| void LocalFrameView::Show() { |
| if (!IsSelfVisible()) { |
| SetSelfVisible(true); |
| SetNeedsCompositingUpdate(kCompositingUpdateRebuildTree); |
| if (IsParentVisible()) { |
| ForAllChildViewsAndPlugins( |
| [](EmbeddedContentView& embedded_content_view) { |
| embedded_content_view.SetParentVisible(true); |
| }); |
| } |
| } |
| } |
| |
| void LocalFrameView::Hide() { |
| if (IsSelfVisible()) { |
| if (IsParentVisible()) { |
| ForAllChildViewsAndPlugins( |
| [](EmbeddedContentView& embedded_content_view) { |
| embedded_content_view.SetParentVisible(false); |
| }); |
| } |
| SetSelfVisible(false); |
| SetNeedsCompositingUpdate(kCompositingUpdateRebuildTree); |
| } |
| } |
| |
| int LocalFrameView::ViewportWidth() const { |
| int viewport_width = GetLayoutSize().Width(); |
| return AdjustForAbsoluteZoom::AdjustInt(viewport_width, GetLayoutView()); |
| } |
| |
| ScrollableArea* LocalFrameView::GetScrollableArea() { |
| if (viewport_scrollable_area_) |
| return viewport_scrollable_area_.Get(); |
| |
| return LayoutViewport(); |
| } |
| |
| PaintLayerScrollableArea* LocalFrameView::LayoutViewport() const { |
| auto* layout_view = GetLayoutView(); |
| return layout_view ? layout_view->GetScrollableArea() : nullptr; |
| } |
| |
| RootFrameViewport* LocalFrameView::GetRootFrameViewport() { |
| return viewport_scrollable_area_.Get(); |
| } |
| |
| void LocalFrameView::CollectAnnotatedRegions( |
| LayoutObject& layout_object, |
| Vector<AnnotatedRegionValue>& regions) const { |
| // LayoutTexts don't have their own style, they just use their parent's style, |
| // so we don't want to include them. |
| if (layout_object.IsText()) |
| return; |
| |
| layout_object.AddAnnotatedRegions(regions); |
| for (LayoutObject* curr = layout_object.SlowFirstChild(); curr; |
| curr = curr->NextSibling()) |
| CollectAnnotatedRegions(*curr, regions); |
| } |
| |
| bool LocalFrameView::UpdateViewportIntersectionsForSubtree( |
| unsigned parent_flags) { |
| // TODO(dcheng): Since LocalFrameView tree updates are deferred, FrameViews |
| // might still be in the LocalFrameView hierarchy even though the associated |
| // Document is already detached. Investigate if this check and a similar check |
| // in lifecycle updates are still needed when there are no more deferred |
| // LocalFrameView updates: https://crbug.com/561683 |
| if (!GetFrame().GetDocument()->IsActive()) |
| return false; |
| |
| unsigned flags = GetIntersectionObservationFlags(parent_flags); |
| bool needs_occlusion_tracking = false; |
| |
| if (!NeedsLayout() || IsDisplayLocked()) { |
| // Notify javascript IntersectionObservers |
| if (IntersectionObserverController* controller = |
| GetFrame().GetDocument()->GetIntersectionObserverController()) { |
| needs_occlusion_tracking |= |
| controller->ComputeIntersections(flags, EnsureUkmAggregator()); |
| } |
| intersection_observation_state_ = kNotNeeded; |
| } |
| |
| { |
| SCOPED_UMA_AND_UKM_TIMER( |
| EnsureUkmAggregator(), |
| LocalFrameUkmAggregator::kUpdateViewportIntersection); |
| UpdateViewportIntersection(flags, needs_occlusion_tracking); |
| } |
| |
| for (Frame* child = frame_->Tree().FirstChild(); child; |
| child = child->Tree().NextSibling()) { |
| needs_occlusion_tracking |= |
| child->View()->UpdateViewportIntersectionsForSubtree(flags); |
| } |
| |
| for (PortalContents* portal : |
| DocumentPortals::From(*frame_->GetDocument()).GetPortals()) { |
| if (Frame* frame = portal->GetFrame()) { |
| needs_occlusion_tracking |= |
| frame->View()->UpdateViewportIntersectionsForSubtree(flags); |
| } |
| } |
| |
| return needs_occlusion_tracking; |
| } |
| |
| void LocalFrameView::DeliverSynchronousIntersectionObservations() { |
| if (IntersectionObserverController* controller = |
| GetFrame().GetDocument()->GetIntersectionObserverController()) { |
| controller->DeliverNotifications( |
| IntersectionObserver::kDeliverDuringPostLifecycleSteps); |
| } |
| ForAllChildLocalFrameViews([](LocalFrameView& frame_view) { |
| frame_view.DeliverSynchronousIntersectionObservations(); |
| }); |
| } |
| |
| void LocalFrameView::CrossOriginToMainFrameChanged() { |
| // If any of these conditions hold, then a change in cross-origin status does |
| // not affect throttling. |
| if (lifecycle_updates_throttled_ || IsSubtreeThrottled() || |
| IsDisplayLocked() || !IsHiddenForThrottling()) { |
| return; |
| } |
| RenderThrottlingStatusChanged(); |
| // Immediately propagate changes to children. |
| UpdateRenderThrottlingStatus(IsHiddenForThrottling(), IsSubtreeThrottled(), |
| IsDisplayLocked(), true); |
| } |
| |
| void LocalFrameView::CrossOriginToParentFrameChanged() { |
| if (LayoutView* layout_view = GetLayoutView()) { |
| if (PaintLayer* root_layer = layout_view->Layer()) |
| root_layer->SetNeedsCompositingInputsUpdate(); |
| } |
| } |
| |
| void LocalFrameView::VisibilityForThrottlingChanged() { |
| if (FrameScheduler* frame_scheduler = frame_->GetFrameScheduler()) { |
| // TODO(szager): Per crbug.com/994443, maybe this should be: |
| // SetFrameVisible(IsHiddenForThrottling() || IsSubtreeThrottled()); |
| frame_scheduler->SetFrameVisible(!IsHiddenForThrottling()); |
| } |
| } |
| |
| void LocalFrameView::VisibilityChanged( |
| blink::mojom::FrameVisibility visibility) { |
| frame_->GetLocalFrameHostRemote().VisibilityChanged(visibility); |
| } |
| |
| void LocalFrameView::RenderThrottlingStatusChanged() { |
| TRACE_EVENT0("blink", "LocalFrameView::RenderThrottlingStatusChanged"); |
| DCHECK(!IsInPerformLayout()); |
| DCHECK(!frame_->GetDocument() || !frame_->GetDocument()->InStyleRecalc()); |
| |
| // We may record more/less pre-composited layers under the frame. |
| if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) |
| SetPaintArtifactCompositorNeedsUpdate(); |
| |
| if (!CanThrottleRendering()) { |
| // Start ticking animation frames again if necessary. |
| if (GetPage()) |
| GetPage()->Animator().ScheduleVisualUpdate(frame_.Get()); |
| // Ensure we'll recompute viewport intersection for the frame subtree during |
| // the scheduled visual update. |
| SetIntersectionObservationState(kRequired); |
| // When a frame is throttled, we typically delete its previous painted |
| // output, so it will need to be repainted, even if nothing else has |
| // changed. |
| if (LayoutView* layout_view = GetLayoutView()) |
| layout_view->Layer()->SetNeedsRepaint(); |
| } else if (GetFrame().IsLocalRoot()) { |
| // By this point, every frame in the local frame tree has become throttled, |
| // so painting the tree should just clear the previous painted output. |
| DCHECK(!IsUpdatingLifecycle()); |
| AllowThrottlingScope allow_throtting(*this); |
| RunPaintLifecyclePhase(); |
| } |
| |
| #if DCHECK_IS_ON() |
| // Make sure we never have an unthrottled frame inside a throttled one. |
| LocalFrameView* parent = ParentFrameView(); |
| while (parent) { |
| DCHECK(CanThrottleRendering() || !parent->CanThrottleRendering()); |
| parent = parent->ParentFrameView(); |
| } |
| #endif |
| } |
| |
| void LocalFrameView::SetNeedsForcedCompositingUpdate() { |
| needs_forced_compositing_update_ = true; |
| if (LocalFrameView* parent = ParentFrameView()) |
| parent->SetNeedsForcedCompositingUpdate(); |
| } |
| |
| void LocalFrameView::SetIntersectionObservationState( |
| IntersectionObservationState state) { |
| if (intersection_observation_state_ >= state) |
| return; |
| intersection_observation_state_ = state; |
| |
| // If an intersection observation is required, force all ancestors to update. |
| // Otherwise, an update could stop at a throttled frame before reaching this. |
| if (state == kRequired) { |
| Frame* parent_frame = frame_->Tree().Parent(); |
| if (auto* parent_local_frame = DynamicTo<LocalFrame>(parent_frame)) { |
| if (parent_local_frame->View()) |
| parent_local_frame->View()->SetIntersectionObservationState(kRequired); |
| } |
| } |
| } |
| |
| void LocalFrameView::SetPaintArtifactCompositorNeedsUpdate() { |
| LocalFrameView* root = GetFrame().LocalFrameRoot().View(); |
| if (root && root->paint_artifact_compositor_) |
| root->paint_artifact_compositor_->SetNeedsUpdate(); |
| } |
| |
| PaintArtifactCompositor* LocalFrameView::GetPaintArtifactCompositor() const { |
| LocalFrameView* root = GetFrame().LocalFrameRoot().View(); |
| return root ? root->paint_artifact_compositor_.get() : nullptr; |
| } |
| |
| unsigned LocalFrameView::GetIntersectionObservationFlags( |
| unsigned parent_flags) const { |
| unsigned flags = 0; |
| |
| const LocalFrame& target_frame = GetFrame(); |
| const Frame& root_frame = target_frame.Tree().Top(); |
| if (&root_frame == &target_frame || |
| target_frame.GetSecurityContext()->GetSecurityOrigin()->CanAccess( |
| root_frame.GetSecurityContext()->GetSecurityOrigin())) { |
| flags |= IntersectionObservation::kReportImplicitRootBounds; |
| } |
| |
| // Observers with explicit roots only need to be checked on the same frame, |
| // since in this case target and root must be in the same document. |
| if (intersection_observation_state_ != kNotNeeded) { |
| flags |= (IntersectionObservation::kExplicitRootObserversNeedUpdate | |
| IntersectionObservation::kImplicitRootObserversNeedUpdate); |
| } |
| |
| // For observers with implicit roots, we need to check state on the whole |
| // local frame tree, as passed down from the parent. |
| flags |= (parent_flags & |
| IntersectionObservation::kImplicitRootObserversNeedUpdate); |
| |
| // The kIgnoreDelay parameter is used to force computation in an OOPIF which |
| // is hidden in the parent document, thus not running lifecycle updates. It |
| // applies to the entire frame tree. |
| flags |= (parent_flags & IntersectionObservation::kIgnoreDelay); |
| |
| return flags; |
| } |
| |
| bool LocalFrameView::ShouldThrottleRendering() const { |
| bool throttled_for_global_reasons = LocalFrameTreeAllowsThrottling() && |
| CanThrottleRendering() && |
| frame_->GetDocument(); |
| if (!throttled_for_global_reasons || needs_forced_compositing_update_) |
| return false; |
| |
| // If we're currently running a lifecycle update, and we are required to run |
| // the IntersectionObserver steps at the end of the update, then there are two |
| // courses of action, depending on whether this frame is display locked by its |
| // parent frame: |
| // |
| // - If it is NOT display locked, then we suppress throttling to force the |
| // lifecycle update to proceed up to the state required to run |
| // IntersectionObserver. |
| // |
| // - If it IS display locked, then we still need IntersectionObserver to |
| // run; but the display lock status will short-circuit the |
| // IntersectionObserver algorithm and create degenerate "not intersecting" |
| // notifications. Hence, we don't need to force lifecycle phases to run, |
| // because IntersectionObserver will not need access to up-to-date |
| // geometry. So there is no point in suppressing throttling here. |
| auto* local_frame_root_view = GetFrame().LocalFrameRoot().View(); |
| if (local_frame_root_view->IsUpdatingLifecycle() && |
| intersection_observation_state_ == kRequired && !IsDisplayLocked()) { |
| return Lifecycle().GetState() >= DocumentLifecycle::kPrePaintClean; |
| } |
| |
| return true; |
| } |
| |
| bool LocalFrameView::ShouldThrottleRenderingForTest() const { |
| AllowThrottlingScope allow_throttling(*this); |
| return ShouldThrottleRendering(); |
| } |
| |
| bool LocalFrameView::CanThrottleRendering() const { |
| if (lifecycle_updates_throttled_ || IsSubtreeThrottled() || |
| IsDisplayLocked()) { |
| return true; |
| } |
| // We only throttle hidden cross-origin frames. This is to avoid a situation |
| // where an ancestor frame directly depends on the pipeline timing of a |
| // descendant and breaks as a result of throttling. The rationale is that |
| // cross-origin frames must already communicate with asynchronous messages, |
| // so they should be able to tolerate some delay in receiving replies from a |
| // throttled peer. |
| return IsHiddenForThrottling() && frame_->IsCrossOriginToMainFrame(); |
| } |
| |
| void LocalFrameView::UpdateRenderThrottlingStatus(bool hidden_for_throttling, |
| bool subtree_throttled, |
| bool display_locked, |
| bool recurse) { |
| bool was_throttled = CanThrottleRendering(); |
| FrameView::UpdateRenderThrottlingStatus( |
| hidden_for_throttling, subtree_throttled, display_locked, recurse); |
| if (was_throttled != CanThrottleRendering()) |
| RenderThrottlingStatusChanged(); |
| } |
| |
| void LocalFrameView::BeginLifecycleUpdates() { |
| // Avoid pumping frames for the initially empty document. |
| // TODO(schenney): This seems pointless because main frame updates do occur |
| // for pages like about:blank, at least according to log messages. |
| if (GetFrame().GetDocument()->IsInitialEmptyDocument()) |
| return; |
| lifecycle_updates_throttled_ = false; |
| |
| LayoutView* layout_view = GetLayoutView(); |
| bool layout_view_is_empty = layout_view && !layout_view->FirstChild(); |
| if (layout_view_is_empty && !DidFirstLayout() && !NeedsLayout()) { |
| // Make sure a display:none iframe gets an initial layout pass. |
| layout_view->SetNeedsLayout(layout_invalidation_reason::kAddedToLayout, |
| kMarkOnlyThis); |
| } |
| |
| ScheduleAnimation(); |
| SetIntersectionObservationState(kRequired); |
| |
| // Non-main-frame lifecycle and commit deferral are controlled by their |
| // main frame. |
| if (!GetFrame().IsMainFrame()) |
| return; |
| |
| Document* document = GetFrame().GetDocument(); |
| ChromeClient& chrome_client = GetFrame().GetPage()->GetChromeClient(); |
| |
| // Determine if we want to defer commits to the compositor once lifecycle |
| // updates start. Doing so allows us to update the page lifecycle but not |
| // present the results to screen until we see first contentful paint is |
| // available or until a timer expires. |
| // This is enabled only when the document loading is regular HTML served |
| // over HTTP/HTTPs. And only defer commits once. This method gets called |
| // multiple times, and we do not want to defer a second time if we have |
| // already done so once and resumed commits already. |
| if (document && |
| document->DeferredCompositorCommitIsAllowed() && |
| !have_deferred_commits_) { |
| chrome_client.StartDeferringCommits( |
| GetFrame(), base::TimeDelta::FromMilliseconds(kCommitDelayDefaultInMs)); |
| have_deferred_commits_ = true; |
| } |
| |
| chrome_client.BeginLifecycleUpdates(GetFrame()); |
| } |
| |
| void LocalFrameView::SetInitialViewportSize(const IntSize& viewport_size) { |
| if (viewport_size == initial_viewport_size_) |
| return; |
| |
| initial_viewport_size_ = viewport_size; |
| if (Document* document = frame_->GetDocument()) |
| document->GetStyleEngine().InitialViewportChanged(); |
| } |
| |
| int LocalFrameView::InitialViewportWidth() const { |
| DCHECK(frame_->IsMainFrame()); |
| return initial_viewport_size_.Width(); |
| } |
| |
| int LocalFrameView::InitialViewportHeight() const { |
| DCHECK(frame_->IsMainFrame()); |
| return initial_viewport_size_.Height(); |
| } |
| |
| MainThreadScrollingReasons LocalFrameView::MainThreadScrollingReasonsPerFrame() |
| const { |
| MainThreadScrollingReasons reasons = |
| static_cast<MainThreadScrollingReasons>(0); |
| |
| if (ShouldThrottleRendering()) |
| return reasons; |
| |
| if (RequiresMainThreadScrollingForBackgroundAttachmentFixed()) { |
| reasons |= |
| cc::MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects; |
| } |
| return reasons; |
| } |
| |
| MainThreadScrollingReasons LocalFrameView::GetMainThreadScrollingReasons() |
| const { |
| MainThreadScrollingReasons reasons = |
| static_cast<MainThreadScrollingReasons>(0); |
| |
| if (!GetPage()->GetSettings().GetThreadedScrollingEnabled()) |
| reasons |= cc::MainThreadScrollingReason::kThreadedScrollingDisabled; |
| |
| if (!GetPage()->MainFrame()->IsLocalFrame()) |
| return reasons; |
| |
| // TODO(alexmos,kenrb): For OOPIF, local roots that are different from |
| // the main frame can't be used in the calculation, since they use |
| // different compositors with unrelated state, which breaks some of the |
| // calculations below. |
| if (&frame_->LocalFrameRoot() != GetPage()->MainFrame()) |
| return reasons; |
| |
| // Walk the tree to the root. Use the gathered reasons to determine |
| // whether the target frame should be scrolled on main thread regardless |
| // other subframes on the same page. |
| for (Frame* frame = frame_; frame; frame = frame->Tree().Parent()) { |
| auto* local_frame = DynamicTo<LocalFrame>(frame); |
| if (!local_frame) |
| continue; |
| reasons |= local_frame->View()->MainThreadScrollingReasonsPerFrame(); |
| } |
| |
| DCHECK( |
| !cc::MainThreadScrollingReason::HasNonCompositedScrollReasons(reasons)); |
| return reasons; |
| } |
| |
| String LocalFrameView::MainThreadScrollingReasonsAsText() { |
| MainThreadScrollingReasons reasons = 0; |
| DCHECK(Lifecycle().GetState() >= DocumentLifecycle::kPrePaintClean); |
| const auto* properties = GetLayoutView()->FirstFragment().PaintProperties(); |
| if (properties && properties->Scroll()) |
| reasons = properties->Scroll()->GetMainThreadScrollingReasons(); |
| return String(cc::MainThreadScrollingReason::AsText(reasons).c_str()); |
| } |
| |
| bool LocalFrameView::MapToVisualRectInRemoteRootFrame( |
| PhysicalRect& rect, |
| bool apply_overflow_clip) { |
| DCHECK(frame_->IsLocalRoot()); |
| // This is the top-level frame, so no mapping necessary. |
| if (frame_->IsMainFrame()) |
| return true; |
| bool result = rect.InclusiveIntersect(PhysicalRect( |
| apply_overflow_clip ? frame_->RemoteViewportIntersection() |
| : frame_->RemoteMainFrameIntersection())); |
| if (result) { |
| if (LayoutView* layout_view = GetLayoutView()) { |
| rect = layout_view->LocalToAncestorRect( |
| rect, nullptr, |
| kTraverseDocumentBoundaries | kApplyRemoteMainFrameTransform); |
| } |
| } |
| return result; |
| } |
| |
| void LocalFrameView::MapLocalToRemoteMainFrame( |
| TransformState& transform_state) { |
| DCHECK(frame_->IsLocalRoot()); |
| // This is the top-level frame, so no mapping necessary. |
| if (frame_->IsMainFrame()) |
| return; |
| transform_state.ApplyTransform( |
| TransformationMatrix(GetFrame().RemoteMainFrameTransform().matrix()), |
| TransformState::kAccumulateTransform); |
| } |
| |
| LayoutUnit LocalFrameView::CaretWidth() const { |
| return LayoutUnit(std::max<float>( |
| 1.0f, GetChromeClient()->WindowToViewportScalar(&GetFrame(), 1.0f))); |
| } |
| |
| void LocalFrameView::DidChangeMobileFriendliness( |
| const blink::MobileFriendliness& mf) { |
| GetFrame().Client()->DidChangeMobileFriendliness(mf); |
| } |
| |
| LocalFrameUkmAggregator& LocalFrameView::EnsureUkmAggregator() { |
| if (!ukm_aggregator_) { |
| ukm_aggregator_ = base::MakeRefCounted<LocalFrameUkmAggregator>( |
| frame_->GetDocument()->UkmSourceID(), |
| frame_->GetDocument()->UkmRecorder()); |
| } |
| return *ukm_aggregator_; |
| } |
| |
| void LocalFrameView::OnFirstContentfulPaint() { |
| GetPage()->GetChromeClient().StopDeferringCommits( |
| *frame_, cc::PaintHoldingCommitTrigger::kFirstContentfulPaint); |
| EnsureUkmAggregator().DidReachFirstContentfulPaint(frame_->IsMainFrame()); |
| } |
| |
| void LocalFrameView::RegisterForLifecycleNotifications( |
| LifecycleNotificationObserver* observer) { |
| lifecycle_observers_.insert(observer); |
| } |
| |
| void LocalFrameView::UnregisterFromLifecycleNotifications( |
| LifecycleNotificationObserver* observer) { |
| lifecycle_observers_.erase(observer); |
| } |
| |
| void LocalFrameView::EnqueueStartOfLifecycleTask(base::OnceClosure closure) { |
| start_of_lifecycle_tasks_.push_back(std::move(closure)); |
| } |
| |
| void LocalFrameView::NotifyVideoIsDominantVisibleStatus( |
| HTMLVideoElement* element, |
| bool is_dominant) { |
| if (is_dominant) { |
| fullscreen_video_elements_.insert(element); |
| return; |
| } |
| |
| fullscreen_video_elements_.erase(element); |
| } |
| |
| bool LocalFrameView::HasDominantVideoElement() const { |
| return !fullscreen_video_elements_.IsEmpty(); |
| } |
| |
| #if DCHECK_IS_ON() |
| LocalFrameView::DisallowLayoutInvalidationScope:: |
| DisallowLayoutInvalidationScope(LocalFrameView* view) |
| : local_frame_view_(view) { |
| local_frame_view_->allows_layout_invalidation_after_layout_clean_ = false; |
| local_frame_view_->ForAllChildLocalFrameViews([](LocalFrameView& frame_view) { |
| if (!frame_view.ShouldThrottleRendering()) |
| frame_view.CheckDoesNotNeedLayout(); |
| frame_view.allows_layout_invalidation_after_layout_clean_ = false; |
| }); |
| } |
| |
| LocalFrameView::DisallowLayoutInvalidationScope:: |
| ~DisallowLayoutInvalidationScope() { |
| local_frame_view_->allows_layout_invalidation_after_layout_clean_ = true; |
| local_frame_view_->ForAllChildLocalFrameViews([](LocalFrameView& frame_view) { |
| if (!frame_view.ShouldThrottleRendering()) |
| frame_view.CheckDoesNotNeedLayout(); |
| frame_view.allows_layout_invalidation_after_layout_clean_ = true; |
| }); |
| } |
| |
| #endif |
| |
| void LocalFrameView::UpdateLayerDebugInfoEnabled() { |
| DCHECK(frame_->IsLocalRoot()); |
| #if DCHECK_IS_ON() |
| DCHECK(layer_debug_info_enabled_); |
| #else |
| bool should_enable = |
| cc::frame_viewer_instrumentation::IsTracingLayerTreeSnapshots() || |
| WebTestSupport::IsRunningWebTest() || |
| CoreProbeSink::HasAgentsGlobal(CoreProbeSink::kInspectorLayerTreeAgent); |
| if (should_enable != layer_debug_info_enabled_) { |
| layer_debug_info_enabled_ = should_enable; |
| SetPaintArtifactCompositorNeedsUpdate(); |
| } |
| #endif |
| } |
| |
| OverlayInterstitialAdDetector& |
| LocalFrameView::EnsureOverlayInterstitialAdDetector() { |
| if (!overlay_interstitial_ad_detector_) { |
| overlay_interstitial_ad_detector_ = |
| std::make_unique<OverlayInterstitialAdDetector>(); |
| } |
| return *overlay_interstitial_ad_detector_.get(); |
| } |
| |
| WTF::Vector<const TransformPaintPropertyNode*> |
| LocalFrameView::GetScrollTranslationNodes() { |
| WTF::Vector<const TransformPaintPropertyNode*> scroll_translation_nodes; |
| for (auto area : *ScrollableAreas()) { |
| const auto* paint_properties = |
| area->GetLayoutBox()->FirstFragment().PaintProperties(); |
| if (paint_properties && paint_properties->Scroll()) { |
| scroll_translation_nodes.push_back(paint_properties->ScrollTranslation()); |
| } |
| } |
| return scroll_translation_nodes; |
| } |
| |
| StickyAdDetector& LocalFrameView::EnsureStickyAdDetector() { |
| if (!sticky_ad_detector_) { |
| sticky_ad_detector_ = std::make_unique<StickyAdDetector>(); |
| } |
| return *sticky_ad_detector_.get(); |
| } |
| |
| static PaintLayer* GetFullScreenOverlayVideoLayer(Document& document) { |
| // Recursively find the document that is in fullscreen. |
| Document* content_document = &document; |
| Element* fullscreen_element = |
| Fullscreen::FullscreenElementFrom(*content_document); |
| while (auto* frame_owner = |
| DynamicTo<HTMLFrameOwnerElement>(fullscreen_element)) { |
| content_document = frame_owner->contentDocument(); |
| if (!content_document) |
| return nullptr; |
| fullscreen_element = Fullscreen::FullscreenElementFrom(*content_document); |
| } |
| auto* video_element = DynamicTo<HTMLVideoElement>(fullscreen_element); |
| if (!video_element || !video_element->UsesOverlayFullscreenVideo()) |
| return nullptr; |
| return video_element->GetLayoutBoxModelObject()->Layer(); |
| } |
| |
| static PaintLayer* GetXrOverlayLayer(Document& document) { |
| // immersive-ar DOM overlay mode is very similar to fullscreen video, using |
| // the AR camera image instead of a video element as a background that's |
| // separately composited in the browser. The fullscreened DOM content is shown |
| // on top of that, same as HTML video controls. |
| if (!document.IsXrOverlay()) |
| return nullptr; |
| |
| // When DOM overlay mode is active in iframe content, the parent frame's |
| // document will also be marked as being in DOM overlay mode, with the iframe |
| // element being in fullscreen mode. Find the innermost reachable fullscreen |
| // element to use as the XR overlay layer. This is the overlay element for |
| // same-process iframes, or an iframe element for OOPIF if the overlay element |
| // is in another process. |
| Document* content_document = &document; |
| Element* fullscreen_element = |
| Fullscreen::FullscreenElementFrom(*content_document); |
| while (auto* frame_owner = |
| DynamicTo<HTMLFrameOwnerElement>(fullscreen_element)) { |
| content_document = frame_owner->contentDocument(); |
| if (!content_document) { |
| // This is an OOPIF iframe, treat it as the fullscreen element. |
| break; |
| } |
| fullscreen_element = Fullscreen::FullscreenElementFrom(*content_document); |
| } |
| |
| if (!fullscreen_element) |
| return nullptr; |
| |
| const auto* object = fullscreen_element->GetLayoutBoxModelObject(); |
| if (!object) { |
| // Currently, only HTML fullscreen elements are supported for this mode, |
| // not others such as SVG or MathML. |
| DVLOG(1) << "no LayoutBoxModelObject for element " << fullscreen_element; |
| return nullptr; |
| } |
| |
| return object->Layer(); |
| } |
| |
| PaintLayer* LocalFrameView::GetFullScreenOverlayLayer() const { |
| Document* doc = frame_->GetDocument(); |
| DCHECK(doc); |
| |
| // For WebXR DOM Overlay, the fullscreen overlay layer comes from either the |
| // overlay element itself, or from an iframe element if the overlay element is |
| // in an OOPIF. This layer is needed even for non-main-frame scenarios to |
| // ensure the background remains transparent. |
| if (doc->IsXrOverlay()) |
| return GetXrOverlayLayer(*doc); |
| |
| // Fullscreen overlay video layers are only used for the main frame. |
| DCHECK(frame_->IsMainFrame()); |
| return GetFullScreenOverlayVideoLayer(*doc); |
| } |
| |
| void LocalFrameView::RunPaintBenchmark(int repeat_count, |
| cc::PaintBenchmarkResult& result) { |
| DCHECK_EQ(Lifecycle().GetState(), DocumentLifecycle::kPaintClean); |
| |
| auto run_benchmark = [&](PaintBenchmarkMode mode) -> double { |
| constexpr int kTimeCheckInterval = 1; |
| constexpr int kWarmupRuns = 0; |
| constexpr base::TimeDelta kTimeLimit = base::TimeDelta::FromMilliseconds(1); |
| |
| base::TimeDelta min_time = base::TimeDelta::Max(); |
| for (int i = 0; i < repeat_count; i++) { |
| // Run for a minimum amount of time to avoid problems with timer |
| // quantization when the time is very small. |
| base::LapTimer timer(kWarmupRuns, kTimeLimit, kTimeCheckInterval); |
| do { |
| RunPaintLifecyclePhase(mode); |
| timer.NextLap(); |
| } while (!timer.HasTimeLimitExpired()); |
| |
| base::TimeDelta duration = timer.TimePerLap(); |
| if (duration < min_time) |
| min_time = duration; |
| } |
| return min_time.InMillisecondsF(); |
| }; |
| |
| result.record_time_ms = run_benchmark(PaintBenchmarkMode::kForcePaint); |
| result.record_time_caching_disabled_ms = |
| run_benchmark(PaintBenchmarkMode::kCachingDisabled); |
| result.record_time_subsequence_caching_disabled_ms = |
| run_benchmark(PaintBenchmarkMode::kSubsequenceCachingDisabled); |
| result.record_time_partial_invalidation_ms = |
| run_benchmark(PaintBenchmarkMode::kPartialInvalidation); |
| result.raster_invalidation_and_convert_time_ms = |
| run_benchmark(PaintBenchmarkMode::kForceRasterInvalidationAndConvert); |
| result.paint_artifact_compositor_update_time_ms = |
| run_benchmark(PaintBenchmarkMode::kForcePaintArtifactCompositorUpdate); |
| |
| result.painter_memory_usage = 0; |
| if (paint_controller_) { |
| result.painter_memory_usage += |
| paint_controller_->ApproximateUnsharedMemoryUsage(); |
| } |
| if (paint_artifact_compositor_) { |
| result.painter_memory_usage += |
| paint_artifact_compositor_->ApproximateUnsharedMemoryUsage(); |
| } |
| if (auto* root = GetLayoutView()->Compositor()->PaintRootGraphicsLayer()) { |
| result.painter_memory_usage += |
| root->ApproximateUnsharedMemoryUsageRecursive(); |
| } |
| } |
| |
| } // namespace blink |