blob: a7ee396d7d9b5b933390a2b374bb71302a1a9b5e [file] [log] [blame]
/*
* Copyright (C) 1999 Lars Knoll (knoll@kde.org)
* (C) 2000 Simon Hausmann <hausmann@kde.org>
* (C) 2000 Stefan Schimanski (1Stein@gmx.de)
* Copyright (C) 2004, 2005, 2006, 2009 Apple Inc. All rights reserved.
* Copyright (C) Research In Motion Limited 2011. 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/layout/layout_embedded_content.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/renderer/core/accessibility/ax_object_cache.h"
#include "third_party/blink/renderer/core/exported/web_plugin_container_impl.h"
#include "third_party/blink/renderer/core/frame/embedded_content_view.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_view.h"
#include "third_party/blink/renderer/core/html/fenced_frame/html_fenced_frame_element.h"
#include "third_party/blink/renderer/core/html/html_frame_element_base.h"
#include "third_party/blink/renderer/core/html/html_plugin_element.h"
#include "third_party/blink/renderer/core/layout/geometry/physical_offset.h"
#include "third_party/blink/renderer/core/layout/hit_test_location.h"
#include "third_party/blink/renderer/core/layout/hit_test_result.h"
#include "third_party/blink/renderer/core/layout/layout_replaced.h"
#include "third_party/blink/renderer/core/layout/layout_view.h"
#include "third_party/blink/renderer/core/paint/embedded_content_painter.h"
#include "third_party/blink/renderer/core/paint/paint_layer.h"
#include "third_party/blink/renderer/platform/transforms/affine_transform.h"
#include "ui/gfx/geometry/point_conversions.h"
#include "ui/gfx/geometry/point_f.h"
namespace blink {
LayoutEmbeddedContent::LayoutEmbeddedContent(HTMLFrameOwnerElement* element)
: LayoutReplaced(element) {
DCHECK(element);
SetInline(false);
}
void LayoutEmbeddedContent::WillBeDestroyed() {
NOT_DESTROYED();
if (auto* frame_owner = GetFrameOwnerElement())
frame_owner->SetEmbeddedContentView(nullptr);
LayoutReplaced::WillBeDestroyed();
ClearNode();
}
FrameView* LayoutEmbeddedContent::ChildFrameView() const {
NOT_DESTROYED();
return DynamicTo<FrameView>(GetEmbeddedContentView());
}
LayoutView* LayoutEmbeddedContent::ChildLayoutView() const {
NOT_DESTROYED();
if (HTMLFrameOwnerElement* owner_element = GetFrameOwnerElement()) {
if (Document* content_document = owner_element->contentDocument())
return content_document->GetLayoutView();
}
return nullptr;
}
WebPluginContainerImpl* LayoutEmbeddedContent::Plugin() const {
NOT_DESTROYED();
EmbeddedContentView* embedded_content_view = GetEmbeddedContentView();
if (embedded_content_view && embedded_content_view->IsPluginView())
return To<WebPluginContainerImpl>(embedded_content_view);
return nullptr;
}
EmbeddedContentView* LayoutEmbeddedContent::GetEmbeddedContentView() const {
NOT_DESTROYED();
if (auto* frame_owner = GetFrameOwnerElement())
return frame_owner->OwnedEmbeddedContentView();
return nullptr;
}
const std::optional<PhysicalSize> LayoutEmbeddedContent::FrozenFrameSize()
const {
// The `<fencedframe>` element can freeze the child frame size when navigated.
if (const auto* fenced_frame = DynamicTo<HTMLFencedFrameElement>(GetNode()))
return fenced_frame->FrozenFrameSize();
return std::nullopt;
}
AffineTransform LayoutEmbeddedContent::EmbeddedContentTransform() const {
auto frozen_size = FrozenFrameSize();
if (!frozen_size || frozen_size->IsEmpty()) {
const PhysicalOffset content_box_offset = PhysicalContentBoxOffset();
return AffineTransform().Translate(content_box_offset.left,
content_box_offset.top);
}
AffineTransform translate_and_scale;
auto replaced_rect = ReplacedContentRect();
translate_and_scale.Translate(replaced_rect.X(), replaced_rect.Y());
translate_and_scale.Scale(replaced_rect.Width() / frozen_size->width,
replaced_rect.Height() / frozen_size->height);
return translate_and_scale;
}
PhysicalOffset LayoutEmbeddedContent::EmbeddedContentFromBorderBox(
const PhysicalOffset& offset) const {
gfx::PointF point(offset);
return PhysicalOffset::FromPointFRound(
EmbeddedContentTransform().Inverse().MapPoint(point));
}
gfx::PointF LayoutEmbeddedContent::EmbeddedContentFromBorderBox(
const gfx::PointF& point) const {
return EmbeddedContentTransform().Inverse().MapPoint(point);
}
PhysicalOffset LayoutEmbeddedContent::BorderBoxFromEmbeddedContent(
const PhysicalOffset& offset) const {
gfx::PointF point(offset);
return PhysicalOffset::FromPointFRound(
EmbeddedContentTransform().MapPoint(point));
}
gfx::Rect LayoutEmbeddedContent::BorderBoxFromEmbeddedContent(
const gfx::Rect& rect) const {
return EmbeddedContentTransform().MapRect(rect);
}
PaintLayerType LayoutEmbeddedContent::LayerTypeRequired() const {
NOT_DESTROYED();
PaintLayerType type = LayoutReplaced::LayerTypeRequired();
if (type != kNoPaintLayer)
return type;
return kForcedPaintLayer;
}
bool LayoutEmbeddedContent::PointOverResizer(
const HitTestResult& result,
const HitTestLocation& location,
const PhysicalOffset& accumulated_offset) const {
NOT_DESTROYED();
if (const auto* scrollable_area = GetScrollableArea()) {
const HitTestRequest::HitTestRequestType hit_type =
result.GetHitTestRequest().GetType();
const blink::ResizerHitTestType resizer_type =
hit_type & HitTestRequest::kTouchEvent ? kResizerForTouch
: kResizerForPointer;
return scrollable_area->IsAbsolutePointInResizeControl(
ToRoundedPoint(location.Point() - accumulated_offset), resizer_type);
}
return false;
}
bool LayoutEmbeddedContent::NodeAtPointOverEmbeddedContentView(
HitTestResult& result,
const HitTestLocation& hit_test_location,
const PhysicalOffset& accumulated_offset,
HitTestPhase phase) {
NOT_DESTROYED();
bool had_result = result.InnerNode();
bool inside = LayoutReplaced::NodeAtPoint(result, hit_test_location,
accumulated_offset, phase);
// Check to see if we are really over the EmbeddedContentView itself (and not
// just in the border/padding area or the resizer area).
if ((inside || hit_test_location.IsRectBasedTest()) && !had_result &&
result.InnerNode() == GetNode()) {
bool is_over_content_view =
PhysicalContentBoxRect().Contains(result.LocalPoint()) &&
!result.IsOverResizer();
result.SetIsOverEmbeddedContentView(is_over_content_view);
}
return inside;
}
bool LayoutEmbeddedContent::NodeAtPoint(
HitTestResult& result,
const HitTestLocation& hit_test_location,
const PhysicalOffset& accumulated_offset,
HitTestPhase phase) {
NOT_DESTROYED();
auto* local_frame_view = DynamicTo<LocalFrameView>(ChildFrameView());
bool skip_contents =
(result.GetHitTestRequest().GetStopNode() == this ||
!result.GetHitTestRequest().AllowsChildFrameContent() ||
PointOverResizer(result, hit_test_location, accumulated_offset));
if (!local_frame_view || skip_contents) {
return NodeAtPointOverEmbeddedContentView(result, hit_test_location,
accumulated_offset, phase);
}
// A hit test can never hit an off-screen element; only off-screen iframes are
// throttled; therefore, hit tests can skip descending into throttled iframes.
// We also check the document lifecycle state because the frame may have been
// throttled at the time lifecycle updates happened, in which case it will not
// be up-to-date and we can't hit test it.
if (local_frame_view->ShouldThrottleRendering() ||
!local_frame_view->GetFrame().GetDocument() ||
local_frame_view->GetFrame().GetDocument()->Lifecycle().GetState() <
DocumentLifecycle::kPrePaintClean) {
return NodeAtPointOverEmbeddedContentView(result, hit_test_location,
accumulated_offset, phase);
}
DCHECK_GE(GetDocument().Lifecycle().GetState(),
DocumentLifecycle::kPrePaintClean);
if (phase == HitTestPhase::kForeground) {
auto* child_layout_view = local_frame_view->GetLayoutView();
if (VisibleToHitTestRequest(result.GetHitTestRequest()) &&
child_layout_view) {
PhysicalOffset content_offset(BorderLeft() + PaddingLeft(),
BorderTop() + PaddingTop());
HitTestLocation new_hit_test_location(
hit_test_location, -accumulated_offset - content_offset);
HitTestRequest new_hit_test_request(
result.GetHitTestRequest().GetType() |
HitTestRequest::kChildFrameHitTest,
result.GetHitTestRequest().GetStopNode());
HitTestResult child_frame_result(new_hit_test_request,
new_hit_test_location);
// The frame's layout and style must be up to date if we reach here.
bool is_inside_child_frame = child_layout_view->HitTestNoLifecycleUpdate(
new_hit_test_location, child_frame_result);
if (result.GetHitTestRequest().ListBased()) {
result.Append(child_frame_result);
} else if (is_inside_child_frame) {
// Force the result not to be cacheable because the parent frame should
// not cache this result; as it won't be notified of changes in the
// child.
child_frame_result.SetCacheable(false);
result = child_frame_result;
}
// Don't trust |isInsideChildFrame|. For rect-based hit-test, returns
// true only when the hit test rect is totally within the iframe,
// i.e. nodeAtPointOverEmbeddedContentView() also returns true.
// Use a temporary HitTestResult because we don't want to collect the
// iframe element itself if the hit-test rect is totally within the
// iframe.
if (is_inside_child_frame) {
if (!hit_test_location.IsRectBasedTest())
return true;
HitTestResult point_over_embedded_content_view_result = result;
bool point_over_embedded_content_view =
NodeAtPointOverEmbeddedContentView(
point_over_embedded_content_view_result, hit_test_location,
accumulated_offset, phase);
if (point_over_embedded_content_view)
return true;
result = point_over_embedded_content_view_result;
return false;
}
}
}
return NodeAtPointOverEmbeddedContentView(result, hit_test_location,
accumulated_offset, phase);
}
void LayoutEmbeddedContent::StyleDidChange(StyleDifference diff,
const ComputedStyle* old_style) {
NOT_DESTROYED();
LayoutReplaced::StyleDidChange(diff, old_style);
const ComputedStyle& new_style = StyleRef();
if (Frame* frame = GetFrameOwnerElement()->ContentFrame())
frame->UpdateInertIfPossible();
if (EmbeddedContentView* embedded_content_view = GetEmbeddedContentView()) {
if (new_style.Visibility() != EVisibility::kVisible) {
embedded_content_view->Hide();
} else {
embedded_content_view->Show();
}
}
auto* frame_owner = GetFrameOwnerElement();
if (!frame_owner)
return;
if (old_style && new_style.UsedColorScheme() != old_style->UsedColorScheme())
frame_owner->SetColorScheme(new_style.UsedColorScheme());
if (old_style &&
new_style.VisibleToHitTesting() == old_style->VisibleToHitTesting()) {
return;
}
if (auto* frame = frame_owner->ContentFrame())
frame->UpdateVisibleToHitTesting();
}
void LayoutEmbeddedContent::PaintReplaced(
const PaintInfo& paint_info,
const PhysicalOffset& paint_offset) const {
NOT_DESTROYED();
if (ChildPaintBlockedByDisplayLock())
return;
EmbeddedContentPainter(*this).PaintReplaced(paint_info, paint_offset);
}
CursorDirective LayoutEmbeddedContent::GetCursor(const PhysicalOffset& point,
ui::Cursor& cursor) const {
NOT_DESTROYED();
if (Plugin()) {
// A plugin is responsible for setting the cursor when the pointer is over
// it.
return kDoNotSetCursor;
}
return LayoutReplaced::GetCursor(point, cursor);
}
PhysicalRect LayoutEmbeddedContent::ReplacedContentRectFrom(
const PhysicalRect& base_content_rect) const {
NOT_DESTROYED();
PhysicalRect content_rect = base_content_rect;
// IFrames set as the root scroller should get their size from their parent.
// When scrolling starts so as to hide the URL bar, IFRAME wouldn't resize to
// match the now expanded size of the viewport until the scrolling stops. This
// makes sure the |ReplacedContentRect| matches the expanded viewport even
// before IFRAME resizes, for clipping to work correctly.
if (ChildFrameView() && View() && IsEffectiveRootScroller()) {
content_rect.offset = PhysicalOffset();
content_rect.size = View()->ViewRect().size;
}
if (const std::optional<PhysicalSize> frozen_size = FrozenFrameSize()) {
// TODO(kojii): Setting the `offset` to non-zero values breaks
// hit-testing/inputs. Even different size is suspicious, as the input
// system forwards mouse events to the child frame even when the mouse is
// outside of the child frame. Revisit this when the input system supports
// different |ReplacedContentRect| from |PhysicalContentBoxRect|.
PhysicalSize frozen_layout_size = *frozen_size;
content_rect =
ComputeReplacedContentRect(base_content_rect, &frozen_layout_size);
}
// We don't propagate sub-pixel into sub-frame layout, in other words, the
// rect is snapped at the document boundary, and sub-pixel movement could
// cause the sub-frame to layout due to the 1px snap difference. In order to
// avoid that, the size of sub-frame is rounded in advance.
return PreSnappedRectForPersistentSizing(content_rect);
}
void LayoutEmbeddedContent::UpdateOnEmbeddedContentViewChange() {
NOT_DESTROYED();
if (!Style())
return;
if (EmbeddedContentView* embedded_content_view = GetEmbeddedContentView()) {
if (!NeedsLayout())
UpdateGeometry(*embedded_content_view);
if (StyleRef().Visibility() != EVisibility::kVisible)
embedded_content_view->Hide();
else
embedded_content_view->Show();
}
// One of the reasons of the following is that the layout tree in the new
// embedded content view may have already had some paint property and paint
// invalidation flags set, and we need to propagate the flags into the host
// view. Adding, changing and removing are also significant changes to the
// tree so setting the flags ensures the required updates.
SetNeedsPaintPropertyUpdate();
SetShouldDoFullPaintInvalidation();
}
void LayoutEmbeddedContent::UpdateGeometry(
EmbeddedContentView& embedded_content_view) {
NOT_DESTROYED();
// TODO(wangxianzhu): We reset subpixel accumulation at some boundaries, so
// the following code is incorrect when some ancestors are such boundaries.
// What about multicol? Need a LayoutBox function to query sub-pixel
// accumulation.
PhysicalRect replaced_rect = ReplacedContentRect();
TransformState transform_state(TransformState::kApplyTransformDirection,
gfx::PointF(),
gfx::QuadF(gfx::RectF(replaced_rect)));
MapLocalToAncestor(nullptr, transform_state, 0);
transform_state.Flatten();
PhysicalOffset absolute_location =
PhysicalOffset::FromPointFRound(transform_state.LastPlanarPoint());
PhysicalRect absolute_replaced_rect = replaced_rect;
absolute_replaced_rect.Move(absolute_location);
gfx::RectF absolute_bounding_box =
transform_state.LastPlanarQuad().BoundingBox();
gfx::Rect frame_rect(gfx::Point(),
ToPixelSnappedRect(absolute_replaced_rect).size());
// Normally the location of the frame rect is ignored by the painter, but
// currently it is still used by a family of coordinate conversion function in
// LocalFrameView. This is incorrect because coordinate conversion
// needs to take transform and into account. A few callers still use the
// family of conversion function, including but not exhaustive:
// LocalFrameView::updateViewportIntersectionIfNeeded()
// RemoteFrameView::frameRectsChanged().
// WebPluginContainerImpl::reportGeometry()
// TODO(trchen): Remove this hack once we fixed all callers.
frame_rect.set_origin(gfx::ToRoundedPoint(absolute_bounding_box.origin()));
// 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; however, each implementation of
// EmbeddedContentView::FrameRect() must add the root layer's scroll offset
// into its position.
// TODO(szager): Refactor this functionality into EmbeddedContentView, rather
// than reimplementing in each concrete subclass.
LayoutView* layout_view = View();
if (layout_view && layout_view->IsScrollContainer()) {
// Floored because the PixelSnappedScrollOffset returns a ScrollOffset
// which is a float-type but frame_rect in a content view is an gfx::Rect.
// We may want to reevaluate the use of pixel snapping that since scroll
// offsets/layout can be fractional.
frame_rect.Offset(layout_view->PixelSnappedScrolledContentOffset());
}
embedded_content_view.SetFrameRect(frame_rect);
}
bool LayoutEmbeddedContent::IsThrottledFrameView() const {
NOT_DESTROYED();
if (auto* local_frame_view = DynamicTo<LocalFrameView>(ChildFrameView()))
return local_frame_view->ShouldThrottleRendering();
return false;
}
} // namespace blink