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