| /* |
| * 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/color.h" |
| #include "third_party/blink/renderer/platform/graphics/graphics_layer.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/drawing_recorder.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/layout_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_); |
| content_layer_ = cc::PictureLayer::Create(this); |
| content_layer_->SetTransformOrigin(FloatPoint3D()); |
| |
| compositor_animation_ = CompositorAnimation::Create(); |
| DCHECK(compositor_animation_); |
| compositor_animation_->SetAnimationDelegate(this); |
| |
| compositor_animation_->AttachElement(element_id()); |
| content_layer_->SetIsDrawable(true); |
| content_layer_->SetOpacity(1); |
| content_layer_->SetElementId(element_id()); |
| geometry_needs_update_ = true; |
| } |
| |
| 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(); |
| node_.Clear(); |
| } |
| |
| void LinkHighlightImpl::AttachLinkHighlightToCompositingLayer( |
| const LayoutBoxModelObject& paint_invalidation_container) { |
| 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) |
| 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 { |
| 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) { |
| 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); |
| DCHECK(quads.size()); |
| Path new_path; |
| |
| for (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())); |
| |
| bool path_has_changed = !(new_path == path_); |
| if (path_has_changed) { |
| path_ = new_path; |
| content_layer_->SetBounds( |
| static_cast<gfx::Size>(EnclosingIntRect(bounding_rect).Size())); |
| } |
| |
| content_layer_->SetPosition(bounding_rect.Location()); |
| |
| return path_has_changed; |
| } |
| |
| gfx::Rect LinkHighlightImpl::PaintableRegion() { |
| return gfx::Rect(content_layer_->bounds()); |
| } |
| |
| scoped_refptr<cc::DisplayItemList> |
| LinkHighlightImpl::PaintContentsToDisplayList( |
| PaintingControlSetting painting_control) { |
| auto display_list = base::MakeRefCounted<cc::DisplayItemList>(); |
| if (!node_ || !node_->GetLayoutObject()) { |
| display_list->Finalize(); |
| return display_list; |
| } |
| |
| 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( |
| node_->GetLayoutObject()->StyleRef().TapHighlightColor().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); |
| |
| content_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 layout tests we don't fade out. |
| curve->AddKeyframe(CompositorFloatKeyframe( |
| (kFadeDuration + extra_duration_required).InSecondsF(), |
| LayoutTestSupport::IsRunningLayoutTest() ? kStartOpacity : 0, |
| timing_function)); |
| |
| std::unique_ptr<CompositorKeyframeModel> keyframe_model = |
| CompositorKeyframeModel::Create(*curve, CompositorTargetProperty::OPACITY, |
| 0, 0); |
| |
| content_layer_->SetIsDrawable(true); |
| 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(); |
| } |
| |
| class LinkHighlightDisplayItemClientForTracking : public DisplayItemClient { |
| String DebugName() const final { return "LinkHighlight"; } |
| LayoutRect VisualRect() const final { return LayoutRect(); } |
| }; |
| |
| void LinkHighlightImpl::UpdateGeometry() { |
| // 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; |
| |
| bool has_layout_object = node_ && node_->GetLayoutObject(); |
| if (has_layout_object) { |
| 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. |
| content_layer_->SetNeedsDisplay(); |
| |
| if (current_graphics_layer_) { |
| gfx::Rect rect = gfx::ToEnclosingRect( |
| gfx::RectF(Layer()->position(), gfx::SizeF(Layer()->bounds()))); |
| current_graphics_layer_->TrackRasterInvalidation( |
| LinkHighlightDisplayItemClientForTracking(), IntRect(rect), |
| PaintInvalidationReason::kFullLayer); |
| } |
| } |
| } else { |
| ClearGraphicsLayerLinkHighlightPointer(); |
| ReleaseResources(); |
| } |
| } |
| |
| 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() { |
| return content_layer_.get(); |
| } |
| |
| CompositorAnimation* LinkHighlightImpl::GetCompositorAnimation() const { |
| return compositor_animation_.get(); |
| } |
| |
| CompositorElementId LinkHighlightImpl::element_id() { |
| return CompositorElementIdFromUniqueObjectId(unique_id_); |
| } |
| |
| const EffectPaintPropertyNode* LinkHighlightImpl::effect() { |
| if (!node_) |
| return nullptr; |
| |
| if (auto* layout_object = node_->GetLayoutObject()) { |
| if (auto* properties = layout_object->FirstFragment().PaintProperties()) |
| return properties->LinkHighlightEffect(); |
| } |
| |
| return nullptr; |
| } |
| |
| } // namespace blink |