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