<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[172483] trunk/Source/WebKit2</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.webkit.org/projects/webkit/changeset/172483">172483</a></dd>
<dt>Author</dt> <dd>timothy_horton@apple.com</dd>
<dt>Date</dt> <dd>2014-08-12 12:31:04 -0700 (Tue, 12 Aug 2014)</dd>
</dl>

<h3>Log Message</h3>
<pre>Add a fade transition to services highlights
https://bugs.webkit.org/show_bug.cgi?id=135829
&lt;rdar://problem/17935736&gt;

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.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkSourceWebKit2ChangeLog">trunk/Source/WebKit2/ChangeLog</a></li>
<li><a href="#trunkSourceWebKit2WebProcessWebPagePageOverlaycpp">trunk/Source/WebKit2/WebProcess/WebPage/PageOverlay.cpp</a></li>
<li><a href="#trunkSourceWebKit2WebProcessWebPagePageOverlayh">trunk/Source/WebKit2/WebProcess/WebPage/PageOverlay.h</a></li>
<li><a href="#trunkSourceWebKit2WebProcessWebPagePageOverlayControllercpp">trunk/Source/WebKit2/WebProcess/WebPage/PageOverlayController.cpp</a></li>
<li><a href="#trunkSourceWebKit2WebProcessWebPagePageOverlayControllerh">trunk/Source/WebKit2/WebProcess/WebPage/PageOverlayController.h</a></li>
<li><a href="#trunkSourceWebKit2WebProcessWebPageServicesOverlayControllerh">trunk/Source/WebKit2/WebProcess/WebPage/ServicesOverlayController.h</a></li>
<li><a href="#trunkSourceWebKit2WebProcessWebPagemacServicesOverlayControllermm">trunk/Source/WebKit2/WebProcess/WebPage/mac/ServicesOverlayController.mm</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkSourceWebKit2ChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebKit2/ChangeLog (172482 => 172483)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -1,3 +1,102 @@
</span><ins>+2014-08-12  Tim Horton  &lt;timothy_horton@apple.com&gt;
+
+        Add a fade transition to services highlights
+        https://bugs.webkit.org/show_bug.cgi?id=135829
+        &lt;rdar://problem/17935736&gt;
+
+        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.
+
</ins><span class="cx"> 2014-08-11  Andy Estes  &lt;aestes@apple.com&gt;
</span><span class="cx"> 
</span><span class="cx">         [iOS] Get rid of iOS.xcconfig
</span></span></pre></div>
<a id="trunkSourceWebKit2WebProcessWebPagePageOverlaycpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebKit2/WebProcess/WebPage/PageOverlay.cpp (172482 => 172483)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -233,4 +233,9 @@
</span><span class="cx">     m_webPage-&gt;pageOverlayController().clearPageOverlay(*this);
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+WebCore::GraphicsLayer* PageOverlay::layer()
+{
+    return m_webPage-&gt;pageOverlayController().layerForOverlay(*this);
+}
+
</ins><span class="cx"> } // namespace WebKit
</span></span></pre></div>
<a id="trunkSourceWebKit2WebProcessWebPagePageOverlayh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebKit2/WebProcess/WebPage/PageOverlay.h (172482 => 172483)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -35,6 +35,7 @@
</span><span class="cx"> 
</span><span class="cx"> namespace WebCore {
</span><span class="cx"> class GraphicsContext;
</span><ins>+class GraphicsLayer;
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> namespace WebKit {
</span><span class="lines">@@ -95,6 +96,8 @@
</span><span class="cx"> 
</span><span class="cx">     WebCore::RGBA32 backgroundColor() const { return m_backgroundColor; }
</span><span class="cx">     void setBackgroundColor(WebCore::RGBA32);
</span><ins>+
+    WebCore::GraphicsLayer* layer();
</ins><span class="cx">     
</span><span class="cx"> protected:
</span><span class="cx">     explicit PageOverlay(Client*, OverlayType);
</span></span></pre></div>
<a id="trunkSourceWebKit2WebProcessWebPagePageOverlayControllercpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebKit2/WebProcess/WebPage/PageOverlayController.cpp (172482 => 172483)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -171,6 +171,12 @@
</span><span class="cx">     m_overlayGraphicsLayers.get(&amp;overlay)-&gt;setDrawsContent(false);
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+GraphicsLayer* PageOverlayController::layerForOverlay(PageOverlay&amp; overlay) const
+{
+    ASSERT(m_pageOverlays.contains(&amp;overlay));
+    return m_overlayGraphicsLayers.get(&amp;overlay);
+}
+
</ins><span class="cx"> void PageOverlayController::didChangeViewSize()
</span><span class="cx"> {
</span><span class="cx">     for (auto&amp; overlayAndLayer : m_overlayGraphicsLayers) {
</span></span></pre></div>
<a id="trunkSourceWebKit2WebProcessWebPagePageOverlayControllerh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebKit2/WebProcess/WebPage/PageOverlayController.h (172482 => 172483)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -57,6 +57,7 @@
</span><span class="cx">     void setPageOverlayNeedsDisplay(PageOverlay&amp;, const WebCore::IntRect&amp;);
</span><span class="cx">     void setPageOverlayOpacity(PageOverlay&amp;, float);
</span><span class="cx">     void clearPageOverlay(PageOverlay&amp;);
</span><ins>+    WebCore::GraphicsLayer* layerForOverlay(PageOverlay&amp;) const;
</ins><span class="cx"> 
</span><span class="cx">     void didChangeViewSize();
</span><span class="cx">     void didChangeDocumentSize();
</span></span></pre></div>
<a id="trunkSourceWebKit2WebProcessWebPageServicesOverlayControllerh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebKit2/WebProcess/WebPage/ServicesOverlayController.h (172482 => 172483)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -30,6 +30,7 @@
</span><span class="cx"> 
</span><span class="cx"> #include &quot;PageOverlay.h&quot;
</span><span class="cx"> #include &quot;WebFrame.h&quot;
</span><ins>+#include &lt;WebCore/GraphicsLayerClient.h&gt;
</ins><span class="cx"> #include &lt;WebCore/Range.h&gt;
</span><span class="cx"> #include &lt;WebCore/Timer.h&gt;
</span><span class="cx"> #include &lt;wtf/RefCounted.h&gt;
</span><span class="lines">@@ -55,14 +56,18 @@
</span><span class="cx">     void selectionRectsDidChange(const Vector&lt;WebCore::LayoutRect&gt;&amp;, const Vector&lt;WebCore::GapRects&gt;&amp;, bool isTextOnly);
</span><span class="cx"> 
</span><span class="cx"> private:
</span><del>-    class Highlight : public RefCounted&lt;Highlight&gt; {
</del><ins>+    class Highlight : public RefCounted&lt;Highlight&gt;, private WebCore::GraphicsLayerClient {
</ins><span class="cx">         WTF_MAKE_NONCOPYABLE(Highlight);
</span><span class="cx">     public:
</span><del>-        static PassRefPtr&lt;Highlight&gt; createForSelection(RetainPtr&lt;DDHighlightRef&gt;);
-        static PassRefPtr&lt;Highlight&gt; createForTelephoneNumber(RetainPtr&lt;DDHighlightRef&gt;, PassRefPtr&lt;WebCore::Range&gt;);
</del><ins>+        static PassRefPtr&lt;Highlight&gt; createForSelection(ServicesOverlayController&amp;, RetainPtr&lt;DDHighlightRef&gt;);
+        static PassRefPtr&lt;Highlight&gt; createForTelephoneNumber(ServicesOverlayController&amp;, RetainPtr&lt;DDHighlightRef&gt;, PassRefPtr&lt;WebCore::Range&gt;);
+        ~Highlight();
</ins><span class="cx"> 
</span><ins>+        void invalidate();
+
</ins><span class="cx">         DDHighlightRef ddHighlight() const { return m_ddHighlight.get(); }
</span><span class="cx">         WebCore::Range* range() const { return m_range.get(); }
</span><ins>+        WebCore::GraphicsLayer* layer() const { return m_graphicsLayer.get(); }
</ins><span class="cx"> 
</span><span class="cx">         enum class Type {
</span><span class="cx">             TelephoneNumber,
</span><span class="lines">@@ -70,19 +75,25 @@
</span><span class="cx">         };
</span><span class="cx">         Type type() const { return m_type; }
</span><span class="cx"> 
</span><ins>+        void fadeIn();
+        void fadeOut();
+
</ins><span class="cx">     private:
</span><del>-        explicit Highlight(Type type, RetainPtr&lt;DDHighlightRef&gt; ddHighlight, PassRefPtr&lt;WebCore::Range&gt; range)
-            : m_ddHighlight(ddHighlight)
-            , m_range(range)
-            , m_type(type)
-        {
-            ASSERT(m_ddHighlight);
-            ASSERT(type != Type::TelephoneNumber || m_range);
-        }
</del><ins>+        explicit Highlight(ServicesOverlayController&amp;, Type, RetainPtr&lt;DDHighlightRef&gt;, PassRefPtr&lt;WebCore::Range&gt;);
</ins><span class="cx"> 
</span><ins>+        // 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&amp;, WebCore::GraphicsLayerPaintingPhase, const WebCore::FloatRect&amp; inClip) override;
+        virtual float deviceScaleFactor() const override;
+
+        void didFinishFadeOutAnimation();
+
</ins><span class="cx">         RetainPtr&lt;DDHighlightRef&gt; m_ddHighlight;
</span><span class="cx">         RefPtr&lt;WebCore::Range&gt; m_range;
</span><ins>+        std::unique_ptr&lt;WebCore::GraphicsLayer&gt; m_graphicsLayer;
</ins><span class="cx">         Type m_type;
</span><ins>+        ServicesOverlayController* m_controller;
</ins><span class="cx">     };
</span><span class="cx"> 
</span><span class="cx">     // PageOverlay::Client
</span><span class="lines">@@ -104,33 +115,44 @@
</span><span class="cx"> 
</span><span class="cx">     void determineActiveHighlight(bool&amp; mouseIsOverButton);
</span><span class="cx">     void clearActiveHighlight();
</span><ins>+    Highlight* activeHighlight() const { return m_activeHighlight.get(); }
</ins><span class="cx"> 
</span><span class="cx">     bool hasRelevantSelectionServices();
</span><span class="cx"> 
</span><span class="cx">     bool mouseIsOverHighlight(Highlight&amp;, bool&amp; mouseIsOverButton) const;
</span><del>-    std::chrono::milliseconds remainingTimeUntilHighlightShouldBeShown() const;
-    void repaintHighlightTimerFired(WebCore::Timer&lt;ServicesOverlayController&gt;&amp;);
</del><ins>+    std::chrono::milliseconds remainingTimeUntilHighlightShouldBeShown(Highlight*) const;
+    void determineActiveHighlightTimerFired(WebCore::Timer&lt;ServicesOverlayController&gt;&amp;);
</ins><span class="cx"> 
</span><span class="cx">     static bool highlightsAreEquivalent(const Highlight* a, const Highlight* b);
</span><span class="cx"> 
</span><span class="cx">     Vector&lt;RefPtr&lt;WebCore::Range&gt;&gt; telephoneNumberRangesForFocusedFrame();
</span><span class="cx"> 
</span><ins>+    void didCreateHighlight(Highlight*);
+    void willDestroyHighlight(Highlight*);
+    void didFinishFadingOutHighlight(Highlight*);
+
+    WebPage&amp; webPage() const { return m_webPage; }
+
</ins><span class="cx">     WebPage&amp; m_webPage;
</span><span class="cx">     PageOverlay* m_servicesOverlay;
</span><span class="cx"> 
</span><span class="cx">     RefPtr&lt;Highlight&gt; m_activeHighlight;
</span><ins>+    RefPtr&lt;Highlight&gt; m_nextActiveHighlight;
</ins><span class="cx">     HashSet&lt;RefPtr&lt;Highlight&gt;&gt; m_potentialHighlights;
</span><ins>+    HashSet&lt;RefPtr&lt;Highlight&gt;&gt; m_animatingHighlights;
</ins><span class="cx"> 
</span><ins>+    HashSet&lt;Highlight*&gt; m_highlights;
+
</ins><span class="cx">     Vector&lt;WebCore::LayoutRect&gt; m_currentSelectionRects;
</span><span class="cx">     bool m_isTextOnly;
</span><span class="cx"> 
</span><span class="cx">     std::chrono::steady_clock::time_point m_lastSelectionChangeTime;
</span><del>-    std::chrono::steady_clock::time_point m_lastActiveHighlightChangeTime;
</del><ins>+    std::chrono::steady_clock::time_point m_nextActiveHighlightChangeTime;
</ins><span class="cx"> 
</span><span class="cx">     RefPtr&lt;Highlight&gt; m_currentMouseDownOnButtonHighlight;
</span><span class="cx">     WebCore::IntPoint m_mousePosition;
</span><span class="cx"> 
</span><del>-    WebCore::Timer&lt;ServicesOverlayController&gt; m_repaintHighlightTimer;
</del><ins>+    WebCore::Timer&lt;ServicesOverlayController&gt; m_determineActiveHighlightTimer;
</ins><span class="cx"> };
</span><span class="cx"> 
</span><span class="cx"> } // namespace WebKit
</span></span></pre></div>
<a id="trunkSourceWebKit2WebProcessWebPagemacServicesOverlayControllermm"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebKit2/WebProcess/WebPage/mac/ServicesOverlayController.mm (172482 => 172483)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -31,13 +31,17 @@
</span><span class="cx"> #import &quot;Logging.h&quot;
</span><span class="cx"> #import &quot;WebPage.h&quot;
</span><span class="cx"> #import &quot;WebProcess.h&quot;
</span><ins>+#import &lt;QuartzCore/QuartzCore.h&gt;
</ins><span class="cx"> #import &lt;WebCore/Document.h&gt;
</span><span class="cx"> #import &lt;WebCore/FloatQuad.h&gt;
</span><span class="cx"> #import &lt;WebCore/FocusController.h&gt;
</span><span class="cx"> #import &lt;WebCore/FrameView.h&gt;
</span><span class="cx"> #import &lt;WebCore/GapRects.h&gt;
</span><span class="cx"> #import &lt;WebCore/GraphicsContext.h&gt;
</span><ins>+#import &lt;WebCore/GraphicsLayer.h&gt;
+#import &lt;WebCore/GraphicsLayerCA.h&gt;
</ins><span class="cx"> #import &lt;WebCore/MainFrame.h&gt;
</span><ins>+#import &lt;WebCore/PlatformCAAnimationMac.h&gt;
</ins><span class="cx"> #import &lt;WebCore/SoftLinking.h&gt;
</span><span class="cx"> 
</span><span class="cx"> #if __has_include(&lt;DataDetectors/DDHighlightDrawing.h&gt;)
</span><span class="lines">@@ -50,6 +54,8 @@
</span><span class="cx"> #import &lt;DataDetectors/DDHighlightDrawing_Private.h&gt;
</span><span class="cx"> #endif
</span><span class="cx"> 
</span><ins>+const float highlightFadeAnimationDuration = 0.3;
+
</ins><span class="cx"> typedef NSUInteger DDHighlightStyle;
</span><span class="cx"> static const DDHighlightStyle DDHighlightNoOutlineWithArrow = (1 &lt;&lt; 16);
</span><span class="cx"> static const DDHighlightStyle DDHighlightOutlineWithArrow = (1 &lt;&lt; 16) | 1;
</span><span class="lines">@@ -64,16 +70,123 @@
</span><span class="cx"> 
</span><span class="cx"> namespace WebKit {
</span><span class="cx"> 
</span><del>-PassRefPtr&lt;ServicesOverlayController::Highlight&gt; ServicesOverlayController::Highlight::createForSelection(RetainPtr&lt;DDHighlightRef&gt; ddHighlight)
</del><ins>+PassRefPtr&lt;ServicesOverlayController::Highlight&gt; ServicesOverlayController::Highlight::createForSelection(ServicesOverlayController&amp; controller, RetainPtr&lt;DDHighlightRef&gt; ddHighlight)
</ins><span class="cx"> {
</span><del>-    return adoptRef(new Highlight(Type::Selection, ddHighlight, nullptr));
</del><ins>+    return adoptRef(new Highlight(controller, Type::Selection, ddHighlight, nullptr));
</ins><span class="cx"> }
</span><span class="cx"> 
</span><del>-PassRefPtr&lt;ServicesOverlayController::Highlight&gt; ServicesOverlayController::Highlight::createForTelephoneNumber(RetainPtr&lt;DDHighlightRef&gt; ddHighlight, PassRefPtr&lt;Range&gt; range)
</del><ins>+PassRefPtr&lt;ServicesOverlayController::Highlight&gt; ServicesOverlayController::Highlight::createForTelephoneNumber(ServicesOverlayController&amp; controller, RetainPtr&lt;DDHighlightRef&gt; ddHighlight, PassRefPtr&lt;Range&gt; range)
</ins><span class="cx"> {
</span><del>-    return adoptRef(new Highlight(Type::TelephoneNumber, ddHighlight, range));
</del><ins>+    return adoptRef(new Highlight(controller, Type::TelephoneNumber, ddHighlight, range));
</ins><span class="cx"> }
</span><span class="cx"> 
</span><ins>+ServicesOverlayController::Highlight::Highlight(ServicesOverlayController&amp; controller, Type type, RetainPtr&lt;DDHighlightRef&gt; ddHighlight, PassRefPtr&lt;WebCore::Range&gt; range)
+    : m_ddHighlight(ddHighlight)
+    , m_range(range)
+    , m_type(type)
+    , m_controller(&amp;controller)
+{
+    ASSERT(m_ddHighlight);
+    ASSERT(type != Type::TelephoneNumber || m_range);
+
+    DrawingArea* drawingArea = controller.webPage().drawingArea();
+    m_graphicsLayer = GraphicsLayer::create(drawingArea ? drawingArea-&gt;graphicsLayerFactory() : nullptr, *this);
+    m_graphicsLayer-&gt;setDrawsContent(true);
+    m_graphicsLayer-&gt;setNeedsDisplay();
+
+    CGRect highlightBoundingRect = DDHighlightGetBoundingRect(ddHighlight.get());
+    m_graphicsLayer-&gt;setPosition(FloatPoint(highlightBoundingRect.origin));
+    m_graphicsLayer-&gt;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())-&gt;platformCALayer()-&gt;setOpacity(0);
+
+    controller.didCreateHighlight(this);
+}
+
+ServicesOverlayController::Highlight::~Highlight()
+{
+    if (m_controller)
+        m_controller-&gt;willDestroyHighlight(this);
+}
+
+void ServicesOverlayController::Highlight::invalidate()
+{
+    layer()-&gt;removeFromParent();
+    m_controller = nullptr;
+}
+
+void ServicesOverlayController::Highlight::notifyFlushRequired(const GraphicsLayer*)
+{
+    if (!m_controller)
+        return;
+
+    if (DrawingArea* drawingArea = m_controller-&gt;webPage().drawingArea())
+        drawingArea-&gt;scheduleCompositingLayerFlush();
+}
+
+void ServicesOverlayController::Highlight::paintContents(const GraphicsLayer*, GraphicsContext&amp; graphicsContext, GraphicsLayerPaintingPhase, const FloatRect&amp; 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-&gt;webPage().deviceScaleFactor();
+}
+
+void ServicesOverlayController::Highlight::fadeIn()
+{
+    RetainPtr&lt;CABasicAnimation&gt; animation = [CABasicAnimation animationWithKeyPath:@&quot;opacity&quot;];
+    [animation setDuration:highlightFadeAnimationDuration];
+    [animation setFillMode:kCAFillModeForwards];
+    [animation setRemovedOnCompletion:false];
+    [animation setToValue:@1];
+
+    RefPtr&lt;PlatformCAAnimation&gt; platformAnimation = PlatformCAAnimationMac::create(animation.get());
+    toGraphicsLayerCA(layer())-&gt;platformCALayer()-&gt;addAnimationForKey(&quot;FadeHighlightIn&quot;, platformAnimation.get());
+}
+
+void ServicesOverlayController::Highlight::fadeOut()
+{
+    RetainPtr&lt;CABasicAnimation&gt; animation = [CABasicAnimation animationWithKeyPath:@&quot;opacity&quot;];
+    [animation setDuration:highlightFadeAnimationDuration];
+    [animation setFillMode:kCAFillModeForwards];
+    [animation setRemovedOnCompletion:false];
+    [animation setToValue:@0];
+
+    RefPtr&lt;Highlight&gt; retainedSelf = this;
+    [CATransaction begin];
+    [CATransaction setCompletionBlock:[retainedSelf] () {
+        retainedSelf-&gt;didFinishFadeOutAnimation();
+    }];
+
+    RefPtr&lt;PlatformCAAnimation&gt; platformAnimation = PlatformCAAnimationMac::create(animation.get());
+    toGraphicsLayerCA(layer())-&gt;platformCALayer()-&gt;addAnimationForKey(&quot;FadeHighlightOut&quot;, platformAnimation.get());
+    [CATransaction commit];
+}
+
+void ServicesOverlayController::Highlight::didFinishFadeOutAnimation()
+{
+    if (!m_controller)
+        return;
+
+    if (m_controller-&gt;activeHighlight() == this)
+        return;
+
+    layer()-&gt;removeFromParent();
+}
+
</ins><span class="cx"> static IntRect textQuadsToBoundingRectForRange(Range&amp; range)
</span><span class="cx"> {
</span><span class="cx">     Vector&lt;FloatQuad&gt; textQuads;
</span><span class="lines">@@ -88,12 +201,14 @@
</span><span class="cx">     : m_webPage(webPage)
</span><span class="cx">     , m_servicesOverlay(nullptr)
</span><span class="cx">     , m_isTextOnly(false)
</span><del>-    , m_repaintHighlightTimer(this, &amp;ServicesOverlayController::repaintHighlightTimerFired)
</del><ins>+    , m_determineActiveHighlightTimer(this, &amp;ServicesOverlayController::determineActiveHighlightTimerFired)
</ins><span class="cx"> {
</span><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> ServicesOverlayController::~ServicesOverlayController()
</span><span class="cx"> {
</span><ins>+    for (auto&amp; highlight : m_highlights)
+        highlight-&gt;invalidate();
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> void ServicesOverlayController::pageOverlayDestroyed(PageOverlay*)
</span><span class="lines">@@ -280,57 +395,33 @@
</span><span class="cx">     return hovered;
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-std::chrono::milliseconds ServicesOverlayController::remainingTimeUntilHighlightShouldBeShown() const
</del><ins>+std::chrono::milliseconds ServicesOverlayController::remainingTimeUntilHighlightShouldBeShown(Highlight* highlight) const
</ins><span class="cx"> {
</span><ins>+    if (!highlight)
+        return std::chrono::milliseconds::zero();
+
</ins><span class="cx">     // Highlight hysteresis is only for selection services, because telephone number highlights are already much more stable
</span><span class="cx">     // by virtue of being expanded to include the entire telephone number.
</span><del>-    if (m_activeHighlight-&gt;type() == Highlight::Type::TelephoneNumber)
</del><ins>+    if (highlight-&gt;type() == Highlight::Type::TelephoneNumber)
</ins><span class="cx">         return std::chrono::milliseconds::zero();
</span><span class="cx"> 
</span><span class="cx">     std::chrono::steady_clock::duration minimumTimeUntilHighlightShouldBeShown = 200_ms;
</span><span class="cx"> 
</span><span class="cx">     auto now = std::chrono::steady_clock::now();
</span><span class="cx">     auto timeSinceLastSelectionChange = now - m_lastSelectionChangeTime;
</span><del>-    auto timeSinceHighlightBecameActive = now - m_lastActiveHighlightChangeTime;
</del><ins>+    auto timeSinceHighlightBecameActive = now - m_nextActiveHighlightChangeTime;
</ins><span class="cx"> 
</span><span class="cx">     return std::chrono::duration_cast&lt;std::chrono::milliseconds&gt;(std::max(minimumTimeUntilHighlightShouldBeShown - timeSinceLastSelectionChange, minimumTimeUntilHighlightShouldBeShown - timeSinceHighlightBecameActive));
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-void ServicesOverlayController::repaintHighlightTimerFired(WebCore::Timer&lt;ServicesOverlayController&gt;&amp;)
</del><ins>+void ServicesOverlayController::determineActiveHighlightTimerFired(Timer&lt;ServicesOverlayController&gt;&amp;)
</ins><span class="cx"> {
</span><del>-    if (m_servicesOverlay)
-        m_servicesOverlay-&gt;setNeedsDisplay();
-}
-
-void ServicesOverlayController::drawHighlight(Highlight&amp; highlight, WebCore::GraphicsContext&amp; graphicsContext)
-{
</del><span class="cx">     bool mouseIsOverButton;
</span><del>-    if (!mouseIsOverHighlight(highlight, mouseIsOverButton)) {
-        LOG(Services, &quot;ServicesOverlayController::drawHighlight - Mouse is not over highlight, so drawing nothing&quot;);
-        return;
-    }
-
-    auto remainingTimeUntilHighlightShouldBeShown = this-&gt;remainingTimeUntilHighlightShouldBeShown();
-    if (remainingTimeUntilHighlightShouldBeShown &gt; 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);
</del><ins>+    determineActiveHighlight(mouseIsOverButton);
</ins><span class="cx"> }
</span><span class="cx"> 
</span><del>-void ServicesOverlayController::drawRect(PageOverlay* overlay, WebCore::GraphicsContext&amp; graphicsContext, const WebCore::IntRect&amp; dirtyRect)
</del><ins>+void ServicesOverlayController::drawRect(PageOverlay* overlay, GraphicsContext&amp; graphicsContext, const IntRect&amp; dirtyRect)
</ins><span class="cx"> {
</span><del>-    bool mouseIsOverButton;
-    determineActiveHighlight(mouseIsOverButton);
-
-    if (m_activeHighlight)
-        drawHighlight(*m_activeHighlight, graphicsContext);
</del><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> void ServicesOverlayController::clearActiveHighlight()
</span><span class="lines">@@ -357,7 +448,7 @@
</span><span class="cx"> 
</span><span class="cx"> void ServicesOverlayController::buildPhoneNumberHighlights()
</span><span class="cx"> {
</span><del>-    removeAllPotentialHighlightsOfType(Highlight::Type::TelephoneNumber);
</del><ins>+    HashSet&lt;RefPtr&lt;Highlight&gt;&gt; newPotentialHighlights;
</ins><span class="cx"> 
</span><span class="cx">     Frame* mainFrame = m_webPage.mainFrame();
</span><span class="cx">     FrameView&amp; mainFrameView = *mainFrame-&gt;view();
</span><span class="lines">@@ -381,10 +472,32 @@
</span><span class="cx">             CGRect cgRect = rect;
</span><span class="cx">             RetainPtr&lt;DDHighlightRef&gt; ddHighlight = adoptCF(DDHighlightCreateWithRectsInVisibleRectWithStyleAndDirection(nullptr, &amp;cgRect, 1, mainFrameView.visibleContentRect(), DDHighlightOutlineWithArrow, YES, NSWritingDirectionNatural, NO, YES));
</span><span class="cx"> 
</span><del>-            m_potentialHighlights.add(Highlight::createForTelephoneNumber(ddHighlight, range));
</del><ins>+            newPotentialHighlights.add(Highlight::createForTelephoneNumber(*this, ddHighlight, range));
</ins><span class="cx">         }
</span><span class="cx">     }
</span><span class="cx"> 
</span><ins>+    // If any old Highlights are equivalent (by Range) to a new Highlight, reuse the old
+    // one so that any metadata is retained.
+    HashSet&lt;RefPtr&lt;Highlight&gt;&gt; reusedPotentialHighlights;
+
+    for (auto&amp; oldHighlight : m_potentialHighlights) {
+        if (oldHighlight-&gt;type() != Highlight::Type::TelephoneNumber)
+            continue;
+
+        for (auto&amp; 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());
+
</ins><span class="cx">     didRebuildPotentialHighlights();
</span><span class="cx"> }
</span><span class="cx"> 
</span><span class="lines">@@ -402,7 +515,7 @@
</span><span class="cx">         CGRect visibleRect = m_webPage.corePage()-&gt;mainFrame().view()-&gt;visibleContentRect();
</span><span class="cx">         RetainPtr&lt;DDHighlightRef&gt; ddHighlight = adoptCF(DDHighlightCreateWithRectsInVisibleRectWithStyleAndDirection(nullptr, cgRects.begin(), cgRects.size(), visibleRect, DDHighlightNoOutlineWithArrow, YES, NSWritingDirectionNatural, NO, YES));
</span><span class="cx">         
</span><del>-        m_potentialHighlights.add(Highlight::createForSelection(ddHighlight));
</del><ins>+        m_potentialHighlights.add(Highlight::createForSelection(*this, ddHighlight));
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     didRebuildPotentialHighlights();
</span><span class="lines">@@ -425,19 +538,19 @@
</span><span class="cx">         return;
</span><span class="cx"> 
</span><span class="cx">     createOverlayIfNeeded();
</span><ins>+
+    bool mouseIsOverButton;
+    determineActiveHighlight(mouseIsOverButton);
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> void ServicesOverlayController::createOverlayIfNeeded()
</span><span class="cx"> {
</span><del>-    if (m_servicesOverlay) {
-        m_servicesOverlay-&gt;setNeedsDisplay();
</del><ins>+    if (m_servicesOverlay)
</ins><span class="cx">         return;
</span><del>-    }
</del><span class="cx"> 
</span><span class="cx">     RefPtr&lt;PageOverlay&gt; overlay = PageOverlay::create(this, PageOverlay::OverlayType::Document);
</span><span class="cx">     m_servicesOverlay = overlay.get();
</span><span class="cx">     m_webPage.installPageOverlay(overlay.release(), PageOverlay::FadeMode::DoNotFade);
</span><del>-    m_servicesOverlay-&gt;setNeedsDisplay();
</del><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> Vector&lt;RefPtr&lt;Range&gt;&gt; ServicesOverlayController::telephoneNumberRangesForFocusedFrame()
</span><span class="lines">@@ -467,13 +580,13 @@
</span><span class="cx"> {
</span><span class="cx">     mouseIsOverActiveHighlightButton = false;
</span><span class="cx"> 
</span><del>-    RefPtr&lt;Highlight&gt; oldActiveHighlight = m_activeHighlight.release();
</del><ins>+    RefPtr&lt;Highlight&gt; newActiveHighlight;
</ins><span class="cx"> 
</span><span class="cx">     for (auto&amp; highlight : m_potentialHighlights) {
</span><span class="cx">         if (highlight-&gt;type() == Highlight::Type::Selection) {
</span><span class="cx">             // If we've already found a new active highlight, and it's
</span><span class="cx">             // a telephone number highlight, prefer that over this selection highlight.
</span><del>-            if (m_activeHighlight &amp;&amp; m_activeHighlight-&gt;type() == Highlight::Type::TelephoneNumber)
</del><ins>+            if (newActiveHighlight &amp;&amp; newActiveHighlight-&gt;type() == Highlight::Type::TelephoneNumber)
</ins><span class="cx">                 continue;
</span><span class="cx"> 
</span><span class="cx">             // If this highlight has no compatible services, it can't be active, unless we have telephone number highlights to show in the combined menu.
</span><span class="lines">@@ -486,14 +599,38 @@
</span><span class="cx">         if (!mouseIsOverHighlight(*highlight, mouseIsOverButton))
</span><span class="cx">             continue;
</span><span class="cx"> 
</span><del>-        m_activeHighlight = highlight;
</del><ins>+        newActiveHighlight = highlight;
</ins><span class="cx">         mouseIsOverActiveHighlightButton = mouseIsOverButton;
</span><span class="cx">     }
</span><span class="cx"> 
</span><del>-    if (!highlightsAreEquivalent(oldActiveHighlight.get(), m_activeHighlight.get())) {
-        m_lastActiveHighlightChangeTime = std::chrono::steady_clock::now();
-        m_servicesOverlay-&gt;setNeedsDisplay();
</del><ins>+    if (!this-&gt;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();
+        }
+
</ins><span class="cx">         m_currentMouseDownOnButtonHighlight = nullptr;
</span><ins>+
+        if (m_activeHighlight) {
+            m_activeHighlight-&gt;fadeOut();
+            m_activeHighlight = nullptr;
+        }
+
+        auto remainingTimeUntilHighlightShouldBeShown = this-&gt;remainingTimeUntilHighlightShouldBeShown(newActiveHighlight.get());
+        if (remainingTimeUntilHighlightShouldBeShown &gt; std::chrono::steady_clock::duration::zero()) {
+            m_determineActiveHighlightTimer.startOneShot(remainingTimeUntilHighlightShouldBeShown);
+            return;
+        }
+
+        m_activeHighlight = m_nextActiveHighlight.release();
+
+        if (m_activeHighlight) {
+            m_servicesOverlay-&gt;layer()-&gt;addChild(m_activeHighlight-&gt;layer());
+            m_activeHighlight-&gt;fadeIn();
+        }
</ins><span class="cx">     }
</span><span class="cx"> }
</span><span class="cx"> 
</span><span class="lines">@@ -515,7 +652,7 @@
</span><span class="cx">         RefPtr&lt;Highlight&gt; mouseDownHighlight = m_currentMouseDownOnButtonHighlight;
</span><span class="cx">         m_currentMouseDownOnButtonHighlight = nullptr;
</span><span class="cx"> 
</span><del>-        if (mouseIsOverActiveHighlightButton &amp;&amp; mouseDownHighlight &amp;&amp; remainingTimeUntilHighlightShouldBeShown() &lt;= std::chrono::steady_clock::duration::zero()) {
</del><ins>+        if (mouseIsOverActiveHighlightButton &amp;&amp; mouseDownHighlight) {
</ins><span class="cx">             handleClick(m_mousePosition, *mouseDownHighlight);
</span><span class="cx">             return true;
</span><span class="cx">         }
</span><span class="lines">@@ -536,7 +673,6 @@
</span><span class="cx">     if (event.type() == WebEvent::MouseDown) {
</span><span class="cx">         if (m_activeHighlight &amp;&amp; mouseIsOverActiveHighlightButton) {
</span><span class="cx">             m_currentMouseDownOnButtonHighlight = m_activeHighlight;
</span><del>-            m_servicesOverlay-&gt;setNeedsDisplay();
</del><span class="cx">             return true;
</span><span class="cx">         }
</span><span class="cx"> 
</span><span class="lines">@@ -546,7 +682,7 @@
</span><span class="cx">     return false;
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-void ServicesOverlayController::handleClick(const WebCore::IntPoint&amp; clickPoint, Highlight&amp; highlight)
</del><ins>+void ServicesOverlayController::handleClick(const IntPoint&amp; clickPoint, Highlight&amp; highlight)
</ins><span class="cx"> {
</span><span class="cx">     FrameView* frameView = m_webPage.mainFrameView();
</span><span class="cx">     if (!frameView)
</span><span class="lines">@@ -566,6 +702,18 @@
</span><span class="cx">         m_webPage.handleTelephoneNumberClick(highlight.range()-&gt;text(), windowPoint);
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+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);
+}
+
</ins><span class="cx"> } // namespace WebKit
</span><span class="cx"> 
</span><span class="cx"> #endif // #if ENABLE(SERVICE_CONTROLS) || ENABLE(TELEPHONE_NUMBER_DETECTION) &amp;&amp; PLATFORM(MAC)
</span></span></pre>
</div>
</div>

</body>
</html>