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