| /* |
| * 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 "web/LinkHighlightImpl.h" |
| |
| #include "core/dom/DOMNodeIds.h" |
| #include "core/dom/LayoutTreeBuilderTraversal.h" |
| #include "core/dom/Node.h" |
| #include "core/frame/FrameView.h" |
| #include "core/frame/LocalFrame.h" |
| #include "core/layout/LayoutBoxModelObject.h" |
| #include "core/layout/LayoutObject.h" |
| #include "core/layout/compositing/CompositedLayerMapping.h" |
| #include "core/paint/PaintLayer.h" |
| #include "platform/RuntimeEnabledFeatures.h" |
| #include "platform/animation/CompositorAnimation.h" |
| #include "platform/animation/CompositorAnimationCurve.h" |
| #include "platform/animation/CompositorFloatAnimationCurve.h" |
| #include "platform/animation/CompositorTargetProperty.h" |
| #include "platform/animation/TimingFunction.h" |
| #include "platform/graphics/Color.h" |
| #include "platform/graphics/CompositorElementId.h" |
| #include "platform/graphics/CompositorMutableProperties.h" |
| #include "platform/graphics/GraphicsLayer.h" |
| #include "platform/graphics/paint/DrawingRecorder.h" |
| #include "public/platform/Platform.h" |
| #include "public/platform/WebCompositorSupport.h" |
| #include "public/platform/WebContentLayer.h" |
| #include "public/platform/WebDisplayItemList.h" |
| #include "public/platform/WebFloatPoint.h" |
| #include "public/platform/WebLayer.h" |
| #include "public/platform/WebRect.h" |
| #include "public/platform/WebSize.h" |
| #include "public/web/WebKit.h" |
| #include "third_party/skia/include/core/SkCanvas.h" |
| #include "third_party/skia/include/core/SkMatrix44.h" |
| #include "third_party/skia/include/core/SkPictureRecorder.h" |
| #include "ui/gfx/geometry/rect.h" |
| #include "web/WebLocalFrameImpl.h" |
| #include "web/WebSettingsImpl.h" |
| #include "web/WebViewImpl.h" |
| #include "wtf/CurrentTime.h" |
| #include "wtf/PtrUtil.h" |
| #include "wtf/Vector.h" |
| #include <memory> |
| |
| namespace blink { |
| |
| std::unique_ptr<LinkHighlightImpl> LinkHighlightImpl::create( |
| Node* node, |
| WebViewImpl* owningWebViewImpl) { |
| return wrapUnique(new LinkHighlightImpl(node, owningWebViewImpl)); |
| } |
| |
| LinkHighlightImpl::LinkHighlightImpl(Node* node, WebViewImpl* owningWebViewImpl) |
| : m_node(node), |
| m_owningWebViewImpl(owningWebViewImpl), |
| m_currentGraphicsLayer(0), |
| m_isScrollingGraphicsLayer(false), |
| m_geometryNeedsUpdate(false), |
| m_isAnimating(false), |
| m_startTime(monotonicallyIncreasingTime()) { |
| DCHECK(m_node); |
| DCHECK(owningWebViewImpl); |
| WebCompositorSupport* compositorSupport = |
| Platform::current()->compositorSupport(); |
| DCHECK(compositorSupport); |
| m_contentLayer = wrapUnique(compositorSupport->createContentLayer(this)); |
| m_clipLayer = wrapUnique(compositorSupport->createLayer()); |
| m_clipLayer->setTransformOrigin(WebFloatPoint3D()); |
| m_clipLayer->addChild(m_contentLayer->layer()); |
| |
| m_compositorPlayer = CompositorAnimationPlayer::create(); |
| DCHECK(m_compositorPlayer); |
| m_compositorPlayer->setAnimationDelegate(this); |
| if (m_owningWebViewImpl->linkHighlightsTimeline()) |
| m_owningWebViewImpl->linkHighlightsTimeline()->playerAttached(*this); |
| |
| CompositorElementId elementId = createCompositorElementId( |
| DOMNodeIds::idForNode(node), CompositorSubElementId::LinkHighlight); |
| m_compositorPlayer->attachElement(elementId); |
| m_contentLayer->layer()->setDrawsContent(true); |
| m_contentLayer->layer()->setOpacity(1); |
| m_contentLayer->layer()->setElementId(elementId); |
| m_geometryNeedsUpdate = true; |
| } |
| |
| LinkHighlightImpl::~LinkHighlightImpl() { |
| if (m_compositorPlayer->isElementAttached()) |
| m_compositorPlayer->detachElement(); |
| if (m_owningWebViewImpl->linkHighlightsTimeline()) |
| m_owningWebViewImpl->linkHighlightsTimeline()->playerDestroyed(*this); |
| m_compositorPlayer->setAnimationDelegate(nullptr); |
| m_compositorPlayer.reset(); |
| |
| clearGraphicsLayerLinkHighlightPointer(); |
| releaseResources(); |
| } |
| |
| WebContentLayer* LinkHighlightImpl::contentLayer() { |
| return m_contentLayer.get(); |
| } |
| |
| WebLayer* LinkHighlightImpl::clipLayer() { |
| return m_clipLayer.get(); |
| } |
| |
| void LinkHighlightImpl::releaseResources() { |
| m_node.clear(); |
| } |
| |
| void LinkHighlightImpl::attachLinkHighlightToCompositingLayer( |
| const LayoutBoxModelObject& paintInvalidationContainer) { |
| GraphicsLayer* newGraphicsLayer = |
| paintInvalidationContainer.layer()->graphicsLayerBacking(); |
| m_isScrollingGraphicsLayer = false; |
| // FIXME: There should always be a GraphicsLayer. See crbug.com/431961. |
| if (paintInvalidationContainer.layer()->needsCompositedScrolling() && |
| m_node->layoutObject() != &paintInvalidationContainer) { |
| newGraphicsLayer = |
| paintInvalidationContainer.layer()->graphicsLayerBackingForScrolling(); |
| m_isScrollingGraphicsLayer = true; |
| } |
| if (!newGraphicsLayer) |
| return; |
| |
| m_clipLayer->setTransform(SkMatrix44(SkMatrix44::kIdentity_Constructor)); |
| |
| if (m_currentGraphicsLayer != newGraphicsLayer) { |
| if (m_currentGraphicsLayer) |
| clearGraphicsLayerLinkHighlightPointer(); |
| |
| m_currentGraphicsLayer = newGraphicsLayer; |
| m_currentGraphicsLayer->addLinkHighlight(this); |
| } |
| } |
| |
| static void convertTargetSpaceQuadToCompositedLayer( |
| const FloatQuad& targetSpaceQuad, |
| LayoutObject* targetLayoutObject, |
| const LayoutBoxModelObject& paintInvalidationContainer, |
| FloatQuad& compositedSpaceQuad) { |
| DCHECK(targetLayoutObject); |
| for (unsigned i = 0; i < 4; ++i) { |
| IntPoint point; |
| switch (i) { |
| case 0: |
| point = roundedIntPoint(targetSpaceQuad.p1()); |
| break; |
| case 1: |
| point = roundedIntPoint(targetSpaceQuad.p2()); |
| break; |
| case 2: |
| point = roundedIntPoint(targetSpaceQuad.p3()); |
| break; |
| case 3: |
| point = roundedIntPoint(targetSpaceQuad.p4()); |
| break; |
| } |
| |
| // FIXME: this does not need to be absolute, just in the paint invalidation |
| // container's space. |
| point = targetLayoutObject->frame()->view()->contentsToRootFrame(point); |
| point = |
| paintInvalidationContainer.frame()->view()->rootFrameToContents(point); |
| FloatPoint floatPoint = |
| paintInvalidationContainer.absoluteToLocal(point, UseTransforms); |
| PaintLayer::mapPointInPaintInvalidationContainerToBacking( |
| paintInvalidationContainer, floatPoint); |
| |
| switch (i) { |
| case 0: |
| compositedSpaceQuad.setP1(floatPoint); |
| break; |
| case 1: |
| compositedSpaceQuad.setP2(floatPoint); |
| break; |
| case 2: |
| compositedSpaceQuad.setP3(floatPoint); |
| break; |
| case 3: |
| compositedSpaceQuad.setP4(floatPoint); |
| break; |
| } |
| } |
| } |
| |
| 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>& outQuads) const { |
| if (!node.layoutObject()) |
| return; |
| |
| LayoutObject* layoutObject = node.layoutObject(); |
| |
| // 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 (layoutObject->isLayoutInline()) { |
| for (Node* child = LayoutTreeBuilderTraversal::firstChild(node); child; |
| child = LayoutTreeBuilderTraversal::nextSibling(*child)) |
| computeQuads(*child, outQuads); |
| } else { |
| // FIXME: this does not need to be absolute, just in the paint invalidation |
| // container's space. |
| layoutObject->absoluteQuads(outQuads); |
| } |
| } |
| |
| bool LinkHighlightImpl::computeHighlightLayerPathAndPosition( |
| const LayoutBoxModelObject& paintInvalidationContainer) { |
| if (!m_node || !m_node->layoutObject() || !m_currentGraphicsLayer) |
| 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 (!paintInvalidationContainer.layer()->compositedLayerMapping() && |
| !paintInvalidationContainer.layer()->groupedMapping()) |
| return false; |
| |
| // Get quads for node in absolute coordinates. |
| Vector<FloatQuad> quads; |
| computeQuads(*m_node, quads); |
| DCHECK(quads.size()); |
| Path newPath; |
| |
| for (size_t quadIndex = 0; quadIndex < quads.size(); ++quadIndex) { |
| FloatQuad absoluteQuad = quads[quadIndex]; |
| |
| // 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 (m_isScrollingGraphicsLayer) { |
| DoubleSize adjustedScrollOffset = paintInvalidationContainer.layer() |
| ->getScrollableArea() |
| ->adjustedScrollOffset(); |
| absoluteQuad.move(adjustedScrollOffset.width(), |
| adjustedScrollOffset.height()); |
| } |
| |
| // Transform node quads in target absolute coords to local coordinates in |
| // the compositor layer. |
| FloatQuad transformedQuad; |
| convertTargetSpaceQuadToCompositedLayer( |
| absoluteQuad, m_node->layoutObject(), paintInvalidationContainer, |
| transformedQuad); |
| |
| // 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 && transformedQuad.isRectilinear() && |
| !m_owningWebViewImpl->settingsImpl() |
| ->mockGestureTapHighlightsEnabled()) { |
| FloatSize rectRoundingRadii(3, 3); |
| newPath.addRoundedRect(transformedQuad.boundingBox(), rectRoundingRadii); |
| } else { |
| addQuadToPath(transformedQuad, newPath); |
| } |
| } |
| |
| FloatRect boundingRect = newPath.boundingRect(); |
| newPath.translate(-toFloatSize(boundingRect.location())); |
| |
| bool pathHasChanged = !(newPath == m_path); |
| if (pathHasChanged) { |
| m_path = newPath; |
| m_contentLayer->layer()->setBounds(enclosingIntRect(boundingRect).size()); |
| } |
| |
| m_contentLayer->layer()->setPosition(boundingRect.location()); |
| |
| return pathHasChanged; |
| } |
| |
| gfx::Rect LinkHighlightImpl::paintableRegion() { |
| return gfx::Rect(0, 0, contentLayer()->layer()->bounds().width, |
| contentLayer()->layer()->bounds().height); |
| } |
| |
| void LinkHighlightImpl::paintContents( |
| WebDisplayItemList* webDisplayItemList, |
| WebContentLayerClient::PaintingControlSetting paintingControl) { |
| if (!m_node || !m_node->layoutObject()) |
| return; |
| |
| SkPictureRecorder recorder; |
| gfx::Rect visualRect = paintableRegion(); |
| SkCanvas* canvas = |
| recorder.beginRecording(visualRect.width(), visualRect.height()); |
| |
| SkPaint paint; |
| paint.setStyle(SkPaint::kFill_Style); |
| paint.setFlags(SkPaint::kAntiAlias_Flag); |
| paint.setColor(m_node->layoutObject()->style()->tapHighlightColor().rgb()); |
| canvas->drawPath(m_path.getSkPath(), paint); |
| |
| webDisplayItemList->appendDrawingItem( |
| WebRect(visualRect.x(), visualRect.y(), visualRect.width(), |
| visualRect.height()), |
| recorder.finishRecordingAsPicture()); |
| } |
| |
| void LinkHighlightImpl::startHighlightAnimationIfNeeded() { |
| if (m_isAnimating) |
| return; |
| |
| m_isAnimating = true; |
| const float startOpacity = 1; |
| // FIXME: Should duration be configurable? |
| const float fadeDuration = 0.1f; |
| const float minPreFadeDuration = 0.1f; |
| |
| m_contentLayer->layer()->setOpacity(startOpacity); |
| |
| std::unique_ptr<CompositorFloatAnimationCurve> curve = |
| CompositorFloatAnimationCurve::create(); |
| |
| const auto& timingFunction = *CubicBezierTimingFunction::preset( |
| CubicBezierTimingFunction::EaseType::EASE); |
| |
| curve->addKeyframe(CompositorFloatKeyframe(0, startOpacity, timingFunction)); |
| // Make sure we have displayed for at least minPreFadeDuration before starting |
| // to fade out. |
| float extraDurationRequired = std::max( |
| 0.f, minPreFadeDuration - |
| static_cast<float>(monotonicallyIncreasingTime() - m_startTime)); |
| if (extraDurationRequired) |
| curve->addKeyframe(CompositorFloatKeyframe(extraDurationRequired, |
| startOpacity, timingFunction)); |
| // For layout tests we don't fade out. |
| curve->addKeyframe(CompositorFloatKeyframe( |
| fadeDuration + extraDurationRequired, layoutTestMode() ? startOpacity : 0, |
| timingFunction)); |
| |
| std::unique_ptr<CompositorAnimation> animation = CompositorAnimation::create( |
| *curve, CompositorTargetProperty::OPACITY, 0, 0); |
| |
| m_contentLayer->layer()->setDrawsContent(true); |
| m_compositorPlayer->addAnimation(std::move(animation)); |
| |
| invalidate(); |
| m_owningWebViewImpl->mainFrameImpl()->frameWidget()->scheduleAnimation(); |
| } |
| |
| void LinkHighlightImpl::clearGraphicsLayerLinkHighlightPointer() { |
| if (m_currentGraphicsLayer) { |
| m_currentGraphicsLayer->removeLinkHighlight(this); |
| m_currentGraphicsLayer = 0; |
| } |
| } |
| |
| 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 (!m_geometryNeedsUpdate) |
| return; |
| |
| m_geometryNeedsUpdate = false; |
| |
| bool hasLayoutObject = m_node && m_node->layoutObject(); |
| if (hasLayoutObject) { |
| const LayoutBoxModelObject& paintInvalidationContainer = |
| m_node->layoutObject()->containerForPaintInvalidation(); |
| attachLinkHighlightToCompositingLayer(paintInvalidationContainer); |
| if (computeHighlightLayerPathAndPosition(paintInvalidationContainer)) { |
| // 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. |
| m_contentLayer->layer()->invalidate(); |
| |
| if (m_currentGraphicsLayer) |
| m_currentGraphicsLayer->trackPaintInvalidation( |
| LinkHighlightDisplayItemClientForTracking(), |
| enclosingIntRect( |
| FloatRect(layer()->position().x, layer()->position().y, |
| layer()->bounds().width, layer()->bounds().height)), |
| PaintInvalidationFull); |
| } |
| } else { |
| clearGraphicsLayerLinkHighlightPointer(); |
| releaseResources(); |
| } |
| } |
| |
| void LinkHighlightImpl::clearCurrentGraphicsLayer() { |
| m_currentGraphicsLayer = 0; |
| m_geometryNeedsUpdate = true; |
| } |
| |
| void LinkHighlightImpl::invalidate() { |
| // Make sure we update geometry on the next callback from |
| // WebViewImpl::layout(). |
| m_geometryNeedsUpdate = true; |
| } |
| |
| WebLayer* LinkHighlightImpl::layer() { |
| return clipLayer(); |
| } |
| |
| CompositorAnimationPlayer* LinkHighlightImpl::compositorPlayer() const { |
| return m_compositorPlayer.get(); |
| } |
| |
| } // namespace blink |