2 * Copyright (C) 2014 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
27 #import "ServicesOverlayController.h"
29 #if ENABLE(SERVICE_CONTROLS) || ENABLE(TELEPHONE_NUMBER_DETECTION) && PLATFORM(MAC)
33 #import "WebProcess.h"
34 #import <QuartzCore/QuartzCore.h>
35 #import <WebCore/Document.h>
36 #import <WebCore/FloatQuad.h>
37 #import <WebCore/FocusController.h>
38 #import <WebCore/FrameView.h>
39 #import <WebCore/GapRects.h>
40 #import <WebCore/GraphicsContext.h>
41 #import <WebCore/GraphicsLayer.h>
42 #import <WebCore/GraphicsLayerCA.h>
43 #import <WebCore/MainFrame.h>
44 #import <WebCore/PlatformCAAnimationMac.h>
45 #import <WebCore/SoftLinking.h>
47 #if __has_include(<DataDetectors/DDHighlightDrawing.h>)
48 #import <DataDetectors/DDHighlightDrawing.h>
50 typedef void* DDHighlightRef;
53 #if __has_include(<DataDetectors/DDHighlightDrawing_Private.h>)
54 #import <DataDetectors/DDHighlightDrawing_Private.h>
57 const float highlightFadeAnimationDuration = 0.3;
59 typedef NSUInteger DDHighlightStyle;
60 static const DDHighlightStyle DDHighlightNoOutlineWithArrow = (1 << 16);
61 static const DDHighlightStyle DDHighlightOutlineWithArrow = (1 << 16) | 1;
63 SOFT_LINK_PRIVATE_FRAMEWORK_OPTIONAL(DataDetectors)
64 SOFT_LINK(DataDetectors, DDHighlightCreateWithRectsInVisibleRectWithStyleAndDirection, DDHighlightRef, (CFAllocatorRef allocator, CGRect* rects, CFIndex count, CGRect globalVisibleRect, DDHighlightStyle style, Boolean withArrow, NSWritingDirection writingDirection, Boolean endsWithEOL, Boolean flipped), (allocator, rects, count, globalVisibleRect, style, withArrow, writingDirection, endsWithEOL, flipped))
65 SOFT_LINK(DataDetectors, DDHighlightGetLayerWithContext, CGLayerRef, (DDHighlightRef highlight, CGContextRef context), (highlight, context))
66 SOFT_LINK(DataDetectors, DDHighlightGetBoundingRect, CGRect, (DDHighlightRef highlight), (highlight))
67 SOFT_LINK(DataDetectors, DDHighlightPointIsOnHighlight, Boolean, (DDHighlightRef highlight, CGPoint point, Boolean* onButton), (highlight, point, onButton))
69 using namespace WebCore;
73 PassRefPtr<ServicesOverlayController::Highlight> ServicesOverlayController::Highlight::createForSelection(ServicesOverlayController& controller, RetainPtr<DDHighlightRef> ddHighlight)
75 return adoptRef(new Highlight(controller, Type::Selection, ddHighlight, nullptr));
78 PassRefPtr<ServicesOverlayController::Highlight> ServicesOverlayController::Highlight::createForTelephoneNumber(ServicesOverlayController& controller, RetainPtr<DDHighlightRef> ddHighlight, PassRefPtr<Range> range)
80 return adoptRef(new Highlight(controller, Type::TelephoneNumber, ddHighlight, range));
83 ServicesOverlayController::Highlight::Highlight(ServicesOverlayController& controller, Type type, RetainPtr<DDHighlightRef> ddHighlight, PassRefPtr<WebCore::Range> range)
84 : m_ddHighlight(ddHighlight)
87 , m_controller(&controller)
89 ASSERT(m_ddHighlight);
90 ASSERT(type != Type::TelephoneNumber || m_range);
92 DrawingArea* drawingArea = controller.webPage().drawingArea();
93 m_graphicsLayer = GraphicsLayer::create(drawingArea ? drawingArea->graphicsLayerFactory() : nullptr, *this);
94 m_graphicsLayer->setDrawsContent(true);
95 m_graphicsLayer->setNeedsDisplay();
97 CGRect highlightBoundingRect = DDHighlightGetBoundingRect(ddHighlight.get());
98 m_graphicsLayer->setPosition(FloatPoint(highlightBoundingRect.origin));
99 m_graphicsLayer->setSize(FloatSize(highlightBoundingRect.size));
101 // Set directly on the PlatformCALayer so that when we leave the 'from' value implicit
102 // in our animations, we get the right initial value regardless of flush timing.
103 toGraphicsLayerCA(layer())->platformCALayer()->setOpacity(0);
105 controller.didCreateHighlight(this);
108 ServicesOverlayController::Highlight::~Highlight()
111 m_controller->willDestroyHighlight(this);
114 void ServicesOverlayController::Highlight::invalidate()
116 layer()->removeFromParent();
117 m_controller = nullptr;
120 void ServicesOverlayController::Highlight::notifyFlushRequired(const GraphicsLayer*)
125 if (DrawingArea* drawingArea = m_controller->webPage().drawingArea())
126 drawingArea->scheduleCompositingLayerFlush();
129 void ServicesOverlayController::Highlight::paintContents(const GraphicsLayer*, GraphicsContext& graphicsContext, GraphicsLayerPaintingPhase, const FloatRect& inClip)
131 CGContextRef cgContext = graphicsContext.platformContext();
133 CGLayerRef highlightLayer = DDHighlightGetLayerWithContext(ddHighlight(), cgContext);
134 CGRect highlightBoundingRect = DDHighlightGetBoundingRect(ddHighlight());
135 highlightBoundingRect.origin = CGPointZero;
137 CGContextDrawLayerInRect(cgContext, highlightBoundingRect, highlightLayer);
140 float ServicesOverlayController::Highlight::deviceScaleFactor() const
145 return m_controller->webPage().deviceScaleFactor();
148 void ServicesOverlayController::Highlight::fadeIn()
150 RetainPtr<CABasicAnimation> animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
151 [animation setDuration:highlightFadeAnimationDuration];
152 [animation setFillMode:kCAFillModeForwards];
153 [animation setRemovedOnCompletion:false];
154 [animation setToValue:@1];
156 RefPtr<PlatformCAAnimation> platformAnimation = PlatformCAAnimationMac::create(animation.get());
157 toGraphicsLayerCA(layer())->platformCALayer()->addAnimationForKey("FadeHighlightIn", platformAnimation.get());
160 void ServicesOverlayController::Highlight::fadeOut()
162 RetainPtr<CABasicAnimation> animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
163 [animation setDuration:highlightFadeAnimationDuration];
164 [animation setFillMode:kCAFillModeForwards];
165 [animation setRemovedOnCompletion:false];
166 [animation setToValue:@0];
168 RefPtr<Highlight> retainedSelf = this;
169 [CATransaction begin];
170 [CATransaction setCompletionBlock:[retainedSelf] () {
171 retainedSelf->didFinishFadeOutAnimation();
174 RefPtr<PlatformCAAnimation> platformAnimation = PlatformCAAnimationMac::create(animation.get());
175 toGraphicsLayerCA(layer())->platformCALayer()->addAnimationForKey("FadeHighlightOut", platformAnimation.get());
176 [CATransaction commit];
179 void ServicesOverlayController::Highlight::didFinishFadeOutAnimation()
184 if (m_controller->activeHighlight() == this)
187 layer()->removeFromParent();
190 static IntRect textQuadsToBoundingRectForRange(Range& range)
192 Vector<FloatQuad> textQuads;
193 range.textQuads(textQuads);
194 FloatRect boundingRect;
195 for (auto& quad : textQuads)
196 boundingRect.unite(quad.boundingBox());
197 return enclosingIntRect(boundingRect);
200 ServicesOverlayController::ServicesOverlayController(WebPage& webPage)
202 , m_servicesOverlay(nullptr)
203 , m_isTextOnly(false)
204 , m_determineActiveHighlightTimer(this, &ServicesOverlayController::determineActiveHighlightTimerFired)
208 ServicesOverlayController::~ServicesOverlayController()
210 for (auto& highlight : m_highlights)
211 highlight->invalidate();
214 void ServicesOverlayController::pageOverlayDestroyed(PageOverlay*)
216 // Before the overlay is destroyed, it should have moved out of the WebPage,
217 // at which point we already cleared our back pointer.
218 ASSERT(!m_servicesOverlay);
221 void ServicesOverlayController::willMoveToWebPage(PageOverlay*, WebPage* webPage)
226 ASSERT(m_servicesOverlay);
227 m_servicesOverlay = nullptr;
230 void ServicesOverlayController::didMoveToWebPage(PageOverlay*, WebPage*)
234 static const uint8_t AlignmentNone = 0;
235 static const uint8_t AlignmentLeft = 1 << 0;
236 static const uint8_t AlignmentRight = 1 << 1;
238 static void expandForGap(Vector<LayoutRect>& rects, uint8_t* alignments, const GapRects& gap)
240 if (!gap.left().isEmpty()) {
241 LayoutUnit leftEdge = gap.left().x();
242 for (unsigned i = 0; i < 3; ++i) {
243 if (alignments[i] & AlignmentLeft)
244 rects[i].shiftXEdgeTo(leftEdge);
248 if (!gap.right().isEmpty()) {
249 LayoutUnit rightEdge = gap.right().maxX();
250 for (unsigned i = 0; i < 3; ++i) {
251 if (alignments[i] & AlignmentRight)
252 rects[i].shiftMaxXEdgeTo(rightEdge);
257 static inline void stitchRects(Vector<LayoutRect>& rects)
259 if (rects.size() <= 1)
262 Vector<LayoutRect> newRects;
264 // FIXME: Need to support vertical layout.
265 // First stitch together all the rects on the first line of the selection.
266 size_t indexFromStart = 0;
267 LayoutUnit firstTop = rects[indexFromStart].y();
268 LayoutRect& currentRect = rects[indexFromStart++];
269 while (indexFromStart < rects.size() && rects[indexFromStart].y() == firstTop)
270 currentRect.unite(rects[indexFromStart++]);
272 newRects.append(currentRect);
273 if (indexFromStart == rects.size()) {
274 // All the rects are on one line. There is nothing else to do.
275 rects.swap(newRects);
279 // Next stitch together all the rects on the last line of the selection.
280 size_t indexFromEnd = rects.size() - 1;
281 LayoutUnit lastTop = rects[indexFromEnd].y();
282 LayoutRect lastRect = rects[indexFromEnd];
283 while (indexFromEnd != indexFromStart && rects[--indexFromEnd].y() == lastTop)
284 lastRect.unite(rects[indexFromEnd]);
286 if (indexFromEnd == indexFromStart) {
287 // All the rects are on two lines only. There is nothing else to do.
288 newRects.append(lastRect);
289 rects.swap(newRects);
293 // indexFromStart is the index of the first rectangle on the second line.
294 // indexFromEnd is the index of the last rectangle on the second to the last line.
295 // Stitch together all the rects after the first line until the second to the last included.
296 currentRect = rects[indexFromStart];
297 while (indexFromStart != indexFromEnd)
298 currentRect.unite(rects[++indexFromStart]);
300 newRects.append(currentRect);
301 newRects.append(lastRect);
303 rects.swap(newRects);
306 static void compactRectsWithGapRects(Vector<LayoutRect>& rects, const Vector<GapRects>& gapRects)
310 // FIXME: The following alignments are correct for LTR text.
311 // We should also account for RTL.
312 uint8_t alignments[3];
313 if (rects.size() == 1) {
314 alignments[0] = AlignmentLeft | AlignmentRight;
315 alignments[1] = AlignmentNone;
316 alignments[2] = AlignmentNone;
317 } else if (rects.size() == 2) {
318 alignments[0] = AlignmentRight;
319 alignments[1] = AlignmentLeft;
320 alignments[2] = AlignmentNone;
322 alignments[0] = AlignmentRight;
323 alignments[1] = AlignmentLeft | AlignmentRight;
324 alignments[2] = AlignmentLeft;
327 // Account for each GapRects by extending the edge of certain LayoutRects to meet the gap.
328 for (auto& gap : gapRects)
329 expandForGap(rects, alignments, gap);
331 // If we have 3 rects we might need one final GapRects to align the edges.
332 if (rects.size() == 3) {
335 for (unsigned i = 0; i < 3; ++i) {
336 if (alignments[i] & AlignmentLeft) {
339 else if (rects[i].x() < left.x())
342 if (alignments[i] & AlignmentRight) {
345 else if ((rects[i].x() + rects[i].width()) > (right.x() + right.width()))
350 if (!left.isEmpty() || !right.isEmpty()) {
353 gap.uniteRight(right);
354 expandForGap(rects, alignments, gap);
359 void ServicesOverlayController::selectionRectsDidChange(const Vector<LayoutRect>& rects, const Vector<GapRects>& gapRects, bool isTextOnly)
361 #if __MAC_OS_X_VERSION_MIN_REQUIRED > 1090
362 m_currentSelectionRects = rects;
363 m_isTextOnly = isTextOnly;
365 m_lastSelectionChangeTime = std::chrono::steady_clock::now();
367 compactRectsWithGapRects(m_currentSelectionRects, gapRects);
369 // DataDetectors needs these reversed in order to place the arrow in the right location.
370 m_currentSelectionRects.reverse();
372 LOG(Services, "ServicesOverlayController - Selection rects changed - Now have %lu\n", rects.size());
374 buildSelectionHighlight();
380 void ServicesOverlayController::selectedTelephoneNumberRangesChanged()
382 #if PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED > 1090
383 LOG(Services, "ServicesOverlayController - Telephone number ranges changed\n");
384 buildPhoneNumberHighlights();
386 UNUSED_PARAM(ranges);
390 bool ServicesOverlayController::mouseIsOverHighlight(Highlight& highlight, bool& mouseIsOverButton) const
393 bool hovered = DDHighlightPointIsOnHighlight(highlight.ddHighlight(), (CGPoint)m_mousePosition, &onButton);
394 mouseIsOverButton = onButton;
398 std::chrono::milliseconds ServicesOverlayController::remainingTimeUntilHighlightShouldBeShown(Highlight* highlight) const
401 return std::chrono::milliseconds::zero();
403 // Highlight hysteresis is only for selection services, because telephone number highlights are already much more stable
404 // by virtue of being expanded to include the entire telephone number.
405 if (highlight->type() == Highlight::Type::TelephoneNumber)
406 return std::chrono::milliseconds::zero();
408 std::chrono::steady_clock::duration minimumTimeUntilHighlightShouldBeShown = 200_ms;
410 auto now = std::chrono::steady_clock::now();
411 auto timeSinceLastSelectionChange = now - m_lastSelectionChangeTime;
412 auto timeSinceHighlightBecameActive = now - m_nextActiveHighlightChangeTime;
414 return std::chrono::duration_cast<std::chrono::milliseconds>(std::max(minimumTimeUntilHighlightShouldBeShown - timeSinceLastSelectionChange, minimumTimeUntilHighlightShouldBeShown - timeSinceHighlightBecameActive));
417 void ServicesOverlayController::determineActiveHighlightTimerFired(Timer<ServicesOverlayController>&)
419 bool mouseIsOverButton;
420 determineActiveHighlight(mouseIsOverButton);
423 void ServicesOverlayController::drawRect(PageOverlay* overlay, GraphicsContext& graphicsContext, const IntRect& dirtyRect)
427 void ServicesOverlayController::clearActiveHighlight()
429 if (!m_activeHighlight)
432 if (m_currentMouseDownOnButtonHighlight == m_activeHighlight)
433 m_currentMouseDownOnButtonHighlight = nullptr;
434 m_activeHighlight = nullptr;
437 void ServicesOverlayController::removeAllPotentialHighlightsOfType(Highlight::Type type)
439 Vector<RefPtr<Highlight>> highlightsToRemove;
440 for (auto& highlight : m_potentialHighlights) {
441 if (highlight->type() == type)
442 highlightsToRemove.append(highlight);
445 while (!highlightsToRemove.isEmpty())
446 m_potentialHighlights.remove(highlightsToRemove.takeLast());
449 void ServicesOverlayController::buildPhoneNumberHighlights()
451 HashSet<RefPtr<Highlight>> newPotentialHighlights;
453 Frame* mainFrame = m_webPage.mainFrame();
454 FrameView& mainFrameView = *mainFrame->view();
456 for (Frame* frame = mainFrame; frame; frame = frame->tree().traverseNext()) {
457 auto& ranges = frame->editor().detectedTelephoneNumberRanges();
458 for (auto& range : ranges) {
459 // FIXME: This will choke if the range wraps around the edge of the view.
460 // What should we do in that case?
461 IntRect rect = textQuadsToBoundingRectForRange(*range);
463 // Convert to the main document's coordinate space.
464 // FIXME: It's a little crazy to call contentsToWindow and then windowToContents in order to get the right coordinate space.
465 // We should consider adding conversion functions to ScrollView for contentsToDocument(). Right now, contentsToRootView() is
466 // not equivalent to what we need when you have a topContentInset or a header banner.
467 FrameView* viewForRange = range->ownerDocument().view();
470 rect.setLocation(mainFrameView.windowToContents(viewForRange->contentsToWindow(rect.location())));
472 CGRect cgRect = rect;
473 RetainPtr<DDHighlightRef> ddHighlight = adoptCF(DDHighlightCreateWithRectsInVisibleRectWithStyleAndDirection(nullptr, &cgRect, 1, mainFrameView.visibleContentRect(), DDHighlightOutlineWithArrow, YES, NSWritingDirectionNatural, NO, YES));
475 newPotentialHighlights.add(Highlight::createForTelephoneNumber(*this, ddHighlight, range));
479 // If any old Highlights are equivalent (by Range) to a new Highlight, reuse the old
480 // one so that any metadata is retained.
481 HashSet<RefPtr<Highlight>> reusedPotentialHighlights;
483 for (auto& oldHighlight : m_potentialHighlights) {
484 if (oldHighlight->type() != Highlight::Type::TelephoneNumber)
487 for (auto& newHighlight : newPotentialHighlights) {
488 if (highlightsAreEquivalent(oldHighlight.get(), newHighlight.get())) {
489 reusedPotentialHighlights.add(oldHighlight);
490 newPotentialHighlights.remove(newHighlight);
496 removeAllPotentialHighlightsOfType(Highlight::Type::TelephoneNumber);
498 m_potentialHighlights.add(newPotentialHighlights.begin(), newPotentialHighlights.end());
499 m_potentialHighlights.add(reusedPotentialHighlights.begin(), reusedPotentialHighlights.end());
501 didRebuildPotentialHighlights();
504 void ServicesOverlayController::buildSelectionHighlight()
506 removeAllPotentialHighlightsOfType(Highlight::Type::Selection);
508 Vector<CGRect> cgRects;
509 cgRects.reserveCapacity(m_currentSelectionRects.size());
511 for (auto& rect : m_currentSelectionRects)
512 cgRects.append((CGRect)pixelSnappedIntRect(rect));
514 if (!cgRects.isEmpty()) {
515 CGRect visibleRect = m_webPage.corePage()->mainFrame().view()->visibleContentRect();
516 RetainPtr<DDHighlightRef> ddHighlight = adoptCF(DDHighlightCreateWithRectsInVisibleRectWithStyleAndDirection(nullptr, cgRects.begin(), cgRects.size(), visibleRect, DDHighlightNoOutlineWithArrow, YES, NSWritingDirectionNatural, NO, YES));
518 m_potentialHighlights.add(Highlight::createForSelection(*this, ddHighlight));
521 didRebuildPotentialHighlights();
524 bool ServicesOverlayController::hasRelevantSelectionServices()
526 return (m_isTextOnly && WebProcess::shared().hasSelectionServices()) || WebProcess::shared().hasRichContentServices();
529 void ServicesOverlayController::didRebuildPotentialHighlights()
531 if (m_potentialHighlights.isEmpty()) {
532 if (m_servicesOverlay)
533 m_webPage.uninstallPageOverlay(m_servicesOverlay);
537 if (telephoneNumberRangesForFocusedFrame().isEmpty() && !hasRelevantSelectionServices())
540 createOverlayIfNeeded();
542 bool mouseIsOverButton;
543 determineActiveHighlight(mouseIsOverButton);
546 void ServicesOverlayController::createOverlayIfNeeded()
548 if (m_servicesOverlay)
551 RefPtr<PageOverlay> overlay = PageOverlay::create(this, PageOverlay::OverlayType::Document);
552 m_servicesOverlay = overlay.get();
553 m_webPage.installPageOverlay(overlay.release(), PageOverlay::FadeMode::DoNotFade);
556 Vector<RefPtr<Range>> ServicesOverlayController::telephoneNumberRangesForFocusedFrame()
558 Page* page = m_webPage.corePage();
560 return Vector<RefPtr<Range>>();
562 return page->focusController().focusedOrMainFrame().editor().detectedTelephoneNumberRanges();
565 bool ServicesOverlayController::highlightsAreEquivalent(const Highlight* a, const Highlight* b)
573 if (a->type() == Highlight::Type::TelephoneNumber && b->type() == Highlight::Type::TelephoneNumber && areRangesEqual(a->range(), b->range()))
579 void ServicesOverlayController::determineActiveHighlight(bool& mouseIsOverActiveHighlightButton)
581 mouseIsOverActiveHighlightButton = false;
583 RefPtr<Highlight> newActiveHighlight;
585 for (auto& highlight : m_potentialHighlights) {
586 if (highlight->type() == Highlight::Type::Selection) {
587 // If we've already found a new active highlight, and it's
588 // a telephone number highlight, prefer that over this selection highlight.
589 if (newActiveHighlight && newActiveHighlight->type() == Highlight::Type::TelephoneNumber)
592 // If this highlight has no compatible services, it can't be active, unless we have telephone number highlights to show in the combined menu.
593 if (telephoneNumberRangesForFocusedFrame().isEmpty() && !hasRelevantSelectionServices())
597 // If this highlight isn't hovered, it can't be active.
598 bool mouseIsOverButton;
599 if (!mouseIsOverHighlight(*highlight, mouseIsOverButton))
602 newActiveHighlight = highlight;
603 mouseIsOverActiveHighlightButton = mouseIsOverButton;
606 if (!this->highlightsAreEquivalent(m_activeHighlight.get(), newActiveHighlight.get())) {
607 // When transitioning to a new highlight, we might end up in determineActiveHighlight multiple times
608 // before the new highlight actually becomes active. Keep track of the last next-but-not-yet-active
609 // highlight, and only reset the active highlight hysteresis when that changes.
610 if (m_nextActiveHighlight != newActiveHighlight) {
611 m_nextActiveHighlight = newActiveHighlight;
612 m_nextActiveHighlightChangeTime = std::chrono::steady_clock::now();
615 m_currentMouseDownOnButtonHighlight = nullptr;
617 if (m_activeHighlight) {
618 m_activeHighlight->fadeOut();
619 m_activeHighlight = nullptr;
622 auto remainingTimeUntilHighlightShouldBeShown = this->remainingTimeUntilHighlightShouldBeShown(newActiveHighlight.get());
623 if (remainingTimeUntilHighlightShouldBeShown > std::chrono::steady_clock::duration::zero()) {
624 m_determineActiveHighlightTimer.startOneShot(remainingTimeUntilHighlightShouldBeShown);
628 m_activeHighlight = m_nextActiveHighlight.release();
630 if (m_activeHighlight) {
631 m_servicesOverlay->layer()->addChild(m_activeHighlight->layer());
632 m_activeHighlight->fadeIn();
637 bool ServicesOverlayController::mouseEvent(PageOverlay*, const WebMouseEvent& event)
639 m_mousePosition = m_webPage.corePage()->mainFrame().view()->rootViewToContents(event.position());
641 bool mouseIsOverActiveHighlightButton = false;
642 determineActiveHighlight(mouseIsOverActiveHighlightButton);
644 // Cancel the potential click if any button other than the left button changes state, and ignore the event.
645 if (event.button() != WebMouseEvent::LeftButton) {
646 m_currentMouseDownOnButtonHighlight = nullptr;
650 // If the mouse lifted while still over the highlight button that it went down on, then that is a click.
651 if (event.type() == WebEvent::MouseUp) {
652 RefPtr<Highlight> mouseDownHighlight = m_currentMouseDownOnButtonHighlight;
653 m_currentMouseDownOnButtonHighlight = nullptr;
655 if (mouseIsOverActiveHighlightButton && mouseDownHighlight) {
656 handleClick(m_mousePosition, *mouseDownHighlight);
663 // If the mouse moved outside of the button tracking a potential click, stop tracking the click.
664 if (event.type() == WebEvent::MouseMove) {
665 if (m_currentMouseDownOnButtonHighlight && mouseIsOverActiveHighlightButton)
668 m_currentMouseDownOnButtonHighlight = nullptr;
672 // If the mouse went down over the active highlight's button, track this as a potential click.
673 if (event.type() == WebEvent::MouseDown) {
674 if (m_activeHighlight && mouseIsOverActiveHighlightButton) {
675 m_currentMouseDownOnButtonHighlight = m_activeHighlight;
685 void ServicesOverlayController::handleClick(const IntPoint& clickPoint, Highlight& highlight)
687 FrameView* frameView = m_webPage.mainFrameView();
691 IntPoint windowPoint = frameView->contentsToWindow(clickPoint);
693 if (highlight.type() == Highlight::Type::Selection) {
694 auto telephoneNumberRanges = telephoneNumberRangesForFocusedFrame();
695 Vector<String> selectedTelephoneNumbers;
696 selectedTelephoneNumbers.reserveCapacity(telephoneNumberRanges.size());
697 for (auto& range : telephoneNumberRanges)
698 selectedTelephoneNumbers.append(range->text());
700 m_webPage.handleSelectionServiceClick(m_webPage.corePage()->mainFrame().selection(), selectedTelephoneNumbers, windowPoint);
701 } else if (highlight.type() == Highlight::Type::TelephoneNumber)
702 m_webPage.handleTelephoneNumberClick(highlight.range()->text(), windowPoint);
705 void ServicesOverlayController::didCreateHighlight(Highlight* highlight)
707 ASSERT(!m_highlights.contains(highlight));
708 m_highlights.add(highlight);
711 void ServicesOverlayController::willDestroyHighlight(Highlight* highlight)
713 ASSERT(m_highlights.contains(highlight));
714 m_highlights.remove(highlight);
717 } // namespace WebKit
719 #endif // #if ENABLE(SERVICE_CONTROLS) || ENABLE(TELEPHONE_NUMBER_DETECTION) && PLATFORM(MAC)