| /* |
| * Copyright (C) 2014-2019 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
| * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
| * THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #import "WebImmediateActionController.h" |
| |
| #if PLATFORM(MAC) |
| |
| #import "DOMElementInternal.h" |
| #import "DOMNodeInternal.h" |
| #import "DOMRangeInternal.h" |
| #import "WebElementDictionary.h" |
| #import "WebFrameInternal.h" |
| #import "WebFrameView.h" |
| #import "WebHTMLViewInternal.h" |
| #import "WebUIDelegatePrivate.h" |
| #import "WebViewInternal.h" |
| #import <WebCore/DataDetection.h> |
| #import <WebCore/DictionaryLookup.h> |
| #import <WebCore/Editor.h> |
| #import <WebCore/EventHandler.h> |
| #import <WebCore/FocusController.h> |
| #import <WebCore/FontMetrics.h> |
| #import <WebCore/Frame.h> |
| #import <WebCore/FrameView.h> |
| #import <WebCore/GeometryUtilities.h> |
| #import <WebCore/HTMLConverter.h> |
| #import <WebCore/NodeRenderStyle.h> |
| #import <WebCore/Page.h> |
| #import <WebCore/Range.h> |
| #import <WebCore/RenderElement.h> |
| #import <WebCore/RenderObject.h> |
| #import <WebCore/RuntimeApplicationChecks.h> |
| #import <WebCore/TextIndicator.h> |
| #import <WebCore/TextIterator.h> |
| #import <objc/objc-class.h> |
| #import <objc/objc.h> |
| #import <pal/spi/mac/LookupSPI.h> |
| #import <pal/spi/mac/NSMenuSPI.h> |
| #import <pal/mac/DataDetectorsSoftLink.h> |
| #import <pal/mac/QuickLookUISoftLink.h> |
| |
| @interface WebImmediateActionController () <QLPreviewMenuItemDelegate> |
| @end |
| |
| @interface WebAnimationController : NSObject <NSImmediateActionAnimationController> |
| @end |
| |
| @implementation WebAnimationController |
| @end |
| |
| @implementation WebImmediateActionController |
| |
| - (instancetype)initWithWebView:(WebView *)webView recognizer:(NSImmediateActionGestureRecognizer *)immediateActionRecognizer |
| { |
| if (!(self = [super init])) |
| return nil; |
| |
| _webView = webView; |
| _type = WebImmediateActionNone; |
| _immediateActionRecognizer = immediateActionRecognizer; |
| |
| return self; |
| } |
| |
| - (void)webViewClosed |
| { |
| _webView = nil; |
| |
| id animationController = [_immediateActionRecognizer animationController]; |
| if (PAL::isQuickLookUIFrameworkAvailable() && [animationController isKindOfClass:PAL::getQLPreviewMenuItemClass()]) { |
| QLPreviewMenuItem *menuItem = (QLPreviewMenuItem *)animationController; |
| menuItem.delegate = nil; |
| } |
| |
| _immediateActionRecognizer = nil; |
| _currentActionContext = nil; |
| } |
| |
| - (BOOL)isEnabled |
| { |
| return [_immediateActionRecognizer isEnabled]; |
| } |
| |
| - (void)setEnabled:(BOOL)enabled |
| { |
| if (enabled == [_immediateActionRecognizer isEnabled]) |
| return; |
| |
| [_immediateActionRecognizer setEnabled:enabled]; |
| |
| if (![_immediateActionRecognizer isEnabled]) |
| [self _cancelImmediateAction]; |
| } |
| |
| - (void)webView:(WebView *)webView didHandleScrollWheel:(NSEvent *)event |
| { |
| [_currentQLPreviewMenuItem close]; |
| [self _clearImmediateActionState]; |
| [_webView _clearTextIndicatorWithAnimation:WebCore::TextIndicatorDismissalAnimation::None]; |
| } |
| |
| - (NSImmediateActionGestureRecognizer *)immediateActionRecognizer |
| { |
| return _immediateActionRecognizer.get(); |
| } |
| |
| - (void)_cancelImmediateAction |
| { |
| // Reset the recognizer by turning it off and on again. |
| if ([_immediateActionRecognizer isEnabled]) { |
| [_immediateActionRecognizer setEnabled:NO]; |
| [_immediateActionRecognizer setEnabled:YES]; |
| } |
| |
| [self _clearImmediateActionState]; |
| [_webView _clearTextIndicatorWithAnimation:WebCore::TextIndicatorDismissalAnimation::FadeOut]; |
| } |
| |
| - (void)_clearImmediateActionState |
| { |
| if (!PAL::isDataDetectorsFrameworkAvailable()) |
| return; |
| |
| DDActionsManager *actionsManager = [PAL::getDDActionsManagerClass() sharedManager]; |
| [actionsManager requestBubbleClosureUnanchorOnFailure:YES]; |
| |
| if (_currentActionContext && _hasActivatedActionContext) { |
| _hasActivatedActionContext = NO; |
| [PAL::getDDActionsManagerClass() didUseActions]; |
| } |
| |
| _type = WebImmediateActionNone; |
| _currentActionContext = nil; |
| _currentQLPreviewMenuItem = nil; |
| _contentPreventsDefault = NO; |
| } |
| |
| - (void)performHitTestAtPoint:(NSPoint)viewPoint |
| { |
| using namespace WebCore; |
| |
| auto* coreFrame = core([_webView _selectedOrMainFrame]); |
| if (!coreFrame) |
| return; |
| |
| constexpr OptionSet<HitTestRequest::Type> hitType { HitTestRequest::Type::ReadOnly, HitTestRequest::Type::Active, HitTestRequest::Type::DisallowUserAgentShadowContent, HitTestRequest::Type::AllowChildFrameContent }; |
| _hitTestResult = coreFrame->eventHandler().hitTestResultAtPoint(WebCore::IntPoint(viewPoint), hitType); |
| coreFrame->mainFrame().eventHandler().setImmediateActionStage(WebCore::ImmediateActionStage::PerformedHitTest); |
| |
| if (auto* element = _hitTestResult.targetElement()) |
| _contentPreventsDefault = element->dispatchMouseForceWillBegin(); |
| } |
| |
| #pragma mark NSImmediateActionGestureRecognizerDelegate |
| |
| - (void)immediateActionRecognizerWillPrepare:(NSImmediateActionGestureRecognizer *)immediateActionRecognizer |
| { |
| if (!_webView) |
| return; |
| |
| NSView *documentView = [[[_webView _selectedOrMainFrame] frameView] documentView]; |
| if (![documentView isKindOfClass:[WebHTMLView class]]) { |
| [self _cancelImmediateAction]; |
| return; |
| } |
| |
| if (immediateActionRecognizer != _immediateActionRecognizer) |
| return; |
| |
| [_webView _setMaintainsInactiveSelection:YES]; |
| |
| NSPoint locationInDocumentView = [immediateActionRecognizer locationInView:documentView]; |
| [self performHitTestAtPoint:locationInDocumentView]; |
| [self _updateImmediateActionItem]; |
| |
| if (![_immediateActionRecognizer animationController]) { |
| // FIXME: We should be able to remove the dispatch_async when rdar://problem/19502927 is resolved. |
| RunLoop::main().dispatch([self, strongSelf = retainPtr(self)] { |
| [self _cancelImmediateAction]; |
| }); |
| } |
| } |
| |
| - (void)immediateActionRecognizerWillBeginAnimation:(NSImmediateActionGestureRecognizer *)immediateActionRecognizer |
| { |
| if (!PAL::isDataDetectorsFrameworkAvailable()) |
| return; |
| |
| if (immediateActionRecognizer != _immediateActionRecognizer) |
| return; |
| |
| if (_currentActionContext) { |
| _hasActivatedActionContext = YES; |
| if (![PAL::getDDActionsManagerClass() shouldUseActionsWithContext:_currentActionContext.get()]) |
| [self _cancelImmediateAction]; |
| } |
| } |
| |
| - (void)immediateActionRecognizerDidUpdateAnimation:(NSImmediateActionGestureRecognizer *)immediateActionRecognizer |
| { |
| if (immediateActionRecognizer != _immediateActionRecognizer) |
| return; |
| |
| if (WebCore::Frame* coreFrame = [_webView _mainCoreFrame]) |
| coreFrame->eventHandler().setImmediateActionStage(WebCore::ImmediateActionStage::ActionUpdated); |
| |
| if (_contentPreventsDefault) |
| return; |
| |
| [_webView _setTextIndicatorAnimationProgress:[immediateActionRecognizer animationProgress]]; |
| } |
| |
| - (void)immediateActionRecognizerDidCancelAnimation:(NSImmediateActionGestureRecognizer *)immediateActionRecognizer |
| { |
| if (immediateActionRecognizer != _immediateActionRecognizer) |
| return; |
| |
| if (WebCore::Frame* coreFrame = [_webView _mainCoreFrame]) { |
| WebCore::ImmediateActionStage lastStage = coreFrame->eventHandler().immediateActionStage(); |
| if (lastStage == WebCore::ImmediateActionStage::ActionUpdated) |
| coreFrame->eventHandler().setImmediateActionStage(WebCore::ImmediateActionStage::ActionCancelledAfterUpdate); |
| else |
| coreFrame->eventHandler().setImmediateActionStage(WebCore::ImmediateActionStage::ActionCancelledWithoutUpdate); |
| } |
| |
| [_webView _setTextIndicatorAnimationProgress:0]; |
| [self _clearImmediateActionState]; |
| [_webView _clearTextIndicatorWithAnimation:WebCore::TextIndicatorDismissalAnimation::None]; |
| [_webView _setMaintainsInactiveSelection:NO]; |
| } |
| |
| - (void)immediateActionRecognizerDidCompleteAnimation:(NSImmediateActionGestureRecognizer *)immediateActionRecognizer |
| { |
| if (immediateActionRecognizer != _immediateActionRecognizer) |
| return; |
| |
| if (WebCore::Frame* coreFrame = [_webView _mainCoreFrame]) |
| coreFrame->eventHandler().setImmediateActionStage(WebCore::ImmediateActionStage::ActionCompleted); |
| |
| [_webView _setTextIndicatorAnimationProgress:1]; |
| [_webView _setMaintainsInactiveSelection:NO]; |
| } |
| |
| #pragma mark Immediate actions |
| |
| - (id <NSImmediateActionAnimationController>)_defaultAnimationController |
| { |
| if (_contentPreventsDefault) |
| return adoptNS([[WebAnimationController alloc] init]).autorelease(); |
| |
| NSURL *url = _hitTestResult.absoluteLinkURL(); |
| String absoluteURLString = [url absoluteString]; |
| if (url && _hitTestResult.URLElement()) { |
| if (WTF::protocolIs(absoluteURLString, "mailto")) { |
| _type = WebImmediateActionMailtoLink; |
| return [self _animationControllerForDataDetectedLink]; |
| } |
| |
| if (WTF::protocolIs(absoluteURLString, "tel")) { |
| _type = WebImmediateActionTelLink; |
| return [self _animationControllerForDataDetectedLink]; |
| } |
| |
| if (WTF::protocolIsInHTTPFamily(absoluteURLString)) { |
| _type = WebImmediateActionLinkPreview; |
| |
| auto linkRange = makeRangeSelectingNodeContents(*_hitTestResult.URLElement()); |
| auto indicator = WebCore::TextIndicator::createWithRange(linkRange, { WebCore::TextIndicatorOption::UseBoundingRectAndPaintAllContentForComplexRanges }, WebCore::TextIndicatorPresentationTransition::FadeIn); |
| if (indicator) |
| [_webView _setTextIndicator:*indicator withLifetime:WebCore::TextIndicatorLifetime::Permanent]; |
| |
| QLPreviewMenuItem *item = [NSMenuItem standardQuickLookMenuItem]; |
| item.previewStyle = QLPreviewStylePopover; |
| item.delegate = self; |
| _currentQLPreviewMenuItem = item; |
| return (id <NSImmediateActionAnimationController>)item; |
| } |
| } |
| |
| WebCore::Node* node = _hitTestResult.innerNode(); |
| if ((node && node->isTextNode()) || _hitTestResult.isOverTextInsideFormControlElement()) { |
| if (auto animationController = [self _animationControllerForDataDetectedText]) { |
| _type = WebImmediateActionDataDetectedItem; |
| return animationController; |
| } |
| |
| if (auto animationController = [self _animationControllerForText]) { |
| _type = WebImmediateActionText; |
| return animationController; |
| } |
| } |
| |
| return nil; |
| } |
| |
| - (void)_updateImmediateActionItem |
| { |
| _type = WebImmediateActionNone; |
| |
| id <NSImmediateActionAnimationController> defaultAnimationController = [self _defaultAnimationController]; |
| |
| if (_contentPreventsDefault) { |
| [_immediateActionRecognizer setAnimationController:defaultAnimationController]; |
| return; |
| } |
| |
| // Allow clients the opportunity to override the default immediate action. |
| id customClientAnimationController = nil; |
| if ([[_webView UIDelegate] respondsToSelector:@selector(_webView:immediateActionAnimationControllerForHitTestResult:withType:)]) { |
| RetainPtr<WebElementDictionary> webHitTestResult = adoptNS([[WebElementDictionary alloc] initWithHitTestResult:_hitTestResult]); |
| customClientAnimationController = [(id)[_webView UIDelegate] _webView:_webView immediateActionAnimationControllerForHitTestResult:webHitTestResult.get() withType:_type]; |
| } |
| |
| if (customClientAnimationController == [NSNull null]) { |
| [self _cancelImmediateAction]; |
| return; |
| } |
| |
| #if PLATFORM(MAC) |
| // FIXME: We should not permanently disable this for iTunes. rdar://problem/19461358 |
| if (WebCore::MacApplication::isITunes()) { |
| [self _cancelImmediateAction]; |
| return; |
| } |
| #endif |
| |
| if (customClientAnimationController && [customClientAnimationController conformsToProtocol:@protocol(NSImmediateActionAnimationController)]) |
| [_immediateActionRecognizer setAnimationController:(id <NSImmediateActionAnimationController>)customClientAnimationController]; |
| else |
| [_immediateActionRecognizer setAnimationController:defaultAnimationController]; |
| } |
| |
| #pragma mark QLPreviewMenuItemDelegate implementation |
| |
| - (NSView *)menuItem:(NSMenuItem *)menuItem viewAtScreenPoint:(NSPoint)screenPoint |
| { |
| return _webView; |
| } |
| |
| - (id<QLPreviewItem>)menuItem:(NSMenuItem *)menuItem previewItemAtPoint:(NSPoint)point |
| { |
| if (!_webView) |
| return nil; |
| |
| return _hitTestResult.absoluteLinkURL(); |
| } |
| |
| - (NSRectEdge)menuItem:(NSMenuItem *)menuItem preferredEdgeForPoint:(NSPoint)point |
| { |
| return NSMaxYEdge; |
| } |
| |
| - (void)menuItemDidClose:(NSMenuItem *)menuItem |
| { |
| [self _clearImmediateActionState]; |
| [_webView _clearTextIndicatorWithAnimation:WebCore::TextIndicatorDismissalAnimation::FadeOut]; |
| } |
| |
| static WebCore::IntRect elementBoundingBoxInWindowCoordinatesFromNode(WebCore::Node* node) |
| { |
| if (!node) |
| return { }; |
| |
| WebCore::Frame* frame = node->document().frame(); |
| if (!frame) |
| return { }; |
| |
| WebCore::FrameView* view = frame->view(); |
| if (!view) |
| return { }; |
| |
| WebCore::RenderObject* renderer = node->renderer(); |
| if (!renderer) |
| return { }; |
| |
| return view->contentsToWindow(renderer->absoluteBoundingBoxRect()); |
| } |
| |
| - (NSRect)menuItem:(NSMenuItem *)menuItem itemFrameForPoint:(NSPoint)point |
| { |
| if (!_webView) |
| return NSZeroRect; |
| |
| WebCore::Node* node = _hitTestResult.innerNode(); |
| if (!node) |
| return NSZeroRect; |
| |
| return elementBoundingBoxInWindowCoordinatesFromNode(node); |
| } |
| |
| - (NSSize)menuItem:(NSMenuItem *)menuItem maxSizeForPoint:(NSPoint)point |
| { |
| if (!_webView) |
| return NSZeroSize; |
| |
| NSSize screenSize = _webView.window.screen.frame.size; |
| WebCore::FloatRect largestRect = WebCore::largestRectWithAspectRatioInsideRect(screenSize.width / screenSize.height, _webView.bounds); |
| return NSMakeSize(largestRect.width() * 0.75, largestRect.height() * 0.75); |
| } |
| |
| #pragma mark Data Detectors actions |
| |
| - (id <NSImmediateActionAnimationController>)_animationControllerForDataDetectedText |
| { |
| if (!PAL::isDataDetectorsFrameworkAvailable()) |
| return nil; |
| |
| std::optional<WebCore::DetectedItem> detectedItem; |
| |
| if ([[_webView UIDelegate] respondsToSelector:@selector(_webView:actionContextForHitTestResult:range:)]) { |
| DOMRange *customDataDetectorsRange; |
| auto actionContext = [(id)[_webView UIDelegate] _webView:_webView |
| actionContextForHitTestResult:adoptNS([[WebElementDictionary alloc] initWithHitTestResult:_hitTestResult]).get() |
| range:&customDataDetectorsRange]; |
| if (actionContext && customDataDetectorsRange) { |
| detectedItem = { { |
| actionContext, |
| { }, // FIXME: Seems like an empty rect isn't really OK. |
| makeSimpleRange(*core(customDataDetectorsRange)) |
| } }; |
| } |
| } |
| |
| // If the client didn't give us an action context, try to scan around the hit point. |
| if (!detectedItem) |
| detectedItem = WebCore::DataDetection::detectItemAroundHitTestResult(_hitTestResult); |
| |
| if (!detectedItem) |
| return nil; |
| |
| [detectedItem->actionContext setAltMode:YES]; |
| [detectedItem->actionContext setImmediate:YES]; |
| if (![[PAL::getDDActionsManagerClass() sharedManager] hasActionsForResult:[detectedItem->actionContext mainResult] actionContext:detectedItem->actionContext.get()]) |
| return nil; |
| |
| auto indicator = WebCore::TextIndicator::createWithRange(detectedItem->range, { }, WebCore::TextIndicatorPresentationTransition::FadeIn); |
| |
| _currentActionContext = [detectedItem->actionContext contextForView:_webView altMode:YES interactionStartedHandler:^() { |
| } interactionChangedHandler:^() { |
| if (indicator) |
| [_webView _setTextIndicator:*indicator withLifetime:WebCore::TextIndicatorLifetime::Permanent]; |
| } interactionStoppedHandler:^() { |
| [_webView _clearTextIndicatorWithAnimation:WebCore::TextIndicatorDismissalAnimation::FadeOut]; |
| }]; |
| |
| [_currentActionContext setHighlightFrame:[_webView.window convertRectToScreen:detectedItem->boundingBox]]; |
| |
| NSArray *menuItems = [[PAL::getDDActionsManagerClass() sharedManager] menuItemsForResult:[_currentActionContext mainResult] actionContext:_currentActionContext.get()]; |
| if (menuItems.count != 1) |
| return nil; |
| |
| return menuItems.lastObject; |
| } |
| |
| - (id <NSImmediateActionAnimationController>)_animationControllerForDataDetectedLink |
| { |
| if (!PAL::isDataDetectorsFrameworkAvailable()) |
| return nil; |
| |
| auto actionContext = adoptNS([PAL::allocDDActionContextInstance() init]); |
| |
| if (!actionContext) |
| return nil; |
| |
| [actionContext setAltMode:YES]; |
| [actionContext setImmediate:YES]; |
| |
| auto linkRange = makeRangeSelectingNodeContents(*_hitTestResult.URLElement()); |
| auto indicator = WebCore::TextIndicator::createWithRange(linkRange, { }, WebCore::TextIndicatorPresentationTransition::FadeIn); |
| |
| _currentActionContext = [actionContext contextForView:_webView altMode:YES interactionStartedHandler:^() { |
| } interactionChangedHandler:^() { |
| if (indicator) |
| [_webView _setTextIndicator:*indicator withLifetime:WebCore::TextIndicatorLifetime::Permanent]; |
| } interactionStoppedHandler:^() { |
| [_webView _clearTextIndicatorWithAnimation:WebCore::TextIndicatorDismissalAnimation::FadeOut]; |
| }]; |
| |
| [_currentActionContext setHighlightFrame:[_webView.window convertRectToScreen:elementBoundingBoxInWindowCoordinatesFromNode(_hitTestResult.URLElement())]]; |
| |
| NSArray *menuItems = [[PAL::getDDActionsManagerClass() sharedManager] menuItemsForTargetURL:_hitTestResult.absoluteLinkURL().string() actionContext:_currentActionContext.get()]; |
| if (menuItems.count != 1) |
| return nil; |
| |
| return menuItems.lastObject; |
| } |
| |
| #pragma mark Text action |
| |
| + (WebCore::DictionaryPopupInfo)_dictionaryPopupInfoForRange:(const WebCore::SimpleRange&)range inFrame:(WebCore::Frame*)frame withLookupOptions:(NSDictionary *)lookupOptions indicatorOptions:(OptionSet<WebCore::TextIndicatorOption>)indicatorOptions transition:(WebCore::TextIndicatorPresentationTransition)presentationTransition |
| { |
| auto& editor = frame->editor(); |
| editor.setIsGettingDictionaryPopupInfo(true); |
| |
| // Dictionary API will accept a whitespace-only string and display UI as if it were real text, |
| // so bail out early to avoid that. |
| WebCore::DictionaryPopupInfo popupInfo; |
| if (plainText(range).stripWhiteSpace().isEmpty()) { |
| editor.setIsGettingDictionaryPopupInfo(false); |
| return popupInfo; |
| } |
| |
| auto style = range.start.container->renderStyle(); |
| float scaledDescent = style ? style->fontMetrics().descent() * frame->page()->pageScaleFactor() : 0; |
| |
| auto quads = WebCore::RenderObject::absoluteTextQuads(range); |
| if (quads.isEmpty()) { |
| editor.setIsGettingDictionaryPopupInfo(false); |
| return popupInfo; |
| } |
| |
| auto rangeRect = frame->view()->contentsToWindow(quads[0].enclosingBoundingBox()); |
| |
| popupInfo.origin = NSMakePoint(rangeRect.x(), rangeRect.y() + scaledDescent); |
| popupInfo.options = lookupOptions; |
| |
| auto attributedString = editingAttributedString(range, WebCore::IncludeImages::No).string; |
| auto scaledAttributedString = adoptNS([[NSMutableAttributedString alloc] initWithString:[attributedString string]]); |
| NSFontManager *fontManager = [NSFontManager sharedFontManager]; |
| |
| [attributedString enumerateAttributesInRange:NSMakeRange(0, [attributedString length]) options:0 usingBlock:^(NSDictionary *attributes, NSRange attributeRange, BOOL *stop) { |
| RetainPtr<NSMutableDictionary> scaledAttributes = adoptNS([attributes mutableCopy]); |
| NSFont *font = [scaledAttributes objectForKey:NSFontAttributeName]; |
| if (font) |
| font = [fontManager convertFont:font toSize:font.pointSize * frame->page()->pageScaleFactor()]; |
| if (font) |
| [scaledAttributes setObject:font forKey:NSFontAttributeName]; |
| [scaledAttributedString addAttributes:scaledAttributes.get() range:attributeRange]; |
| }]; |
| |
| popupInfo.attributedString = scaledAttributedString.get(); |
| |
| if (auto textIndicator = WebCore::TextIndicator::createWithRange(range, indicatorOptions, presentationTransition)) |
| popupInfo.textIndicator = textIndicator->data(); |
| |
| editor.setIsGettingDictionaryPopupInfo(false); |
| return popupInfo; |
| } |
| |
| - (id<NSImmediateActionAnimationController>)_animationControllerForText |
| { |
| if (!PAL::getLULookupDefinitionModuleClass()) |
| return nil; |
| |
| auto node = _hitTestResult.innerNode(); |
| if (!node) |
| return nil; |
| |
| auto frame = node->document().frame(); |
| if (!frame) |
| return nil; |
| |
| auto range = WebCore::DictionaryLookup::rangeAtHitTestResult(_hitTestResult); |
| if (!range) |
| return nil; |
| |
| auto [dictionaryRange, options] = WTFMove(*range); |
| auto dictionaryPopupInfo = [WebImmediateActionController _dictionaryPopupInfoForRange:dictionaryRange inFrame:frame withLookupOptions:options indicatorOptions: { } transition: WebCore::TextIndicatorPresentationTransition::FadeIn]; |
| if (!dictionaryPopupInfo.attributedString) |
| return nil; |
| |
| return [_webView _animationControllerForDictionaryLookupPopupInfo:dictionaryPopupInfo]; |
| } |
| |
| @end |
| |
| #endif // PLATFORM(MAC) |