blob: ec2fe323362cebfaf9661810dbf381dc9fd1d83c [file] [log] [blame]
/*
* Copyright (C) 2012 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "third_party/blink/renderer/core/paint/link_highlight_impl.h"
#include <memory>
#include <utility>
#include "base/memory/ptr_util.h"
#include "cc/layers/picture_layer.h"
#include "cc/paint/display_item_list.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/public/platform/web_float_point.h"
#include "third_party/blink/public/platform/web_rect.h"
#include "third_party/blink/public/platform/web_size.h"
#include "third_party/blink/public/web/blink.h"
#include "third_party/blink/renderer/core/dom/layout_tree_builder_traversal.h"
#include "third_party/blink/renderer/core/dom/node.h"
#include "third_party/blink/renderer/core/exported/web_view_impl.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/settings.h"
#include "third_party/blink/renderer/core/frame/web_frame_widget_base.h"
#include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
#include "third_party/blink/renderer/core/layout/layout_box_model_object.h"
#include "third_party/blink/renderer/core/layout/layout_object.h"
#include "third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.h"
#include "third_party/blink/renderer/core/paint/paint_layer.h"
#include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
#include "third_party/blink/renderer/platform/animation/compositor_animation_curve.h"
#include "third_party/blink/renderer/platform/animation/compositor_float_animation_curve.h"
#include "third_party/blink/renderer/platform/animation/compositor_keyframe_model.h"
#include "third_party/blink/renderer/platform/animation/compositor_target_property.h"
#include "third_party/blink/renderer/platform/animation/timing_function.h"
#include "third_party/blink/renderer/platform/graphics/graphics_layer.h"
#include "third_party/blink/renderer/platform/graphics/paint/foreign_layer_display_item.h"
#include "third_party/blink/renderer/platform/graphics/paint/paint_canvas.h"
#include "third_party/blink/renderer/platform/graphics/paint/paint_recorder.h"
#include "third_party/blink/renderer/platform/web_test_support.h"
#include "third_party/blink/renderer/platform/wtf/vector.h"
#include "third_party/skia/include/core/SkMatrix44.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/size_f.h"
namespace blink {
std::unique_ptr<LinkHighlightImpl> LinkHighlightImpl::Create(Node* node) {
return base::WrapUnique(new LinkHighlightImpl(node));
}
LinkHighlightImpl::LinkHighlightImpl(Node* node)
: node_(node),
current_graphics_layer_(nullptr),
is_scrolling_graphics_layer_(false),
geometry_needs_update_(false),
is_animating_(false),
start_time_(CurrentTimeTicks()),
unique_id_(NewUniqueObjectId()) {
DCHECK(node_);
fragments_.emplace_back(element_id());
compositor_animation_ = CompositorAnimation::Create();
DCHECK(compositor_animation_);
compositor_animation_->SetAnimationDelegate(this);
compositor_animation_->AttachElement(element_id());
geometry_needs_update_ = true;
EffectPaintPropertyNode::State state;
state.local_transform_space = &TransformPaintPropertyNode::Root();
state.compositor_element_id = element_id();
state.direct_compositing_reasons = CompositingReason::kActiveOpacityAnimation;
effect_ = EffectPaintPropertyNode::Create(EffectPaintPropertyNode::Root(),
std::move(state));
#if DCHECK_IS_ON()
effect_->SetDebugName("LinkHighlightEffect");
#endif
}
LinkHighlightImpl::~LinkHighlightImpl() {
if (compositor_animation_->IsElementAttached())
compositor_animation_->DetachElement();
compositor_animation_->SetAnimationDelegate(nullptr);
compositor_animation_.reset();
ClearGraphicsLayerLinkHighlightPointer();
ReleaseResources();
}
void LinkHighlightImpl::ReleaseResources() {
if (!node_)
return;
if (auto* layout_object = node_->GetLayoutObject())
layout_object->SetNeedsPaintPropertyUpdate();
else
SetPaintArtifactCompositorNeedsUpdate();
node_.Clear();
}
void LinkHighlightImpl::AttachLinkHighlightToCompositingLayer(
const LayoutBoxModelObject& paint_invalidation_container) {
DCHECK(!RuntimeEnabledFeatures::CompositeAfterPaintEnabled());
GraphicsLayer* new_graphics_layer =
paint_invalidation_container.Layer()->GraphicsLayerBacking(
node_->GetLayoutObject());
is_scrolling_graphics_layer_ = false;
// FIXME: There should always be a GraphicsLayer. See crbug.com/431961.
if (paint_invalidation_container.Layer()->NeedsCompositedScrolling() &&
node_->GetLayoutObject() != &paint_invalidation_container) {
is_scrolling_graphics_layer_ = true;
}
if (!new_graphics_layer) {
ClearGraphicsLayerLinkHighlightPointer();
return;
}
if (current_graphics_layer_ != new_graphics_layer) {
if (current_graphics_layer_)
ClearGraphicsLayerLinkHighlightPointer();
current_graphics_layer_ = new_graphics_layer;
current_graphics_layer_->AddLinkHighlight(this);
}
}
static void AddQuadToPath(const FloatQuad& quad, Path& path) {
// FIXME: Make this create rounded quad-paths, just like the axis-aligned
// case.
path.MoveTo(quad.P1());
path.AddLineTo(quad.P2());
path.AddLineTo(quad.P3());
path.AddLineTo(quad.P4());
path.CloseSubpath();
}
void LinkHighlightImpl::ComputeQuads(const Node& node,
Vector<FloatQuad>& out_quads) const {
DCHECK(!RuntimeEnabledFeatures::CompositeAfterPaintEnabled());
if (!node.GetLayoutObject())
return;
LayoutObject* layout_object = node.GetLayoutObject();
// For inline elements, absoluteQuads will return a line box based on the
// line-height and font metrics, which is technically incorrect as replaced
// elements like images should use their intristic height and expand the
// linebox as needed. To get an appropriately sized highlight we descend
// into the children and have them add their boxes.
if (layout_object->IsLayoutInline()) {
for (Node* child = LayoutTreeBuilderTraversal::FirstChild(node); child;
child = LayoutTreeBuilderTraversal::NextSibling(*child))
ComputeQuads(*child, out_quads);
} else {
// FIXME: this does not need to be absolute, just in the paint invalidation
// container's space.
layout_object->AbsoluteQuads(out_quads, kTraverseDocumentBoundaries);
}
}
bool LinkHighlightImpl::ComputeHighlightLayerPathAndPosition(
const LayoutBoxModelObject& paint_invalidation_container) {
DCHECK(!RuntimeEnabledFeatures::CompositeAfterPaintEnabled());
if (!node_ || !node_->GetLayoutObject() || !current_graphics_layer_)
return false;
// FIXME: This is defensive code to avoid crashes such as those described in
// crbug.com/440887. This should be cleaned up once we fix the root cause of
// of the paint invalidation container not being composited.
if (!paint_invalidation_container.Layer()->GetCompositedLayerMapping() &&
!paint_invalidation_container.Layer()->GroupedMapping())
return false;
// Get quads for node in absolute coordinates.
Vector<FloatQuad> quads;
ComputeQuads(*node_, quads);
Path new_path;
for (wtf_size_t quad_index = 0; quad_index < quads.size(); ++quad_index) {
FloatQuad absolute_quad = quads[quad_index];
// Scrolling content layers have the same offset from layout object as the
// non-scrolling layers. Thus we need to adjust for their scroll offset.
if (is_scrolling_graphics_layer_) {
FloatPoint scroll_position = paint_invalidation_container.Layer()
->GetScrollableArea()
->ScrollPosition();
absolute_quad.Move(ToScrollOffset(scroll_position));
}
absolute_quad.SetP1(FloatPoint(RoundedIntPoint(absolute_quad.P1())));
absolute_quad.SetP2(FloatPoint(RoundedIntPoint(absolute_quad.P2())));
absolute_quad.SetP3(FloatPoint(RoundedIntPoint(absolute_quad.P3())));
absolute_quad.SetP4(FloatPoint(RoundedIntPoint(absolute_quad.P4())));
FloatQuad transformed_quad =
paint_invalidation_container.AbsoluteToLocalQuad(
absolute_quad, kUseTransforms | kTraverseDocumentBoundaries);
FloatPoint offset_to_backing;
PaintLayer::MapPointInPaintInvalidationContainerToBacking(
paint_invalidation_container, offset_to_backing);
// Adjust for offset from LayoutObject.
offset_to_backing.Move(-current_graphics_layer_->OffsetFromLayoutObject());
transformed_quad.Move(ToFloatSize(offset_to_backing));
// FIXME: for now, we'll only use rounded paths if we have a single node
// quad. The reason for this is that we may sometimes get a chain of
// adjacent boxes (e.g. for text nodes) which end up looking like sausage
// links: these should ideally be merged into a single rect before creating
// the path, but that's another CL.
if (quads.size() == 1 && transformed_quad.IsRectilinear() &&
!node_->GetDocument()
.GetSettings()
->GetMockGestureTapHighlightsEnabled()) {
FloatSize rect_rounding_radii(3, 3);
new_path.AddRoundedRect(transformed_quad.BoundingBox(),
rect_rounding_radii);
} else {
AddQuadToPath(transformed_quad, new_path);
}
}
FloatRect bounding_rect = new_path.BoundingRect();
new_path.Translate(-ToFloatSize(bounding_rect.Location()));
DCHECK_EQ(1u, fragments_.size());
fragments_[0].SetColor(
node_->GetLayoutObject()->StyleRef().TapHighlightColor());
auto* layer = fragments_[0].Layer();
bool path_has_changed = new_path != fragments_[0].GetPath();
if (path_has_changed) {
fragments_[0].SetPath(new_path);
layer->SetBounds(
static_cast<gfx::Size>(EnclosingIntRect(bounding_rect).Size()));
}
layer->SetPosition(bounding_rect.Location());
if (RuntimeEnabledFeatures::BlinkGenPropertyTreesEnabled()) {
FloatPoint offset(current_graphics_layer_->GetOffsetFromTransformNode());
offset.MoveBy(bounding_rect.Location());
layer->SetOffsetToTransformParent(gfx::Vector2dF(offset.X(), offset.Y()));
SetPaintArtifactCompositorNeedsUpdate();
}
return path_has_changed;
}
LinkHighlightImpl::LinkHighlightFragment::LinkHighlightFragment(
CompositorElementId element_id) {
layer_ = cc::PictureLayer::Create(this);
layer_->SetTransformOrigin(FloatPoint3D());
layer_->SetIsDrawable(true);
layer_->SetOpacity(1);
// The element id is required for animating layers in layer trees but not
// required when using layer lists.
if (!RuntimeEnabledFeatures::BlinkGenPropertyTreesEnabled() &&
!RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
layer_->SetElementId(element_id);
}
LinkHighlightImpl::LinkHighlightFragment::~LinkHighlightFragment() {
layer_->ClearClient();
}
gfx::Rect LinkHighlightImpl::LinkHighlightFragment::PaintableRegion() {
return gfx::Rect(layer_->bounds());
}
scoped_refptr<cc::DisplayItemList>
LinkHighlightImpl::LinkHighlightFragment::PaintContentsToDisplayList(
PaintingControlSetting painting_control) {
auto display_list = base::MakeRefCounted<cc::DisplayItemList>();
PaintRecorder recorder;
gfx::Rect record_bounds = PaintableRegion();
cc::PaintCanvas* canvas =
recorder.beginRecording(record_bounds.width(), record_bounds.height());
PaintFlags flags;
flags.setStyle(PaintFlags::kFill_Style);
flags.setAntiAlias(true);
flags.setColor(color_.Rgb());
canvas->drawPath(path_.GetSkPath(), flags);
display_list->StartPaint();
display_list->push<cc::DrawRecordOp>(recorder.finishRecordingAsPicture());
display_list->EndPaintOfUnpaired(record_bounds);
display_list->Finalize();
return display_list;
}
void LinkHighlightImpl::StartHighlightAnimationIfNeeded() {
if (is_animating_)
return;
is_animating_ = true;
const float kStartOpacity = 1;
// FIXME: Should duration be configurable?
constexpr auto kFadeDuration = TimeDelta::FromMilliseconds(100);
constexpr auto kMinPreFadeDuration = TimeDelta::FromMilliseconds(100);
for (auto& fragment : fragments_)
fragment.Layer()->SetOpacity(kStartOpacity);
std::unique_ptr<CompositorFloatAnimationCurve> curve =
CompositorFloatAnimationCurve::Create();
const auto& timing_function = *CubicBezierTimingFunction::Preset(
CubicBezierTimingFunction::EaseType::EASE);
curve->AddKeyframe(
CompositorFloatKeyframe(0, kStartOpacity, timing_function));
// Make sure we have displayed for at least minPreFadeDuration before starting
// to fade out.
TimeDelta extra_duration_required = std::max(
TimeDelta(), kMinPreFadeDuration - (CurrentTimeTicks() - start_time_));
if (!extra_duration_required.is_zero()) {
curve->AddKeyframe(CompositorFloatKeyframe(
extra_duration_required.InSecondsF(), kStartOpacity, timing_function));
}
// For web tests we don't fade out.
curve->AddKeyframe(CompositorFloatKeyframe(
(kFadeDuration + extra_duration_required).InSecondsF(),
WebTestSupport::IsRunningWebTest() ? kStartOpacity : 0, timing_function));
std::unique_ptr<CompositorKeyframeModel> keyframe_model =
CompositorKeyframeModel::Create(
*curve, compositor_target_property::OPACITY, 0, 0);
compositor_animation_->AddKeyframeModel(std::move(keyframe_model));
Invalidate();
}
void LinkHighlightImpl::ClearGraphicsLayerLinkHighlightPointer() {
if (current_graphics_layer_) {
current_graphics_layer_->RemoveLinkHighlight(this);
current_graphics_layer_ = nullptr;
}
}
void LinkHighlightImpl::NotifyAnimationStarted(double, int) {}
void LinkHighlightImpl::NotifyAnimationFinished(double, int) {
// Since WebViewImpl may hang on to us for a while, make sure we
// release resources as soon as possible.
ClearGraphicsLayerLinkHighlightPointer();
ReleaseResources();
}
void LinkHighlightImpl::UpdateGeometry() {
DCHECK(!RuntimeEnabledFeatures::CompositeAfterPaintEnabled());
if (!node_ || !node_->GetLayoutObject()) {
ClearGraphicsLayerLinkHighlightPointer();
ReleaseResources();
return;
}
// To avoid unnecessary updates (e.g. other entities have requested animations
// from our WebViewImpl), only proceed if we actually requested an update.
if (!geometry_needs_update_)
return;
geometry_needs_update_ = false;
const LayoutBoxModelObject& paint_invalidation_container =
node_->GetLayoutObject()->ContainerForPaintInvalidation();
AttachLinkHighlightToCompositingLayer(paint_invalidation_container);
if (ComputeHighlightLayerPathAndPosition(paint_invalidation_container)) {
// We only need to invalidate the layer if the highlight size has changed,
// otherwise we can just re-position the layer without needing to
// repaint.
Layer()->SetNeedsDisplay();
if (current_graphics_layer_) {
IntRect rect = IntRect(IntPoint(), IntSize(Layer()->bounds()));
current_graphics_layer_->TrackRasterInvalidation(
*this, rect, PaintInvalidationReason::kFullLayer);
}
}
}
void LinkHighlightImpl::ClearCurrentGraphicsLayer() {
current_graphics_layer_ = nullptr;
geometry_needs_update_ = true;
}
void LinkHighlightImpl::Invalidate() {
// Make sure we update geometry on the next callback from
// WebViewImpl::layout().
geometry_needs_update_ = true;
}
cc::Layer* LinkHighlightImpl::Layer() {
DCHECK(!RuntimeEnabledFeatures::CompositeAfterPaintEnabled());
DCHECK_EQ(1u, fragments_.size());
return fragments_[0].Layer();
}
CompositorAnimation* LinkHighlightImpl::GetCompositorAnimation() const {
return compositor_animation_.get();
}
CompositorElementId LinkHighlightImpl::element_id() const {
return CompositorElementIdFromUniqueObjectId(unique_id_);
}
const EffectPaintPropertyNode& LinkHighlightImpl::Effect() const {
return *effect_;
}
void LinkHighlightImpl::Paint(GraphicsContext& context) {
DCHECK(RuntimeEnabledFeatures::CompositeAfterPaintEnabled());
if (!node_ || !node_->GetLayoutObject()) {
ReleaseResources();
return;
}
const auto* object = node_->GetLayoutObject();
static const FloatSize rect_rounding_radii(3, 3);
auto color = node_->GetLayoutObject()->StyleRef().TapHighlightColor();
// For now, we'll only use rounded rects if we have a single rect because
// otherwise we may sometimes get a chain of adjacent boxes (e.g. for text
// nodes) which end up looking like sausage links: these should ideally be
// merged into a single rect before creating the path.
bool use_rounded_rects = !node_->GetDocument()
.GetSettings()
->GetMockGestureTapHighlightsEnabled() &&
!object->FirstFragment().NextFragment();
size_t index = 0;
for (const auto* fragment = &object->FirstFragment(); fragment;
fragment = fragment->NextFragment(), ++index) {
auto rects = object->PhysicalOutlineRects(
fragment->PaintOffset(), NGOutlineType::kIncludeBlockVisualOverflow);
if (rects.size() > 1)
use_rounded_rects = false;
Path new_path;
for (auto& rect : rects) {
FloatRect snapped_rect(PixelSnappedIntRect(rect));
if (use_rounded_rects)
new_path.AddRoundedRect(snapped_rect, rect_rounding_radii);
else
new_path.AddRect(snapped_rect);
}
if (index == fragments_.size()) {
fragments_.emplace_back(element_id());
// PaintArtifactCompositor needs update for the new cc::PictureLayer we
// just created for the fragment.
SetPaintArtifactCompositorNeedsUpdate();
}
auto& link_highlight_fragment = fragments_[index];
link_highlight_fragment.SetColor(color);
auto bounding_rect = new_path.BoundingRect();
new_path.Translate(-ToFloatSize(bounding_rect.Location()));
auto* layer = link_highlight_fragment.Layer();
if (link_highlight_fragment.GetPath() != new_path) {
link_highlight_fragment.SetPath(new_path);
layer->SetBounds(gfx::Size(EnclosingIntRect(bounding_rect).Size()));
layer->SetNeedsDisplay();
}
// Always set offset because it is excluded from the above equality check.
layer->SetOffsetToTransformParent(
gfx::Vector2dF(bounding_rect.X(), bounding_rect.Y()));
auto property_tree_state = fragment->LocalBorderBoxProperties();
property_tree_state.SetEffect(Effect());
RecordForeignLayer(context, DisplayItem::kForeignLayerLinkHighlight, layer,
property_tree_state);
}
if (index < fragments_.size()) {
fragments_.Shrink(index);
// PaintArtifactCompositor needs update for the cc::PictureLayers we just
// removed for the extra fragments.
SetPaintArtifactCompositorNeedsUpdate();
}
}
void LinkHighlightImpl::SetPaintArtifactCompositorNeedsUpdate() {
DCHECK(node_);
if (auto* frame_view = node_->GetDocument().View())
frame_view->SetPaintArtifactCompositorNeedsUpdate();
}
} // namespace blink