Changeset 244370 in webkit


Ignore:
Timestamp:
Apr 16, 2019 8:34:10 PM (5 years ago)
Author:
Wenson Hsieh
Message:

[iOS] [WebKit2] Add support for honoring -[UIMenuItem dontDismiss]
https://bugs.webkit.org/show_bug.cgi?id=196919
<rdar://problem/41630459>

Reviewed by Tim Horton.

Source/WebKit:

Adds modern WebKit support for -dontDismiss by implementing a couple of new platform hooks. Covered by a new
layout test: editing/selection/ios/selection-after-changing-text-with-callout-menu.html.

  • Platform/spi/ios/UIKitSPI.h:

Declare the private -dontDismiss property of UIMenuItem.

  • UIProcess/API/Cocoa/WKWebView.mm:

(-[WKWebView willFinishIgnoringCalloutBarFadeAfterPerformingAction]):

Additionally teach the web view (not just the content view) to respond to the hook. This matters in the case
where the WebKit client (most notably, Mail) overrides WKWebView methods to define custom actions in the menu
controller. This scenario is exercised by the new layout test.

  • UIProcess/ios/WKContentViewInteraction.h:
  • UIProcess/ios/WKContentViewInteraction.mm:

(-[WKContentView willFinishIgnoringCalloutBarFadeAfterPerformingAction]):

If an action was performed where callout bar fading was ignored, then in WebKit, don't allow selection changes
to fade the callout bar until after the next remote layer tree commit.

(-[WKContentView _updateChangedSelection:]):

Stop suppressing selection updates when showing B/I/U controls, now that we can properly honor the -dontDismiss
property. This was originally introduced in <rdar://problem/15199925>, presumably to ensure that B/I/U buttons
(which have -dontDismiss set to YES) don't trigger selection change and end up dismissing themselves; however,
if triggering B/I/U actually changes the selection rects, this also means that the selection rects on-screen
would be stale after triggering these actions. This effect is most noticeable when bolding text.

(-[WKContentView shouldAllowHidingSelectionCommands]):

Tools:

Add iOS support for several new testing hooks. See below for more detail.

  • DumpRenderTree/ios/UIScriptControllerIOS.mm:

(WTR::UIScriptController::isDismissingMenu const):

Add a new script controller method to query whether the platform menu (on iOS, the callout bar) is done
dismissing. We consider the menu to be dismissing in between the -WillHide and -DidHide notifications sent
by UIKit when dismissing the callout bar (i.e. UIMenuController).

  • TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl:
  • TestRunnerShared/UIScriptContext/UIScriptController.cpp:

(WTR::UIScriptController::isDismissingMenu const):

  • TestRunnerShared/UIScriptContext/UIScriptController.h:
  • WebKitTestRunner/InjectedBundle/Bindings/TestRunner.idl:
  • WebKitTestRunner/InjectedBundle/InjectedBundle.cpp:

(WTR::InjectedBundle::didReceiveMessageToPage):

  • WebKitTestRunner/InjectedBundle/TestRunner.cpp:

(WTR::TestRunner::setAllowedMenuActions):

Add a new helper method to specify a list of allowed actions when bringing up the menu. On iOS, in the case of
actions supported by the platform, this matches against method selector names (for instance, "SelectAll", or
"Copy", or "Paste"). In the case of the custom actions installed via installCustomMenuAction, we instead match
against the name of the custom action.

(WTR::TestRunner::installCustomMenuAction):

Add a new helper method to install a custom action for the context menu (on iOS, this is the callout bar). This
takes the name of the action (which appears in a button in the callout bar), whether the action should cause
the callout bar to automatically dismiss, and finally, a JavaScript callback that is invoked when the action is
triggered.

(WTR::TestRunner::performCustomMenuAction):

Invoked when the custom menu action is triggered.

  • WebKitTestRunner/InjectedBundle/TestRunner.h:
  • WebKitTestRunner/TestController.cpp:

(WTR::TestController::installCustomMenuAction):
(WTR::TestController::setAllowedMenuActions):

  • WebKitTestRunner/TestController.h:
  • WebKitTestRunner/TestInvocation.cpp:

(WTR::TestInvocation::didReceiveMessageFromInjectedBundle):
(WTR::TestInvocation::performCustomMenuAction):

Add plumbing to call back into the injected bundle when performing the custom action.

  • WebKitTestRunner/TestInvocation.h:
  • WebKitTestRunner/cocoa/TestControllerCocoa.mm:

(WTR::TestController::installCustomMenuAction):
(WTR::TestController::setAllowedMenuActions):

  • WebKitTestRunner/cocoa/TestRunnerWKWebView.h:
  • WebKitTestRunner/cocoa/TestRunnerWKWebView.mm:

(-[TestRunnerWKWebView initWithFrame:configuration:]):
(-[TestRunnerWKWebView becomeFirstResponder]):
(-[TestRunnerWKWebView _addCustomItemToMenuControllerIfNecessary]):

Helper method that converts web view's current custom menu action info into a UIMenuItem, and adds it to the
shared menu controller. This is also invoked when the web view becomes first responder, which matches behavior
in the Mail app on iOS.

(-[TestRunnerWKWebView installCustomMenuAction:dismissesAutomatically:callback:]):
(-[TestRunnerWKWebView setAllowedMenuActions:]):
(-[TestRunnerWKWebView resetCustomMenuAction]):
(-[TestRunnerWKWebView performCustomAction:]):
(-[TestRunnerWKWebView canPerformAction:withSender:]):
(-[TestRunnerWKWebView _willHideMenu]):
(-[TestRunnerWKWebView _didHideMenu]):

  • WebKitTestRunner/ios/TestControllerIOS.mm:

(WTR::TestController::platformResetStateToConsistentValues):

Reset both any custom installed actions on the shared menu controller, as well as the list of allowed actions,
if specified.

  • WebKitTestRunner/ios/UIScriptControllerIOS.mm:

(WTR::UIScriptController::isDismissingMenu const):

LayoutTests:

Add a new iOS layout test that installs a custom, non-dismissing action in the callout menu that enlarges text.
The test then activates this custom menu item and checks that the selection rects after triggering this custom
action are updated, and the callout bar is still showing.

  • editing/selection/ios/selection-after-changing-text-with-callout-menu-expected.txt: Added.
  • editing/selection/ios/selection-after-changing-text-with-callout-menu.html: Added.

This test additionally suppresses all callout bar menu items except for the custom "Embiggen" action, to ensure
that the "Embiggen" option can be tapped from the layout test without having to navigate callout bar items by
tapping on the "Next" and "Show styles" buttons. This latter approach is very challenging to make reliable in
automation; when navigating submenus in the callout bar, the next button can't be tapped until the current
callout bar transition animation is complete, but there's no delegate method invoked or notification posted when
this happens.

  • resources/ui-helper.js:

(window.UIHelper.isShowingMenu):
(window.UIHelper.isDismissingMenu):
(window.UIHelper.rectForMenuAction):
(window.UIHelper.async.chooseMenuAction):

Additionally add a few more UIHelper methods.

(window.UIHelper):

Location:
trunk
Files:
2 added
25 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r244359 r244370  
     12019-04-16  Wenson Hsieh  <wenson_hsieh@apple.com>
     2
     3        [iOS] [WebKit2] Add support for honoring -[UIMenuItem dontDismiss]
     4        https://bugs.webkit.org/show_bug.cgi?id=196919
     5        <rdar://problem/41630459>
     6
     7        Reviewed by Tim Horton.
     8
     9        Add a new iOS layout test that installs a custom, non-dismissing action in the callout menu that enlarges text.
     10        The test then activates this custom menu item and checks that the selection rects after triggering this custom
     11        action are updated, and the callout bar is still showing.
     12
     13        * editing/selection/ios/selection-after-changing-text-with-callout-menu-expected.txt: Added.
     14        * editing/selection/ios/selection-after-changing-text-with-callout-menu.html: Added.
     15
     16        This test additionally suppresses all callout bar menu items except for the custom "Embiggen" action, to ensure
     17        that the "Embiggen" option can be tapped from the layout test without having to navigate callout bar items by
     18        tapping on the "Next" and "Show styles" buttons. This latter approach is very challenging to make reliable in
     19        automation; when navigating submenus in the callout bar, the next button can't be tapped until the current
     20        callout bar transition animation is complete, but there's no delegate method invoked or notification posted when
     21        this happens.
     22
     23        * resources/ui-helper.js:
     24        (window.UIHelper.isShowingMenu):
     25        (window.UIHelper.isDismissingMenu):
     26        (window.UIHelper.rectForMenuAction):
     27        (window.UIHelper.async.chooseMenuAction):
     28
     29        Additionally add a few more UIHelper methods.
     30
     31        (window.UIHelper):
     32
    1332019-04-16  John Wilander  <wilander@apple.com>
    234
  • trunk/LayoutTests/resources/ui-helper.js

    r244220 r244370  
    828828    }
    829829
     830    static isShowingMenu()
     831    {
     832        return new Promise(resolve => {
     833            testRunner.runUIScript(`uiController.isShowingMenu`, result => resolve(result === "true"));
     834        });
     835    }
     836
     837    static isDismissingMenu()
     838    {
     839        return new Promise(resolve => {
     840            testRunner.runUIScript(`uiController.isDismissingMenu`, result => resolve(result === "true"));
     841        });
     842    }
     843
    830844    static menuRect()
    831845    {
     
    839853        return new Promise(resolve => testRunner.runUIScript(`uiController.setHardwareKeyboardAttached(${attached ? "true" : "false"})`, resolve));
    840854    }
     855
     856    static rectForMenuAction(action)
     857    {
     858        return new Promise(resolve => {
     859            testRunner.runUIScript(`
     860                const rect = uiController.rectForMenuAction("${action}");
     861                uiController.uiScriptComplete(rect ? JSON.stringify(rect) : "");
     862            `, stringResult => {
     863                resolve(stringResult.length ? JSON.parse(stringResult) : null);
     864            });
     865        });
     866    }
     867
     868    static async chooseMenuAction(action)
     869    {
     870        const menuRect = await this.rectForMenuAction(action);
     871        if (menuRect)
     872            await this.activateAt(menuRect.left + menuRect.width / 2, menuRect.top + menuRect.height / 2);
     873    }
    841874}
  • trunk/Source/WebKit/ChangeLog

    r244369 r244370  
     12019-04-16  Wenson Hsieh  <wenson_hsieh@apple.com>
     2
     3        [iOS] [WebKit2] Add support for honoring -[UIMenuItem dontDismiss]
     4        https://bugs.webkit.org/show_bug.cgi?id=196919
     5        <rdar://problem/41630459>
     6
     7        Reviewed by Tim Horton.
     8
     9        Adds modern WebKit support for -dontDismiss by implementing a couple of new platform hooks. Covered by a new
     10        layout test: editing/selection/ios/selection-after-changing-text-with-callout-menu.html.
     11
     12        * Platform/spi/ios/UIKitSPI.h:
     13
     14        Declare the private -dontDismiss property of UIMenuItem.
     15
     16        * UIProcess/API/Cocoa/WKWebView.mm:
     17        (-[WKWebView willFinishIgnoringCalloutBarFadeAfterPerformingAction]):
     18
     19        Additionally teach the web view (not just the content view) to respond to the hook. This matters in the case
     20        where the WebKit client (most notably, Mail) overrides WKWebView methods to define custom actions in the menu
     21        controller. This scenario is exercised by the new layout test.
     22
     23        * UIProcess/ios/WKContentViewInteraction.h:
     24        * UIProcess/ios/WKContentViewInteraction.mm:
     25        (-[WKContentView willFinishIgnoringCalloutBarFadeAfterPerformingAction]):
     26
     27        If an action was performed where callout bar fading was ignored, then in WebKit, don't allow selection changes
     28        to fade the callout bar until after the next remote layer tree commit.
     29
     30        (-[WKContentView _updateChangedSelection:]):
     31
     32        Stop suppressing selection updates when showing B/I/U controls, now that we can properly honor the -dontDismiss
     33        property. This was originally introduced in <rdar://problem/15199925>, presumably to ensure that B/I/U buttons
     34        (which have -dontDismiss set to YES) don't trigger selection change and end up dismissing themselves; however,
     35        if triggering B/I/U actually changes the selection rects, this also means that the selection rects on-screen
     36        would be stale after triggering these actions. This effect is most noticeable when bolding text.
     37
     38        (-[WKContentView shouldAllowHidingSelectionCommands]):
     39
    1402019-04-16  Ross Kirsling  <ross.kirsling@sony.com>
    241
  • trunk/Source/WebKit/Platform/spi/ios/UIKitSPI.h

    r244085 r244370  
    4949#import <UIKit/UIKeyboard_Private.h>
    5050#import <UIKit/UILongPressGestureRecognizer_Private.h>
     51#import <UIKit/UIMenuController_Private.h>
    5152#import <UIKit/UIPeripheralHost.h>
    5253#import <UIKit/UIPeripheralHost_Private.h>
     
    988989#endif
    989990
     991@interface UIMenuItem (UIMenuController_SPI)
     992@property (nonatomic) BOOL dontDismiss;
     993@end
     994
    990995@interface UICalloutBar : UIView
    991996+ (UICalloutBar *)activeCalloutBar;
  • trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm

    r244220 r244370  
    15541554}
    15551555
     1556- (void)willFinishIgnoringCalloutBarFadeAfterPerformingAction
     1557{
     1558    [_contentView willFinishIgnoringCalloutBarFadeAfterPerformingAction];
     1559}
     1560
    15561561static inline CGFloat floorToDevicePixel(CGFloat input, float deviceScaleFactor)
    15571562{
  • trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.h

    r244296 r244370  
    320320
    321321    BOOL _hasSetUpInteractions;
     322    NSUInteger _ignoreSelectionCommandFadeCount;
    322323    CompletionHandler<void(WebCore::DOMPasteAccessResponse)> _domPasteRequestHandler;
    323324    BlockPtr<void(UIWKAutocorrectionContext *)> _pendingAutocorrectionContextHandler;
     
    450451- (void)_didChangeWebViewEditability;
    451452
     453- (void)willFinishIgnoringCalloutBarFadeAfterPerformingAction;
     454
    452455// UIWebFormAccessoryDelegate protocol
    453456- (void)accessoryDone;
  • trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm

    r244368 r244370  
    38813881}
    38823882
     3883- (void)willFinishIgnoringCalloutBarFadeAfterPerformingAction
     3884{
     3885    _ignoreSelectionCommandFadeCount++;
     3886    _page->callAfterNextPresentationUpdate([weakSelf = WeakObjCPtr<WKContentView>(self)] (auto) {
     3887        if (auto strongSelf = weakSelf.get())
     3888            strongSelf->_ignoreSelectionCommandFadeCount--;
     3889    });
     3890}
     3891
    38833892- (void)_didChangeWebViewEditability
    38843893{
     
    56485657        if (_textSelectionAssistant) {
    56495658            _markedText = (_page->editorState().hasComposition) ? _page->editorState().markedText : String();
    5650             if (!_showingTextStyleOptions)
    5651                 [_textSelectionAssistant selectionChanged];
     5659            [_textSelectionAssistant selectionChanged];
    56525660        }
    56535661
     
    56695677        _needsDeferredEndScrollingSelectionUpdate = NO;
    56705678    }
     5679}
     5680
     5681- (BOOL)shouldAllowHidingSelectionCommands
     5682{
     5683    ASSERT(_ignoreSelectionCommandFadeCount >= 0);
     5684    return !_ignoreSelectionCommandFadeCount;
    56715685}
    56725686
  • trunk/Tools/ChangeLog

    r244368 r244370  
     12019-04-16  Wenson Hsieh  <wenson_hsieh@apple.com>
     2
     3        [iOS] [WebKit2] Add support for honoring -[UIMenuItem dontDismiss]
     4        https://bugs.webkit.org/show_bug.cgi?id=196919
     5        <rdar://problem/41630459>
     6
     7        Reviewed by Tim Horton.
     8
     9        Add iOS support for several new testing hooks. See below for more detail.
     10
     11        * DumpRenderTree/ios/UIScriptControllerIOS.mm:
     12        (WTR::UIScriptController::isDismissingMenu const):
     13
     14        Add a new script controller method to query whether the platform menu (on iOS, the callout bar) is done
     15        dismissing. We consider the menu to be dismissing in between the `-WillHide` and `-DidHide` notifications sent
     16        by UIKit when dismissing the callout bar (i.e. UIMenuController).
     17
     18        * TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl:
     19        * TestRunnerShared/UIScriptContext/UIScriptController.cpp:
     20        (WTR::UIScriptController::isDismissingMenu const):
     21        * TestRunnerShared/UIScriptContext/UIScriptController.h:
     22        * WebKitTestRunner/InjectedBundle/Bindings/TestRunner.idl:
     23        * WebKitTestRunner/InjectedBundle/InjectedBundle.cpp:
     24        (WTR::InjectedBundle::didReceiveMessageToPage):
     25        * WebKitTestRunner/InjectedBundle/TestRunner.cpp:
     26        (WTR::TestRunner::setAllowedMenuActions):
     27
     28        Add a new helper method to specify a list of allowed actions when bringing up the menu. On iOS, in the case of
     29        actions supported by the platform, this matches against method selector names (for instance, "SelectAll", or
     30        "Copy", or "Paste"). In the case of the custom actions installed via `installCustomMenuAction`, we instead match
     31        against the name of the custom action.
     32
     33        (WTR::TestRunner::installCustomMenuAction):
     34
     35        Add a new helper method to install a custom action for the context menu (on iOS, this is the callout bar). This
     36        takes the name of the action (which appears in a button in the callout bar), whether the action should cause
     37        the callout bar to automatically dismiss, and finally, a JavaScript callback that is invoked when the action is
     38        triggered.
     39
     40        (WTR::TestRunner::performCustomMenuAction):
     41
     42        Invoked when the custom menu action is triggered.
     43
     44        * WebKitTestRunner/InjectedBundle/TestRunner.h:
     45        * WebKitTestRunner/TestController.cpp:
     46        (WTR::TestController::installCustomMenuAction):
     47        (WTR::TestController::setAllowedMenuActions):
     48        * WebKitTestRunner/TestController.h:
     49        * WebKitTestRunner/TestInvocation.cpp:
     50        (WTR::TestInvocation::didReceiveMessageFromInjectedBundle):
     51        (WTR::TestInvocation::performCustomMenuAction):
     52
     53        Add plumbing to call back into the injected bundle when performing the custom action.
     54
     55        * WebKitTestRunner/TestInvocation.h:
     56        * WebKitTestRunner/cocoa/TestControllerCocoa.mm:
     57        (WTR::TestController::installCustomMenuAction):
     58        (WTR::TestController::setAllowedMenuActions):
     59        * WebKitTestRunner/cocoa/TestRunnerWKWebView.h:
     60        * WebKitTestRunner/cocoa/TestRunnerWKWebView.mm:
     61        (-[TestRunnerWKWebView initWithFrame:configuration:]):
     62        (-[TestRunnerWKWebView becomeFirstResponder]):
     63        (-[TestRunnerWKWebView _addCustomItemToMenuControllerIfNecessary]):
     64
     65        Helper method that converts web view's current custom menu action info into a UIMenuItem, and adds it to the
     66        shared menu controller. This is also invoked when the web view becomes first responder, which matches behavior
     67        in the Mail app on iOS.
     68
     69        (-[TestRunnerWKWebView installCustomMenuAction:dismissesAutomatically:callback:]):
     70        (-[TestRunnerWKWebView setAllowedMenuActions:]):
     71        (-[TestRunnerWKWebView resetCustomMenuAction]):
     72        (-[TestRunnerWKWebView performCustomAction:]):
     73        (-[TestRunnerWKWebView canPerformAction:withSender:]):
     74        (-[TestRunnerWKWebView _willHideMenu]):
     75        (-[TestRunnerWKWebView _didHideMenu]):
     76        * WebKitTestRunner/ios/TestControllerIOS.mm:
     77        (WTR::TestController::platformResetStateToConsistentValues):
     78
     79        Reset both any custom installed actions on the shared menu controller, as well as the list of allowed actions,
     80        if specified.
     81
     82        * WebKitTestRunner/ios/UIScriptControllerIOS.mm:
     83        (WTR::UIScriptController::isDismissingMenu const):
     84
    1852019-04-16  Megan Gardner  <megan_gardner@apple.com>
    286
  • trunk/Tools/DumpRenderTree/ios/UIScriptControllerIOS.mm

    r244220 r244370  
    382382}
    383383
     384bool UIScriptController::isDismissingMenu() const
     385{
     386    return false;
     387}
     388
    384389void UIScriptController::platformSetDidEndScrollingCallback()
    385390{
  • trunk/Tools/TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl

    r244220 r244370  
    228228    attribute object didShowMenuCallback;
    229229    attribute object didHideMenuCallback;
     230    readonly attribute boolean isDismissingMenu;
    230231    readonly attribute boolean isShowingMenu;
    231232    readonly attribute object menuRect;
  • trunk/Tools/TestRunnerShared/UIScriptContext/UIScriptController.cpp

    r244220 r244370  
    599599}
    600600
     601bool UIScriptController::isDismissingMenu() const
     602{
     603    return false;
     604}
     605
    601606bool UIScriptController::isShowingMenu() const
    602607{
  • trunk/Tools/TestRunnerShared/UIScriptContext/UIScriptController.h

    r244220 r244370  
    176176    JSValueRef willPresentPopoverCallback() const;
    177177
     178    bool isDismissingMenu() const;
    178179    bool isShowingMenu() const;
    179180    JSObjectRef rectForMenuAction(JSStringRef action) const;
  • trunk/Tools/WebKitTestRunner/InjectedBundle/Bindings/TestRunner.idl

    r244288 r244370  
    272272
    273273    void accummulateLogsForChannel(DOMString channel);
     274
     275    // Contextual menu actions
     276    void setAllowedMenuActions(object actions);
     277    void installCustomMenuAction(DOMString name, boolean dismissesAutomatically, object callback);
    274278
    275279    // Gamepad
  • trunk/Tools/WebKitTestRunner/InjectedBundle/InjectedBundle.cpp

    r243927 r244370  
    446446        return;
    447447    }
     448
     449    if (WKStringIsEqualToUTF8CString(messageName, "PerformCustomMenuAction")) {
     450        m_testRunner->performCustomMenuAction();
     451        return;
     452    }
    448453   
    449454    WKRetainPtr<WKStringRef> errorMessageName(AdoptWK, WKStringCreateWithUTF8CString("Error"));
  • trunk/Tools/WebKitTestRunner/InjectedBundle/TestRunner.cpp

    r244288 r244370  
    794794    TextFieldDidBeginEditingCallbackID,
    795795    TextFieldDidEndEditingCallbackID,
     796    CustomMenuActionCallbackID,
    796797    FirstUIScriptCallbackID = 100
    797798};
     
    13411342    JSValueRef resultValue = JSValueMakeString(context, result);
    13421343    callTestRunnerCallback(callbackID, 1, &resultValue);
     1344}
     1345
     1346void TestRunner::setAllowedMenuActions(JSValueRef actions)
     1347{
     1348    WKRetainPtr<WKStringRef> messageName(AdoptWK, WKStringCreateWithUTF8CString("SetAllowedMenuActions"));
     1349    WKRetainPtr<WKMutableArrayRef> messageBody(AdoptWK, WKMutableArrayCreate());
     1350
     1351    auto page = InjectedBundle::singleton().page()->page();
     1352    auto mainFrame = WKBundlePageGetMainFrame(page);
     1353    auto context = WKBundleFrameGetJavaScriptContext(mainFrame);
     1354    auto lengthPropertyName = adopt(JSStringCreateWithUTF8CString("length"));
     1355    auto actionsArray = JSValueToObject(context, actions, nullptr);
     1356    auto lengthValue = JSObjectGetProperty(context, actionsArray, lengthPropertyName.get(), nullptr);
     1357    if (!JSValueIsNumber(context, lengthValue))
     1358        return;
     1359
     1360    auto length = static_cast<size_t>(JSValueToNumber(context, lengthValue, 0));
     1361    for (size_t i = 0; i < length; ++i) {
     1362        auto value = JSObjectGetPropertyAtIndex(context, actionsArray, i, 0);
     1363        if (!JSValueIsString(context, value))
     1364            continue;
     1365
     1366        auto actionName = adopt(JSValueToStringCopy(context, value, 0));
     1367        WKRetainPtr<WKStringRef> action(AdoptWK, WKStringCreateWithJSString(actionName.get()));
     1368        WKArrayAppendItem(messageBody.get(), action.get());
     1369    }
     1370
     1371    WKBundlePagePostMessage(page, messageName.get(), messageBody.get());
     1372}
     1373
     1374void TestRunner::installCustomMenuAction(JSStringRef name, bool dismissesAutomatically, JSValueRef callback)
     1375{
     1376    cacheTestRunnerCallback(CustomMenuActionCallbackID, callback);
     1377
     1378    WKRetainPtr<WKStringRef> messageName(AdoptWK, WKStringCreateWithUTF8CString("InstallCustomMenuAction"));
     1379    WKRetainPtr<WKMutableDictionaryRef> messageBody(AdoptWK, WKMutableDictionaryCreate());
     1380
     1381    WKRetainPtr<WKStringRef> nameKey(AdoptWK, WKStringCreateWithUTF8CString("name"));
     1382    WKRetainPtr<WKStringRef> nameValue(AdoptWK, WKStringCreateWithJSString(name));
     1383    WKDictionarySetItem(messageBody.get(), nameKey.get(), nameValue.get());
     1384
     1385    WKRetainPtr<WKStringRef> dismissesAutomaticallyKey(AdoptWK, WKStringCreateWithUTF8CString("dismissesAutomatically"));
     1386    WKRetainPtr<WKBooleanRef> dismissesAutomaticallyValue(AdoptWK, WKBooleanCreate(dismissesAutomatically));
     1387    WKDictionarySetItem(messageBody.get(), dismissesAutomaticallyKey.get(), dismissesAutomaticallyValue.get());
     1388
     1389    WKBundlePagePostMessage(InjectedBundle::singleton().page()->page(), messageName.get(), messageBody.get());
    13431390}
    13441391
     
    24662513}
    24672514
     2515void TestRunner::performCustomMenuAction()
     2516{
     2517    callTestRunnerCallback(CustomMenuActionCallbackID);
     2518}
     2519
    24682520size_t TestRunner::userScriptInjectedCount() const
    24692521{
  • trunk/Tools/WebKitTestRunner/InjectedBundle/TestRunner.h

    r244288 r244370  
    355355    void runUIScript(JSStringRef script, JSValueRef callback);
    356356    void runUIScriptCallback(unsigned callbackID, JSStringRef result);
     357
     358    // Contextual menu actions
     359    void setAllowedMenuActions(JSValueRef);
     360    void installCustomMenuAction(JSStringRef name, bool dismissesAutomatically, JSValueRef callback);
     361    void performCustomMenuAction();
    357362
    358363    void installDidBeginSwipeCallback(JSValueRef);
  • trunk/Tools/WebKitTestRunner/TestController.cpp

    r244288 r244370  
    35123512}
    35133513
     3514void TestController::installCustomMenuAction(const String&, bool)
     3515{
     3516}
     3517
     3518void TestController::setAllowedMenuActions(const Vector<String>&)
     3519{
     3520}
     3521
    35143522#endif
    35153523
  • trunk/Tools/WebKitTestRunner/TestController.h

    r244288 r244370  
    297297#endif
    298298
     299    void setAllowedMenuActions(const Vector<String>&);
     300    void installCustomMenuAction(const String& name, bool dismissesAutomatically);
     301
    299302    bool canDoServerTrustEvaluationInNetworkProcess() const;
    300303    uint64_t serverTrustEvaluationCallbackCallsCount() const { return m_serverTrustEvaluationCallbackCallsCount; }
  • trunk/Tools/WebKitTestRunner/TestInvocation.cpp

    r244288 r244370  
    769769    }
    770770
     771    if (WKStringIsEqualToUTF8CString(messageName, "InstallCustomMenuAction")) {
     772        auto messageBodyDictionary = static_cast<WKDictionaryRef>(messageBody);
     773        WKRetainPtr<WKStringRef> nameKey(AdoptWK, WKStringCreateWithUTF8CString("name"));
     774        WKRetainPtr<WKStringRef> name = static_cast<WKStringRef>(WKDictionaryGetItemForKey(messageBodyDictionary, nameKey.get()));
     775        WKRetainPtr<WKStringRef> dismissesAutomaticallyKey(AdoptWK, WKStringCreateWithUTF8CString("dismissesAutomatically"));
     776        auto dismissesAutomatically = static_cast<WKBooleanRef>(WKDictionaryGetItemForKey(messageBodyDictionary, dismissesAutomaticallyKey.get()));
     777        TestController::singleton().installCustomMenuAction(toWTFString(name.get()), WKBooleanGetValue(dismissesAutomatically));
     778        return;
     779    }
     780
     781    if (WKStringIsEqualToUTF8CString(messageName, "SetAllowedMenuActions")) {
     782        auto messageBodyArray = static_cast<WKArrayRef>(messageBody);
     783        auto size = WKArrayGetSize(messageBodyArray);
     784        Vector<String> actions;
     785        actions.reserveInitialCapacity(size);
     786        for (size_t index = 0; index < size; ++index)
     787            actions.append(toWTFString(static_cast<WKStringRef>(WKArrayGetItemAtIndex(messageBodyArray, index))));
     788        TestController::singleton().setAllowedMenuActions(actions);
     789        return;
     790    }
     791
    771792    if (WKStringIsEqualToUTF8CString(messageName, "SetOpenPanelFileURLs")) {
    772793        TestController::singleton().setOpenPanelFileURLs(static_cast<WKArrayRef>(messageBody));
     
    17861807}
    17871808
     1809void TestInvocation::performCustomMenuAction()
     1810{
     1811    WKRetainPtr<WKStringRef> messageName = adoptWK(WKStringCreateWithUTF8CString("PerformCustomMenuAction"));
     1812    WKPagePostMessageToInjectedBundle(TestController::singleton().mainWebView()->page(), messageName.get(), 0);
     1813}
     1814
    17881815} // namespace WTR
  • trunk/Tools/WebKitTestRunner/TestInvocation.h

    r241451 r244370  
    8989
    9090    void dumpAdClickAttribution();
     91    void performCustomMenuAction();
    9192
    9293private:
  • trunk/Tools/WebKitTestRunner/cocoa/TestControllerCocoa.mm

    r244134 r244370  
    401401}
    402402
     403void TestController::installCustomMenuAction(const String& name, bool dismissesAutomatically)
     404{
     405#if PLATFORM(IOS_FAMILY)
     406    auto* invocation = m_currentInvocation.get();
     407    [m_mainWebView->platformView() installCustomMenuAction:name dismissesAutomatically:dismissesAutomatically callback:[invocation] {
     408        if (TestController::singleton().isCurrentInvocation(invocation))
     409            invocation->performCustomMenuAction();
     410    }];
     411#else
     412    UNUSED_PARAM(name);
     413    UNUSED_PARAM(dismissesAutomatically);
     414#endif
     415}
     416
     417void TestController::setAllowedMenuActions(const Vector<String>& actions)
     418{
     419#if PLATFORM(IOS_FAMILY)
     420    auto actionNames = adoptNS([[NSMutableArray<NSString *> alloc] initWithCapacity:actions.size()]);
     421    for (auto action : actions)
     422        [actionNames addObject:action];
     423    [m_mainWebView->platformView() setAllowedMenuActions:actionNames.get()];
     424#else
     425    UNUSED_PARAM(actions);
     426#endif
     427}
     428
    403429bool TestController::isDoingMediaCapture() const
    404430{
  • trunk/Tools/WebKitTestRunner/cocoa/TestRunnerWKWebView.h

    r244096 r244370  
    5050@property (nonatomic, copy) NSString *accessibilitySpeakSelectionContent;
    5151
     52- (void)setAllowedMenuActions:(NSArray<NSString *> *)actions;
     53
     54- (void)resetCustomMenuAction;
     55- (void)installCustomMenuAction:(NSString *)name dismissesAutomatically:(BOOL)dismissesAutomatically callback:(dispatch_block_t)callback;
     56
    5257- (void)resetInteractionCallbacks;
    5358- (void)zoomToScale:(double)scale animated:(BOOL)animated completionHandler:(void (^)(void))completionHandler;
     
    5964@property (nonatomic, readonly, getter=isShowingKeyboard) BOOL showingKeyboard;
    6065@property (nonatomic, readonly, getter=isShowingMenu) BOOL showingMenu;
     66@property (nonatomic, readonly, getter=isDismissingMenu) BOOL dismissingMenu;
    6167@property (nonatomic, readonly, getter=isShowingPopover) BOOL showingPopover;
    6268@property (nonatomic, assign) BOOL usesSafariLikeRotation;
  • trunk/Tools/WebKitTestRunner/cocoa/TestRunnerWKWebView.mm

    r244096 r244370  
    3030#import <WebKit/WKUIDelegatePrivate.h>
    3131#import <wtf/Assertions.h>
     32#import <wtf/BlockPtr.h>
     33#import <wtf/Optional.h>
    3234#import <wtf/RetainPtr.h>
    3335
     
    4648#endif
    4749
     50struct CustomMenuActionInfo {
     51    RetainPtr<NSString> name;
     52    BOOL dismissesAutomatically { NO };
     53    BlockPtr<void()> callback;
     54};
     55
    4856@interface TestRunnerWKWebView () <WKUIDelegatePrivate> {
    4957    RetainPtr<NSNumber> m_stableStateOverride;
    5058    BOOL _isInteractingWithFormControl;
    5159    BOOL _scrollingUpdatesDisabled;
     60    Optional<CustomMenuActionInfo> _customMenuActionInfo;
     61    RetainPtr<NSArray<NSString *>> _allowedMenuActions;
    5262}
    5363
     
    5666@property (nonatomic, getter=isShowingKeyboard, setter=setIsShowingKeyboard:) BOOL showingKeyboard;
    5767@property (nonatomic, getter=isShowingMenu, setter=setIsShowingMenu:) BOOL showingMenu;
     68@property (nonatomic, getter=isDismissingMenu, setter=setIsDismissingMenu:) BOOL dismissingMenu;
    5869@property (nonatomic, getter=isShowingPopover, setter=setIsShowingPopover:) BOOL showingPopover;
    5970
     
    8091        [center addObserver:self selector:@selector(_invokeHideKeyboardCallbackIfNecessary) name:UIKeyboardDidHideNotification object:nil];
    8192        [center addObserver:self selector:@selector(_didShowMenu) name:UIMenuControllerDidShowMenuNotification object:nil];
     93        [center addObserver:self selector:@selector(_willHideMenu) name:UIMenuControllerWillHideMenuNotification object:nil];
    8294        [center addObserver:self selector:@selector(_didHideMenu) name:UIMenuControllerDidHideMenuNotification object:nil];
    8395        [center addObserver:self selector:@selector(_willPresentPopover) name:@"UIPopoverControllerWillPresentPopoverNotification" object:nil];
     
    131143    if (self.didDismissForcePressPreviewCallback)
    132144        self.didDismissForcePressPreviewCallback();
     145}
     146
     147- (BOOL)becomeFirstResponder
     148{
     149    BOOL wasFirstResponder = self.isFirstResponder;
     150    BOOL becameFirstResponder = [super becomeFirstResponder];
     151    if (!wasFirstResponder && becameFirstResponder)
     152        [self _addCustomItemToMenuControllerIfNecessary];
     153    return becameFirstResponder;
     154}
     155
     156- (void)_addCustomItemToMenuControllerIfNecessary
     157{
     158    if (!_customMenuActionInfo)
     159        return;
     160
     161    auto item = adoptNS([[UIMenuItem alloc] initWithTitle:_customMenuActionInfo->name.get() action:@selector(performCustomAction:)]);
     162    [item setDontDismiss:!_customMenuActionInfo->dismissesAutomatically];
     163    UIMenuController *controller = UIMenuController.sharedMenuController;
     164    controller.menuItems = @[ item.get() ];
     165    [controller update];
     166}
     167
     168- (void)installCustomMenuAction:(NSString *)name dismissesAutomatically:(BOOL)dismissesAutomatically callback:(dispatch_block_t)callback
     169{
     170    _customMenuActionInfo = {{ name, dismissesAutomatically, callback }};
     171    [self _addCustomItemToMenuControllerIfNecessary];
     172}
     173
     174- (void)setAllowedMenuActions:(NSArray<NSString *> *)actions
     175{
     176    _allowedMenuActions = actions;
     177}
     178
     179- (void)resetCustomMenuAction
     180{
     181    _customMenuActionInfo.reset();
     182    UIMenuController.sharedMenuController.menuItems = @[ ];
     183}
     184
     185- (void)performCustomAction:(id)sender
     186{
     187    if (!_customMenuActionInfo)
     188        return;
     189
     190    if (!_customMenuActionInfo->callback) {
     191        ASSERT_NOT_REACHED();
     192        return;
     193    }
     194
     195    _customMenuActionInfo->callback();
     196}
     197
     198- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
     199{
     200    BOOL isCustomAction = action == @selector(performCustomAction:);
     201    BOOL canPerformActionByDefault = [super canPerformAction:action withSender:sender];
     202    if (isCustomAction)
     203        canPerformActionByDefault = _customMenuActionInfo.hasValue();
     204
     205    if (canPerformActionByDefault && _allowedMenuActions && sender == UIMenuController.sharedMenuController) {
     206        BOOL isAllowed = NO;
     207        if (isCustomAction) {
     208            for (NSString *allowedAction in _allowedMenuActions.get()) {
     209                if ([[_customMenuActionInfo->name lowercaseString] isEqualToString:allowedAction.lowercaseString]) {
     210                    isAllowed = YES;
     211                    break;
     212                }
     213            }
     214        } else {
     215            for (NSString *allowedAction in _allowedMenuActions.get()) {
     216                NSString *lowercaseSelectorName = [[allowedAction lowercaseString] stringByAppendingString:@":"];
     217                if ([NSStringFromSelector(action).lowercaseString isEqualToString:lowercaseSelectorName]) {
     218                    isAllowed = YES;
     219                    break;
     220                }
     221            }
     222        }
     223        if (!isAllowed)
     224            return NO;
     225    }
     226    return canPerformActionByDefault;
    133227}
    134228
     
    196290}
    197291
     292- (void)_willHideMenu
     293{
     294    self.dismissingMenu = YES;
     295}
     296
    198297- (void)_didHideMenu
    199298{
     299    self.dismissingMenu = NO;
     300
    200301    if (!self.showingMenu)
    201302        return;
  • trunk/Tools/WebKitTestRunner/ios/TestControllerIOS.mm

    r244220 r244370  
    164164        [webView _clearInterfaceOrientationOverride];
    165165        [webView resetInteractionCallbacks];
     166        [webView resetCustomMenuAction];
     167        [webView setAllowedMenuActions:nil];
    166168
    167169        UIScrollView *scrollView = webView.scrollView;
  • trunk/Tools/WebKitTestRunner/ios/UIScriptControllerIOS.mm

    r244220 r244370  
    972972}
    973973
     974bool UIScriptController::isDismissingMenu() const
     975{
     976    return TestController::singleton().mainWebView()->platformView().dismissingMenu;
     977}
     978
    974979bool UIScriptController::isShowingMenu() const
    975980{
Note: See TracChangeset for help on using the changeset viewer.