Hovering over a slotted Text node clears hover state
[WebKit-https.git] / Source / WebKit / mac / WebView / WebImmediateActionController.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 "WebImmediateActionController.h"
27
28 #if PLATFORM(MAC)
29
30 #import "DOMElementInternal.h"
31 #import "DOMNodeInternal.h"
32 #import "DOMRangeInternal.h"
33 #import "WebElementDictionary.h"
34 #import "WebFrameInternal.h"
35 #import "WebHTMLView.h"
36 #import "WebHTMLViewInternal.h"
37 #import "WebUIDelegatePrivate.h"
38 #import "WebViewInternal.h"
39 #import <WebCore/DataDetection.h>
40 #import <WebCore/DataDetectorsSPI.h>
41 #import <WebCore/DictionaryLookup.h>
42 #import <WebCore/Editor.h>
43 #import <WebCore/EventHandler.h>
44 #import <WebCore/FocusController.h>
45 #import <WebCore/Frame.h>
46 #import <WebCore/FrameView.h>
47 #import <WebCore/GeometryUtilities.h>
48 #import <WebCore/HTMLConverter.h>
49 #import <WebCore/LookupSPI.h>
50 #import <WebCore/NSMenuSPI.h>
51 #import <WebCore/Page.h>
52 #import <WebCore/QuickLookMacSPI.h>
53 #import <WebCore/RenderElement.h>
54 #import <WebCore/RenderObject.h>
55 #import <WebCore/RuntimeApplicationChecks.h>
56 #import <WebCore/SoftLinking.h>
57 #import <WebCore/TextIndicator.h>
58 #import <objc/objc-class.h>
59 #import <objc/objc.h>
60
61 SOFT_LINK_FRAMEWORK_IN_UMBRELLA(Quartz, QuickLookUI)
62 SOFT_LINK_CLASS(QuickLookUI, QLPreviewMenuItem)
63
64 @interface WebImmediateActionController () <QLPreviewMenuItemDelegate>
65 @end
66
67 @interface WebAnimationController : NSObject <NSImmediateActionAnimationController>
68 @end
69
70 @implementation WebAnimationController
71 @end
72
73 using namespace WebCore;
74
75 @implementation WebImmediateActionController
76
77 - (instancetype)initWithWebView:(WebView *)webView recognizer:(NSImmediateActionGestureRecognizer *)immediateActionRecognizer
78 {
79     if (!(self = [super init]))
80         return nil;
81
82     _webView = webView;
83     _type = WebImmediateActionNone;
84     _immediateActionRecognizer = immediateActionRecognizer;
85
86     return self;
87 }
88
89 - (void)webViewClosed
90 {
91     _webView = nil;
92
93     id animationController = [_immediateActionRecognizer animationController];
94     if ([animationController isKindOfClass:NSClassFromString(@"QLPreviewMenuItem")]) {
95         QLPreviewMenuItem *menuItem = (QLPreviewMenuItem *)animationController;
96         menuItem.delegate = nil;
97     }
98
99     _immediateActionRecognizer = nil;
100     _currentActionContext = nil;
101 }
102
103 - (void)webView:(WebView *)webView didHandleScrollWheel:(NSEvent *)event
104 {
105     [_currentQLPreviewMenuItem close];
106     [self _clearImmediateActionState];
107     [_webView _clearTextIndicatorWithAnimation:TextIndicatorWindowDismissalAnimation::None];
108 }
109
110 - (NSImmediateActionGestureRecognizer *)immediateActionRecognizer
111 {
112     return _immediateActionRecognizer.get();
113 }
114
115 - (void)_cancelImmediateAction
116 {
117     // Reset the recognizer by turning it off and on again.
118     [_immediateActionRecognizer setEnabled:NO];
119     [_immediateActionRecognizer setEnabled:YES];
120
121     [self _clearImmediateActionState];
122     [_webView _clearTextIndicatorWithAnimation:TextIndicatorWindowDismissalAnimation::FadeOut];
123 }
124
125 - (void)_clearImmediateActionState
126 {
127     if (!DataDetectorsLibrary())
128         return;
129
130     DDActionsManager *actionsManager = [getDDActionsManagerClass() sharedManager];
131     if ([actionsManager respondsToSelector:@selector(requestBubbleClosureUnanchorOnFailure:)])
132         [actionsManager requestBubbleClosureUnanchorOnFailure:YES];
133
134     if (_currentActionContext && _hasActivatedActionContext) {
135         _hasActivatedActionContext = NO;
136         [getDDActionsManagerClass() didUseActions];
137     }
138
139     _type = WebImmediateActionNone;
140     _currentActionContext = nil;
141     _currentQLPreviewMenuItem = nil;
142     _contentPreventsDefault = NO;
143 }
144
145 - (void)performHitTestAtPoint:(NSPoint)viewPoint
146 {
147     Frame* coreFrame = core([[[[_webView _selectedOrMainFrame] frameView] documentView] _frame]);
148     if (!coreFrame)
149         return;
150     _hitTestResult = coreFrame->eventHandler().hitTestResultAtPoint(IntPoint(viewPoint));
151     coreFrame->eventHandler().setImmediateActionStage(ImmediateActionStage::PerformedHitTest);
152
153     if (Element* element = _hitTestResult.targetElement())
154         _contentPreventsDefault = element->dispatchMouseForceWillBegin();
155 }
156
157 #pragma mark NSImmediateActionGestureRecognizerDelegate
158
159 - (void)immediateActionRecognizerWillPrepare:(NSImmediateActionGestureRecognizer *)immediateActionRecognizer
160 {
161     if (!_webView)
162         return;
163
164     NSView *documentView = [[[_webView _selectedOrMainFrame] frameView] documentView];
165     if (![documentView isKindOfClass:[WebHTMLView class]]) {
166         [self _cancelImmediateAction];
167         return;
168     }
169
170     if (immediateActionRecognizer != _immediateActionRecognizer)
171         return;
172
173     [_webView _setMaintainsInactiveSelection:YES];
174
175     NSPoint locationInDocumentView = [immediateActionRecognizer locationInView:documentView];
176     [self performHitTestAtPoint:locationInDocumentView];
177     [self _updateImmediateActionItem];
178
179     if (![_immediateActionRecognizer animationController]) {
180         // FIXME: We should be able to remove the dispatch_async when rdar://problem/19502927 is resolved.
181         dispatch_async(dispatch_get_main_queue(), ^{
182             [self _cancelImmediateAction];
183         });
184     }
185 }
186
187 - (void)immediateActionRecognizerWillBeginAnimation:(NSImmediateActionGestureRecognizer *)immediateActionRecognizer
188 {
189     if (!DataDetectorsLibrary())
190         return;
191
192     if (immediateActionRecognizer != _immediateActionRecognizer)
193         return;
194
195     if (_currentActionContext) {
196         _hasActivatedActionContext = YES;
197         if (![getDDActionsManagerClass() shouldUseActionsWithContext:_currentActionContext.get()])
198             [self _cancelImmediateAction];
199     }
200 }
201
202 - (void)immediateActionRecognizerDidUpdateAnimation:(NSImmediateActionGestureRecognizer *)immediateActionRecognizer
203 {
204     if (immediateActionRecognizer != _immediateActionRecognizer)
205         return;
206
207     Frame* coreFrame = core([[[[_webView _selectedOrMainFrame] frameView] documentView] _frame]);
208     if (!coreFrame)
209         return;
210     coreFrame->eventHandler().setImmediateActionStage(ImmediateActionStage::ActionUpdated);
211     if (_contentPreventsDefault)
212         return;
213
214     [_webView _setTextIndicatorAnimationProgress:[immediateActionRecognizer animationProgress]];
215 }
216
217 - (void)immediateActionRecognizerDidCancelAnimation:(NSImmediateActionGestureRecognizer *)immediateActionRecognizer
218 {
219     if (immediateActionRecognizer != _immediateActionRecognizer)
220         return;
221
222     NSView *documentView = [[[_webView _selectedOrMainFrame] frameView] documentView];
223     if (![documentView isKindOfClass:[WebHTMLView class]])
224         return;
225
226     Frame* coreFrame = core([(WebHTMLView *)documentView _frame]);
227     if (coreFrame) {
228         ImmediateActionStage lastStage = coreFrame->eventHandler().immediateActionStage();
229         if (lastStage == ImmediateActionStage::ActionUpdated)
230             coreFrame->eventHandler().setImmediateActionStage(ImmediateActionStage::ActionCancelledAfterUpdate);
231         else
232             coreFrame->eventHandler().setImmediateActionStage(ImmediateActionStage::ActionCancelledWithoutUpdate);
233     }
234
235     [_webView _setTextIndicatorAnimationProgress:0];
236     [self _clearImmediateActionState];
237     [_webView _clearTextIndicatorWithAnimation:TextIndicatorWindowDismissalAnimation::None];
238     [_webView _setMaintainsInactiveSelection:NO];
239 }
240
241 - (void)immediateActionRecognizerDidCompleteAnimation:(NSImmediateActionGestureRecognizer *)immediateActionRecognizer
242 {
243     if (immediateActionRecognizer != _immediateActionRecognizer)
244         return;
245
246     Frame* coreFrame = core([[[[_webView _selectedOrMainFrame] frameView] documentView] _frame]);
247     if (!coreFrame)
248         return;
249     coreFrame->eventHandler().setImmediateActionStage(ImmediateActionStage::ActionCompleted);
250
251     [_webView _setTextIndicatorAnimationProgress:1];
252     [_webView _setMaintainsInactiveSelection:NO];
253 }
254
255 #pragma mark Immediate actions
256
257 - (id <NSImmediateActionAnimationController>)_defaultAnimationController
258 {
259     if (_contentPreventsDefault)
260         return [[[WebAnimationController alloc] init] autorelease];
261
262     NSURL *url = _hitTestResult.absoluteLinkURL();
263     NSString *absoluteURLString = [url absoluteString];
264     if (url && _hitTestResult.URLElement()) {
265         if (protocolIs(absoluteURLString, "mailto")) {
266             _type = WebImmediateActionMailtoLink;
267             return [self _animationControllerForDataDetectedLink];
268         }
269
270         if (protocolIs(absoluteURLString, "tel")) {
271             _type = WebImmediateActionTelLink;
272             return [self _animationControllerForDataDetectedLink];
273         }
274
275         if (WebCore::protocolIsInHTTPFamily(absoluteURLString)) {
276             _type = WebImmediateActionLinkPreview;
277
278             RefPtr<Range> linkRange = rangeOfContents(*_hitTestResult.URLElement());
279             auto indicator = TextIndicator::createWithRange(*linkRange, TextIndicatorOptionUseBoundingRectAndPaintAllContentForComplexRanges, TextIndicatorPresentationTransition::FadeIn);
280             if (indicator)
281                 [_webView _setTextIndicator:*indicator withLifetime:TextIndicatorWindowLifetime::Permanent];
282
283             QLPreviewMenuItem *item = [NSMenuItem standardQuickLookMenuItem];
284             item.previewStyle = QLPreviewStylePopover;
285             item.delegate = self;
286             _currentQLPreviewMenuItem = item;
287             return (id <NSImmediateActionAnimationController>)item;
288         }
289     }
290
291     Node* node = _hitTestResult.innerNode();
292     if ((node && node->isTextNode()) || _hitTestResult.isOverTextInsideFormControlElement()) {
293         if (auto animationController = [self _animationControllerForDataDetectedText]) {
294             _type = WebImmediateActionDataDetectedItem;
295             return animationController;
296         }
297
298         if (auto animationController = [self _animationControllerForText]) {
299             _type = WebImmediateActionText;
300             return animationController;
301         }
302     }
303
304     return nil;
305 }
306
307 - (void)_updateImmediateActionItem
308 {
309     _type = WebImmediateActionNone;
310
311     id <NSImmediateActionAnimationController> defaultAnimationController = [self _defaultAnimationController];
312
313     if (_contentPreventsDefault) {
314         [_immediateActionRecognizer setAnimationController:defaultAnimationController];
315         return;
316     }
317
318     // Allow clients the opportunity to override the default immediate action.
319     id customClientAnimationController = nil;
320     if ([[_webView UIDelegate] respondsToSelector:@selector(_webView:immediateActionAnimationControllerForHitTestResult:withType:)]) {
321         RetainPtr<WebElementDictionary> webHitTestResult = adoptNS([[WebElementDictionary alloc] initWithHitTestResult:_hitTestResult]);
322         customClientAnimationController = [(id)[_webView UIDelegate] _webView:_webView immediateActionAnimationControllerForHitTestResult:webHitTestResult.get() withType:_type];
323     }
324
325     if (customClientAnimationController == [NSNull null]) {
326         [self _cancelImmediateAction];
327         return;
328     }
329
330 #if PLATFORM(MAC)
331     // FIXME: We should not permanently disable this for iTunes. rdar://problem/19461358
332     if (MacApplication::isITunes()) {
333         [self _cancelImmediateAction];
334         return;
335     }
336 #endif
337
338     if (customClientAnimationController && [customClientAnimationController conformsToProtocol:@protocol(NSImmediateActionAnimationController)])
339         [_immediateActionRecognizer setAnimationController:(id <NSImmediateActionAnimationController>)customClientAnimationController];
340     else
341         [_immediateActionRecognizer setAnimationController:defaultAnimationController];
342 }
343
344 #pragma mark QLPreviewMenuItemDelegate implementation
345
346 - (NSView *)menuItem:(NSMenuItem *)menuItem viewAtScreenPoint:(NSPoint)screenPoint
347 {
348     return _webView;
349 }
350
351 - (id<QLPreviewItem>)menuItem:(NSMenuItem *)menuItem previewItemAtPoint:(NSPoint)point
352 {
353     if (!_webView)
354         return nil;
355
356     return _hitTestResult.absoluteLinkURL();
357 }
358
359 - (NSRectEdge)menuItem:(NSMenuItem *)menuItem preferredEdgeForPoint:(NSPoint)point
360 {
361     return NSMaxYEdge;
362 }
363
364 - (void)menuItemDidClose:(NSMenuItem *)menuItem
365 {
366     [self _clearImmediateActionState];
367     [_webView _clearTextIndicatorWithAnimation:TextIndicatorWindowDismissalAnimation::FadeOut];
368 }
369
370 static IntRect elementBoundingBoxInWindowCoordinatesFromNode(Node* node)
371 {
372     if (!node)
373         return IntRect();
374
375     Frame* frame = node->document().frame();
376     if (!frame)
377         return IntRect();
378
379     FrameView* view = frame->view();
380     if (!view)
381         return IntRect();
382
383     RenderObject* renderer = node->renderer();
384     if (!renderer)
385         return IntRect();
386
387     return view->contentsToWindow(renderer->absoluteBoundingBoxRect());
388 }
389
390 - (NSRect)menuItem:(NSMenuItem *)menuItem itemFrameForPoint:(NSPoint)point
391 {
392     if (!_webView)
393         return NSZeroRect;
394
395     Node* node = _hitTestResult.innerNode();
396     if (!node)
397         return NSZeroRect;
398
399     return elementBoundingBoxInWindowCoordinatesFromNode(node);
400 }
401
402 - (NSSize)menuItem:(NSMenuItem *)menuItem maxSizeForPoint:(NSPoint)point
403 {
404     if (!_webView)
405         return NSZeroSize;
406
407     NSSize screenSize = _webView.window.screen.frame.size;
408     FloatRect largestRect = largestRectWithAspectRatioInsideRect(screenSize.width / screenSize.height, _webView.bounds);
409     return NSMakeSize(largestRect.width() * 0.75, largestRect.height() * 0.75);
410 }
411
412 #pragma mark Data Detectors actions
413
414 - (id <NSImmediateActionAnimationController>)_animationControllerForDataDetectedText
415 {
416     if (!DataDetectorsLibrary())
417         return nil;
418
419     RefPtr<Range> detectedDataRange;
420     FloatRect detectedDataBoundingBox;
421     RetainPtr<DDActionContext> actionContext;
422
423     if ([[_webView UIDelegate] respondsToSelector:@selector(_webView:actionContextForHitTestResult:range:)]) {
424         RetainPtr<WebElementDictionary> hitTestDictionary = adoptNS([[WebElementDictionary alloc] initWithHitTestResult:_hitTestResult]);
425
426         DOMRange *customDataDetectorsRange;
427         actionContext = [(id)[_webView UIDelegate] _webView:_webView actionContextForHitTestResult:hitTestDictionary.get() range:&customDataDetectorsRange];
428
429         if (actionContext && customDataDetectorsRange)
430             detectedDataRange = core(customDataDetectorsRange);
431     }
432
433     // If the client didn't give us an action context, try to scan around the hit point.
434     if (!actionContext || !detectedDataRange)
435         actionContext = DataDetection::detectItemAroundHitTestResult(_hitTestResult, detectedDataBoundingBox, detectedDataRange);
436
437     if (!actionContext || !detectedDataRange)
438         return nil;
439
440     [actionContext setAltMode:YES];
441     [actionContext setImmediate:YES];
442     if ([[getDDActionsManagerClass() sharedManager] respondsToSelector:@selector(hasActionsForResult:actionContext:)]) {
443         if (![[getDDActionsManagerClass() sharedManager] hasActionsForResult:[actionContext mainResult] actionContext:actionContext.get()])
444             return nil;
445     }
446
447     auto indicator = TextIndicator::createWithRange(*detectedDataRange, TextIndicatorOptionDefault, TextIndicatorPresentationTransition::FadeIn);
448
449     _currentActionContext = [actionContext contextForView:_webView altMode:YES interactionStartedHandler:^() {
450     } interactionChangedHandler:^() {
451         if (indicator)
452             [_webView _setTextIndicator:*indicator withLifetime:TextIndicatorWindowLifetime::Permanent];
453     } interactionStoppedHandler:^() {
454         [_webView _clearTextIndicatorWithAnimation:TextIndicatorWindowDismissalAnimation::FadeOut];
455     }];
456
457     [_currentActionContext setHighlightFrame:[_webView.window convertRectToScreen:detectedDataBoundingBox]];
458
459     NSArray *menuItems = [[getDDActionsManagerClass() sharedManager] menuItemsForResult:[_currentActionContext mainResult] actionContext:_currentActionContext.get()];
460     if (menuItems.count != 1)
461         return nil;
462
463     return menuItems.lastObject;
464 }
465
466 - (id <NSImmediateActionAnimationController>)_animationControllerForDataDetectedLink
467 {
468     if (!DataDetectorsLibrary())
469         return nil;
470
471     RetainPtr<DDActionContext> actionContext = adoptNS([allocDDActionContextInstance() init]);
472
473     if (!actionContext)
474         return nil;
475
476     [actionContext setAltMode:YES];
477     [actionContext setImmediate:YES];
478
479     RefPtr<Range> linkRange = rangeOfContents(*_hitTestResult.URLElement());
480     if (!linkRange)
481         return nullptr;
482     auto indicator = TextIndicator::createWithRange(*linkRange, TextIndicatorOptionDefault, TextIndicatorPresentationTransition::FadeIn);
483
484     _currentActionContext = [actionContext contextForView:_webView altMode:YES interactionStartedHandler:^() {
485     } interactionChangedHandler:^() {
486         if (indicator)
487             [_webView _setTextIndicator:*indicator withLifetime:TextIndicatorWindowLifetime::Permanent];
488     } interactionStoppedHandler:^() {
489         [_webView _clearTextIndicatorWithAnimation:TextIndicatorWindowDismissalAnimation::FadeOut];
490     }];
491
492     [_currentActionContext setHighlightFrame:[_webView.window convertRectToScreen:elementBoundingBoxInWindowCoordinatesFromNode(_hitTestResult.URLElement())]];
493
494     NSArray *menuItems = [[getDDActionsManagerClass() sharedManager] menuItemsForTargetURL:_hitTestResult.absoluteLinkURL() actionContext:_currentActionContext.get()];
495     if (menuItems.count != 1)
496         return nil;
497     
498     return menuItems.lastObject;
499 }
500
501 #pragma mark Text action
502
503 + (DictionaryPopupInfo)_dictionaryPopupInfoForRange:(Range&)range inFrame:(Frame*)frame withLookupOptions:(NSDictionary *)lookupOptions indicatorOptions:(TextIndicatorOptions)indicatorOptions transition:(TextIndicatorPresentationTransition)presentationTransition
504 {
505     Editor& editor = frame->editor();
506     editor.setIsGettingDictionaryPopupInfo(true);
507
508     // Dictionary API will accept a whitespace-only string and display UI as if it were real text,
509     // so bail out early to avoid that.
510     DictionaryPopupInfo popupInfo;
511     if (range.text().stripWhiteSpace().isEmpty()) {
512         editor.setIsGettingDictionaryPopupInfo(false);
513         return popupInfo;
514     }
515
516     RenderObject* renderer = range.startContainer().renderer();
517     const RenderStyle& style = renderer->style();
518
519     Vector<FloatQuad> quads;
520     range.absoluteTextQuads(quads);
521     if (quads.isEmpty()) {
522         editor.setIsGettingDictionaryPopupInfo(false);
523         return popupInfo;
524     }
525
526     IntRect rangeRect = frame->view()->contentsToWindow(quads[0].enclosingBoundingBox());
527
528     popupInfo.origin = NSMakePoint(rangeRect.x(), rangeRect.y() + (style.fontMetrics().descent() * frame->page()->pageScaleFactor()));
529     popupInfo.options = lookupOptions;
530
531     NSAttributedString *nsAttributedString = editingAttributedStringFromRange(range, IncludeImagesInAttributedString::No);
532     RetainPtr<NSMutableAttributedString> scaledNSAttributedString = adoptNS([[NSMutableAttributedString alloc] initWithString:[nsAttributedString string]]);
533     NSFontManager *fontManager = [NSFontManager sharedFontManager];
534
535     [nsAttributedString enumerateAttributesInRange:NSMakeRange(0, [nsAttributedString length]) options:0 usingBlock:^(NSDictionary *attributes, NSRange attributeRange, BOOL *stop) {
536         RetainPtr<NSMutableDictionary> scaledAttributes = adoptNS([attributes mutableCopy]);
537
538         NSFont *font = [scaledAttributes objectForKey:NSFontAttributeName];
539         if (font) {
540             font = [fontManager convertFont:font toSize:[font pointSize] * frame->page()->pageScaleFactor()];
541             [scaledAttributes setObject:font forKey:NSFontAttributeName];
542         }
543
544         [scaledNSAttributedString addAttributes:scaledAttributes.get() range:attributeRange];
545     }];
546
547     popupInfo.attributedString = scaledNSAttributedString.get();
548
549     if (auto textIndicator = TextIndicator::createWithRange(range, indicatorOptions, presentationTransition))
550         popupInfo.textIndicator = textIndicator->data();
551
552     editor.setIsGettingDictionaryPopupInfo(false);
553     return popupInfo;
554 }
555
556 - (id<NSImmediateActionAnimationController>)_animationControllerForText
557 {
558     if (!getLULookupDefinitionModuleClass())
559         return nil;
560
561     Node* node = _hitTestResult.innerNode();
562     if (!node)
563         return nil;
564
565     Frame* frame = node->document().frame();
566     if (!frame)
567         return nil;
568
569     NSDictionary *options = nil;
570     RefPtr<Range> dictionaryRange = DictionaryLookup::rangeAtHitTestResult(_hitTestResult, &options);
571     if (!dictionaryRange)
572         return nil;
573
574     DictionaryPopupInfo dictionaryPopupInfo = [WebImmediateActionController _dictionaryPopupInfoForRange:*dictionaryRange inFrame:frame withLookupOptions:options indicatorOptions:TextIndicatorOptionDefault transition: TextIndicatorPresentationTransition::FadeIn];
575     if (!dictionaryPopupInfo.attributedString)
576         return nil;
577
578     return [_webView _animationControllerForDictionaryLookupPopupInfo:dictionaryPopupInfo];
579 }
580
581 @end
582
583 #endif // PLATFORM(MAC)