<!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>[172555] branches/safari-600.1-branch/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/172555">172555</a></dd>
<dt>Author</dt> <dd>lforschler@apple.com</dd>
<dt>Date</dt> <dd>2014-08-13 20:43:43 -0700 (Wed, 13 Aug 2014)</dd>
</dl>
<h3>Log Message</h3>
<pre>Merged <a href="http://trac.webkit.org/projects/webkit/changeset/172483">r172483</a>. r<rdar://problem/17935736></pre>
<h3>Modified Paths</h3>
<ul>
<li><a href="#branchessafari6001branchSourceWebKit2ChangeLog">branches/safari-600.1-branch/Source/WebKit2/ChangeLog</a></li>
<li><a href="#branchessafari6001branchSourceWebKit2WebProcessWebPagePageOverlaycpp">branches/safari-600.1-branch/Source/WebKit2/WebProcess/WebPage/PageOverlay.cpp</a></li>
<li><a href="#branchessafari6001branchSourceWebKit2WebProcessWebPagePageOverlayh">branches/safari-600.1-branch/Source/WebKit2/WebProcess/WebPage/PageOverlay.h</a></li>
<li><a href="#branchessafari6001branchSourceWebKit2WebProcessWebPagePageOverlayControllercpp">branches/safari-600.1-branch/Source/WebKit2/WebProcess/WebPage/PageOverlayController.cpp</a></li>
<li><a href="#branchessafari6001branchSourceWebKit2WebProcessWebPagePageOverlayControllerh">branches/safari-600.1-branch/Source/WebKit2/WebProcess/WebPage/PageOverlayController.h</a></li>
<li><a href="#branchessafari6001branchSourceWebKit2WebProcessWebPageServicesOverlayControllerh">branches/safari-600.1-branch/Source/WebKit2/WebProcess/WebPage/ServicesOverlayController.h</a></li>
<li><a href="#branchessafari6001branchSourceWebKit2WebProcessWebPagemacServicesOverlayControllermm">branches/safari-600.1-branch/Source/WebKit2/WebProcess/WebPage/mac/ServicesOverlayController.mm</a></li>
</ul>
</div>
<div id="patch">
<h3>Diff</h3>
<a id="branchessafari6001branchSourceWebKit2ChangeLog"></a>
<div class="modfile"><h4>Modified: branches/safari-600.1-branch/Source/WebKit2/ChangeLog (172554 => 172555)</h4>
<pre class="diff"><span>
<span class="info">--- branches/safari-600.1-branch/Source/WebKit2/ChangeLog        2014-08-14 03:41:10 UTC (rev 172554)
+++ branches/safari-600.1-branch/Source/WebKit2/ChangeLog        2014-08-14 03:43:43 UTC (rev 172555)
</span><span class="lines">@@ -1,5 +1,108 @@
</span><span class="cx"> 2014-08-13 Lucas Forschler <lforschler@apple.com>
</span><span class="cx">
</span><ins>+ Merge r172483
+
+ 2014-08-12 Tim Horton <timothy_horton@apple.com>
+
+ 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-13 Lucas Forschler <lforschler@apple.com>
+
</ins><span class="cx"> Merge r172395
</span><span class="cx">
</span><span class="cx"> 2014-08-08 Enrica Casucci <enrica@apple.com>
</span></span></pre></div>
<a id="branchessafari6001branchSourceWebKit2WebProcessWebPagePageOverlaycpp"></a>
<div class="modfile"><h4>Modified: branches/safari-600.1-branch/Source/WebKit2/WebProcess/WebPage/PageOverlay.cpp (172554 => 172555)</h4>
<pre class="diff"><span>
<span class="info">--- branches/safari-600.1-branch/Source/WebKit2/WebProcess/WebPage/PageOverlay.cpp        2014-08-14 03:41:10 UTC (rev 172554)
+++ branches/safari-600.1-branch/Source/WebKit2/WebProcess/WebPage/PageOverlay.cpp        2014-08-14 03:43:43 UTC (rev 172555)
</span><span class="lines">@@ -233,4 +233,9 @@
</span><span class="cx"> m_webPage->pageOverlayController().clearPageOverlay(*this);
</span><span class="cx"> }
</span><span class="cx">
</span><ins>+WebCore::GraphicsLayer* PageOverlay::layer()
+{
+ return m_webPage->pageOverlayController().layerForOverlay(*this);
+}
+
</ins><span class="cx"> } // namespace WebKit
</span></span></pre></div>
<a id="branchessafari6001branchSourceWebKit2WebProcessWebPagePageOverlayh"></a>
<div class="modfile"><h4>Modified: branches/safari-600.1-branch/Source/WebKit2/WebProcess/WebPage/PageOverlay.h (172554 => 172555)</h4>
<pre class="diff"><span>
<span class="info">--- branches/safari-600.1-branch/Source/WebKit2/WebProcess/WebPage/PageOverlay.h        2014-08-14 03:41:10 UTC (rev 172554)
+++ branches/safari-600.1-branch/Source/WebKit2/WebProcess/WebPage/PageOverlay.h        2014-08-14 03:43:43 UTC (rev 172555)
</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="branchessafari6001branchSourceWebKit2WebProcessWebPagePageOverlayControllercpp"></a>
<div class="modfile"><h4>Modified: branches/safari-600.1-branch/Source/WebKit2/WebProcess/WebPage/PageOverlayController.cpp (172554 => 172555)</h4>
<pre class="diff"><span>
<span class="info">--- branches/safari-600.1-branch/Source/WebKit2/WebProcess/WebPage/PageOverlayController.cpp        2014-08-14 03:41:10 UTC (rev 172554)
+++ branches/safari-600.1-branch/Source/WebKit2/WebProcess/WebPage/PageOverlayController.cpp        2014-08-14 03:43:43 UTC (rev 172555)
</span><span class="lines">@@ -171,6 +171,12 @@
</span><span class="cx"> m_overlayGraphicsLayers.get(&overlay)->setDrawsContent(false);
</span><span class="cx"> }
</span><span class="cx">
</span><ins>+GraphicsLayer* PageOverlayController::layerForOverlay(PageOverlay& overlay) const
+{
+ ASSERT(m_pageOverlays.contains(&overlay));
+ return m_overlayGraphicsLayers.get(&overlay);
+}
+
</ins><span class="cx"> void PageOverlayController::didChangeViewSize()
</span><span class="cx"> {
</span><span class="cx"> for (auto& overlayAndLayer : m_overlayGraphicsLayers) {
</span></span></pre></div>
<a id="branchessafari6001branchSourceWebKit2WebProcessWebPagePageOverlayControllerh"></a>
<div class="modfile"><h4>Modified: branches/safari-600.1-branch/Source/WebKit2/WebProcess/WebPage/PageOverlayController.h (172554 => 172555)</h4>
<pre class="diff"><span>
<span class="info">--- branches/safari-600.1-branch/Source/WebKit2/WebProcess/WebPage/PageOverlayController.h        2014-08-14 03:41:10 UTC (rev 172554)
+++ branches/safari-600.1-branch/Source/WebKit2/WebProcess/WebPage/PageOverlayController.h        2014-08-14 03:43:43 UTC (rev 172555)
</span><span class="lines">@@ -57,6 +57,7 @@
</span><span class="cx"> void setPageOverlayNeedsDisplay(PageOverlay&, const WebCore::IntRect&);
</span><span class="cx"> void setPageOverlayOpacity(PageOverlay&, float);
</span><span class="cx"> void clearPageOverlay(PageOverlay&);
</span><ins>+ WebCore::GraphicsLayer* layerForOverlay(PageOverlay&) const;
</ins><span class="cx">
</span><span class="cx"> void didChangeViewSize();
</span><span class="cx"> void didChangeDocumentSize();
</span></span></pre></div>
<a id="branchessafari6001branchSourceWebKit2WebProcessWebPageServicesOverlayControllerh"></a>
<div class="modfile"><h4>Modified: branches/safari-600.1-branch/Source/WebKit2/WebProcess/WebPage/ServicesOverlayController.h (172554 => 172555)</h4>
<pre class="diff"><span>
<span class="info">--- branches/safari-600.1-branch/Source/WebKit2/WebProcess/WebPage/ServicesOverlayController.h        2014-08-14 03:41:10 UTC (rev 172554)
+++ branches/safari-600.1-branch/Source/WebKit2/WebProcess/WebPage/ServicesOverlayController.h        2014-08-14 03:43:43 UTC (rev 172555)
</span><span class="lines">@@ -30,6 +30,7 @@
</span><span class="cx">
</span><span class="cx"> #include "PageOverlay.h"
</span><span class="cx"> #include "WebFrame.h"
</span><ins>+#include <WebCore/GraphicsLayerClient.h>
</ins><span class="cx"> #include <WebCore/Range.h>
</span><span class="cx"> #include <WebCore/Timer.h>
</span><span class="cx"> #include <wtf/RefCounted.h>
</span><span class="lines">@@ -55,14 +56,18 @@
</span><span class="cx"> void selectionRectsDidChange(const Vector<WebCore::LayoutRect>&, const Vector<WebCore::GapRects>&, bool isTextOnly);
</span><span class="cx">
</span><span class="cx"> private:
</span><del>- class Highlight : public RefCounted<Highlight> {
</del><ins>+ class Highlight : public RefCounted<Highlight>, private WebCore::GraphicsLayerClient {
</ins><span class="cx"> WTF_MAKE_NONCOPYABLE(Highlight);
</span><span class="cx"> public:
</span><del>- static PassRefPtr<Highlight> createForSelection(RetainPtr<DDHighlightRef>);
- static PassRefPtr<Highlight> createForTelephoneNumber(RetainPtr<DDHighlightRef>, PassRefPtr<WebCore::Range>);
</del><ins>+ static PassRefPtr<Highlight> createForSelection(ServicesOverlayController&, RetainPtr<DDHighlightRef>);
+ static PassRefPtr<Highlight> createForTelephoneNumber(ServicesOverlayController&, RetainPtr<DDHighlightRef>, PassRefPtr<WebCore::Range>);
+ ~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<DDHighlightRef> ddHighlight, PassRefPtr<WebCore::Range> range)
- : m_ddHighlight(ddHighlight)
- , m_range(range)
- , m_type(type)
- {
- ASSERT(m_ddHighlight);
- ASSERT(type != Type::TelephoneNumber || m_range);
- }
</del><ins>+ explicit Highlight(ServicesOverlayController&, Type, RetainPtr<DDHighlightRef>, PassRefPtr<WebCore::Range>);
</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&, WebCore::GraphicsLayerPaintingPhase, const WebCore::FloatRect& inClip) override;
+ virtual float deviceScaleFactor() const override;
+
+ void didFinishFadeOutAnimation();
+
</ins><span class="cx"> RetainPtr<DDHighlightRef> m_ddHighlight;
</span><span class="cx"> RefPtr<WebCore::Range> m_range;
</span><ins>+ std::unique_ptr<WebCore::GraphicsLayer> 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& 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&, bool& mouseIsOverButton) const;
</span><del>- std::chrono::milliseconds remainingTimeUntilHighlightShouldBeShown() const;
- void repaintHighlightTimerFired(WebCore::Timer<ServicesOverlayController>&);
</del><ins>+ std::chrono::milliseconds remainingTimeUntilHighlightShouldBeShown(Highlight*) const;
+ void determineActiveHighlightTimerFired(WebCore::Timer<ServicesOverlayController>&);
</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<RefPtr<WebCore::Range>> telephoneNumberRangesForFocusedFrame();
</span><span class="cx">
</span><ins>+ void didCreateHighlight(Highlight*);
+ void willDestroyHighlight(Highlight*);
+ void didFinishFadingOutHighlight(Highlight*);
+
+ WebPage& webPage() const { return m_webPage; }
+
</ins><span class="cx"> WebPage& m_webPage;
</span><span class="cx"> PageOverlay* m_servicesOverlay;
</span><span class="cx">
</span><span class="cx"> RefPtr<Highlight> m_activeHighlight;
</span><ins>+ RefPtr<Highlight> m_nextActiveHighlight;
</ins><span class="cx"> HashSet<RefPtr<Highlight>> m_potentialHighlights;
</span><ins>+ HashSet<RefPtr<Highlight>> m_animatingHighlights;
</ins><span class="cx">
</span><ins>+ HashSet<Highlight*> m_highlights;
+
</ins><span class="cx"> Vector<WebCore::LayoutRect> 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<Highlight> m_currentMouseDownOnButtonHighlight;
</span><span class="cx"> WebCore::IntPoint m_mousePosition;
</span><span class="cx">
</span><del>- WebCore::Timer<ServicesOverlayController> m_repaintHighlightTimer;
</del><ins>+ WebCore::Timer<ServicesOverlayController> m_determineActiveHighlightTimer;
</ins><span class="cx"> };
</span><span class="cx">
</span><span class="cx"> } // namespace WebKit
</span></span></pre></div>
<a id="branchessafari6001branchSourceWebKit2WebProcessWebPagemacServicesOverlayControllermm"></a>
<div class="modfile"><h4>Modified: branches/safari-600.1-branch/Source/WebKit2/WebProcess/WebPage/mac/ServicesOverlayController.mm (172554 => 172555)</h4>
<pre class="diff"><span>
<span class="info">--- branches/safari-600.1-branch/Source/WebKit2/WebProcess/WebPage/mac/ServicesOverlayController.mm        2014-08-14 03:41:10 UTC (rev 172554)
+++ branches/safari-600.1-branch/Source/WebKit2/WebProcess/WebPage/mac/ServicesOverlayController.mm        2014-08-14 03:43:43 UTC (rev 172555)
</span><span class="lines">@@ -31,13 +31,17 @@
</span><span class="cx"> #import "Logging.h"
</span><span class="cx"> #import "WebPage.h"
</span><span class="cx"> #import "WebProcess.h"
</span><ins>+#import <QuartzCore/QuartzCore.h>
</ins><span class="cx"> #import <WebCore/Document.h>
</span><span class="cx"> #import <WebCore/FloatQuad.h>
</span><span class="cx"> #import <WebCore/FocusController.h>
</span><span class="cx"> #import <WebCore/FrameView.h>
</span><span class="cx"> #import <WebCore/GapRects.h>
</span><span class="cx"> #import <WebCore/GraphicsContext.h>
</span><ins>+#import <WebCore/GraphicsLayer.h>
+#import <WebCore/GraphicsLayerCA.h>
</ins><span class="cx"> #import <WebCore/MainFrame.h>
</span><ins>+#import <WebCore/PlatformCAAnimationMac.h>
</ins><span class="cx"> #import <WebCore/SoftLinking.h>
</span><span class="cx">
</span><span class="cx"> #if __has_include(<DataDetectors/DDHighlightDrawing.h>)
</span><span class="lines">@@ -50,6 +54,8 @@
</span><span class="cx"> #import <DataDetectors/DDHighlightDrawing_Private.h>
</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 << 16);
</span><span class="cx"> static const DDHighlightStyle DDHighlightOutlineWithArrow = (1 << 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<ServicesOverlayController::Highlight> ServicesOverlayController::Highlight::createForSelection(RetainPtr<DDHighlightRef> ddHighlight)
</del><ins>+PassRefPtr<ServicesOverlayController::Highlight> ServicesOverlayController::Highlight::createForSelection(ServicesOverlayController& controller, RetainPtr<DDHighlightRef> 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<ServicesOverlayController::Highlight> ServicesOverlayController::Highlight::createForTelephoneNumber(RetainPtr<DDHighlightRef> ddHighlight, PassRefPtr<Range> range)
</del><ins>+PassRefPtr<ServicesOverlayController::Highlight> ServicesOverlayController::Highlight::createForTelephoneNumber(ServicesOverlayController& controller, RetainPtr<DDHighlightRef> ddHighlight, PassRefPtr<Range> 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& 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();
+}
+
</ins><span class="cx"> static IntRect textQuadsToBoundingRectForRange(Range& range)
</span><span class="cx"> {
</span><span class="cx"> Vector<FloatQuad> 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, &ServicesOverlayController::repaintHighlightTimerFired)
</del><ins>+ , m_determineActiveHighlightTimer(this, &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& highlight : m_highlights)
+ highlight->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->type() == Highlight::Type::TelephoneNumber)
</del><ins>+ if (highlight->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<std::chrono::milliseconds>(std::max(minimumTimeUntilHighlightShouldBeShown - timeSinceLastSelectionChange, minimumTimeUntilHighlightShouldBeShown - timeSinceHighlightBecameActive));
</span><span class="cx"> }
</span><span class="cx">
</span><del>-void ServicesOverlayController::repaintHighlightTimerFired(WebCore::Timer<ServicesOverlayController>&)
</del><ins>+void ServicesOverlayController::determineActiveHighlightTimerFired(Timer<ServicesOverlayController>&)
</ins><span class="cx"> {
</span><del>- if (m_servicesOverlay)
- m_servicesOverlay->setNeedsDisplay();
-}
-
-void ServicesOverlayController::drawHighlight(Highlight& highlight, WebCore::GraphicsContext& graphicsContext)
-{
</del><span class="cx"> bool mouseIsOverButton;
</span><del>- 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);
</del><ins>+ determineActiveHighlight(mouseIsOverButton);
</ins><span class="cx"> }
</span><span class="cx">
</span><del>-void ServicesOverlayController::drawRect(PageOverlay* overlay, WebCore::GraphicsContext& graphicsContext, const WebCore::IntRect& dirtyRect)
</del><ins>+void ServicesOverlayController::drawRect(PageOverlay* overlay, GraphicsContext& graphicsContext, const IntRect& 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<RefPtr<Highlight>> newPotentialHighlights;
</ins><span class="cx">
</span><span class="cx"> Frame* mainFrame = m_webPage.mainFrame();
</span><span class="cx"> FrameView& mainFrameView = *mainFrame->view();
</span><span class="lines">@@ -381,10 +472,32 @@
</span><span class="cx"> CGRect cgRect = rect;
</span><span class="cx"> RetainPtr<DDHighlightRef> ddHighlight = adoptCF(DDHighlightCreateWithRectsInVisibleRectWithStyleAndDirection(nullptr, &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<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());
+
</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()->mainFrame().view()->visibleContentRect();
</span><span class="cx"> RetainPtr<DDHighlightRef> 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->setNeedsDisplay();
</del><ins>+ if (m_servicesOverlay)
</ins><span class="cx"> return;
</span><del>- }
</del><span class="cx">
</span><span class="cx"> RefPtr<PageOverlay> 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->setNeedsDisplay();
</del><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> Vector<RefPtr<Range>> 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<Highlight> oldActiveHighlight = m_activeHighlight.release();
</del><ins>+ RefPtr<Highlight> newActiveHighlight;
</ins><span class="cx">
</span><span class="cx"> for (auto& highlight : m_potentialHighlights) {
</span><span class="cx"> if (highlight->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 && m_activeHighlight->type() == Highlight::Type::TelephoneNumber)
</del><ins>+ if (newActiveHighlight && newActiveHighlight->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->setNeedsDisplay();
</del><ins>+ 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();
+ }
+
</ins><span class="cx"> m_currentMouseDownOnButtonHighlight = nullptr;
</span><ins>+
+ 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();
+ }
</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<Highlight> mouseDownHighlight = m_currentMouseDownOnButtonHighlight;
</span><span class="cx"> m_currentMouseDownOnButtonHighlight = nullptr;
</span><span class="cx">
</span><del>- if (mouseIsOverActiveHighlightButton && mouseDownHighlight && remainingTimeUntilHighlightShouldBeShown() <= std::chrono::steady_clock::duration::zero()) {
</del><ins>+ if (mouseIsOverActiveHighlightButton && 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 && mouseIsOverActiveHighlightButton) {
</span><span class="cx"> m_currentMouseDownOnButtonHighlight = m_activeHighlight;
</span><del>- m_servicesOverlay->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& clickPoint, Highlight& highlight)
</del><ins>+void ServicesOverlayController::handleClick(const IntPoint& clickPoint, Highlight& 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()->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) && PLATFORM(MAC)
</span></span></pre>
</div>
</div>
</body>
</html>