Implement Data Detectors immediate actions for WebKit2
authortimothy_horton@apple.com <timothy_horton@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 15 Dec 2014 19:45:23 +0000 (19:45 +0000)
committertimothy_horton@apple.com <timothy_horton@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 15 Dec 2014 19:45:23 +0000 (19:45 +0000)
https://bugs.webkit.org/show_bug.cgi?id=139617
<rdar://problem/19198539>

Reviewed by Beth Dakin.

* Shared/API/c/WKImmediateActionTypes.h:
Add a new type.

* UIProcess/API/mac/WKView.mm:
(-[WKView mouseDown:]):
Send mouseDown along to the WKImmediateActionController too.

(-[WKView initWithFrame:context:configuration:webView:]):
Temporarily disable action menus.

(-[WKView _dismissContentRelativeChildWindows]):
Hide the immediate action preview when detaching child windows.

* UIProcess/mac/WKImmediateActionController.h:
* UIProcess/mac/WKImmediateActionController.mm:
(-[WKImmediateActionController willDestroyView:]):
Clear the DDActionContext when tearing down the view.

(-[WKImmediateActionController wkView:willHandleMouseDown:]):
(-[WKImmediateActionController _cancelImmediateAction]):
(-[WKImmediateActionController _clearImmediateActionState]):
(-[WKImmediateActionController immediateActionRecognizerWillPrepare:]):
(-[WKImmediateActionController immediateActionRecognizerWillBeginAnimation:]):
(-[WKImmediateActionController _defaultAnimationController]):
(-[WKImmediateActionController _updateImmediateActionItem]):
(-[WKImmediateActionController hidePreview]):
(-[WKImmediateActionController popoverWillClose:]):
(-[WKImmediateActionController _menuItemForDataDetectedText]):
Copy most of the DataDetectors implementation into WKImmediateActionController.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@177298 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/WebKit2/ChangeLog
Source/WebKit2/Shared/API/c/WKImmediateActionTypes.h
Source/WebKit2/UIProcess/API/mac/WKView.mm
Source/WebKit2/UIProcess/mac/WKActionMenuController.mm
Source/WebKit2/UIProcess/mac/WKImmediateActionController.h
Source/WebKit2/UIProcess/mac/WKImmediateActionController.mm

index b760f2d..54eeb27 100644 (file)
@@ -1,3 +1,41 @@
+2014-12-15  Timothy Horton  <timothy_horton@apple.com>
+
+        Implement Data Detectors immediate actions for WebKit2
+        https://bugs.webkit.org/show_bug.cgi?id=139617
+        <rdar://problem/19198539>
+
+        Reviewed by Beth Dakin.
+
+        * Shared/API/c/WKImmediateActionTypes.h:
+        Add a new type.
+
+        * UIProcess/API/mac/WKView.mm:
+        (-[WKView mouseDown:]):
+        Send mouseDown along to the WKImmediateActionController too.
+
+        (-[WKView initWithFrame:context:configuration:webView:]):
+        Temporarily disable action menus.
+
+        (-[WKView _dismissContentRelativeChildWindows]):
+        Hide the immediate action preview when detaching child windows.
+
+        * UIProcess/mac/WKImmediateActionController.h:
+        * UIProcess/mac/WKImmediateActionController.mm:
+        (-[WKImmediateActionController willDestroyView:]):
+        Clear the DDActionContext when tearing down the view.
+
+        (-[WKImmediateActionController wkView:willHandleMouseDown:]):
+        (-[WKImmediateActionController _cancelImmediateAction]):
+        (-[WKImmediateActionController _clearImmediateActionState]):
+        (-[WKImmediateActionController immediateActionRecognizerWillPrepare:]):
+        (-[WKImmediateActionController immediateActionRecognizerWillBeginAnimation:]):
+        (-[WKImmediateActionController _defaultAnimationController]):
+        (-[WKImmediateActionController _updateImmediateActionItem]):
+        (-[WKImmediateActionController hidePreview]):
+        (-[WKImmediateActionController popoverWillClose:]):
+        (-[WKImmediateActionController _menuItemForDataDetectedText]):
+        Copy most of the DataDetectors implementation into WKImmediateActionController.
+
 2014-12-15  Myles C. Maxfield  <mmaxfield@apple.com>
 
         Delete Notation because we don't use it
index 17e5a92..8c2db4d 100644 (file)
@@ -35,6 +35,7 @@ extern "C" {
 enum {
     kWKImmediateActionNone = 0,
     kWKImmediateActionLinkPreview,
+    kWKImmediateActionDataDetectedItem,
 };
 typedef uint32_t _WKImmediateActionType;
 
index 6030a9d..5968099 100644 (file)
@@ -1244,6 +1244,7 @@ NATIVE_MOUSE_EVENT_HANDLER(rightMouseUp)
     [self _dismissContentRelativeChildWindows];
 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
     [_data->_actionMenuController wkView:self willHandleMouseDown:event];
+    [_data->_immediateActionController wkView:self willHandleMouseDown:event];
 #endif
     [self mouseDownInternal:event];
 }
@@ -3611,13 +3612,14 @@ static NSString *pathWithUniqueFilenameForPath(NSString *path)
         [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_dictionaryLookupPopoverWillClose:) name:getLUNotificationPopoverWillClose() object:nil];
 
 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
-    if ([self respondsToSelector:@selector(setActionMenu:)]) {
+    // FIXME: Temporarily disable action menu installation.
+    /*if ([self respondsToSelector:@selector(setActionMenu:)]) {
         RetainPtr<NSMenu> menu = adoptNS([[NSMenu alloc] init]);
         self.actionMenu = menu.get();
         _data->_actionMenuController = adoptNS([[WKActionMenuController alloc] initWithPage:*_data->_page view:self]);
         self.actionMenu.delegate = _data->_actionMenuController.get();
         self.actionMenu.autoenablesItems = NO;
-    }
+    }*/
 
     if (Class gestureClass = NSClassFromString(@"NSImmediateActionGestureRecognizer")) {
         RetainPtr<NSImmediateActionGestureRecognizer> recognizer = adoptNS([(NSImmediateActionGestureRecognizer *)[gestureClass alloc] initWithTarget:nil action:NULL]);
@@ -4328,6 +4330,10 @@ static NSString *pathWithUniqueFilenameForPath(NSString *path)
     if ([actionsManager respondsToSelector:@selector(requestBubbleClosureUnanchorOnFailure:)])
         [actionsManager requestBubbleClosureUnanchorOnFailure:YES];
 
+#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000 && WK_API_ENABLED
+    [_data->_immediateActionController hidePreview];
+#endif
+
     [self _setTextIndicator:nullptr fadeOut:NO];
 
     static_cast<PageClient&>(*_data->_pageClient).dismissCorrectionPanel(ReasonForDismissingAlternativeTextIgnored);
index 43d4def..4c10f23 100644 (file)
@@ -390,7 +390,7 @@ static NSString *pathToPhotoOnDisk(NSString *suggestedFilename)
         page->send(Messages::WebPage::DataDetectorsDidPresentUI(overlayID));
     } interactionChangedHandler:^() {
         if (_hitTestResult.detectedDataTextIndicator)
-            _page->setTextIndicator(_hitTestResult.detectedDataTextIndicator->data(), false);
+            page->setTextIndicator(_hitTestResult.detectedDataTextIndicator->data(), false);
         page->send(Messages::WebPage::DataDetectorsDidChangeUI(overlayID));
     } interactionStoppedHandler:^() {
         page->send(Messages::WebPage::DataDetectorsDidHideUI(overlayID));
index 27936ce..b06fa93 100644 (file)
@@ -44,6 +44,7 @@ enum class ImmediateActionState {
 };
 }
 
+@class DDActionContext;
 @class WKPagePreviewViewController;
 @class WKView;
 
@@ -60,6 +61,9 @@ enum class ImmediateActionState {
 
     NSPoint _eventLocationInView;
 
+    BOOL _hasActivatedActionContext;
+    RetainPtr<DDActionContext> _currentActionContext;
+
 #if WK_API_ENABLED
     RetainPtr<NSPopover> _previewPopover;
     String _previewPopoverOriginalURL;
@@ -71,9 +75,11 @@ enum class ImmediateActionState {
 - (instancetype)initWithPage:(WebKit::WebPageProxy&)page view:(WKView *)wkView recognizer:(NSImmediateActionGestureRecognizer *)immediateActionRecognizer;
 - (void)willDestroyView:(WKView *)view;
 - (void)didPerformActionMenuHitTest:(const WebKit::ActionMenuHitTestResult&)hitTestResult userData:(API::Object*)userData;
+- (void)wkView:(WKView *)wkView willHandleMouseDown:(NSEvent *)event;
 
 #if WK_API_ENABLED
 - (void)setPreviewTitle:(NSString *)previewTitle;
+- (void)hidePreview;
 #endif // WK_API_ENABLED
 
 @end
index aa85cb7..143a2ec 100644 (file)
@@ -35,6 +35,7 @@
 #import "WebPageProxy.h"
 #import "WebPageProxyMessages.h"
 #import "WebProcessProxy.h"
+#import <WebCore/DataDetectorsSPI.h>
 #import <WebCore/GeometryUtilities.h>
 #import <WebCore/NSMenuSPI.h>
 #import <WebCore/QuickLookMacSPI.h>
@@ -78,14 +79,36 @@ using namespace WebKit;
     _wkView = nil;
     _hitTestResult = ActionMenuHitTestResult();
     _immediateActionRecognizer = nil;
+    _currentActionContext = nil;
+}
+
+- (void)wkView:(WKView *)wkView willHandleMouseDown:(NSEvent *)event
+{
+    [self _clearImmediateActionState];
+}
+
+- (void)_cancelImmediateAction
+{
+    // Reset the recognizer by turning it off and on again.
+    _immediateActionRecognizer.enabled = NO;
+    _immediateActionRecognizer.enabled = YES;
+
+    [self _clearImmediateActionState];
 }
 
 - (void)_clearImmediateActionState
 {
+    if (_currentActionContext && _hasActivatedActionContext) {
+        [getDDActionsManagerClass() didUseActions];
+        _hasActivatedActionContext = NO;
+    }
+
     _state = ImmediateActionState::None;
     _hitTestResult = ActionMenuHitTestResult();
     _type = kWKImmediateActionNone;
+    _currentActionContext = nil;
     _userData = nil;
+    _immediateActionRecognizer.animationController = nil;
 }
 
 - (void)didPerformActionMenuHitTest:(const ActionMenuHitTestResult&)hitTestResult userData:(API::Object*)userData
@@ -105,6 +128,8 @@ using namespace WebKit;
     if (immediateActionRecognizer != _immediateActionRecognizer)
         return;
 
+    [_wkView _dismissContentRelativeChildWindows];
+
     _eventLocationInView = [immediateActionRecognizer locationInView:immediateActionRecognizer.view];
     _page->performActionMenuHitTestAtLocation(_eventLocationInView);
 
@@ -117,9 +142,10 @@ using namespace WebKit;
     if (immediateActionRecognizer != _immediateActionRecognizer)
         return;
 
-    ASSERT(_state != ImmediateActionState::None);
+    if (_state == ImmediateActionState::None)
+        return;
 
-    // FIXME: We need to be able to cancel this if the gesture recognizer goes away.
+    // FIXME: We need to be able to cancel this if the gesture recognizer is cancelled.
     // FIXME: Connection can be null if the process is closed; we should clean up better in that case.
     if (_state == ImmediateActionState::Pending) {
         if (auto* connection = _page->process().connection()) {
@@ -131,6 +157,17 @@ using namespace WebKit;
 
     if (_state != ImmediateActionState::Ready)
         [self _updateImmediateActionItem];
+
+    if (!_immediateActionRecognizer.animationController) {
+        [self _cancelImmediateAction];
+        return;
+    }
+
+    if (_currentActionContext) {
+        _hasActivatedActionContext = YES;
+        if (![getDDActionsManagerClass() shouldUseActionsWithContext:_currentActionContext.get()])
+            [self _cancelImmediateAction];
+    }
 }
 
 - (void)immediateActionRecognizerDidCancelAnimation:(NSImmediateActionGestureRecognizer *)immediateActionRecognizer
@@ -162,16 +199,12 @@ using namespace WebKit;
 
 #pragma mark Immediate actions
 
-- (void)_updateImmediateActionItem
+- (id <NSImmediateActionAnimationController>)_defaultAnimationController
 {
     RefPtr<WebHitTestResult> hitTestResult = [self _webHitTestResult];
 
-    _type = kWKImmediateActionNone;
-    _immediateActionRecognizer.animationController = nil;
-    id <NSImmediateActionAnimationController> defaultAnimationController = nil;
-
     if (!hitTestResult)
-        return;
+        return nil;
 
     String absoluteLinkURL = hitTestResult->absoluteLinkURL();
     if (!absoluteLinkURL.isEmpty() && WebCore::protocolIsInHTTPFamily(absoluteLinkURL)) {
@@ -186,18 +219,39 @@ using namespace WebKit;
                 [qlPreviewLinkItem setPreviewStyle:QLPreviewStylePopover];
                 [qlPreviewLinkItem setDelegate:self];
             }
-            defaultAnimationController = (id<NSImmediateActionAnimationController>)qlPreviewLinkItem.get();
-        } else {
+            return (id<NSImmediateActionAnimationController>)qlPreviewLinkItem.get();
+        }
+
 #if WK_API_ENABLED
-            [self _createPreviewPopoverIfNeededForURL:absoluteLinkURL];
-            defaultAnimationController = (id<NSImmediateActionAnimationController>)_previewPopover.get();
+        [self _createPreviewPopoverIfNeededForURL:absoluteLinkURL];
+        return (id<NSImmediateActionAnimationController>)_previewPopover.get();
+#else
+        return nil;
 #endif // WK_API_ENABLED
+    }
+
+    if (hitTestResult->isTextNode() || hitTestResult->isOverTextInsideFormControlElement()) {
+        if (NSMenuItem *immediateActionItem = [self _menuItemForDataDetectedText]) {
+            _type = kWKImmediateActionDataDetectedItem;
+            return (id<NSImmediateActionAnimationController>)immediateActionItem;
         }
     }
 
+    return nil;
+}
+
+- (void)_updateImmediateActionItem
+{
+    _type = kWKImmediateActionNone;
+
+    id <NSImmediateActionAnimationController> defaultAnimationController = [self _defaultAnimationController];
+
+    RefPtr<WebHitTestResult> hitTestResult = [self _webHitTestResult];
     id customClientAnimationController = [_wkView _immediateActionAnimationControllerForHitTestResult:toAPI(hitTestResult.get()) withType:_type userData:toAPI(_userData.get())];
-    if (customClientAnimationController == [NSNull null])
+    if (customClientAnimationController == [NSNull null]) {
+        [self _cancelImmediateAction];
         return;
+    }
     if (customClientAnimationController && [customClientAnimationController conformsToProtocol:@protocol(NSImmediateActionAnimationController)])
         _immediateActionRecognizer.animationController = (id <NSImmediateActionAnimationController>)customClientAnimationController;
     else
@@ -236,7 +290,7 @@ using namespace WebKit;
     [_previewPopover setDelegate:self];
 }
 
-- (void)_clearPreviewPopover
+- (void)hidePreview
 {
     if (_previewViewController) {
         _previewViewController->_delegate = nil;
@@ -257,7 +311,7 @@ using namespace WebKit;
 
 - (void)popoverWillClose:(NSNotification *)notification
 {
-    [self _clearPreviewPopover];
+    [self hidePreview];
 }
 
 static bool targetSizeFitsInAvailableSpace(NSSize targetSize, NSSize availableSpace)
@@ -391,6 +445,45 @@ static bool targetSizeFitsInAvailableSpace(NSSize targetSize, NSSize availableSp
     return NSMaxYEdge;
 }
 
+#pragma mark Data Detectors actions
+
+- (NSMenuItem *)_menuItemForDataDetectedText
+{
+    DDActionContext *actionContext = _hitTestResult.actionContext.get();
+    if (!actionContext)
+        return nil;
+
+    actionContext.altMode = YES;
+    if ([[getDDActionsManagerClass() sharedManager] respondsToSelector:@selector(hasActionsForResult:actionContext:)]) {
+        if (![[getDDActionsManagerClass() sharedManager] hasActionsForResult:actionContext.mainResult actionContext:actionContext])
+            return nil;
+    }
+
+    RefPtr<WebPageProxy> page = _page;
+    PageOverlay::PageOverlayID overlayID = _hitTestResult.detectedDataOriginatingPageOverlay;
+    _currentActionContext = [actionContext contextForView:_wkView altMode:YES interactionStartedHandler:^() {
+        page->send(Messages::WebPage::DataDetectorsDidPresentUI(overlayID));
+    } interactionChangedHandler:^() {
+        if (_hitTestResult.detectedDataTextIndicator)
+            page->setTextIndicator(_hitTestResult.detectedDataTextIndicator->data(), false);
+        page->send(Messages::WebPage::DataDetectorsDidChangeUI(overlayID));
+    } interactionStoppedHandler:^() {
+        page->send(Messages::WebPage::DataDetectorsDidHideUI(overlayID));
+        page->clearTextIndicator();
+    }];
+
+    [_currentActionContext setHighlightFrame:[_wkView.window convertRectToScreen:[_wkView convertRect:_hitTestResult.detectedDataBoundingBox toView:nil]]];
+
+    NSArray *menuItems = [[getDDActionsManagerClass() sharedManager] menuItemsForResult:[_currentActionContext mainResult] actionContext:_currentActionContext.get()];
+
+    if (menuItems.count != 1)
+        return nil;
+
+    if (_hitTestResult.detectedDataTextIndicator)
+        _hitTestResult.detectedDataTextIndicator->setPresentationTransition(TextIndicatorPresentationTransition::Bounce);
+    return menuItems.lastObject;
+}
+
 @end
 
 #endif // PLATFORM(MAC)