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