4d8a8c0a037fc488331229dbf725ee6df05b0424
[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 <WebCore/Document.h>
35 #import <WebCore/FloatQuad.h>
36 #import <WebCore/FocusController.h>
37 #import <WebCore/FrameView.h>
38 #import <WebCore/GapRects.h>
39 #import <WebCore/GraphicsContext.h>
40 #import <WebCore/MainFrame.h>
41 #import <WebCore/SoftLinking.h>
42
43 #if __has_include(<DataDetectors/DDHighlightDrawing.h>)
44 #import <DataDetectors/DDHighlightDrawing.h>
45 #else
46 typedef void* DDHighlightRef;
47 #endif
48
49 #if __has_include(<DataDetectors/DDHighlightDrawing_Private.h>)
50 #import <DataDetectors/DDHighlightDrawing_Private.h>
51 #endif
52
53 typedef NSUInteger DDHighlightStyle;
54 static const DDHighlightStyle DDHighlightNoOutlineWithArrow = (1 << 16);
55 static const DDHighlightStyle DDHighlightOutlineWithArrow = (1 << 16) | 1;
56
57 SOFT_LINK_PRIVATE_FRAMEWORK_OPTIONAL(DataDetectors)
58 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))
59 SOFT_LINK(DataDetectors, DDHighlightGetLayerWithContext, CGLayerRef, (DDHighlightRef highlight, CGContextRef context), (highlight, context))
60 SOFT_LINK(DataDetectors, DDHighlightGetBoundingRect, CGRect, (DDHighlightRef highlight), (highlight))
61 SOFT_LINK(DataDetectors, DDHighlightPointIsOnHighlight, Boolean, (DDHighlightRef highlight, CGPoint point, Boolean* onButton), (highlight, point, onButton))
62
63 using namespace WebCore;
64
65 namespace WebKit {
66
67 PassRefPtr<ServicesOverlayController::Highlight> ServicesOverlayController::Highlight::createForSelection(RetainPtr<DDHighlightRef> ddHighlight)
68 {
69     return adoptRef(new Highlight(Type::Selection, ddHighlight, nullptr));
70 }
71
72 PassRefPtr<ServicesOverlayController::Highlight> ServicesOverlayController::Highlight::createForTelephoneNumber(RetainPtr<DDHighlightRef> ddHighlight, PassRefPtr<Range> range)
73 {
74     return adoptRef(new Highlight(Type::TelephoneNumber, ddHighlight, range));
75 }
76
77 static IntRect textQuadsToBoundingRectForRange(Range& range)
78 {
79     Vector<FloatQuad> textQuads;
80     range.textQuads(textQuads);
81     FloatRect boundingRect;
82     for (auto& quad : textQuads)
83         boundingRect.unite(quad.boundingBox());
84     return enclosingIntRect(boundingRect);
85 }
86
87 ServicesOverlayController::ServicesOverlayController(WebPage& webPage)
88     : m_webPage(webPage)
89     , m_servicesOverlay(nullptr)
90     , m_isTextOnly(false)
91     , m_repaintHighlightTimer(this, &ServicesOverlayController::repaintHighlightTimerFired)
92 {
93 }
94
95 ServicesOverlayController::~ServicesOverlayController()
96 {
97 }
98
99 void ServicesOverlayController::pageOverlayDestroyed(PageOverlay*)
100 {
101     // Before the overlay is destroyed, it should have moved out of the WebPage,
102     // at which point we already cleared our back pointer.
103     ASSERT(!m_servicesOverlay);
104 }
105
106 void ServicesOverlayController::willMoveToWebPage(PageOverlay*, WebPage* webPage)
107 {
108     if (webPage)
109         return;
110
111     ASSERT(m_servicesOverlay);
112     m_servicesOverlay = nullptr;
113 }
114
115 void ServicesOverlayController::didMoveToWebPage(PageOverlay*, WebPage*)
116 {
117 }
118
119 static const uint8_t AlignmentNone = 0;
120 static const uint8_t AlignmentLeft = 1 << 0;
121 static const uint8_t AlignmentRight = 1 << 1;
122
123 static void expandForGap(Vector<LayoutRect>& rects, uint8_t* alignments, const GapRects& gap)
124 {
125     if (!gap.left().isEmpty()) {
126         LayoutUnit leftEdge = gap.left().x();
127         for (unsigned i = 0; i < 3; ++i) {
128             if (alignments[i] & AlignmentLeft)
129                 rects[i].shiftXEdgeTo(leftEdge);
130         }
131     }
132
133     if (!gap.right().isEmpty()) {
134         LayoutUnit rightEdge = gap.right().maxX();
135         for (unsigned i = 0; i < 3; ++i) {
136             if (alignments[i] & AlignmentRight)
137                 rects[i].shiftMaxXEdgeTo(rightEdge);
138         }
139     }
140 }
141
142 static inline void stitchRects(Vector<LayoutRect>& rects)
143 {
144     if (rects.size() <= 1)
145         return;
146     
147     Vector<LayoutRect> newRects;
148     
149     // FIXME: Need to support vertical layout.
150     // First stitch together all the rects on the first line of the selection.
151     size_t indexFromStart = 0;
152     LayoutUnit firstTop = rects[indexFromStart].y();
153     LayoutRect& currentRect = rects[indexFromStart++];
154     while (indexFromStart < rects.size() && rects[indexFromStart].y() == firstTop)
155         currentRect.unite(rects[indexFromStart++]);
156     
157     newRects.append(currentRect);
158     if (indexFromStart == rects.size()) {
159         // All the rects are on one line. There is nothing else to do.
160         rects.swap(newRects);
161         return;
162     }
163     
164     // Next stitch together all the rects on the last line of the selection.
165     size_t indexFromEnd = rects.size() - 1;
166     LayoutUnit lastTop = rects[indexFromEnd].y();
167     LayoutRect lastRect = rects[indexFromEnd];
168     while (indexFromEnd != indexFromStart && rects[--indexFromEnd].y() == lastTop)
169         lastRect.unite(rects[indexFromEnd]);
170     
171     if (indexFromEnd == indexFromStart) {
172         // All the rects are on two lines only. There is nothing else to do.
173         newRects.append(lastRect);
174         rects.swap(newRects);
175         return;
176     }
177     
178     // indexFromStart is the index of the first rectangle on the second line.
179     // indexFromEnd is the index of the last rectangle on the second to the last line.
180     // Stitch together all the rects after the first line until the second to the last included.
181     currentRect = rects[indexFromStart];
182     while (indexFromStart != indexFromEnd)
183         currentRect.unite(rects[++indexFromStart]);
184     
185     newRects.append(currentRect);
186     newRects.append(lastRect);
187
188     rects.swap(newRects);
189 }
190
191 static void compactRectsWithGapRects(Vector<LayoutRect>& rects, const Vector<GapRects>& gapRects)
192 {
193     stitchRects(rects);
194     
195     // FIXME: The following alignments are correct for LTR text.
196     // We should also account for RTL.
197     uint8_t alignments[3];
198     if (rects.size() == 1) {
199         alignments[0] = AlignmentLeft | AlignmentRight;
200         alignments[1] = AlignmentNone;
201         alignments[2] = AlignmentNone;
202     } else if (rects.size() == 2) {
203         alignments[0] = AlignmentRight;
204         alignments[1] = AlignmentLeft;
205         alignments[2] = AlignmentNone;
206     } else {
207         alignments[0] = AlignmentRight;
208         alignments[1] = AlignmentLeft | AlignmentRight;
209         alignments[2] = AlignmentLeft;
210     }
211
212     // Account for each GapRects by extending the edge of certain LayoutRects to meet the gap.
213     for (auto& gap : gapRects)
214         expandForGap(rects, alignments, gap);
215
216     // If we have 3 rects we might need one final GapRects to align the edges.
217     if (rects.size() == 3) {
218         LayoutRect left;
219         LayoutRect right;
220         for (unsigned i = 0; i < 3; ++i) {
221             if (alignments[i] & AlignmentLeft) {
222                 if (left.isEmpty())
223                     left = rects[i];
224                 else if (rects[i].x() < left.x())
225                     left = rects[i];
226             }
227             if (alignments[i] & AlignmentRight) {
228                 if (right.isEmpty())
229                     right = rects[i];
230                 else if ((rects[i].x() + rects[i].width()) > (right.x() + right.width()))
231                     right = rects[i];
232             }
233         }
234
235         if (!left.isEmpty() || !right.isEmpty()) {
236             GapRects gap;
237             gap.uniteLeft(left);
238             gap.uniteRight(right);
239             expandForGap(rects, alignments, gap);
240         }
241     }
242 }
243
244 void ServicesOverlayController::selectionRectsDidChange(const Vector<LayoutRect>& rects, const Vector<GapRects>& gapRects, bool isTextOnly)
245 {
246 #if __MAC_OS_X_VERSION_MIN_REQUIRED > 1090
247     m_currentSelectionRects = rects;
248     m_isTextOnly = isTextOnly;
249
250     m_lastSelectionChangeTime = std::chrono::steady_clock::now();
251
252     compactRectsWithGapRects(m_currentSelectionRects, gapRects);
253
254     // DataDetectors needs these reversed in order to place the arrow in the right location.
255     m_currentSelectionRects.reverse();
256
257     LOG(Services, "ServicesOverlayController - Selection rects changed - Now have %lu\n", rects.size());
258
259     buildSelectionHighlight();
260 #else
261     UNUSED_PARAM(rects);
262 #endif
263 }
264
265 void ServicesOverlayController::selectedTelephoneNumberRangesChanged()
266 {
267 #if PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED > 1090
268     LOG(Services, "ServicesOverlayController - Telephone number ranges changed\n");
269     buildPhoneNumberHighlights();
270 #else
271     UNUSED_PARAM(ranges);
272 #endif
273 }
274
275 bool ServicesOverlayController::mouseIsOverHighlight(Highlight& highlight, bool& mouseIsOverButton) const
276 {
277     Boolean onButton;
278     bool hovered = DDHighlightPointIsOnHighlight(highlight.ddHighlight(), (CGPoint)m_mousePosition, &onButton);
279     mouseIsOverButton = onButton;
280     return hovered;
281 }
282
283 std::chrono::milliseconds ServicesOverlayController::remainingTimeUntilHighlightShouldBeShown() const
284 {
285     // Highlight hysteresis is only for selection services, because telephone number highlights are already much more stable
286     // by virtue of being expanded to include the entire telephone number.
287     if (m_activeHighlight->type() == Highlight::Type::TelephoneNumber)
288         return std::chrono::milliseconds::zero();
289
290     std::chrono::steady_clock::duration minimumTimeUntilHighlightShouldBeShown = 200_ms;
291
292     auto now = std::chrono::steady_clock::now();
293     auto timeSinceLastSelectionChange = now - m_lastSelectionChangeTime;
294     auto timeSinceHighlightBecameActive = now - m_lastActiveHighlightChangeTime;
295
296     return std::chrono::duration_cast<std::chrono::milliseconds>(std::max(minimumTimeUntilHighlightShouldBeShown - timeSinceLastSelectionChange, minimumTimeUntilHighlightShouldBeShown - timeSinceHighlightBecameActive));
297 }
298
299 void ServicesOverlayController::repaintHighlightTimerFired(WebCore::Timer<ServicesOverlayController>&)
300 {
301     if (m_servicesOverlay)
302         m_servicesOverlay->setNeedsDisplay();
303 }
304
305 void ServicesOverlayController::drawHighlight(Highlight& highlight, WebCore::GraphicsContext& graphicsContext)
306 {
307     bool mouseIsOverButton;
308     if (!mouseIsOverHighlight(highlight, mouseIsOverButton)) {
309         LOG(Services, "ServicesOverlayController::drawHighlight - Mouse is not over highlight, so drawing nothing");
310         return;
311     }
312
313     auto remainingTimeUntilHighlightShouldBeShown = this->remainingTimeUntilHighlightShouldBeShown();
314     if (remainingTimeUntilHighlightShouldBeShown > std::chrono::steady_clock::duration::zero()) {
315         m_repaintHighlightTimer.startOneShot(remainingTimeUntilHighlightShouldBeShown);
316         return;
317     }
318
319     CGContextRef cgContext = graphicsContext.platformContext();
320     
321     CGLayerRef highlightLayer = DDHighlightGetLayerWithContext(highlight.ddHighlight(), cgContext);
322     CGRect highlightBoundingRect = DDHighlightGetBoundingRect(highlight.ddHighlight());
323
324     CGContextDrawLayerInRect(cgContext, highlightBoundingRect, highlightLayer);
325 }
326
327 void ServicesOverlayController::drawRect(PageOverlay* overlay, WebCore::GraphicsContext& graphicsContext, const WebCore::IntRect& dirtyRect)
328 {
329     bool mouseIsOverButton;
330     determineActiveHighlight(mouseIsOverButton);
331
332     if (m_activeHighlight)
333         drawHighlight(*m_activeHighlight, graphicsContext);
334 }
335
336 void ServicesOverlayController::clearActiveHighlight()
337 {
338     if (!m_activeHighlight)
339         return;
340
341     if (m_currentMouseDownOnButtonHighlight == m_activeHighlight)
342         m_currentMouseDownOnButtonHighlight = nullptr;
343     m_activeHighlight = nullptr;
344 }
345
346 void ServicesOverlayController::removeAllPotentialHighlightsOfType(Highlight::Type type)
347 {
348     Vector<RefPtr<Highlight>> highlightsToRemove;
349     for (auto& highlight : m_potentialHighlights) {
350         if (highlight->type() == type)
351             highlightsToRemove.append(highlight);
352     }
353
354     while (!highlightsToRemove.isEmpty())
355         m_potentialHighlights.remove(highlightsToRemove.takeLast());
356 }
357
358 void ServicesOverlayController::buildPhoneNumberHighlights()
359 {
360     removeAllPotentialHighlightsOfType(Highlight::Type::TelephoneNumber);
361
362     Frame* mainFrame = m_webPage.mainFrame();
363     FrameView& mainFrameView = *mainFrame->view();
364
365     for (Frame* frame = mainFrame; frame; frame = frame->tree().traverseNext()) {
366         auto& ranges = frame->editor().detectedTelephoneNumberRanges();
367         for (auto& range : ranges) {
368             // FIXME: This will choke if the range wraps around the edge of the view.
369             // What should we do in that case?
370             IntRect rect = textQuadsToBoundingRectForRange(*range);
371
372             // Convert to the main document's coordinate space.
373             // FIXME: It's a little crazy to call contentsToWindow and then windowToContents in order to get the right coordinate space.
374             // We should consider adding conversion functions to ScrollView for contentsToDocument(). Right now, contentsToRootView() is
375             // not equivalent to what we need when you have a topContentInset or a header banner.
376             FrameView* viewForRange = range->ownerDocument().view();
377             if (!viewForRange)
378                 continue;
379             rect.setLocation(mainFrameView.windowToContents(viewForRange->contentsToWindow(rect.location())));
380
381             CGRect cgRect = rect;
382             RetainPtr<DDHighlightRef> ddHighlight = adoptCF(DDHighlightCreateWithRectsInVisibleRectWithStyleAndDirection(nullptr, &cgRect, 1, mainFrameView.visibleContentRect(), DDHighlightOutlineWithArrow, YES, NSWritingDirectionNatural, NO, YES));
383
384             m_potentialHighlights.add(Highlight::createForTelephoneNumber(ddHighlight, range));
385         }
386     }
387
388     didRebuildPotentialHighlights();
389 }
390
391 void ServicesOverlayController::buildSelectionHighlight()
392 {
393     removeAllPotentialHighlightsOfType(Highlight::Type::Selection);
394
395     Vector<CGRect> cgRects;
396     cgRects.reserveCapacity(m_currentSelectionRects.size());
397
398     for (auto& rect : m_currentSelectionRects)
399         cgRects.append((CGRect)pixelSnappedIntRect(rect));
400
401     if (!cgRects.isEmpty()) {
402         CGRect visibleRect = m_webPage.corePage()->mainFrame().view()->visibleContentRect();
403         RetainPtr<DDHighlightRef> ddHighlight = adoptCF(DDHighlightCreateWithRectsInVisibleRectWithStyleAndDirection(nullptr, cgRects.begin(), cgRects.size(), visibleRect, DDHighlightNoOutlineWithArrow, YES, NSWritingDirectionNatural, NO, YES));
404         
405         m_potentialHighlights.add(Highlight::createForSelection(ddHighlight));
406     }
407
408     didRebuildPotentialHighlights();
409 }
410
411 bool ServicesOverlayController::hasRelevantSelectionServices()
412 {
413     return (m_isTextOnly && WebProcess::shared().hasSelectionServices()) || WebProcess::shared().hasRichContentServices();
414 }
415
416 void ServicesOverlayController::didRebuildPotentialHighlights()
417 {
418     if (m_potentialHighlights.isEmpty()) {
419         if (m_servicesOverlay)
420             m_webPage.uninstallPageOverlay(m_servicesOverlay);
421         return;
422     }
423
424     if (telephoneNumberRangesForFocusedFrame().isEmpty() && !hasRelevantSelectionServices())
425         return;
426
427     createOverlayIfNeeded();
428 }
429
430 void ServicesOverlayController::createOverlayIfNeeded()
431 {
432     if (m_servicesOverlay) {
433         m_servicesOverlay->setNeedsDisplay();
434         return;
435     }
436
437     RefPtr<PageOverlay> overlay = PageOverlay::create(this, PageOverlay::OverlayType::Document);
438     m_servicesOverlay = overlay.get();
439     m_webPage.installPageOverlay(overlay.release(), PageOverlay::FadeMode::DoNotFade);
440     m_servicesOverlay->setNeedsDisplay();
441 }
442
443 Vector<RefPtr<Range>> ServicesOverlayController::telephoneNumberRangesForFocusedFrame()
444 {
445     Page* page = m_webPage.corePage();
446     if (!page)
447         return Vector<RefPtr<Range>>();
448
449     return page->focusController().focusedOrMainFrame().editor().detectedTelephoneNumberRanges();
450 }
451
452 bool ServicesOverlayController::highlightsAreEquivalent(const Highlight* a, const Highlight* b)
453 {
454     if (a == b)
455         return true;
456
457     if (!a || !b)
458         return false;
459
460     if (a->type() == Highlight::Type::TelephoneNumber && b->type() == Highlight::Type::TelephoneNumber && areRangesEqual(a->range(), b->range()))
461         return true;
462
463     return false;
464 }
465
466 void ServicesOverlayController::determineActiveHighlight(bool& mouseIsOverActiveHighlightButton)
467 {
468     mouseIsOverActiveHighlightButton = false;
469
470     RefPtr<Highlight> oldActiveHighlight = m_activeHighlight.release();
471
472     for (auto& highlight : m_potentialHighlights) {
473         if (highlight->type() == Highlight::Type::Selection) {
474             // If we've already found a new active highlight, and it's
475             // a telephone number highlight, prefer that over this selection highlight.
476             if (m_activeHighlight && m_activeHighlight->type() == Highlight::Type::TelephoneNumber)
477                 continue;
478
479             // If this highlight has no compatible services, it can't be active, unless we have telephone number highlights to show in the combined menu.
480             if (telephoneNumberRangesForFocusedFrame().isEmpty() && !hasRelevantSelectionServices())
481                 continue;
482         }
483
484         // If this highlight isn't hovered, it can't be active.
485         bool mouseIsOverButton;
486         if (!mouseIsOverHighlight(*highlight, mouseIsOverButton))
487             continue;
488
489         m_activeHighlight = highlight;
490         mouseIsOverActiveHighlightButton = mouseIsOverButton;
491     }
492
493     if (!highlightsAreEquivalent(oldActiveHighlight.get(), m_activeHighlight.get())) {
494         m_lastActiveHighlightChangeTime = std::chrono::steady_clock::now();
495         m_servicesOverlay->setNeedsDisplay();
496         m_currentMouseDownOnButtonHighlight = nullptr;
497     }
498 }
499
500 bool ServicesOverlayController::mouseEvent(PageOverlay*, const WebMouseEvent& event)
501 {
502     m_mousePosition = m_webPage.corePage()->mainFrame().view()->rootViewToContents(event.position());
503
504     bool mouseIsOverActiveHighlightButton = false;
505     determineActiveHighlight(mouseIsOverActiveHighlightButton);
506
507     // Cancel the potential click if any button other than the left button changes state, and ignore the event.
508     if (event.button() != WebMouseEvent::LeftButton) {
509         m_currentMouseDownOnButtonHighlight = nullptr;
510         return false;
511     }
512
513     // If the mouse lifted while still over the highlight button that it went down on, then that is a click.
514     if (event.type() == WebEvent::MouseUp) {
515         RefPtr<Highlight> mouseDownHighlight = m_currentMouseDownOnButtonHighlight;
516         m_currentMouseDownOnButtonHighlight = nullptr;
517
518         if (mouseIsOverActiveHighlightButton && mouseDownHighlight && remainingTimeUntilHighlightShouldBeShown() <= std::chrono::steady_clock::duration::zero()) {
519             handleClick(m_mousePosition, *mouseDownHighlight);
520             return true;
521         }
522         
523         return false;
524     }
525
526     // If the mouse moved outside of the button tracking a potential click, stop tracking the click.
527     if (event.type() == WebEvent::MouseMove) {
528         if (m_currentMouseDownOnButtonHighlight && mouseIsOverActiveHighlightButton)
529             return true;
530
531         m_currentMouseDownOnButtonHighlight = nullptr;
532         return false;
533     }
534
535     // If the mouse went down over the active highlight's button, track this as a potential click.
536     if (event.type() == WebEvent::MouseDown) {
537         if (m_activeHighlight && mouseIsOverActiveHighlightButton) {
538             m_currentMouseDownOnButtonHighlight = m_activeHighlight;
539             m_servicesOverlay->setNeedsDisplay();
540             return true;
541         }
542
543         return false;
544     }
545
546     return false;
547 }
548
549 void ServicesOverlayController::handleClick(const WebCore::IntPoint& clickPoint, Highlight& highlight)
550 {
551     FrameView* frameView = m_webPage.mainFrameView();
552     if (!frameView)
553         return;
554
555     IntPoint windowPoint = frameView->contentsToWindow(clickPoint);
556
557     if (highlight.type() == Highlight::Type::Selection) {
558         auto telephoneNumberRanges = telephoneNumberRangesForFocusedFrame();
559         Vector<String> selectedTelephoneNumbers;
560         selectedTelephoneNumbers.reserveCapacity(telephoneNumberRanges.size());
561         for (auto& range : telephoneNumberRanges)
562             selectedTelephoneNumbers.append(range->text());
563
564         m_webPage.handleSelectionServiceClick(m_webPage.corePage()->mainFrame().selection(), selectedTelephoneNumbers, windowPoint);
565     } else if (highlight.type() == Highlight::Type::TelephoneNumber)
566         m_webPage.handleTelephoneNumberClick(highlight.range()->text(), windowPoint);
567 }
568
569 } // namespace WebKit
570
571 #endif // #if ENABLE(SERVICE_CONTROLS) || ENABLE(TELEPHONE_NUMBER_DETECTION) && PLATFORM(MAC)