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