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