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