9c44b86eed7b5bff0fd6eb69675116fd9ab58a82
[WebKit-https.git] / Source / WebCore / page / mac / ServicesOverlayController.mm
1 /*
2  * Copyright (C) 2014 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
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.
12  *
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.
24  */
25
26 #import "config.h"
27 #import "ServicesOverlayController.h"
28
29 #if (ENABLE(SERVICE_CONTROLS) || ENABLE(TELEPHONE_NUMBER_DETECTION)) && PLATFORM(MAC)
30
31 #import "Chrome.h"
32 #import "ChromeClient.h"
33 #import "Document.h"
34 #import "Editor.h"
35 #import "EventHandler.h"
36 #import "FloatQuad.h"
37 #import "FocusController.h"
38 #import "Frame.h"
39 #import "FrameSelection.h"
40 #import "FrameView.h"
41 #import "GapRects.h"
42 #import "GraphicsContext.h"
43 #import "GraphicsLayer.h"
44 #import "GraphicsLayerCA.h"
45 #import "Logging.h"
46 #import "Page.h"
47 #import "PageOverlayController.h"
48 #import "PlatformCAAnimationCocoa.h"
49 #import "Settings.h"
50 #import <QuartzCore/QuartzCore.h>
51 #import <pal/spi/mac/DataDetectorsSPI.h>
52 #import <wtf/SoftLinking.h>
53
54 const float highlightFadeAnimationDuration = 0.3;
55
56 namespace WebCore {
57
58 Ref<ServicesOverlayController::Highlight> ServicesOverlayController::Highlight::createForSelection(ServicesOverlayController& controller, RetainPtr<DDHighlightRef> ddHighlight, Ref<Range>&& range)
59 {
60     return adoptRef(*new Highlight(controller, Highlight::SelectionType, ddHighlight, WTFMove(range)));
61 }
62
63 Ref<ServicesOverlayController::Highlight> ServicesOverlayController::Highlight::createForTelephoneNumber(ServicesOverlayController& controller, RetainPtr<DDHighlightRef> ddHighlight, Ref<Range>&& range)
64 {
65     return adoptRef(*new Highlight(controller, Highlight::TelephoneNumberType, ddHighlight, WTFMove(range)));
66 }
67
68 ServicesOverlayController::Highlight::Highlight(ServicesOverlayController& controller, Type type, RetainPtr<DDHighlightRef> ddHighlight, Ref<WebCore::Range>&& range)
69     : m_range(WTFMove(range))
70     , m_type(type)
71     , m_controller(&controller)
72 {
73     ASSERT(ddHighlight);
74
75     auto& page = controller.page();
76     m_graphicsLayer = GraphicsLayer::create(page.chrome().client().graphicsLayerFactory(), *this);
77     m_graphicsLayer->setDrawsContent(true);
78
79     setDDHighlight(ddHighlight.get());
80
81     // Set directly on the PlatformCALayer so that when we leave the 'from' value implicit
82     // in our animations, we get the right initial value regardless of flush timing.
83     downcast<GraphicsLayerCA>(*layer()).platformCALayer()->setOpacity(0);
84
85     controller.didCreateHighlight(this);
86 }
87
88 ServicesOverlayController::Highlight::~Highlight()
89 {
90     if (m_controller)
91         m_controller->willDestroyHighlight(this);
92 }
93
94 void ServicesOverlayController::Highlight::setDDHighlight(DDHighlightRef highlight)
95 {
96     if (!DataDetectorsLibrary())
97         return;
98
99     if (!m_controller)
100         return;
101
102     m_ddHighlight = highlight;
103
104     if (!m_ddHighlight)
105         return;
106
107     CGRect highlightBoundingRect = DDHighlightGetBoundingRect(m_ddHighlight.get());
108     m_graphicsLayer->setPosition(FloatPoint(highlightBoundingRect.origin));
109     m_graphicsLayer->setSize(FloatSize(highlightBoundingRect.size));
110
111     m_graphicsLayer->setNeedsDisplay();
112 }
113
114 void ServicesOverlayController::Highlight::invalidate()
115 {
116     layer()->removeFromParent();
117     m_controller = nullptr;
118 }
119
120 void ServicesOverlayController::Highlight::notifyFlushRequired(const GraphicsLayer*)
121 {
122     if (!m_controller)
123         return;
124
125     m_controller->page().chrome().client().scheduleCompositingLayerFlush();
126 }
127
128 void ServicesOverlayController::Highlight::paintContents(const GraphicsLayer*, GraphicsContext& graphicsContext, GraphicsLayerPaintingPhase, const FloatRect&, GraphicsLayerPaintBehavior)
129 {
130     if (!DataDetectorsLibrary())
131         return;
132
133     CGContextRef cgContext = graphicsContext.platformContext();
134
135     ALLOW_DEPRECATED_DECLARATIONS_BEGIN
136     CGLayerRef highlightLayer = DDHighlightGetLayerWithContext(ddHighlight(), cgContext);
137     ALLOW_DEPRECATED_DECLARATIONS_END
138     CGRect highlightBoundingRect = DDHighlightGetBoundingRect(ddHighlight());
139     highlightBoundingRect.origin = CGPointZero;
140
141     CGContextDrawLayerInRect(cgContext, highlightBoundingRect, highlightLayer);
142 }
143
144 float ServicesOverlayController::Highlight::deviceScaleFactor() const
145 {
146     if (!m_controller)
147         return 1;
148
149     return m_controller->page().deviceScaleFactor();
150 }
151
152 void ServicesOverlayController::Highlight::fadeIn()
153 {
154     RetainPtr<CABasicAnimation> animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
155     [animation setDuration:highlightFadeAnimationDuration];
156     [animation setFillMode:kCAFillModeForwards];
157     [animation setRemovedOnCompletion:false];
158     [animation setToValue:@1];
159
160     RefPtr<PlatformCAAnimation> platformAnimation = PlatformCAAnimationCocoa::create(animation.get());
161     downcast<GraphicsLayerCA>(*layer()).platformCALayer()->addAnimationForKey("FadeHighlightIn", *platformAnimation);
162 }
163
164 void ServicesOverlayController::Highlight::fadeOut()
165 {
166     RetainPtr<CABasicAnimation> animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
167     [animation setDuration:highlightFadeAnimationDuration];
168     [animation setFillMode:kCAFillModeForwards];
169     [animation setRemovedOnCompletion:false];
170     [animation setToValue:@0];
171
172     RefPtr<Highlight> retainedSelf = this;
173     [CATransaction begin];
174     [CATransaction setCompletionBlock:[retainedSelf] () {
175         retainedSelf->didFinishFadeOutAnimation();
176     }];
177
178     RefPtr<PlatformCAAnimation> platformAnimation = PlatformCAAnimationCocoa::create(animation.get());
179     downcast<GraphicsLayerCA>(*layer()).platformCALayer()->addAnimationForKey("FadeHighlightOut", *platformAnimation);
180     [CATransaction commit];
181 }
182
183 void ServicesOverlayController::Highlight::didFinishFadeOutAnimation()
184 {
185     if (!m_controller)
186         return;
187
188     if (m_controller->activeHighlight() == this)
189         return;
190
191     layer()->removeFromParent();
192 }
193
194 static IntRect textQuadsToBoundingRectForRange(Range& range)
195 {
196     Vector<FloatQuad> textQuads;
197     range.absoluteTextQuads(textQuads);
198     FloatRect boundingRect;
199     for (auto& quad : textQuads)
200         boundingRect.unite(quad.boundingBox());
201     return enclosingIntRect(boundingRect);
202 }
203
204 ServicesOverlayController::ServicesOverlayController(Page& page)
205     : m_page(page)
206     , m_determineActiveHighlightTimer(*this, &ServicesOverlayController::determineActiveHighlightTimerFired)
207     , m_buildHighlightsTimer(*this, &ServicesOverlayController::buildPotentialHighlightsIfNeeded)
208 {
209 }
210
211 ServicesOverlayController::~ServicesOverlayController()
212 {
213     for (auto& highlight : m_highlights)
214         highlight->invalidate();
215 }
216
217 void ServicesOverlayController::willMoveToPage(PageOverlay&, Page* page)
218 {
219     if (page)
220         return;
221
222     ASSERT(m_servicesOverlay);
223     m_servicesOverlay = nullptr;
224 }
225
226 void ServicesOverlayController::didMoveToPage(PageOverlay&, Page*)
227 {
228 }
229
230 static const uint8_t AlignmentNone = 0;
231 static const uint8_t AlignmentLeft = 1 << 0;
232 static const uint8_t AlignmentRight = 1 << 1;
233
234 static void expandForGap(Vector<LayoutRect>& rects, uint8_t* alignments, const GapRects& gap)
235 {
236     if (!gap.left().isEmpty()) {
237         LayoutUnit leftEdge = gap.left().x();
238         for (unsigned i = 0; i < rects.size(); ++i) {
239             if (alignments[i] & AlignmentLeft)
240                 rects[i].shiftXEdgeTo(leftEdge);
241         }
242     }
243
244     if (!gap.right().isEmpty()) {
245         LayoutUnit rightEdge = gap.right().maxX();
246         for (unsigned i = 0; i < rects.size(); ++i) {
247             if (alignments[i] & AlignmentRight)
248                 rects[i].shiftMaxXEdgeTo(rightEdge);
249         }
250     }
251 }
252
253 static inline void stitchRects(Vector<LayoutRect>& rects)
254 {
255     if (rects.size() <= 1)
256         return;
257     
258     Vector<LayoutRect> newRects;
259     
260     // FIXME: Need to support vertical layout.
261     // First stitch together all the rects on the first line of the selection.
262     size_t indexFromStart = 0;
263     LayoutUnit firstTop = rects[indexFromStart].y();
264     LayoutRect& currentRect = rects[indexFromStart];
265     while (indexFromStart < rects.size() && rects[indexFromStart].y() == firstTop)
266         currentRect.unite(rects[indexFromStart++]);
267     
268     newRects.append(currentRect);
269     if (indexFromStart == rects.size()) {
270         // All the rects are on one line. There is nothing else to do.
271         rects.swap(newRects);
272         return;
273     }
274     
275     // Next stitch together all the rects on the last line of the selection.
276     size_t indexFromEnd = rects.size() - 1;
277     LayoutUnit lastTop = rects[indexFromEnd].y();
278     LayoutRect lastRect = rects[indexFromEnd];
279     while (indexFromEnd >= indexFromStart && rects[indexFromEnd].y() == lastTop)
280         lastRect.unite(rects[indexFromEnd--]);
281     
282     // indexFromStart is the index of the first rectangle on the second line.
283     // indexFromEnd is the index of the last rectangle on the second to the last line.
284     // if they are equal, there is one additional rectangle for the line in the middle.
285     if (indexFromEnd == indexFromStart)
286         newRects.append(rects[indexFromStart]);
287     
288     if (indexFromEnd <= indexFromStart) {
289         // There are no more rects to stitch. Just append the last line.
290         newRects.append(lastRect);
291         rects.swap(newRects);
292         return;
293     }
294     
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]);
299     
300     newRects.append(currentRect);
301     newRects.append(lastRect);
302
303     rects.swap(newRects);
304 }
305
306 static void compactRectsWithGapRects(Vector<LayoutRect>& rects, const Vector<GapRects>& gapRects)
307 {
308     stitchRects(rects);
309     
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;
321     } else {
322         alignments[0] = AlignmentRight;
323         alignments[1] = AlignmentLeft | AlignmentRight;
324         alignments[2] = AlignmentLeft;
325     }
326
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);
330
331     // If we have 3 rects we might need one final GapRects to align the edges.
332     if (rects.size() == 3) {
333         LayoutRect left;
334         LayoutRect right;
335         for (unsigned i = 0; i < 3; ++i) {
336             if (alignments[i] & AlignmentLeft) {
337                 if (left.isEmpty())
338                     left = rects[i];
339                 else if (rects[i].x() < left.x())
340                     left = rects[i];
341             }
342             if (alignments[i] & AlignmentRight) {
343                 if (right.isEmpty())
344                     right = rects[i];
345                 else if ((rects[i].x() + rects[i].width()) > (right.x() + right.width()))
346                     right = rects[i];
347             }
348         }
349
350         if (!left.isEmpty() || !right.isEmpty()) {
351             GapRects gap;
352             gap.uniteLeft(left);
353             gap.uniteRight(right);
354             expandForGap(rects, alignments, gap);
355         }
356     }
357 }
358
359 void ServicesOverlayController::selectionRectsDidChange(const Vector<LayoutRect>& rects, const Vector<GapRects>& gapRects, bool isTextOnly)
360 {
361     m_currentSelectionRects = rects;
362     m_isTextOnly = isTextOnly;
363
364     m_lastSelectionChangeTime = MonotonicTime::now();
365
366     compactRectsWithGapRects(m_currentSelectionRects, gapRects);
367
368     // DataDetectors needs these reversed in order to place the arrow in the right location.
369     m_currentSelectionRects.reverse();
370
371     LOG(Services, "ServicesOverlayController - Selection rects changed - Now have %lu\n", rects.size());
372     invalidateHighlightsOfType(Highlight::SelectionType);
373 }
374
375 void ServicesOverlayController::selectedTelephoneNumberRangesChanged()
376 {
377     LOG(Services, "ServicesOverlayController - Telephone number ranges changed\n");
378     invalidateHighlightsOfType(Highlight::TelephoneNumberType);
379 }
380
381 void ServicesOverlayController::invalidateHighlightsOfType(Highlight::Type type)
382 {
383     if (!m_page.settings().serviceControlsEnabled())
384         return;
385
386     m_dirtyHighlightTypes |= type;
387     m_buildHighlightsTimer.startOneShot(0_s);
388 }
389
390 void ServicesOverlayController::buildPotentialHighlightsIfNeeded()
391 {
392     if (!m_dirtyHighlightTypes)
393         return;
394
395     if (m_dirtyHighlightTypes & Highlight::TelephoneNumberType)
396         buildPhoneNumberHighlights();
397
398     if (m_dirtyHighlightTypes & Highlight::SelectionType)
399         buildSelectionHighlight();
400
401     m_dirtyHighlightTypes = 0;
402
403     if (m_potentialHighlights.isEmpty()) {
404         if (m_servicesOverlay)
405             m_page.pageOverlayController().uninstallPageOverlay(*m_servicesOverlay, PageOverlay::FadeMode::DoNotFade);
406         return;
407     }
408
409     if (telephoneNumberRangesForFocusedFrame().isEmpty() && !hasRelevantSelectionServices())
410         return;
411
412     createOverlayIfNeeded();
413
414     bool mouseIsOverButton;
415     determineActiveHighlight(mouseIsOverButton);
416 }
417
418 bool ServicesOverlayController::mouseIsOverHighlight(Highlight& highlight, bool& mouseIsOverButton) const
419 {
420     if (!DataDetectorsLibrary())
421         return false;
422
423     Boolean onButton;
424     bool hovered = DDHighlightPointIsOnHighlight(highlight.ddHighlight(), (CGPoint)m_mousePosition, &onButton);
425     mouseIsOverButton = onButton;
426     return hovered;
427 }
428
429 Seconds ServicesOverlayController::remainingTimeUntilHighlightShouldBeShown(Highlight* highlight) const
430 {
431     if (!highlight)
432         return 0_s;
433
434     Seconds minimumTimeUntilHighlightShouldBeShown = 200_ms;
435     if (m_page.focusController().focusedOrMainFrame().selection().selection().isContentEditable())
436         minimumTimeUntilHighlightShouldBeShown = 1_s;
437
438     bool mousePressed = mainFrame().eventHandler().mousePressed();
439
440     // Highlight hysteresis is only for selection services, because telephone number highlights are already much more stable
441     // by virtue of being expanded to include the entire telephone number. However, we will still avoid highlighting
442     // telephone numbers while the mouse is down.
443     if (highlight->type() == Highlight::TelephoneNumberType)
444         return mousePressed ? minimumTimeUntilHighlightShouldBeShown : 0_s;
445
446     MonotonicTime now = MonotonicTime::now();
447     Seconds timeSinceLastSelectionChange = now - m_lastSelectionChangeTime;
448     Seconds timeSinceHighlightBecameActive = now - m_nextActiveHighlightChangeTime;
449     Seconds timeSinceLastMouseUp = mousePressed ? 0_s : now - m_lastMouseUpTime;
450
451     return minimumTimeUntilHighlightShouldBeShown - std::min(std::min(timeSinceLastSelectionChange, timeSinceHighlightBecameActive), timeSinceLastMouseUp);
452 }
453
454 void ServicesOverlayController::determineActiveHighlightTimerFired()
455 {
456     bool mouseIsOverButton;
457     determineActiveHighlight(mouseIsOverButton);
458 }
459
460 void ServicesOverlayController::drawRect(PageOverlay&, GraphicsContext&, const IntRect&)
461 {
462 }
463
464 void ServicesOverlayController::clearActiveHighlight()
465 {
466     if (!m_activeHighlight)
467         return;
468
469     if (m_currentMouseDownOnButtonHighlight == m_activeHighlight)
470         m_currentMouseDownOnButtonHighlight = nullptr;
471     m_activeHighlight = nullptr;
472 }
473
474 void ServicesOverlayController::removeAllPotentialHighlightsOfType(Highlight::Type type)
475 {
476     Vector<RefPtr<Highlight>> highlightsToRemove;
477     for (auto& highlight : m_potentialHighlights) {
478         if (highlight->type() == type)
479             highlightsToRemove.append(highlight);
480     }
481
482     while (!highlightsToRemove.isEmpty())
483         m_potentialHighlights.remove(highlightsToRemove.takeLast());
484 }
485
486 void ServicesOverlayController::buildPhoneNumberHighlights()
487 {
488     Vector<RefPtr<Range>> phoneNumberRanges;
489     for (Frame* frame = &mainFrame(); frame; frame = frame->tree().traverseNext())
490         phoneNumberRanges.appendVector(frame->editor().detectedTelephoneNumberRanges());
491
492     if (phoneNumberRanges.isEmpty()) {
493         removeAllPotentialHighlightsOfType(Highlight::TelephoneNumberType);
494         return;
495     }
496
497     if (!DataDetectorsLibrary())
498         return;
499
500     HashSet<RefPtr<Highlight>> newPotentialHighlights;
501
502     FrameView& mainFrameView = *mainFrame().view();
503
504     for (auto& range : phoneNumberRanges) {
505         // FIXME: This will choke if the range wraps around the edge of the view.
506         // What should we do in that case?
507         IntRect rect = textQuadsToBoundingRectForRange(*range);
508
509         // Convert to the main document's coordinate space.
510         // FIXME: It's a little crazy to call contentsToWindow and then windowToContents in order to get the right coordinate space.
511         // We should consider adding conversion functions to ScrollView for contentsToDocument(). Right now, contentsToRootView() is
512         // not equivalent to what we need when you have a topContentInset or a header banner.
513         FrameView* viewForRange = range->ownerDocument().view();
514         if (!viewForRange)
515             continue;
516         rect.setLocation(mainFrameView.windowToContents(viewForRange->contentsToWindow(rect.location())));
517
518         CGRect cgRect = rect;
519         RetainPtr<DDHighlightRef> ddHighlight = adoptCF(DDHighlightCreateWithRectsInVisibleRectWithStyleAndDirection(nullptr, &cgRect, 1, mainFrameView.visibleContentRect(), DDHighlightStyleBubbleStandard | DDHighlightStyleStandardIconArrow, YES, NSWritingDirectionNatural, NO, YES));
520
521         newPotentialHighlights.add(Highlight::createForTelephoneNumber(*this, ddHighlight, range.releaseNonNull()));
522     }
523
524     replaceHighlightsOfTypePreservingEquivalentHighlights(newPotentialHighlights, Highlight::TelephoneNumberType);
525 }
526
527 void ServicesOverlayController::buildSelectionHighlight()
528 {
529     if (m_currentSelectionRects.isEmpty()) {
530         removeAllPotentialHighlightsOfType(Highlight::SelectionType);
531         return;
532     }
533
534     if (!DataDetectorsLibrary())
535         return;
536
537     HashSet<RefPtr<Highlight>> newPotentialHighlights;
538
539     Vector<CGRect> cgRects;
540     cgRects.reserveCapacity(m_currentSelectionRects.size());
541
542     RefPtr<Range> selectionRange = m_page.selection().firstRange();
543     if (selectionRange) {
544         FrameView* mainFrameView = mainFrame().view();
545         if (!mainFrameView)
546             return;
547
548         FrameView* viewForRange = selectionRange->ownerDocument().view();
549
550         for (auto& rect : m_currentSelectionRects) {
551             IntRect currentRect = snappedIntRect(rect);
552             currentRect.setLocation(mainFrameView->windowToContents(viewForRange->contentsToWindow(currentRect.location())));
553             cgRects.append((CGRect)currentRect);
554         }
555
556         if (!cgRects.isEmpty()) {
557             CGRect visibleRect = mainFrameView->visibleContentRect();
558             RetainPtr<DDHighlightRef> ddHighlight = adoptCF(DDHighlightCreateWithRectsInVisibleRectWithStyleAndDirection(nullptr, cgRects.begin(), cgRects.size(), visibleRect, DDHighlightStyleBubbleNone | DDHighlightStyleStandardIconArrow | DDHighlightStyleButtonShowAlways, YES, NSWritingDirectionNatural, NO, YES));
559             
560             newPotentialHighlights.add(Highlight::createForSelection(*this, ddHighlight, selectionRange.releaseNonNull()));
561         }
562     }
563
564     replaceHighlightsOfTypePreservingEquivalentHighlights(newPotentialHighlights, Highlight::SelectionType);
565 }
566
567 void ServicesOverlayController::replaceHighlightsOfTypePreservingEquivalentHighlights(HashSet<RefPtr<Highlight>>& newPotentialHighlights, Highlight::Type type)
568 {
569     // If any old Highlights are equivalent (by Range) to a new Highlight, reuse the old
570     // one so that any metadata is retained.
571     HashSet<RefPtr<Highlight>> reusedPotentialHighlights;
572
573     for (auto& oldHighlight : m_potentialHighlights) {
574         if (oldHighlight->type() != type)
575             continue;
576
577         for (auto& newHighlight : newPotentialHighlights) {
578             if (highlightsAreEquivalent(oldHighlight.get(), newHighlight.get())) {
579                 oldHighlight->setDDHighlight(newHighlight->ddHighlight());
580
581                 reusedPotentialHighlights.add(oldHighlight);
582                 newPotentialHighlights.remove(newHighlight);
583
584                 break;
585             }
586         }
587     }
588
589     removeAllPotentialHighlightsOfType(type);
590
591     m_potentialHighlights.add(newPotentialHighlights.begin(), newPotentialHighlights.end());
592     m_potentialHighlights.add(reusedPotentialHighlights.begin(), reusedPotentialHighlights.end());
593 }
594
595 bool ServicesOverlayController::hasRelevantSelectionServices()
596 {
597     return m_page.chrome().client().hasRelevantSelectionServices(m_isTextOnly);
598 }
599
600 void ServicesOverlayController::createOverlayIfNeeded()
601 {
602     if (m_servicesOverlay)
603         return;
604
605     if (!m_page.settings().serviceControlsEnabled())
606         return;
607
608     auto overlay = PageOverlay::create(*this, PageOverlay::OverlayType::Document);
609     m_servicesOverlay = overlay.ptr();
610     m_page.pageOverlayController().installPageOverlay(WTFMove(overlay), PageOverlay::FadeMode::DoNotFade);
611 }
612
613 Vector<RefPtr<Range>> ServicesOverlayController::telephoneNumberRangesForFocusedFrame()
614 {
615     return m_page.focusController().focusedOrMainFrame().editor().detectedTelephoneNumberRanges();
616 }
617
618 bool ServicesOverlayController::highlightsAreEquivalent(const Highlight* a, const Highlight* b)
619 {
620     if (a == b)
621         return true;
622     if (!a || !b)
623         return false;
624     return a->type() == b->type() && areRangesEqual(&a->range(), &b->range());
625 }
626
627 ServicesOverlayController::Highlight* ServicesOverlayController::findTelephoneNumberHighlightContainingSelectionHighlight(Highlight& selectionHighlight)
628 {
629     if (selectionHighlight.type() != Highlight::SelectionType)
630         return nullptr;
631
632     const VisibleSelection& selection = m_page.selection();
633     if (!selection.isRange())
634         return nullptr;
635
636     RefPtr<Range> activeSelectionRange = selection.toNormalizedRange();
637     if (!activeSelectionRange)
638         return nullptr;
639
640     for (auto& highlight : m_potentialHighlights) {
641         if (highlight->type() != Highlight::TelephoneNumberType)
642             continue;
643
644         if (highlight->range().contains(*activeSelectionRange))
645             return highlight.get();
646     }
647
648     return nullptr;
649 }
650
651 void ServicesOverlayController::determineActiveHighlight(bool& mouseIsOverActiveHighlightButton)
652 {
653     buildPotentialHighlightsIfNeeded();
654
655     mouseIsOverActiveHighlightButton = false;
656
657     RefPtr<Highlight> newActiveHighlight;
658
659     for (auto& highlight : m_potentialHighlights) {
660         if (highlight->type() == Highlight::SelectionType) {
661             // If we've already found a new active highlight, and it's
662             // a telephone number highlight, prefer that over this selection highlight.
663             if (newActiveHighlight && newActiveHighlight->type() == Highlight::TelephoneNumberType)
664                 continue;
665
666             // If this highlight has no compatible services, it can't be active.
667             if (!hasRelevantSelectionServices())
668                 continue;
669         }
670
671         // If this highlight isn't hovered, it can't be active.
672         bool mouseIsOverButton;
673         if (!mouseIsOverHighlight(*highlight, mouseIsOverButton))
674             continue;
675
676         newActiveHighlight = highlight;
677         mouseIsOverActiveHighlightButton = mouseIsOverButton;
678     }
679
680     // If our new active highlight is a selection highlight that is completely contained
681     // by one of the phone number highlights, we'll make the phone number highlight active even if it's not hovered.
682     if (newActiveHighlight && newActiveHighlight->type() == Highlight::SelectionType) {
683         if (Highlight* containedTelephoneNumberHighlight = findTelephoneNumberHighlightContainingSelectionHighlight(*newActiveHighlight)) {
684             newActiveHighlight = containedTelephoneNumberHighlight;
685
686             // We will always initially choose the telephone number highlight over the selection highlight if the
687             // mouse is over the telephone number highlight's button, so we know that it's not hovered if we got here.
688             mouseIsOverActiveHighlightButton = false;
689         }
690     }
691
692     if (!this->highlightsAreEquivalent(m_activeHighlight.get(), newActiveHighlight.get())) {
693         // When transitioning to a new highlight, we might end up in determineActiveHighlight multiple times
694         // before the new highlight actually becomes active. Keep track of the last next-but-not-yet-active
695         // highlight, and only reset the active highlight hysteresis when that changes.
696         if (m_nextActiveHighlight != newActiveHighlight) {
697             m_nextActiveHighlight = newActiveHighlight;
698             m_nextActiveHighlightChangeTime = MonotonicTime::now();
699         }
700
701         m_currentMouseDownOnButtonHighlight = nullptr;
702
703         if (m_activeHighlight) {
704             m_activeHighlight->fadeOut();
705             m_activeHighlight = nullptr;
706         }
707
708         auto remainingTimeUntilHighlightShouldBeShown = this->remainingTimeUntilHighlightShouldBeShown(newActiveHighlight.get());
709         if (remainingTimeUntilHighlightShouldBeShown > 0_s) {
710             m_determineActiveHighlightTimer.startOneShot(remainingTimeUntilHighlightShouldBeShown);
711             return;
712         }
713
714         m_activeHighlight = WTFMove(m_nextActiveHighlight);
715
716         if (m_activeHighlight) {
717             m_servicesOverlay->layer().addChild(m_activeHighlight->layer());
718             m_activeHighlight->fadeIn();
719         }
720     }
721 }
722
723 bool ServicesOverlayController::mouseEvent(PageOverlay&, const PlatformMouseEvent& event)
724 {
725     m_mousePosition = mainFrame().view()->windowToContents(event.position());
726
727     bool mouseIsOverActiveHighlightButton = false;
728     determineActiveHighlight(mouseIsOverActiveHighlightButton);
729
730     // Cancel the potential click if any button other than the left button changes state, and ignore the event.
731     if (event.button() != MouseButton::LeftButton) {
732         m_currentMouseDownOnButtonHighlight = nullptr;
733         return false;
734     }
735
736     // If the mouse lifted while still over the highlight button that it went down on, then that is a click.
737     if (event.type() == PlatformEvent::MouseReleased) {
738         RefPtr<Highlight> mouseDownHighlight = m_currentMouseDownOnButtonHighlight;
739         m_currentMouseDownOnButtonHighlight = nullptr;
740
741         m_lastMouseUpTime = MonotonicTime::now();
742
743         if (mouseIsOverActiveHighlightButton && mouseDownHighlight) {
744             handleClick(m_mousePosition, *mouseDownHighlight);
745             return true;
746         }
747         
748         return false;
749     }
750
751     // If the mouse moved outside of the button tracking a potential click, stop tracking the click.
752     if (event.type() == PlatformEvent::MouseMoved) {
753         if (m_currentMouseDownOnButtonHighlight && mouseIsOverActiveHighlightButton)
754             return true;
755
756         m_currentMouseDownOnButtonHighlight = nullptr;
757         return false;
758     }
759
760     // If the mouse went down over the active highlight's button, track this as a potential click.
761     if (event.type() == PlatformEvent::MousePressed) {
762         if (m_activeHighlight && mouseIsOverActiveHighlightButton) {
763             m_currentMouseDownOnButtonHighlight = m_activeHighlight;
764             return true;
765         }
766
767         return false;
768     }
769
770     return false;
771 }
772
773 void ServicesOverlayController::didScrollFrame(PageOverlay&, Frame& frame)
774 {
775     if (frame.isMainFrame())
776         return;
777
778     invalidateHighlightsOfType(Highlight::TelephoneNumberType);
779     invalidateHighlightsOfType(Highlight::SelectionType);
780     buildPotentialHighlightsIfNeeded();
781
782     bool mouseIsOverActiveHighlightButton;
783     determineActiveHighlight(mouseIsOverActiveHighlightButton);
784 }
785
786 void ServicesOverlayController::handleClick(const IntPoint& clickPoint, Highlight& highlight)
787 {
788     FrameView* frameView = mainFrame().view();
789     if (!frameView)
790         return;
791
792     IntPoint windowPoint = frameView->contentsToWindow(clickPoint);
793
794     if (highlight.type() == Highlight::SelectionType) {
795         auto telephoneNumberRanges = telephoneNumberRangesForFocusedFrame();
796         Vector<String> selectedTelephoneNumbers;
797         selectedTelephoneNumbers.reserveCapacity(telephoneNumberRanges.size());
798         for (auto& range : telephoneNumberRanges)
799             selectedTelephoneNumbers.append(range->text());
800
801         m_page.chrome().client().handleSelectionServiceClick(m_page.focusController().focusedOrMainFrame().selection(), selectedTelephoneNumbers, windowPoint);
802     } else if (highlight.type() == Highlight::TelephoneNumberType)
803         m_page.chrome().client().handleTelephoneNumberClick(highlight.range().text(), windowPoint);
804 }
805
806 Frame& ServicesOverlayController::mainFrame() const
807 {
808     return m_page.mainFrame();
809 }
810
811 void ServicesOverlayController::didCreateHighlight(Highlight* highlight)
812 {
813     ASSERT(!m_highlights.contains(highlight));
814     m_highlights.add(highlight);
815 }
816
817 void ServicesOverlayController::willDestroyHighlight(Highlight* highlight)
818 {
819     ASSERT(m_highlights.contains(highlight));
820     m_highlights.remove(highlight);
821 }
822
823 } // namespace WebKit
824
825 #endif // (ENABLE(SERVICE_CONTROLS) || ENABLE(TELEPHONE_NUMBER_DETECTION)) && PLATFORM(MAC)