From 193f3d584c34a250d1cdf8577a405120a016853f Mon Sep 17 00:00:00 2001 From: "timothy_horton@apple.com" Date: Tue, 12 Aug 2014 19:31:04 +0000 Subject: [PATCH] Add a fade transition to services highlights https://bugs.webkit.org/show_bug.cgi?id=135829 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. git-svn-id: https://svn.webkit.org/repository/webkit/trunk@172483 268f45cc-cd09-0410-ab3c-d52691b4dbfc --- Source/WebKit2/ChangeLog | 99 +++++++ .../WebProcess/WebPage/PageOverlay.cpp | 5 + .../WebKit2/WebProcess/WebPage/PageOverlay.h | 3 + .../WebPage/PageOverlayController.cpp | 6 + .../WebPage/PageOverlayController.h | 1 + .../WebPage/ServicesOverlayController.h | 52 +++- .../WebPage/mac/ServicesOverlayController.mm | 256 ++++++++++++++---- 7 files changed, 353 insertions(+), 69 deletions(-) diff --git a/Source/WebKit2/ChangeLog b/Source/WebKit2/ChangeLog index 49b83499b2e4..6ff0b28baa77 100644 --- a/Source/WebKit2/ChangeLog +++ b/Source/WebKit2/ChangeLog @@ -1,3 +1,102 @@ +2014-08-12 Tim Horton + + Add a fade transition to services highlights + https://bugs.webkit.org/show_bug.cgi?id=135829 + + + Reviewed by Enrica Casucci. + + Add a smooth fade to highlight installation and uninstallation. + To do so, we make each highlight paint into its own small layer. + + * WebProcess/WebPage/PageOverlay.cpp: + (WebKit::PageOverlay::layer): + * WebProcess/WebPage/PageOverlay.h: + * WebProcess/WebPage/PageOverlayController.cpp: + (WebKit::PageOverlayController::layerForOverlay): + * WebProcess/WebPage/PageOverlayController.h: + Expose the GraphicsLayer on PageOverlay. + + * WebProcess/WebPage/ServicesOverlayController.h: + (WebKit::ServicesOverlayController::Highlight::layer): + (WebKit::ServicesOverlayController::activeHighlight): + (WebKit::ServicesOverlayController::webPage): + (WebKit::ServicesOverlayController::Highlight::Highlight): Deleted. + + * WebProcess/WebPage/mac/ServicesOverlayController.mm: + (WebKit::ServicesOverlayController::Highlight::createForSelection): + (WebKit::ServicesOverlayController::Highlight::createForTelephoneNumber): + (WebKit::ServicesOverlayController::Highlight::Highlight): + Highlights now own a GraphicsLayer, which are later installed + as sublayers of the ServicesOverlayController's PageOverlay layer. + These layers are sized and positioned according to the DDHighlight's bounds. + + (WebKit::ServicesOverlayController::Highlight::~Highlight): + (WebKit::ServicesOverlayController::Highlight::invalidate): + ServicesOverlayController will invalidate any remaining highlights + when it is torn down, so they can clear their backpointers. + + (WebKit::ServicesOverlayController::Highlight::notifyFlushRequired): + Forward flush notifications to the DrawingArea. + + (WebKit::ServicesOverlayController::Highlight::paintContents): + Paint the DDHighlight into the layer. Translation is done by the layer position, + so we zero the bounds origin when painting. + + (WebKit::ServicesOverlayController::Highlight::deviceScaleFactor): + Forward the deviceScaleFactor so that things are painted at the right scale. + + (WebKit::ServicesOverlayController::Highlight::fadeIn): + (WebKit::ServicesOverlayController::Highlight::fadeOut): + Apply a fade animation to the layer. + + (WebKit::ServicesOverlayController::Highlight::didFinishFadeOutAnimation): + When the fade completes, unparent the layer, unless it has become active again. + + (WebKit::ServicesOverlayController::ServicesOverlayController): + (WebKit::ServicesOverlayController::~ServicesOverlayController): + Invalidate all highlights, so they can clear their backpointers. + + (WebKit::ServicesOverlayController::remainingTimeUntilHighlightShouldBeShown): + Make remainingTimeUntilHighlightShouldBeShown act upon a particular highlight + instead of always the active highlight. + + (WebKit::ServicesOverlayController::determineActiveHighlightTimerFired): Rename. + + (WebKit::ServicesOverlayController::drawRect): + drawRect is no longer called and will no longer do anything; all of the + painting is done in sublayers. + + (WebKit::ServicesOverlayController::buildPhoneNumberHighlights): + Ensure that phone number Highlights stay stable even while the selection + changes, by comparing the underlying Ranges and keeping around old Highlights + that match the new ones. This enables us to e.g. fade in while changing + the selection within a phone number. + + (WebKit::ServicesOverlayController::buildSelectionHighlight): + (WebKit::ServicesOverlayController::didRebuildPotentialHighlights): + (WebKit::ServicesOverlayController::createOverlayIfNeeded): + Don't call setNeedsDisplay; the overlay doesn't have backing store. + Instead, call determineActiveHighlight, which will install/uninstall + highlights as necessary. + + (WebKit::ServicesOverlayController::determineActiveHighlight): + Apply fade in/fade out to the overlays. + Keep track of which highlight we're going to activate, until the hysteresis + delay is up, then actually make it active/parent it/fade it in. + We now will have no active highlight between the fade out of the previous one + and the fade in of the new one (during the hysteresis delay). + + (WebKit::ServicesOverlayController::mouseEvent): + The overlay now will not become active until the delay is up, so we don't + need to check it again here. + + (WebKit::ServicesOverlayController::handleClick): + (WebKit::ServicesOverlayController::didCreateHighlight): + (WebKit::ServicesOverlayController::willDestroyHighlight): + (WebKit::ServicesOverlayController::repaintHighlightTimerFired): Deleted. + (WebKit::ServicesOverlayController::drawHighlight): Deleted. + 2014-08-11 Andy Estes [iOS] Get rid of iOS.xcconfig diff --git a/Source/WebKit2/WebProcess/WebPage/PageOverlay.cpp b/Source/WebKit2/WebProcess/WebPage/PageOverlay.cpp index 9f96003e840e..588a3549bf91 100644 --- a/Source/WebKit2/WebProcess/WebPage/PageOverlay.cpp +++ b/Source/WebKit2/WebProcess/WebPage/PageOverlay.cpp @@ -233,4 +233,9 @@ void PageOverlay::clear() m_webPage->pageOverlayController().clearPageOverlay(*this); } +WebCore::GraphicsLayer* PageOverlay::layer() +{ + return m_webPage->pageOverlayController().layerForOverlay(*this); +} + } // namespace WebKit diff --git a/Source/WebKit2/WebProcess/WebPage/PageOverlay.h b/Source/WebKit2/WebProcess/WebPage/PageOverlay.h index e5082ad4d869..8025edf87a88 100644 --- a/Source/WebKit2/WebProcess/WebPage/PageOverlay.h +++ b/Source/WebKit2/WebProcess/WebPage/PageOverlay.h @@ -35,6 +35,7 @@ namespace WebCore { class GraphicsContext; +class GraphicsLayer; } namespace WebKit { @@ -95,6 +96,8 @@ public: WebCore::RGBA32 backgroundColor() const { return m_backgroundColor; } void setBackgroundColor(WebCore::RGBA32); + + WebCore::GraphicsLayer* layer(); protected: explicit PageOverlay(Client*, OverlayType); diff --git a/Source/WebKit2/WebProcess/WebPage/PageOverlayController.cpp b/Source/WebKit2/WebProcess/WebPage/PageOverlayController.cpp index 4f7b18aaade4..eb666b902a43 100644 --- a/Source/WebKit2/WebProcess/WebPage/PageOverlayController.cpp +++ b/Source/WebKit2/WebProcess/WebPage/PageOverlayController.cpp @@ -171,6 +171,12 @@ void PageOverlayController::clearPageOverlay(PageOverlay& overlay) m_overlayGraphicsLayers.get(&overlay)->setDrawsContent(false); } +GraphicsLayer* PageOverlayController::layerForOverlay(PageOverlay& overlay) const +{ + ASSERT(m_pageOverlays.contains(&overlay)); + return m_overlayGraphicsLayers.get(&overlay); +} + void PageOverlayController::didChangeViewSize() { for (auto& overlayAndLayer : m_overlayGraphicsLayers) { diff --git a/Source/WebKit2/WebProcess/WebPage/PageOverlayController.h b/Source/WebKit2/WebProcess/WebPage/PageOverlayController.h index 4f1689b85873..8d1b5cdce266 100644 --- a/Source/WebKit2/WebProcess/WebPage/PageOverlayController.h +++ b/Source/WebKit2/WebProcess/WebPage/PageOverlayController.h @@ -57,6 +57,7 @@ public: void setPageOverlayNeedsDisplay(PageOverlay&, const WebCore::IntRect&); void setPageOverlayOpacity(PageOverlay&, float); void clearPageOverlay(PageOverlay&); + WebCore::GraphicsLayer* layerForOverlay(PageOverlay&) const; void didChangeViewSize(); void didChangeDocumentSize(); diff --git a/Source/WebKit2/WebProcess/WebPage/ServicesOverlayController.h b/Source/WebKit2/WebProcess/WebPage/ServicesOverlayController.h index 1b73f76d2b9d..00ec39942e0c 100644 --- a/Source/WebKit2/WebProcess/WebPage/ServicesOverlayController.h +++ b/Source/WebKit2/WebProcess/WebPage/ServicesOverlayController.h @@ -30,6 +30,7 @@ #include "PageOverlay.h" #include "WebFrame.h" +#include #include #include #include @@ -55,14 +56,18 @@ public: void selectionRectsDidChange(const Vector&, const Vector&, bool isTextOnly); private: - class Highlight : public RefCounted { + class Highlight : public RefCounted, private WebCore::GraphicsLayerClient { WTF_MAKE_NONCOPYABLE(Highlight); public: - static PassRefPtr createForSelection(RetainPtr); - static PassRefPtr createForTelephoneNumber(RetainPtr, PassRefPtr); + static PassRefPtr createForSelection(ServicesOverlayController&, RetainPtr); + static PassRefPtr createForTelephoneNumber(ServicesOverlayController&, RetainPtr, PassRefPtr); + ~Highlight(); + + void invalidate(); DDHighlightRef ddHighlight() const { return m_ddHighlight.get(); } WebCore::Range* range() const { return m_range.get(); } + WebCore::GraphicsLayer* layer() const { return m_graphicsLayer.get(); } enum class Type { TelephoneNumber, @@ -70,19 +75,25 @@ private: }; Type type() const { return m_type; } + void fadeIn(); + void fadeOut(); + private: - explicit Highlight(Type type, RetainPtr ddHighlight, PassRefPtr range) - : m_ddHighlight(ddHighlight) - , m_range(range) - , m_type(type) - { - ASSERT(m_ddHighlight); - ASSERT(type != Type::TelephoneNumber || m_range); - } + explicit Highlight(ServicesOverlayController&, Type, RetainPtr, PassRefPtr); + + // GraphicsLayerClient + virtual void notifyAnimationStarted(const WebCore::GraphicsLayer*, double time) override { } + virtual void notifyFlushRequired(const WebCore::GraphicsLayer*) override; + virtual void paintContents(const WebCore::GraphicsLayer*, WebCore::GraphicsContext&, WebCore::GraphicsLayerPaintingPhase, const WebCore::FloatRect& inClip) override; + virtual float deviceScaleFactor() const override; + + void didFinishFadeOutAnimation(); RetainPtr m_ddHighlight; RefPtr m_range; + std::unique_ptr m_graphicsLayer; Type m_type; + ServicesOverlayController* m_controller; }; // PageOverlay::Client @@ -104,33 +115,44 @@ private: void determineActiveHighlight(bool& mouseIsOverButton); void clearActiveHighlight(); + Highlight* activeHighlight() const { return m_activeHighlight.get(); } bool hasRelevantSelectionServices(); bool mouseIsOverHighlight(Highlight&, bool& mouseIsOverButton) const; - std::chrono::milliseconds remainingTimeUntilHighlightShouldBeShown() const; - void repaintHighlightTimerFired(WebCore::Timer&); + std::chrono::milliseconds remainingTimeUntilHighlightShouldBeShown(Highlight*) const; + void determineActiveHighlightTimerFired(WebCore::Timer&); static bool highlightsAreEquivalent(const Highlight* a, const Highlight* b); Vector> telephoneNumberRangesForFocusedFrame(); + void didCreateHighlight(Highlight*); + void willDestroyHighlight(Highlight*); + void didFinishFadingOutHighlight(Highlight*); + + WebPage& webPage() const { return m_webPage; } + WebPage& m_webPage; PageOverlay* m_servicesOverlay; RefPtr m_activeHighlight; + RefPtr m_nextActiveHighlight; HashSet> m_potentialHighlights; + HashSet> m_animatingHighlights; + + HashSet m_highlights; Vector m_currentSelectionRects; bool m_isTextOnly; std::chrono::steady_clock::time_point m_lastSelectionChangeTime; - std::chrono::steady_clock::time_point m_lastActiveHighlightChangeTime; + std::chrono::steady_clock::time_point m_nextActiveHighlightChangeTime; RefPtr m_currentMouseDownOnButtonHighlight; WebCore::IntPoint m_mousePosition; - WebCore::Timer m_repaintHighlightTimer; + WebCore::Timer m_determineActiveHighlightTimer; }; } // namespace WebKit diff --git a/Source/WebKit2/WebProcess/WebPage/mac/ServicesOverlayController.mm b/Source/WebKit2/WebProcess/WebPage/mac/ServicesOverlayController.mm index 4d8a8c0a037f..6004b8ad4f80 100644 --- a/Source/WebKit2/WebProcess/WebPage/mac/ServicesOverlayController.mm +++ b/Source/WebKit2/WebProcess/WebPage/mac/ServicesOverlayController.mm @@ -31,13 +31,17 @@ #import "Logging.h" #import "WebPage.h" #import "WebProcess.h" +#import #import #import #import #import #import #import +#import +#import #import +#import #import #if __has_include() @@ -50,6 +54,8 @@ typedef void* DDHighlightRef; #import #endif +const float highlightFadeAnimationDuration = 0.3; + typedef NSUInteger DDHighlightStyle; static const DDHighlightStyle DDHighlightNoOutlineWithArrow = (1 << 16); static const DDHighlightStyle DDHighlightOutlineWithArrow = (1 << 16) | 1; @@ -64,14 +70,121 @@ using namespace WebCore; namespace WebKit { -PassRefPtr ServicesOverlayController::Highlight::createForSelection(RetainPtr ddHighlight) +PassRefPtr ServicesOverlayController::Highlight::createForSelection(ServicesOverlayController& controller, RetainPtr ddHighlight) +{ + return adoptRef(new Highlight(controller, Type::Selection, ddHighlight, nullptr)); +} + +PassRefPtr ServicesOverlayController::Highlight::createForTelephoneNumber(ServicesOverlayController& controller, RetainPtr ddHighlight, PassRefPtr range) +{ + return adoptRef(new Highlight(controller, Type::TelephoneNumber, ddHighlight, range)); +} + +ServicesOverlayController::Highlight::Highlight(ServicesOverlayController& controller, Type type, RetainPtr ddHighlight, PassRefPtr 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() { - return adoptRef(new Highlight(Type::Selection, ddHighlight, nullptr)); + layer()->removeFromParent(); + m_controller = nullptr; } -PassRefPtr ServicesOverlayController::Highlight::createForTelephoneNumber(RetainPtr ddHighlight, PassRefPtr range) +void ServicesOverlayController::Highlight::notifyFlushRequired(const GraphicsLayer*) { - return adoptRef(new Highlight(Type::TelephoneNumber, ddHighlight, range)); + 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 animation = [CABasicAnimation animationWithKeyPath:@"opacity"]; + [animation setDuration:highlightFadeAnimationDuration]; + [animation setFillMode:kCAFillModeForwards]; + [animation setRemovedOnCompletion:false]; + [animation setToValue:@1]; + + RefPtr platformAnimation = PlatformCAAnimationMac::create(animation.get()); + toGraphicsLayerCA(layer())->platformCALayer()->addAnimationForKey("FadeHighlightIn", platformAnimation.get()); +} + +void ServicesOverlayController::Highlight::fadeOut() +{ + RetainPtr animation = [CABasicAnimation animationWithKeyPath:@"opacity"]; + [animation setDuration:highlightFadeAnimationDuration]; + [animation setFillMode:kCAFillModeForwards]; + [animation setRemovedOnCompletion:false]; + [animation setToValue:@0]; + + RefPtr retainedSelf = this; + [CATransaction begin]; + [CATransaction setCompletionBlock:[retainedSelf] () { + retainedSelf->didFinishFadeOutAnimation(); + }]; + + RefPtr platformAnimation = PlatformCAAnimationMac::create(animation.get()); + toGraphicsLayerCA(layer())->platformCALayer()->addAnimationForKey("FadeHighlightOut", platformAnimation.get()); + [CATransaction commit]; +} + +void ServicesOverlayController::Highlight::didFinishFadeOutAnimation() +{ + if (!m_controller) + return; + + if (m_controller->activeHighlight() == this) + return; + + layer()->removeFromParent(); } static IntRect textQuadsToBoundingRectForRange(Range& range) @@ -88,12 +201,14 @@ ServicesOverlayController::ServicesOverlayController(WebPage& webPage) : m_webPage(webPage) , m_servicesOverlay(nullptr) , m_isTextOnly(false) - , m_repaintHighlightTimer(this, &ServicesOverlayController::repaintHighlightTimerFired) + , m_determineActiveHighlightTimer(this, &ServicesOverlayController::determineActiveHighlightTimerFired) { } ServicesOverlayController::~ServicesOverlayController() { + for (auto& highlight : m_highlights) + highlight->invalidate(); } void ServicesOverlayController::pageOverlayDestroyed(PageOverlay*) @@ -280,57 +395,33 @@ bool ServicesOverlayController::mouseIsOverHighlight(Highlight& highlight, bool& return hovered; } -std::chrono::milliseconds ServicesOverlayController::remainingTimeUntilHighlightShouldBeShown() const +std::chrono::milliseconds ServicesOverlayController::remainingTimeUntilHighlightShouldBeShown(Highlight* highlight) const { + if (!highlight) + return std::chrono::milliseconds::zero(); + // Highlight hysteresis is only for selection services, because telephone number highlights are already much more stable // by virtue of being expanded to include the entire telephone number. - if (m_activeHighlight->type() == Highlight::Type::TelephoneNumber) + if (highlight->type() == Highlight::Type::TelephoneNumber) return std::chrono::milliseconds::zero(); std::chrono::steady_clock::duration minimumTimeUntilHighlightShouldBeShown = 200_ms; auto now = std::chrono::steady_clock::now(); auto timeSinceLastSelectionChange = now - m_lastSelectionChangeTime; - auto timeSinceHighlightBecameActive = now - m_lastActiveHighlightChangeTime; + auto timeSinceHighlightBecameActive = now - m_nextActiveHighlightChangeTime; return std::chrono::duration_cast(std::max(minimumTimeUntilHighlightShouldBeShown - timeSinceLastSelectionChange, minimumTimeUntilHighlightShouldBeShown - timeSinceHighlightBecameActive)); } -void ServicesOverlayController::repaintHighlightTimerFired(WebCore::Timer&) -{ - if (m_servicesOverlay) - m_servicesOverlay->setNeedsDisplay(); -} - -void ServicesOverlayController::drawHighlight(Highlight& highlight, WebCore::GraphicsContext& graphicsContext) +void ServicesOverlayController::determineActiveHighlightTimerFired(Timer&) { bool mouseIsOverButton; - if (!mouseIsOverHighlight(highlight, mouseIsOverButton)) { - LOG(Services, "ServicesOverlayController::drawHighlight - Mouse is not over highlight, so drawing nothing"); - return; - } - - auto remainingTimeUntilHighlightShouldBeShown = this->remainingTimeUntilHighlightShouldBeShown(); - if (remainingTimeUntilHighlightShouldBeShown > std::chrono::steady_clock::duration::zero()) { - m_repaintHighlightTimer.startOneShot(remainingTimeUntilHighlightShouldBeShown); - return; - } - - CGContextRef cgContext = graphicsContext.platformContext(); - - CGLayerRef highlightLayer = DDHighlightGetLayerWithContext(highlight.ddHighlight(), cgContext); - CGRect highlightBoundingRect = DDHighlightGetBoundingRect(highlight.ddHighlight()); - - CGContextDrawLayerInRect(cgContext, highlightBoundingRect, highlightLayer); + determineActiveHighlight(mouseIsOverButton); } -void ServicesOverlayController::drawRect(PageOverlay* overlay, WebCore::GraphicsContext& graphicsContext, const WebCore::IntRect& dirtyRect) +void ServicesOverlayController::drawRect(PageOverlay* overlay, GraphicsContext& graphicsContext, const IntRect& dirtyRect) { - bool mouseIsOverButton; - determineActiveHighlight(mouseIsOverButton); - - if (m_activeHighlight) - drawHighlight(*m_activeHighlight, graphicsContext); } void ServicesOverlayController::clearActiveHighlight() @@ -357,7 +448,7 @@ void ServicesOverlayController::removeAllPotentialHighlightsOfType(Highlight::Ty void ServicesOverlayController::buildPhoneNumberHighlights() { - removeAllPotentialHighlightsOfType(Highlight::Type::TelephoneNumber); + HashSet> newPotentialHighlights; Frame* mainFrame = m_webPage.mainFrame(); FrameView& mainFrameView = *mainFrame->view(); @@ -381,10 +472,32 @@ void ServicesOverlayController::buildPhoneNumberHighlights() CGRect cgRect = rect; RetainPtr ddHighlight = adoptCF(DDHighlightCreateWithRectsInVisibleRectWithStyleAndDirection(nullptr, &cgRect, 1, mainFrameView.visibleContentRect(), DDHighlightOutlineWithArrow, YES, NSWritingDirectionNatural, NO, YES)); - m_potentialHighlights.add(Highlight::createForTelephoneNumber(ddHighlight, range)); + newPotentialHighlights.add(Highlight::createForTelephoneNumber(*this, ddHighlight, range)); + } + } + + // If any old Highlights are equivalent (by Range) to a new Highlight, reuse the old + // one so that any metadata is retained. + HashSet> reusedPotentialHighlights; + + for (auto& oldHighlight : m_potentialHighlights) { + if (oldHighlight->type() != Highlight::Type::TelephoneNumber) + continue; + + for (auto& newHighlight : newPotentialHighlights) { + if (highlightsAreEquivalent(oldHighlight.get(), newHighlight.get())) { + reusedPotentialHighlights.add(oldHighlight); + newPotentialHighlights.remove(newHighlight); + break; + } } } + removeAllPotentialHighlightsOfType(Highlight::Type::TelephoneNumber); + + m_potentialHighlights.add(newPotentialHighlights.begin(), newPotentialHighlights.end()); + m_potentialHighlights.add(reusedPotentialHighlights.begin(), reusedPotentialHighlights.end()); + didRebuildPotentialHighlights(); } @@ -402,7 +515,7 @@ void ServicesOverlayController::buildSelectionHighlight() CGRect visibleRect = m_webPage.corePage()->mainFrame().view()->visibleContentRect(); RetainPtr ddHighlight = adoptCF(DDHighlightCreateWithRectsInVisibleRectWithStyleAndDirection(nullptr, cgRects.begin(), cgRects.size(), visibleRect, DDHighlightNoOutlineWithArrow, YES, NSWritingDirectionNatural, NO, YES)); - m_potentialHighlights.add(Highlight::createForSelection(ddHighlight)); + m_potentialHighlights.add(Highlight::createForSelection(*this, ddHighlight)); } didRebuildPotentialHighlights(); @@ -425,19 +538,19 @@ void ServicesOverlayController::didRebuildPotentialHighlights() return; createOverlayIfNeeded(); + + bool mouseIsOverButton; + determineActiveHighlight(mouseIsOverButton); } void ServicesOverlayController::createOverlayIfNeeded() { - if (m_servicesOverlay) { - m_servicesOverlay->setNeedsDisplay(); + if (m_servicesOverlay) return; - } RefPtr overlay = PageOverlay::create(this, PageOverlay::OverlayType::Document); m_servicesOverlay = overlay.get(); m_webPage.installPageOverlay(overlay.release(), PageOverlay::FadeMode::DoNotFade); - m_servicesOverlay->setNeedsDisplay(); } Vector> ServicesOverlayController::telephoneNumberRangesForFocusedFrame() @@ -467,13 +580,13 @@ void ServicesOverlayController::determineActiveHighlight(bool& mouseIsOverActive { mouseIsOverActiveHighlightButton = false; - RefPtr oldActiveHighlight = m_activeHighlight.release(); + RefPtr newActiveHighlight; for (auto& highlight : m_potentialHighlights) { if (highlight->type() == Highlight::Type::Selection) { // If we've already found a new active highlight, and it's // a telephone number highlight, prefer that over this selection highlight. - if (m_activeHighlight && m_activeHighlight->type() == Highlight::Type::TelephoneNumber) + if (newActiveHighlight && newActiveHighlight->type() == Highlight::Type::TelephoneNumber) continue; // If this highlight has no compatible services, it can't be active, unless we have telephone number highlights to show in the combined menu. @@ -486,14 +599,38 @@ void ServicesOverlayController::determineActiveHighlight(bool& mouseIsOverActive if (!mouseIsOverHighlight(*highlight, mouseIsOverButton)) continue; - m_activeHighlight = highlight; + newActiveHighlight = highlight; mouseIsOverActiveHighlightButton = mouseIsOverButton; } - if (!highlightsAreEquivalent(oldActiveHighlight.get(), m_activeHighlight.get())) { - m_lastActiveHighlightChangeTime = std::chrono::steady_clock::now(); - m_servicesOverlay->setNeedsDisplay(); + if (!this->highlightsAreEquivalent(m_activeHighlight.get(), newActiveHighlight.get())) { + // When transitioning to a new highlight, we might end up in determineActiveHighlight multiple times + // before the new highlight actually becomes active. Keep track of the last next-but-not-yet-active + // highlight, and only reset the active highlight hysteresis when that changes. + if (m_nextActiveHighlight != newActiveHighlight) { + m_nextActiveHighlight = newActiveHighlight; + m_nextActiveHighlightChangeTime = std::chrono::steady_clock::now(); + } + m_currentMouseDownOnButtonHighlight = nullptr; + + if (m_activeHighlight) { + m_activeHighlight->fadeOut(); + m_activeHighlight = nullptr; + } + + auto remainingTimeUntilHighlightShouldBeShown = this->remainingTimeUntilHighlightShouldBeShown(newActiveHighlight.get()); + if (remainingTimeUntilHighlightShouldBeShown > std::chrono::steady_clock::duration::zero()) { + m_determineActiveHighlightTimer.startOneShot(remainingTimeUntilHighlightShouldBeShown); + return; + } + + m_activeHighlight = m_nextActiveHighlight.release(); + + if (m_activeHighlight) { + m_servicesOverlay->layer()->addChild(m_activeHighlight->layer()); + m_activeHighlight->fadeIn(); + } } } @@ -515,7 +652,7 @@ bool ServicesOverlayController::mouseEvent(PageOverlay*, const WebMouseEvent& ev RefPtr mouseDownHighlight = m_currentMouseDownOnButtonHighlight; m_currentMouseDownOnButtonHighlight = nullptr; - if (mouseIsOverActiveHighlightButton && mouseDownHighlight && remainingTimeUntilHighlightShouldBeShown() <= std::chrono::steady_clock::duration::zero()) { + if (mouseIsOverActiveHighlightButton && mouseDownHighlight) { handleClick(m_mousePosition, *mouseDownHighlight); return true; } @@ -536,7 +673,6 @@ bool ServicesOverlayController::mouseEvent(PageOverlay*, const WebMouseEvent& ev if (event.type() == WebEvent::MouseDown) { if (m_activeHighlight && mouseIsOverActiveHighlightButton) { m_currentMouseDownOnButtonHighlight = m_activeHighlight; - m_servicesOverlay->setNeedsDisplay(); return true; } @@ -546,7 +682,7 @@ bool ServicesOverlayController::mouseEvent(PageOverlay*, const WebMouseEvent& ev return false; } -void ServicesOverlayController::handleClick(const WebCore::IntPoint& clickPoint, Highlight& highlight) +void ServicesOverlayController::handleClick(const IntPoint& clickPoint, Highlight& highlight) { FrameView* frameView = m_webPage.mainFrameView(); if (!frameView) @@ -566,6 +702,18 @@ void ServicesOverlayController::handleClick(const WebCore::IntPoint& clickPoint, m_webPage.handleTelephoneNumberClick(highlight.range()->text(), windowPoint); } +void ServicesOverlayController::didCreateHighlight(Highlight* highlight) +{ + ASSERT(!m_highlights.contains(highlight)); + m_highlights.add(highlight); +} + +void ServicesOverlayController::willDestroyHighlight(Highlight* highlight) +{ + ASSERT(m_highlights.contains(highlight)); + m_highlights.remove(highlight); +} + } // namespace WebKit #endif // #if ENABLE(SERVICE_CONTROLS) || ENABLE(TELEPHONE_NUMBER_DETECTION) && PLATFORM(MAC) -- 2.36.0