| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "third_party/blink/renderer/core/frame/remote_frame_view.h" |
| |
| #include "third_party/blink/renderer/core/dom/element_visibility_observer.h" |
| #include "third_party/blink/renderer/core/frame/local_frame.h" |
| #include "third_party/blink/renderer/core/frame/local_frame_view.h" |
| #include "third_party/blink/renderer/core/frame/remote_frame.h" |
| #include "third_party/blink/renderer/core/frame/remote_frame_client.h" |
| #include "third_party/blink/renderer/core/html/html_frame_owner_element.h" |
| #include "third_party/blink/renderer/core/intersection_observer/intersection_observer_entry.h" |
| #include "third_party/blink/renderer/core/layout/layout_embedded_content.h" |
| #include "third_party/blink/renderer/core/layout/layout_view.h" |
| #include "third_party/blink/renderer/core/page/page.h" |
| #include "third_party/blink/renderer/platform/geometry/int_rect.h" |
| #include "third_party/blink/renderer/platform/graphics/graphics_context.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/cull_rect.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/drawing_recorder.h" |
| |
| namespace blink { |
| |
| RemoteFrameView::RemoteFrameView(RemoteFrame* remote_frame) |
| : remote_frame_(remote_frame), is_attached_(false) { |
| DCHECK(remote_frame); |
| } |
| |
| RemoteFrameView::~RemoteFrameView() = default; |
| |
| LocalFrameView* RemoteFrameView::ParentFrameView() const { |
| if (!is_attached_) |
| return nullptr; |
| |
| Frame* parent_frame = remote_frame_->Tree().Parent(); |
| if (parent_frame && parent_frame->IsLocalFrame()) |
| return ToLocalFrame(parent_frame)->View(); |
| |
| return nullptr; |
| } |
| |
| void RemoteFrameView::AttachToLayout() { |
| DCHECK(!is_attached_); |
| is_attached_ = true; |
| if (ParentFrameView()->IsVisible()) |
| SetParentVisible(true); |
| |
| SetupRenderThrottling(); |
| subtree_throttled_ = ParentFrameView()->CanThrottleRendering(); |
| |
| FrameRectsChanged(); |
| } |
| |
| void RemoteFrameView::DetachFromLayout() { |
| DCHECK(is_attached_); |
| SetParentVisible(false); |
| is_attached_ = false; |
| } |
| |
| RemoteFrameView* RemoteFrameView::Create(RemoteFrame* remote_frame) { |
| RemoteFrameView* view = new RemoteFrameView(remote_frame); |
| view->Show(); |
| return view; |
| } |
| |
| void RemoteFrameView::UpdateViewportIntersectionsForSubtree() { |
| LayoutEmbeddedContent* owner = remote_frame_->OwnerLayoutObject(); |
| if (!owner) |
| return; |
| |
| LocalFrameView* local_root_view = |
| ToLocalFrame(remote_frame_->Tree().Parent())->LocalFrameRoot().View(); |
| if (!local_root_view) |
| return; |
| |
| IntRect viewport_intersection; |
| bool occluded_or_obscured = false; |
| DocumentLifecycle::LifecycleState parent_state = |
| owner->GetDocument().Lifecycle().GetState(); |
| |
| // If the parent LocalFrameView is throttled and out-of-date, then we can't |
| // get any useful information. |
| if (parent_state >= DocumentLifecycle::kLayoutClean) { |
| // Start with rect in remote frame's coordinate space. Then |
| // mapToVisualRectInAncestorSpace will move it to the local root's |
| // coordinate space and account for any clip from containing elements such |
| // as a scrollable div. Passing nullptr as an argument to |
| // mapToVisualRectInAncestorSpace causes it to be clipped to the viewport, |
| // even if there are RemoteFrame ancestors in the frame tree. |
| LayoutRect rect(0, 0, frame_rect_.Width(), frame_rect_.Height()); |
| rect.Move(owner->PhysicalContentBoxOffset()); |
| if (owner->MapToVisualRectInAncestorSpace(nullptr, rect, |
| kUseGeometryMapper)) { |
| IntRect root_visible_rect(IntPoint(), local_root_view->Size()); |
| IntRect intersected_rect = EnclosingIntRect(rect); |
| intersected_rect.Intersect(root_visible_rect); |
| |
| // Translate the intersection rect from the root frame's coordinate space |
| // to the remote frame's coordinate space. |
| FloatRect viewport_intersection_float = |
| remote_frame_->OwnerLayoutObject() |
| ->AncestorToLocalQuad( |
| local_root_view->GetLayoutView(), FloatQuad(intersected_rect), |
| kTraverseDocumentBoundaries | kUseTransforms) |
| .BoundingBox(); |
| viewport_intersection_float.Move( |
| -remote_frame_->OwnerLayoutObject()->PhysicalContentBoxOffset()); |
| viewport_intersection = EnclosingIntRect(viewport_intersection_float); |
| } |
| } |
| |
| if (parent_state >= DocumentLifecycle::kPrePaintClean && |
| RuntimeEnabledFeatures::IntersectionObserverV2Enabled()) { |
| // TODO(layout-dev): As an optimization, we should only check for |
| // occlusion and effects if the remote frame needs it, i.e., if it has at |
| // least one active IntersectionObserver with trackVisibility:true. |
| if (owner->GetDocument() |
| .GetFrame() |
| ->LocalFrameRoot() |
| .MayBeOccludedOrObscuredByRemoteAncestor() || |
| owner->HasDistortingVisualEffects()) { |
| occluded_or_obscured = true; |
| } else { |
| HitTestResult result(owner->HitTestForOcclusion()); |
| occluded_or_obscured = |
| result.InnerNode() && result.InnerNode() != owner->GetNode(); |
| } |
| } |
| |
| if (viewport_intersection == last_viewport_intersection_ && |
| occluded_or_obscured == last_occluded_or_obscured_) { |
| return; |
| } |
| |
| last_viewport_intersection_ = viewport_intersection; |
| last_occluded_or_obscured_ = occluded_or_obscured; |
| remote_frame_->Client()->UpdateRemoteViewportIntersection( |
| viewport_intersection, occluded_or_obscured); |
| } |
| |
| IntRect RemoteFrameView::GetCompositingRect() { |
| LocalFrameView* local_root_view = |
| ToLocalFrame(remote_frame_->Tree().Parent())->LocalFrameRoot().View(); |
| if (!local_root_view || !remote_frame_->OwnerLayoutObject()) |
| return IntRect(); |
| |
| // For main frames we constrain the rect that gets painted to the viewport. |
| // If the local frame root is an OOPIF itself, then we use the root's |
| // intersection rect. This represents a conservative maximum for the area |
| // that needs to be rastered by the OOPIF compositor. |
| IntSize viewport_size = local_root_view->FrameRect().Size(); |
| if (local_root_view->GetPage()->MainFrame() != local_root_view->GetFrame()) { |
| viewport_size = |
| local_root_view->GetFrame().RemoteViewportIntersection().Size(); |
| } |
| |
| // The viewport size needs to account for intermediate CSS transforms before |
| // being compared to the frame size. |
| FloatQuad viewport_quad = |
| remote_frame_->OwnerLayoutObject()->AncestorToLocalQuad( |
| local_root_view->GetLayoutView(), |
| FloatRect(FloatPoint(), FloatSize(viewport_size)), |
| kTraverseDocumentBoundaries | kUseTransforms); |
| IntSize converted_viewport_size = |
| EnclosingIntRect(viewport_quad.BoundingBox()).Size(); |
| |
| IntSize frame_size = FrameRect().Size(); |
| |
| // Iframes that fit within the window viewport get fully rastered. For |
| // iframes that are larger than the window viewport, add a 30% buffer to the |
| // draw area to try to prevent guttering during scroll. |
| // TODO(kenrb): The 30% value is arbitrary, it gives 15% overdraw in both |
| // directions when the iframe extends beyond both edges of the viewport, and |
| // it seems to make guttering rare with slow to medium speed wheel scrolling. |
| // Can we collect UMA data to estimate how much extra rastering this causes, |
| // and possibly how common guttering is? |
| converted_viewport_size.Scale(1.3f); |
| converted_viewport_size.SetWidth( |
| std::min(frame_size.Width(), converted_viewport_size.Width())); |
| converted_viewport_size.SetHeight( |
| std::min(frame_size.Height(), converted_viewport_size.Height())); |
| IntPoint expanded_origin; |
| if (!last_viewport_intersection_.IsEmpty()) { |
| IntSize expanded_size = |
| last_viewport_intersection_.Size().ExpandedTo(converted_viewport_size); |
| expanded_size -= last_viewport_intersection_.Size(); |
| expanded_size.Scale(0.5f, 0.5f); |
| expanded_origin = last_viewport_intersection_.Location() - expanded_size; |
| expanded_origin.ClampNegativeToZero(); |
| } |
| return IntRect(expanded_origin, converted_viewport_size); |
| } |
| |
| void RemoteFrameView::Dispose() { |
| HTMLFrameOwnerElement* owner_element = remote_frame_->DeprecatedLocalOwner(); |
| // ownerElement can be null during frame swaps, because the |
| // RemoteFrameView is disconnected before detachment. |
| if (owner_element && owner_element->OwnedEmbeddedContentView() == this) |
| owner_element->SetEmbeddedContentView(nullptr); |
| } |
| |
| void RemoteFrameView::InvalidateRect(const IntRect& rect) { |
| auto* object = remote_frame_->OwnerLayoutObject(); |
| if (!object) |
| return; |
| |
| LayoutRect repaint_rect(rect); |
| repaint_rect.Move(object->BorderLeft() + object->PaddingLeft(), |
| object->BorderTop() + object->PaddingTop()); |
| object->InvalidatePaintRectangle(repaint_rect); |
| } |
| |
| void RemoteFrameView::SetFrameRect(const IntRect& frame_rect) { |
| if (frame_rect == frame_rect_) |
| return; |
| |
| frame_rect_ = frame_rect; |
| FrameRectsChanged(); |
| } |
| |
| IntRect RemoteFrameView::FrameRect() const { |
| IntPoint location(frame_rect_.Location()); |
| |
| // As an optimization, we don't include the root layer's scroll offset in the |
| // frame rect. As a result, we don't need to recalculate the frame rect every |
| // time the root layer scrolls, but we need to add it in here. |
| LayoutEmbeddedContent* owner = remote_frame_->OwnerLayoutObject(); |
| if (owner) { |
| LayoutView* owner_layout_view = owner->View(); |
| DCHECK(owner_layout_view); |
| if (owner_layout_view->HasOverflowClip()) { |
| IntSize scroll_offset(owner_layout_view->ScrolledContentOffset()); |
| location.SaturatedMove(-scroll_offset.Width(), -scroll_offset.Height()); |
| } |
| } |
| |
| return IntRect(location, frame_rect_.Size()); |
| } |
| |
| void RemoteFrameView::FrameRectsChanged() { |
| // Update the rect to reflect the position of the frame relative to the |
| // containing local frame root. The position of the local root within |
| // any remote frames, if any, is accounted for by the embedder. |
| IntRect frame_rect(FrameRect()); |
| IntRect screen_space_rect = frame_rect; |
| |
| if (LocalFrameView* parent = ParentFrameView()) { |
| screen_space_rect = parent->ConvertToRootFrame(screen_space_rect); |
| } |
| remote_frame_->Client()->FrameRectsChanged(frame_rect, screen_space_rect); |
| } |
| |
| void RemoteFrameView::Paint(GraphicsContext& context, |
| const GlobalPaintFlags flags, |
| const CullRect& rect, |
| const IntSize& paint_offset) const { |
| // Painting remote frames is only for printing. |
| if (!context.Printing()) |
| return; |
| |
| if (!rect.Intersects(FrameRect())) |
| return; |
| |
| DrawingRecorder recorder(context, *GetFrame().OwnerLayoutObject(), |
| DisplayItem::kDocumentBackground); |
| context.Save(); |
| context.Translate(paint_offset.Width(), paint_offset.Height()); |
| |
| DCHECK(context.Canvas()); |
| // Inform the remote frame to print. |
| uint32_t content_id = Print(FrameRect(), context.Canvas()); |
| |
| // Record the place holder id on canvas. |
| context.Canvas()->recordCustomData(content_id); |
| context.Restore(); |
| } |
| |
| void RemoteFrameView::UpdateGeometry() { |
| if (LayoutEmbeddedContent* layout = remote_frame_->OwnerLayoutObject()) |
| layout->UpdateGeometry(*this); |
| } |
| |
| void RemoteFrameView::Hide() { |
| self_visible_ = false; |
| remote_frame_->Client()->VisibilityChanged(false); |
| } |
| |
| void RemoteFrameView::Show() { |
| self_visible_ = true; |
| remote_frame_->Client()->VisibilityChanged(true); |
| } |
| |
| void RemoteFrameView::SetParentVisible(bool visible) { |
| if (parent_visible_ == visible) |
| return; |
| |
| parent_visible_ = visible; |
| if (!self_visible_) |
| return; |
| |
| remote_frame_->Client()->VisibilityChanged(self_visible_ && parent_visible_); |
| } |
| |
| void RemoteFrameView::SetupRenderThrottling() { |
| if (visibility_observer_) |
| return; |
| |
| Element* target_element = GetFrame().DeprecatedLocalOwner(); |
| if (!target_element) |
| return; |
| |
| visibility_observer_ = MakeGarbageCollected<ElementVisibilityObserver>( |
| target_element, WTF::BindRepeating( |
| [](RemoteFrameView* remote_view, bool is_visible) { |
| remote_view->UpdateRenderThrottlingStatus( |
| !is_visible, remote_view->subtree_throttled_); |
| }, |
| WrapWeakPersistent(this))); |
| visibility_observer_->Start(); |
| } |
| |
| void RemoteFrameView::UpdateRenderThrottlingStatus(bool hidden, |
| bool subtree_throttled) { |
| TRACE_EVENT0("blink", "RemoteFrameView::UpdateRenderThrottlingStatus"); |
| if (!remote_frame_->Client()) |
| return; |
| |
| bool was_throttled = CanThrottleRendering(); |
| |
| // Note that we disallow throttling of 0x0 and display:none frames because |
| // some sites use them to drive UI logic. |
| HTMLFrameOwnerElement* owner_element = remote_frame_->DeprecatedLocalOwner(); |
| hidden_for_throttling_ = hidden && !frame_rect_.IsEmpty() && |
| (owner_element && owner_element->GetLayoutObject()); |
| subtree_throttled_ = subtree_throttled; |
| |
| bool is_throttled = CanThrottleRendering(); |
| if (was_throttled != is_throttled) { |
| remote_frame_->Client()->UpdateRenderThrottlingStatus(is_throttled, |
| subtree_throttled_); |
| } |
| } |
| |
| bool RemoteFrameView::CanThrottleRendering() const { |
| if (!RuntimeEnabledFeatures::RenderingPipelineThrottlingEnabled()) |
| return false; |
| if (subtree_throttled_) |
| return true; |
| return hidden_for_throttling_; |
| } |
| |
| void RemoteFrameView::SetIntrinsicSizeInfo( |
| const IntrinsicSizingInfo& size_info) { |
| intrinsic_sizing_info_ = size_info; |
| has_intrinsic_sizing_info_ = true; |
| } |
| |
| bool RemoteFrameView::GetIntrinsicSizingInfo( |
| IntrinsicSizingInfo& sizing_info) const { |
| if (!has_intrinsic_sizing_info_) |
| return false; |
| |
| sizing_info = intrinsic_sizing_info_; |
| return true; |
| } |
| |
| bool RemoteFrameView::HasIntrinsicSizingInfo() const { |
| return has_intrinsic_sizing_info_; |
| } |
| |
| uint32_t RemoteFrameView::Print(const IntRect& rect, |
| cc::PaintCanvas* canvas) const { |
| return remote_frame_->Client()->Print(rect, canvas); |
| } |
| |
| void RemoteFrameView::Trace(blink::Visitor* visitor) { |
| visitor->Trace(remote_frame_); |
| visitor->Trace(visibility_observer_); |
| } |
| |
| } // namespace blink |