Title: [172483] trunk/Source/WebKit2
Revision
172483
Author
[email protected]
Date
2014-08-12 12:31:04 -0700 (Tue, 12 Aug 2014)

Log Message

Add a fade transition to services highlights
https://bugs.webkit.org/show_bug.cgi?id=135829
<rdar://problem/17935736>

Reviewed by Enrica Casucci.

Add a smooth fade to highlight installation and uninstallation.
To do so, we make each highlight paint into its own small layer.

* WebProcess/WebPage/PageOverlay.cpp:
(WebKit::PageOverlay::layer):
* WebProcess/WebPage/PageOverlay.h:
* WebProcess/WebPage/PageOverlayController.cpp:
(WebKit::PageOverlayController::layerForOverlay):
* WebProcess/WebPage/PageOverlayController.h:
Expose the GraphicsLayer on PageOverlay.

* WebProcess/WebPage/ServicesOverlayController.h:
(WebKit::ServicesOverlayController::Highlight::layer):
(WebKit::ServicesOverlayController::activeHighlight):
(WebKit::ServicesOverlayController::webPage):
(WebKit::ServicesOverlayController::Highlight::Highlight): Deleted.

* WebProcess/WebPage/mac/ServicesOverlayController.mm:
(WebKit::ServicesOverlayController::Highlight::createForSelection):
(WebKit::ServicesOverlayController::Highlight::createForTelephoneNumber):
(WebKit::ServicesOverlayController::Highlight::Highlight):
Highlights now own a GraphicsLayer, which are later installed
as sublayers of the ServicesOverlayController's PageOverlay layer.
These layers are sized and positioned according to the DDHighlight's bounds.

(WebKit::ServicesOverlayController::Highlight::~Highlight):
(WebKit::ServicesOverlayController::Highlight::invalidate):
ServicesOverlayController will invalidate any remaining highlights
when it is torn down, so they can clear their backpointers.

(WebKit::ServicesOverlayController::Highlight::notifyFlushRequired):
Forward flush notifications to the DrawingArea.

(WebKit::ServicesOverlayController::Highlight::paintContents):
Paint the DDHighlight into the layer. Translation is done by the layer position,
so we zero the bounds origin when painting.

(WebKit::ServicesOverlayController::Highlight::deviceScaleFactor):
Forward the deviceScaleFactor so that things are painted at the right scale.

(WebKit::ServicesOverlayController::Highlight::fadeIn):
(WebKit::ServicesOverlayController::Highlight::fadeOut):
Apply a fade animation to the layer.

(WebKit::ServicesOverlayController::Highlight::didFinishFadeOutAnimation):
When the fade completes, unparent the layer, unless it has become active again.

(WebKit::ServicesOverlayController::ServicesOverlayController):
(WebKit::ServicesOverlayController::~ServicesOverlayController):
Invalidate all highlights, so they can clear their backpointers.

(WebKit::ServicesOverlayController::remainingTimeUntilHighlightShouldBeShown):
Make remainingTimeUntilHighlightShouldBeShown act upon a particular highlight
instead of always the active highlight.

(WebKit::ServicesOverlayController::determineActiveHighlightTimerFired): Rename.

(WebKit::ServicesOverlayController::drawRect):
drawRect is no longer called and will no longer do anything; all of the
painting is done in sublayers.

(WebKit::ServicesOverlayController::buildPhoneNumberHighlights):
Ensure that phone number Highlights stay stable even while the selection
changes, by comparing the underlying Ranges and keeping around old Highlights
that match the new ones. This enables us to e.g. fade in while changing
the selection within a phone number.

(WebKit::ServicesOverlayController::buildSelectionHighlight):
(WebKit::ServicesOverlayController::didRebuildPotentialHighlights):
(WebKit::ServicesOverlayController::createOverlayIfNeeded):
Don't call setNeedsDisplay; the overlay doesn't have backing store.
Instead, call determineActiveHighlight, which will install/uninstall
highlights as necessary.

(WebKit::ServicesOverlayController::determineActiveHighlight):
Apply fade in/fade out to the overlays.
Keep track of which highlight we're going to activate, until the hysteresis
delay is up, then actually make it active/parent it/fade it in.
We now will have no active highlight between the fade out of the previous one
and the fade in of the new one (during the hysteresis delay).

(WebKit::ServicesOverlayController::mouseEvent):
The overlay now will not become active until the delay is up, so we don't
need to check it again here.

(WebKit::ServicesOverlayController::handleClick):
(WebKit::ServicesOverlayController::didCreateHighlight):
(WebKit::ServicesOverlayController::willDestroyHighlight):
(WebKit::ServicesOverlayController::repaintHighlightTimerFired): Deleted.
(WebKit::ServicesOverlayController::drawHighlight): Deleted.

Modified Paths

Diff

Modified: trunk/Source/WebKit2/ChangeLog (172482 => 172483)


--- trunk/Source/WebKit2/ChangeLog	2014-08-12 19:12:05 UTC (rev 172482)
+++ trunk/Source/WebKit2/ChangeLog	2014-08-12 19:31:04 UTC (rev 172483)
@@ -1,3 +1,102 @@
+2014-08-12  Tim Horton  <[email protected]>
+
+        Add a fade transition to services highlights
+        https://bugs.webkit.org/show_bug.cgi?id=135829
+        <rdar://problem/17935736>
+
+        Reviewed by Enrica Casucci.
+
+        Add a smooth fade to highlight installation and uninstallation.
+        To do so, we make each highlight paint into its own small layer.
+
+        * WebProcess/WebPage/PageOverlay.cpp:
+        (WebKit::PageOverlay::layer):
+        * WebProcess/WebPage/PageOverlay.h:
+        * WebProcess/WebPage/PageOverlayController.cpp:
+        (WebKit::PageOverlayController::layerForOverlay):
+        * WebProcess/WebPage/PageOverlayController.h:
+        Expose the GraphicsLayer on PageOverlay.
+
+        * WebProcess/WebPage/ServicesOverlayController.h:
+        (WebKit::ServicesOverlayController::Highlight::layer):
+        (WebKit::ServicesOverlayController::activeHighlight):
+        (WebKit::ServicesOverlayController::webPage):
+        (WebKit::ServicesOverlayController::Highlight::Highlight): Deleted.
+
+        * WebProcess/WebPage/mac/ServicesOverlayController.mm:
+        (WebKit::ServicesOverlayController::Highlight::createForSelection):
+        (WebKit::ServicesOverlayController::Highlight::createForTelephoneNumber):
+        (WebKit::ServicesOverlayController::Highlight::Highlight):
+        Highlights now own a GraphicsLayer, which are later installed
+        as sublayers of the ServicesOverlayController's PageOverlay layer.
+        These layers are sized and positioned according to the DDHighlight's bounds.
+
+        (WebKit::ServicesOverlayController::Highlight::~Highlight):
+        (WebKit::ServicesOverlayController::Highlight::invalidate):
+        ServicesOverlayController will invalidate any remaining highlights
+        when it is torn down, so they can clear their backpointers.
+
+        (WebKit::ServicesOverlayController::Highlight::notifyFlushRequired):
+        Forward flush notifications to the DrawingArea.
+
+        (WebKit::ServicesOverlayController::Highlight::paintContents):
+        Paint the DDHighlight into the layer. Translation is done by the layer position,
+        so we zero the bounds origin when painting.
+
+        (WebKit::ServicesOverlayController::Highlight::deviceScaleFactor):
+        Forward the deviceScaleFactor so that things are painted at the right scale.
+
+        (WebKit::ServicesOverlayController::Highlight::fadeIn):
+        (WebKit::ServicesOverlayController::Highlight::fadeOut):
+        Apply a fade animation to the layer.
+
+        (WebKit::ServicesOverlayController::Highlight::didFinishFadeOutAnimation):
+        When the fade completes, unparent the layer, unless it has become active again.
+
+        (WebKit::ServicesOverlayController::ServicesOverlayController):
+        (WebKit::ServicesOverlayController::~ServicesOverlayController):
+        Invalidate all highlights, so they can clear their backpointers.
+
+        (WebKit::ServicesOverlayController::remainingTimeUntilHighlightShouldBeShown):
+        Make remainingTimeUntilHighlightShouldBeShown act upon a particular highlight
+        instead of always the active highlight.
+
+        (WebKit::ServicesOverlayController::determineActiveHighlightTimerFired): Rename.
+
+        (WebKit::ServicesOverlayController::drawRect):
+        drawRect is no longer called and will no longer do anything; all of the
+        painting is done in sublayers.
+
+        (WebKit::ServicesOverlayController::buildPhoneNumberHighlights):
+        Ensure that phone number Highlights stay stable even while the selection
+        changes, by comparing the underlying Ranges and keeping around old Highlights
+        that match the new ones. This enables us to e.g. fade in while changing
+        the selection within a phone number.
+
+        (WebKit::ServicesOverlayController::buildSelectionHighlight):
+        (WebKit::ServicesOverlayController::didRebuildPotentialHighlights):
+        (WebKit::ServicesOverlayController::createOverlayIfNeeded):
+        Don't call setNeedsDisplay; the overlay doesn't have backing store.
+        Instead, call determineActiveHighlight, which will install/uninstall
+        highlights as necessary.
+
+        (WebKit::ServicesOverlayController::determineActiveHighlight):
+        Apply fade in/fade out to the overlays.
+        Keep track of which highlight we're going to activate, until the hysteresis
+        delay is up, then actually make it active/parent it/fade it in.
+        We now will have no active highlight between the fade out of the previous one
+        and the fade in of the new one (during the hysteresis delay).
+
+        (WebKit::ServicesOverlayController::mouseEvent):
+        The overlay now will not become active until the delay is up, so we don't
+        need to check it again here.
+
+        (WebKit::ServicesOverlayController::handleClick):
+        (WebKit::ServicesOverlayController::didCreateHighlight):
+        (WebKit::ServicesOverlayController::willDestroyHighlight):
+        (WebKit::ServicesOverlayController::repaintHighlightTimerFired): Deleted.
+        (WebKit::ServicesOverlayController::drawHighlight): Deleted.
+
 2014-08-11  Andy Estes  <[email protected]>
 
         [iOS] Get rid of iOS.xcconfig

Modified: trunk/Source/WebKit2/WebProcess/WebPage/PageOverlay.cpp (172482 => 172483)


--- trunk/Source/WebKit2/WebProcess/WebPage/PageOverlay.cpp	2014-08-12 19:12:05 UTC (rev 172482)
+++ trunk/Source/WebKit2/WebProcess/WebPage/PageOverlay.cpp	2014-08-12 19:31:04 UTC (rev 172483)
@@ -233,4 +233,9 @@
     m_webPage->pageOverlayController().clearPageOverlay(*this);
 }
 
+WebCore::GraphicsLayer* PageOverlay::layer()
+{
+    return m_webPage->pageOverlayController().layerForOverlay(*this);
+}
+
 } // namespace WebKit

Modified: trunk/Source/WebKit2/WebProcess/WebPage/PageOverlay.h (172482 => 172483)


--- trunk/Source/WebKit2/WebProcess/WebPage/PageOverlay.h	2014-08-12 19:12:05 UTC (rev 172482)
+++ trunk/Source/WebKit2/WebProcess/WebPage/PageOverlay.h	2014-08-12 19:31:04 UTC (rev 172483)
@@ -35,6 +35,7 @@
 
 namespace WebCore {
 class GraphicsContext;
+class GraphicsLayer;
 }
 
 namespace WebKit {
@@ -95,6 +96,8 @@
 
     WebCore::RGBA32 backgroundColor() const { return m_backgroundColor; }
     void setBackgroundColor(WebCore::RGBA32);
+
+    WebCore::GraphicsLayer* layer();
     
 protected:
     explicit PageOverlay(Client*, OverlayType);

Modified: trunk/Source/WebKit2/WebProcess/WebPage/PageOverlayController.cpp (172482 => 172483)


--- trunk/Source/WebKit2/WebProcess/WebPage/PageOverlayController.cpp	2014-08-12 19:12:05 UTC (rev 172482)
+++ trunk/Source/WebKit2/WebProcess/WebPage/PageOverlayController.cpp	2014-08-12 19:31:04 UTC (rev 172483)
@@ -171,6 +171,12 @@
     m_overlayGraphicsLayers.get(&overlay)->setDrawsContent(false);
 }
 
+GraphicsLayer* PageOverlayController::layerForOverlay(PageOverlay& overlay) const
+{
+    ASSERT(m_pageOverlays.contains(&overlay));
+    return m_overlayGraphicsLayers.get(&overlay);
+}
+
 void PageOverlayController::didChangeViewSize()
 {
     for (auto& overlayAndLayer : m_overlayGraphicsLayers) {

Modified: trunk/Source/WebKit2/WebProcess/WebPage/PageOverlayController.h (172482 => 172483)


--- trunk/Source/WebKit2/WebProcess/WebPage/PageOverlayController.h	2014-08-12 19:12:05 UTC (rev 172482)
+++ trunk/Source/WebKit2/WebProcess/WebPage/PageOverlayController.h	2014-08-12 19:31:04 UTC (rev 172483)
@@ -57,6 +57,7 @@
     void setPageOverlayNeedsDisplay(PageOverlay&, const WebCore::IntRect&);
     void setPageOverlayOpacity(PageOverlay&, float);
     void clearPageOverlay(PageOverlay&);
+    WebCore::GraphicsLayer* layerForOverlay(PageOverlay&) const;
 
     void didChangeViewSize();
     void didChangeDocumentSize();

Modified: trunk/Source/WebKit2/WebProcess/WebPage/ServicesOverlayController.h (172482 => 172483)


--- trunk/Source/WebKit2/WebProcess/WebPage/ServicesOverlayController.h	2014-08-12 19:12:05 UTC (rev 172482)
+++ trunk/Source/WebKit2/WebProcess/WebPage/ServicesOverlayController.h	2014-08-12 19:31:04 UTC (rev 172483)
@@ -30,6 +30,7 @@
 
 #include "PageOverlay.h"
 #include "WebFrame.h"
+#include <WebCore/GraphicsLayerClient.h>
 #include <WebCore/Range.h>
 #include <WebCore/Timer.h>
 #include <wtf/RefCounted.h>
@@ -55,14 +56,18 @@
     void selectionRectsDidChange(const Vector<WebCore::LayoutRect>&, const Vector<WebCore::GapRects>&, bool isTextOnly);
 
 private:
-    class Highlight : public RefCounted<Highlight> {
+    class Highlight : public RefCounted<Highlight>, private WebCore::GraphicsLayerClient {
         WTF_MAKE_NONCOPYABLE(Highlight);
     public:
-        static PassRefPtr<Highlight> createForSelection(RetainPtr<DDHighlightRef>);
-        static PassRefPtr<Highlight> createForTelephoneNumber(RetainPtr<DDHighlightRef>, PassRefPtr<WebCore::Range>);
+        static PassRefPtr<Highlight> createForSelection(ServicesOverlayController&, RetainPtr<DDHighlightRef>);
+        static PassRefPtr<Highlight> createForTelephoneNumber(ServicesOverlayController&, RetainPtr<DDHighlightRef>, PassRefPtr<WebCore::Range>);
+        ~Highlight();
 
+        void invalidate();
+
         DDHighlightRef ddHighlight() const { return m_ddHighlight.get(); }
         WebCore::Range* range() const { return m_range.get(); }
+        WebCore::GraphicsLayer* layer() const { return m_graphicsLayer.get(); }
 
         enum class Type {
             TelephoneNumber,
@@ -70,19 +75,25 @@
         };
         Type type() const { return m_type; }
 
+        void fadeIn();
+        void fadeOut();
+
     private:
-        explicit Highlight(Type type, RetainPtr<DDHighlightRef> ddHighlight, PassRefPtr<WebCore::Range> range)
-            : m_ddHighlight(ddHighlight)
-            , m_range(range)
-            , m_type(type)
-        {
-            ASSERT(m_ddHighlight);
-            ASSERT(type != Type::TelephoneNumber || m_range);
-        }
+        explicit Highlight(ServicesOverlayController&, Type, RetainPtr<DDHighlightRef>, PassRefPtr<WebCore::Range>);
 
+        // GraphicsLayerClient
+        virtual void notifyAnimationStarted(const WebCore::GraphicsLayer*, double time) override { }
+        virtual void notifyFlushRequired(const WebCore::GraphicsLayer*) override;
+        virtual void paintContents(const WebCore::GraphicsLayer*, WebCore::GraphicsContext&, WebCore::GraphicsLayerPaintingPhase, const WebCore::FloatRect& inClip) override;
+        virtual float deviceScaleFactor() const override;
+
+        void didFinishFadeOutAnimation();
+
         RetainPtr<DDHighlightRef> m_ddHighlight;
         RefPtr<WebCore::Range> m_range;
+        std::unique_ptr<WebCore::GraphicsLayer> m_graphicsLayer;
         Type m_type;
+        ServicesOverlayController* m_controller;
     };
 
     // PageOverlay::Client
@@ -104,33 +115,44 @@
 
     void determineActiveHighlight(bool& mouseIsOverButton);
     void clearActiveHighlight();
+    Highlight* activeHighlight() const { return m_activeHighlight.get(); }
 
     bool hasRelevantSelectionServices();
 
     bool mouseIsOverHighlight(Highlight&, bool& mouseIsOverButton) const;
-    std::chrono::milliseconds remainingTimeUntilHighlightShouldBeShown() const;
-    void repaintHighlightTimerFired(WebCore::Timer<ServicesOverlayController>&);
+    std::chrono::milliseconds remainingTimeUntilHighlightShouldBeShown(Highlight*) const;
+    void determineActiveHighlightTimerFired(WebCore::Timer<ServicesOverlayController>&);
 
     static bool highlightsAreEquivalent(const Highlight* a, const Highlight* b);
 
     Vector<RefPtr<WebCore::Range>> telephoneNumberRangesForFocusedFrame();
 
+    void didCreateHighlight(Highlight*);
+    void willDestroyHighlight(Highlight*);
+    void didFinishFadingOutHighlight(Highlight*);
+
+    WebPage& webPage() const { return m_webPage; }
+
     WebPage& m_webPage;
     PageOverlay* m_servicesOverlay;
 
     RefPtr<Highlight> m_activeHighlight;
+    RefPtr<Highlight> m_nextActiveHighlight;
     HashSet<RefPtr<Highlight>> m_potentialHighlights;
+    HashSet<RefPtr<Highlight>> m_animatingHighlights;
 
+    HashSet<Highlight*> m_highlights;
+
     Vector<WebCore::LayoutRect> m_currentSelectionRects;
     bool m_isTextOnly;
 
     std::chrono::steady_clock::time_point m_lastSelectionChangeTime;
-    std::chrono::steady_clock::time_point m_lastActiveHighlightChangeTime;
+    std::chrono::steady_clock::time_point m_nextActiveHighlightChangeTime;
 
     RefPtr<Highlight> m_currentMouseDownOnButtonHighlight;
     WebCore::IntPoint m_mousePosition;
 
-    WebCore::Timer<ServicesOverlayController> m_repaintHighlightTimer;
+    WebCore::Timer<ServicesOverlayController> m_determineActiveHighlightTimer;
 };
 
 } // namespace WebKit

Modified: trunk/Source/WebKit2/WebProcess/WebPage/mac/ServicesOverlayController.mm (172482 => 172483)


--- trunk/Source/WebKit2/WebProcess/WebPage/mac/ServicesOverlayController.mm	2014-08-12 19:12:05 UTC (rev 172482)
+++ trunk/Source/WebKit2/WebProcess/WebPage/mac/ServicesOverlayController.mm	2014-08-12 19:31:04 UTC (rev 172483)
@@ -31,13 +31,17 @@
 #import "Logging.h"
 #import "WebPage.h"
 #import "WebProcess.h"
+#import <QuartzCore/QuartzCore.h>
 #import <WebCore/Document.h>
 #import <WebCore/FloatQuad.h>
 #import <WebCore/FocusController.h>
 #import <WebCore/FrameView.h>
 #import <WebCore/GapRects.h>
 #import <WebCore/GraphicsContext.h>
+#import <WebCore/GraphicsLayer.h>
+#import <WebCore/GraphicsLayerCA.h>
 #import <WebCore/MainFrame.h>
+#import <WebCore/PlatformCAAnimationMac.h>
 #import <WebCore/SoftLinking.h>
 
 #if __has_include(<DataDetectors/DDHighlightDrawing.h>)
@@ -50,6 +54,8 @@
 #import <DataDetectors/DDHighlightDrawing_Private.h>
 #endif
 
+const float highlightFadeAnimationDuration = 0.3;
+
 typedef NSUInteger DDHighlightStyle;
 static const DDHighlightStyle DDHighlightNoOutlineWithArrow = (1 << 16);
 static const DDHighlightStyle DDHighlightOutlineWithArrow = (1 << 16) | 1;
@@ -64,16 +70,123 @@
 
 namespace WebKit {
 
-PassRefPtr<ServicesOverlayController::Highlight> ServicesOverlayController::Highlight::createForSelection(RetainPtr<DDHighlightRef> ddHighlight)
+PassRefPtr<ServicesOverlayController::Highlight> ServicesOverlayController::Highlight::createForSelection(ServicesOverlayController& controller, RetainPtr<DDHighlightRef> ddHighlight)
 {
-    return adoptRef(new Highlight(Type::Selection, ddHighlight, nullptr));
+    return adoptRef(new Highlight(controller, Type::Selection, ddHighlight, nullptr));
 }
 
-PassRefPtr<ServicesOverlayController::Highlight> ServicesOverlayController::Highlight::createForTelephoneNumber(RetainPtr<DDHighlightRef> ddHighlight, PassRefPtr<Range> range)
+PassRefPtr<ServicesOverlayController::Highlight> ServicesOverlayController::Highlight::createForTelephoneNumber(ServicesOverlayController& controller, RetainPtr<DDHighlightRef> ddHighlight, PassRefPtr<Range> range)
 {
-    return adoptRef(new Highlight(Type::TelephoneNumber, ddHighlight, range));
+    return adoptRef(new Highlight(controller, Type::TelephoneNumber, ddHighlight, range));
 }
 
+ServicesOverlayController::Highlight::Highlight(ServicesOverlayController& controller, Type type, RetainPtr<DDHighlightRef> ddHighlight, PassRefPtr<WebCore::Range> range)
+    : m_ddHighlight(ddHighlight)
+    , m_range(range)
+    , m_type(type)
+    , m_controller(&controller)
+{
+    ASSERT(m_ddHighlight);
+    ASSERT(type != Type::TelephoneNumber || m_range);
+
+    DrawingArea* drawingArea = controller.webPage().drawingArea();
+    m_graphicsLayer = GraphicsLayer::create(drawingArea ? drawingArea->graphicsLayerFactory() : nullptr, *this);
+    m_graphicsLayer->setDrawsContent(true);
+    m_graphicsLayer->setNeedsDisplay();
+
+    CGRect highlightBoundingRect = DDHighlightGetBoundingRect(ddHighlight.get());
+    m_graphicsLayer->setPosition(FloatPoint(highlightBoundingRect.origin));
+    m_graphicsLayer->setSize(FloatSize(highlightBoundingRect.size));
+
+    // Set directly on the PlatformCALayer so that when we leave the 'from' value implicit
+    // in our animations, we get the right initial value regardless of flush timing.
+    toGraphicsLayerCA(layer())->platformCALayer()->setOpacity(0);
+
+    controller.didCreateHighlight(this);
+}
+
+ServicesOverlayController::Highlight::~Highlight()
+{
+    if (m_controller)
+        m_controller->willDestroyHighlight(this);
+}
+
+void ServicesOverlayController::Highlight::invalidate()
+{
+    layer()->removeFromParent();
+    m_controller = nullptr;
+}
+
+void ServicesOverlayController::Highlight::notifyFlushRequired(const GraphicsLayer*)
+{
+    if (!m_controller)
+        return;
+
+    if (DrawingArea* drawingArea = m_controller->webPage().drawingArea())
+        drawingArea->scheduleCompositingLayerFlush();
+}
+
+void ServicesOverlayController::Highlight::paintContents(const GraphicsLayer*, GraphicsContext& graphicsContext, GraphicsLayerPaintingPhase, const FloatRect& inClip)
+{
+    CGContextRef cgContext = graphicsContext.platformContext();
+
+    CGLayerRef highlightLayer = DDHighlightGetLayerWithContext(ddHighlight(), cgContext);
+    CGRect highlightBoundingRect = DDHighlightGetBoundingRect(ddHighlight());
+    highlightBoundingRect.origin = CGPointZero;
+
+    CGContextDrawLayerInRect(cgContext, highlightBoundingRect, highlightLayer);
+}
+
+float ServicesOverlayController::Highlight::deviceScaleFactor() const
+{
+    if (!m_controller)
+        return 1;
+
+    return m_controller->webPage().deviceScaleFactor();
+}
+
+void ServicesOverlayController::Highlight::fadeIn()
+{
+    RetainPtr<CABasicAnimation> animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
+    [animation setDuration:highlightFadeAnimationDuration];
+    [animation setFillMode:kCAFillModeForwards];
+    [animation setRemovedOnCompletion:false];
+    [animation setToValue:@1];
+
+    RefPtr<PlatformCAAnimation> platformAnimation = PlatformCAAnimationMac::create(animation.get());
+    toGraphicsLayerCA(layer())->platformCALayer()->addAnimationForKey("FadeHighlightIn", platformAnimation.get());
+}
+
+void ServicesOverlayController::Highlight::fadeOut()
+{
+    RetainPtr<CABasicAnimation> animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
+    [animation setDuration:highlightFadeAnimationDuration];
+    [animation setFillMode:kCAFillModeForwards];
+    [animation setRemovedOnCompletion:false];
+    [animation setToValue:@0];
+
+    RefPtr<Highlight> retainedSelf = this;
+    [CATransaction begin];
+    [CATransaction setCompletionBlock:[retainedSelf] () {
+        retainedSelf->didFinishFadeOutAnimation();
+    }];
+
+    RefPtr<PlatformCAAnimation> platformAnimation = PlatformCAAnimationMac::create(animation.get());
+    toGraphicsLayerCA(layer())->platformCALayer()->addAnimationForKey("FadeHighlightOut", platformAnimation.get());
+    [CATransaction commit];
+}
+
+void ServicesOverlayController::Highlight::didFinishFadeOutAnimation()
+{
+    if (!m_controller)
+        return;
+
+    if (m_controller->activeHighlight() == this)
+        return;
+
+    layer()->removeFromParent();
+}
+
 static IntRect textQuadsToBoundingRectForRange(Range& range)
 {
     Vector<FloatQuad> textQuads;
@@ -88,12 +201,14 @@
     : m_webPage(webPage)
     , m_servicesOverlay(nullptr)
     , m_isTextOnly(false)
-    , m_repaintHighlightTimer(this, &ServicesOverlayController::repaintHighlightTimerFired)
+    , m_determineActiveHighlightTimer(this, &ServicesOverlayController::determineActiveHighlightTimerFired)
 {
 }
 
 ServicesOverlayController::~ServicesOverlayController()
 {
+    for (auto& highlight : m_highlights)
+        highlight->invalidate();
 }
 
 void ServicesOverlayController::pageOverlayDestroyed(PageOverlay*)
@@ -280,57 +395,33 @@
     return hovered;
 }
 
-std::chrono::milliseconds ServicesOverlayController::remainingTimeUntilHighlightShouldBeShown() const
+std::chrono::milliseconds ServicesOverlayController::remainingTimeUntilHighlightShouldBeShown(Highlight* highlight) const
 {
+    if (!highlight)
+        return std::chrono::milliseconds::zero();
+
     // Highlight hysteresis is only for selection services, because telephone number highlights are already much more stable
     // by virtue of being expanded to include the entire telephone number.
-    if (m_activeHighlight->type() == Highlight::Type::TelephoneNumber)
+    if (highlight->type() == Highlight::Type::TelephoneNumber)
         return std::chrono::milliseconds::zero();
 
     std::chrono::steady_clock::duration minimumTimeUntilHighlightShouldBeShown = 200_ms;
 
     auto now = std::chrono::steady_clock::now();
     auto timeSinceLastSelectionChange = now - m_lastSelectionChangeTime;
-    auto timeSinceHighlightBecameActive = now - m_lastActiveHighlightChangeTime;
+    auto timeSinceHighlightBecameActive = now - m_nextActiveHighlightChangeTime;
 
     return std::chrono::duration_cast<std::chrono::milliseconds>(std::max(minimumTimeUntilHighlightShouldBeShown - timeSinceLastSelectionChange, minimumTimeUntilHighlightShouldBeShown - timeSinceHighlightBecameActive));
 }
 
-void ServicesOverlayController::repaintHighlightTimerFired(WebCore::Timer<ServicesOverlayController>&)
+void ServicesOverlayController::determineActiveHighlightTimerFired(Timer<ServicesOverlayController>&)
 {
-    if (m_servicesOverlay)
-        m_servicesOverlay->setNeedsDisplay();
-}
-
-void ServicesOverlayController::drawHighlight(Highlight& highlight, WebCore::GraphicsContext& graphicsContext)
-{
     bool mouseIsOverButton;
-    if (!mouseIsOverHighlight(highlight, mouseIsOverButton)) {
-        LOG(Services, "ServicesOverlayController::drawHighlight - Mouse is not over highlight, so drawing nothing");
-        return;
-    }
-
-    auto remainingTimeUntilHighlightShouldBeShown = this->remainingTimeUntilHighlightShouldBeShown();
-    if (remainingTimeUntilHighlightShouldBeShown > std::chrono::steady_clock::duration::zero()) {
-        m_repaintHighlightTimer.startOneShot(remainingTimeUntilHighlightShouldBeShown);
-        return;
-    }
-
-    CGContextRef cgContext = graphicsContext.platformContext();
-    
-    CGLayerRef highlightLayer = DDHighlightGetLayerWithContext(highlight.ddHighlight(), cgContext);
-    CGRect highlightBoundingRect = DDHighlightGetBoundingRect(highlight.ddHighlight());
-
-    CGContextDrawLayerInRect(cgContext, highlightBoundingRect, highlightLayer);
+    determineActiveHighlight(mouseIsOverButton);
 }
 
-void ServicesOverlayController::drawRect(PageOverlay* overlay, WebCore::GraphicsContext& graphicsContext, const WebCore::IntRect& dirtyRect)
+void ServicesOverlayController::drawRect(PageOverlay* overlay, GraphicsContext& graphicsContext, const IntRect& dirtyRect)
 {
-    bool mouseIsOverButton;
-    determineActiveHighlight(mouseIsOverButton);
-
-    if (m_activeHighlight)
-        drawHighlight(*m_activeHighlight, graphicsContext);
 }
 
 void ServicesOverlayController::clearActiveHighlight()
@@ -357,7 +448,7 @@
 
 void ServicesOverlayController::buildPhoneNumberHighlights()
 {
-    removeAllPotentialHighlightsOfType(Highlight::Type::TelephoneNumber);
+    HashSet<RefPtr<Highlight>> newPotentialHighlights;
 
     Frame* mainFrame = m_webPage.mainFrame();
     FrameView& mainFrameView = *mainFrame->view();
@@ -381,10 +472,32 @@
             CGRect cgRect = rect;
             RetainPtr<DDHighlightRef> ddHighlight = adoptCF(DDHighlightCreateWithRectsInVisibleRectWithStyleAndDirection(nullptr, &cgRect, 1, mainFrameView.visibleContentRect(), DDHighlightOutlineWithArrow, YES, NSWritingDirectionNatural, NO, YES));
 
-            m_potentialHighlights.add(Highlight::createForTelephoneNumber(ddHighlight, range));
+            newPotentialHighlights.add(Highlight::createForTelephoneNumber(*this, ddHighlight, range));
         }
     }
 
+    // If any old Highlights are equivalent (by Range) to a new Highlight, reuse the old
+    // one so that any metadata is retained.
+    HashSet<RefPtr<Highlight>> reusedPotentialHighlights;
+
+    for (auto& oldHighlight : m_potentialHighlights) {
+        if (oldHighlight->type() != Highlight::Type::TelephoneNumber)
+            continue;
+
+        for (auto& newHighlight : newPotentialHighlights) {
+            if (highlightsAreEquivalent(oldHighlight.get(), newHighlight.get())) {
+                reusedPotentialHighlights.add(oldHighlight);
+                newPotentialHighlights.remove(newHighlight);
+                break;
+            }
+        }
+    }
+
+    removeAllPotentialHighlightsOfType(Highlight::Type::TelephoneNumber);
+
+    m_potentialHighlights.add(newPotentialHighlights.begin(), newPotentialHighlights.end());
+    m_potentialHighlights.add(reusedPotentialHighlights.begin(), reusedPotentialHighlights.end());
+
     didRebuildPotentialHighlights();
 }
 
@@ -402,7 +515,7 @@
         CGRect visibleRect = m_webPage.corePage()->mainFrame().view()->visibleContentRect();
         RetainPtr<DDHighlightRef> ddHighlight = adoptCF(DDHighlightCreateWithRectsInVisibleRectWithStyleAndDirection(nullptr, cgRects.begin(), cgRects.size(), visibleRect, DDHighlightNoOutlineWithArrow, YES, NSWritingDirectionNatural, NO, YES));
         
-        m_potentialHighlights.add(Highlight::createForSelection(ddHighlight));
+        m_potentialHighlights.add(Highlight::createForSelection(*this, ddHighlight));
     }
 
     didRebuildPotentialHighlights();
@@ -425,19 +538,19 @@
         return;
 
     createOverlayIfNeeded();
+
+    bool mouseIsOverButton;
+    determineActiveHighlight(mouseIsOverButton);
 }
 
 void ServicesOverlayController::createOverlayIfNeeded()
 {
-    if (m_servicesOverlay) {
-        m_servicesOverlay->setNeedsDisplay();
+    if (m_servicesOverlay)
         return;
-    }
 
     RefPtr<PageOverlay> overlay = PageOverlay::create(this, PageOverlay::OverlayType::Document);
     m_servicesOverlay = overlay.get();
     m_webPage.installPageOverlay(overlay.release(), PageOverlay::FadeMode::DoNotFade);
-    m_servicesOverlay->setNeedsDisplay();
 }
 
 Vector<RefPtr<Range>> ServicesOverlayController::telephoneNumberRangesForFocusedFrame()
@@ -467,13 +580,13 @@
 {
     mouseIsOverActiveHighlightButton = false;
 
-    RefPtr<Highlight> oldActiveHighlight = m_activeHighlight.release();
+    RefPtr<Highlight> newActiveHighlight;
 
     for (auto& highlight : m_potentialHighlights) {
         if (highlight->type() == Highlight::Type::Selection) {
             // If we've already found a new active highlight, and it's
             // a telephone number highlight, prefer that over this selection highlight.
-            if (m_activeHighlight && m_activeHighlight->type() == Highlight::Type::TelephoneNumber)
+            if (newActiveHighlight && newActiveHighlight->type() == Highlight::Type::TelephoneNumber)
                 continue;
 
             // If this highlight has no compatible services, it can't be active, unless we have telephone number highlights to show in the combined menu.
@@ -486,14 +599,38 @@
         if (!mouseIsOverHighlight(*highlight, mouseIsOverButton))
             continue;
 
-        m_activeHighlight = highlight;
+        newActiveHighlight = highlight;
         mouseIsOverActiveHighlightButton = mouseIsOverButton;
     }
 
-    if (!highlightsAreEquivalent(oldActiveHighlight.get(), m_activeHighlight.get())) {
-        m_lastActiveHighlightChangeTime = std::chrono::steady_clock::now();
-        m_servicesOverlay->setNeedsDisplay();
+    if (!this->highlightsAreEquivalent(m_activeHighlight.get(), newActiveHighlight.get())) {
+        // When transitioning to a new highlight, we might end up in determineActiveHighlight multiple times
+        // before the new highlight actually becomes active. Keep track of the last next-but-not-yet-active
+        // highlight, and only reset the active highlight hysteresis when that changes.
+        if (m_nextActiveHighlight != newActiveHighlight) {
+            m_nextActiveHighlight = newActiveHighlight;
+            m_nextActiveHighlightChangeTime = std::chrono::steady_clock::now();
+        }
+
         m_currentMouseDownOnButtonHighlight = nullptr;
+
+        if (m_activeHighlight) {
+            m_activeHighlight->fadeOut();
+            m_activeHighlight = nullptr;
+        }
+
+        auto remainingTimeUntilHighlightShouldBeShown = this->remainingTimeUntilHighlightShouldBeShown(newActiveHighlight.get());
+        if (remainingTimeUntilHighlightShouldBeShown > std::chrono::steady_clock::duration::zero()) {
+            m_determineActiveHighlightTimer.startOneShot(remainingTimeUntilHighlightShouldBeShown);
+            return;
+        }
+
+        m_activeHighlight = m_nextActiveHighlight.release();
+
+        if (m_activeHighlight) {
+            m_servicesOverlay->layer()->addChild(m_activeHighlight->layer());
+            m_activeHighlight->fadeIn();
+        }
     }
 }
 
@@ -515,7 +652,7 @@
         RefPtr<Highlight> mouseDownHighlight = m_currentMouseDownOnButtonHighlight;
         m_currentMouseDownOnButtonHighlight = nullptr;
 
-        if (mouseIsOverActiveHighlightButton && mouseDownHighlight && remainingTimeUntilHighlightShouldBeShown() <= std::chrono::steady_clock::duration::zero()) {
+        if (mouseIsOverActiveHighlightButton && mouseDownHighlight) {
             handleClick(m_mousePosition, *mouseDownHighlight);
             return true;
         }
@@ -536,7 +673,6 @@
     if (event.type() == WebEvent::MouseDown) {
         if (m_activeHighlight && mouseIsOverActiveHighlightButton) {
             m_currentMouseDownOnButtonHighlight = m_activeHighlight;
-            m_servicesOverlay->setNeedsDisplay();
             return true;
         }
 
@@ -546,7 +682,7 @@
     return false;
 }
 
-void ServicesOverlayController::handleClick(const WebCore::IntPoint& clickPoint, Highlight& highlight)
+void ServicesOverlayController::handleClick(const IntPoint& clickPoint, Highlight& highlight)
 {
     FrameView* frameView = m_webPage.mainFrameView();
     if (!frameView)
@@ -566,6 +702,18 @@
         m_webPage.handleTelephoneNumberClick(highlight.range()->text(), windowPoint);
 }
 
+void ServicesOverlayController::didCreateHighlight(Highlight* highlight)
+{
+    ASSERT(!m_highlights.contains(highlight));
+    m_highlights.add(highlight);
+}
+
+void ServicesOverlayController::willDestroyHighlight(Highlight* highlight)
+{
+    ASSERT(m_highlights.contains(highlight));
+    m_highlights.remove(highlight);
+}
+
 } // namespace WebKit
 
 #endif // #if ENABLE(SERVICE_CONTROLS) || ENABLE(TELEPHONE_NUMBER_DETECTION) && PLATFORM(MAC)
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to