Title: [244370] trunk
Revision
244370
Author
[email protected]
Date
2019-04-16 20:34:10 -0700 (Tue, 16 Apr 2019)

Log 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):

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (244369 => 244370)


--- trunk/LayoutTests/ChangeLog	2019-04-17 02:56:33 UTC (rev 244369)
+++ trunk/LayoutTests/ChangeLog	2019-04-17 03:34:10 UTC (rev 244370)
@@ -1,3 +1,35 @@
+2019-04-16  Wenson Hsieh  <[email protected]>
+
+        [iOS] [WebKit2] Add support for honoring -[UIMenuItem dontDismiss]
+        https://bugs.webkit.org/show_bug.cgi?id=196919
+        <rdar://problem/41630459>
+
+        Reviewed by Tim Horton.
+
+        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):
+
 2019-04-16  John Wilander  <[email protected]>
 
         Set test conditions closer to conversion redirect in LayoutTests/http/tests/adClickAttribution/send-attribution-conversion-request.html

Added: trunk/LayoutTests/editing/selection/ios/selection-after-changing-text-with-callout-menu-expected.txt (0 => 244370)


--- trunk/LayoutTests/editing/selection/ios/selection-after-changing-text-with-callout-menu-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/editing/selection/ios/selection-after-changing-text-with-callout-menu-expected.txt	2019-04-17 03:34:10 UTC (rev 244370)
@@ -0,0 +1,19 @@
+The quick brown fox jumped over the lazy dog.
+This test verifies that interacting with a menu action item on iOS that is marked with 'dontDismiss' does not cause the callout menu to dismiss, even if the action changes the selection. To test manually, select the text and tap an item in the callout menu that shouldn't automatically dismiss the menu but changes the selection rects (for instance, Bold). Verify that the callout menu remains visible after tapping this item.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+PASS selectionRects[0].top is 101
+PASS selectionRects[0].width is 320
+PASS selectionRects[0].left is 1
+PASS selectionRects[0].height is 29
+PASS selectionRects[1].top is 130
+PASS selectionRects[1].width is 172
+PASS selectionRects[1].left is 1
+PASS selectionRects[1].height is 30
+PASS dismissingMenu is false
+PASS showingMenu is true
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/editing/selection/ios/selection-after-changing-text-with-callout-menu.html (0 => 244370)


--- trunk/LayoutTests/editing/selection/ios/selection-after-changing-text-with-callout-menu.html	                        (rev 0)
+++ trunk/LayoutTests/editing/selection/ios/selection-after-changing-text-with-callout-menu.html	2019-04-17 03:34:10 UTC (rev 244370)
@@ -0,0 +1,68 @@
+<!DOCTYPE html> <!-- webkit-test-runner [ useFlexibleViewport=true ] -->
+<html>
+<head>
+<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
+<script src=""
+<script src=""
+<style>
+body, html {
+    width: 100%;
+    height: 100%;
+    margin: 0;
+}
+
+#editor {
+    width: 320px;
+    height: 200px;
+    border: solid 1px tomato;
+    margin-top: 100px;
+}
+
+.big {
+    font-size: 24px;
+}
+</style>
+<script>
+jsTestIsAsync = true;
+
+if (window.testRunner) {
+    testRunner.setAllowedMenuActions(["Embiggen"]);
+    testRunner.installCustomMenuAction("Embiggen", false, () => document.querySelector("#editor").classList.add("big"));
+}
+
+addEventListener("load", runTest);
+async function runTest() {
+    description("This test verifies that interacting with a menu action item on iOS that is marked with 'dontDismiss' does not cause the callout menu to dismiss, even if the action changes the selection. To test manually, select the text and tap an item in the callout menu that shouldn't automatically dismiss the menu but changes the selection rects (for instance, Bold). Verify that the callout menu remains visible after tapping this item.");
+
+    await UIHelper.activateElementAndWaitForInputSession(editor);
+    await UIHelper.keyDown("a", ["metaKey"]);
+    await UIHelper.waitForMenuToShow();
+    await UIHelper.chooseMenuAction("Embiggen");
+
+    selectionRects = null;
+    while (!selectionRects || selectionRects.length != 2)
+        selectionRects = await UIHelper.getUISelectionViewRects();
+    dismissingMenu = await UIHelper.isDismissingMenu();
+    showingMenu = await UIHelper.isShowingMenu();
+
+    shouldBe("selectionRects[0].top", "101");
+    shouldBe("selectionRects[0].width", "320");
+    shouldBe("selectionRects[0].left", "1");
+    shouldBe("selectionRects[0].height", "29");
+    shouldBe("selectionRects[1].top", "130");
+    shouldBe("selectionRects[1].width", "172");
+    shouldBe("selectionRects[1].left", "1");
+    shouldBe("selectionRects[1].height", "30");
+    shouldBe("dismissingMenu", "false");
+    shouldBe("showingMenu", "true");
+
+    finishJSTest();
+}
+</script>
+</head>
+<body>
+<div id="editor" contenteditable>The quick brown fox jumped over the lazy dog.</div>
+<div id="description"></div>
+<div id="console"></div>
+</body>
+</html>

Modified: trunk/LayoutTests/resources/ui-helper.js (244369 => 244370)


--- trunk/LayoutTests/resources/ui-helper.js	2019-04-17 02:56:33 UTC (rev 244369)
+++ trunk/LayoutTests/resources/ui-helper.js	2019-04-17 03:34:10 UTC (rev 244370)
@@ -827,6 +827,20 @@
         });
     }
 
+    static isShowingMenu()
+    {
+        return new Promise(resolve => {
+            testRunner.runUIScript(`uiController.isShowingMenu`, result => resolve(result === "true"));
+        });
+    }
+
+    static isDismissingMenu()
+    {
+        return new Promise(resolve => {
+            testRunner.runUIScript(`uiController.isDismissingMenu`, result => resolve(result === "true"));
+        });
+    }
+
     static menuRect()
     {
         return new Promise(resolve => {
@@ -838,4 +852,23 @@
     {
         return new Promise(resolve => testRunner.runUIScript(`uiController.setHardwareKeyboardAttached(${attached ? "true" : "false"})`, resolve));
     }
+
+    static rectForMenuAction(action)
+    {
+        return new Promise(resolve => {
+            testRunner.runUIScript(`
+                const rect = uiController.rectForMenuAction("${action}");
+                uiController.uiScriptComplete(rect ? JSON.stringify(rect) : "");
+            `, stringResult => {
+                resolve(stringResult.length ? JSON.parse(stringResult) : null);
+            });
+        });
+    }
+
+    static async chooseMenuAction(action)
+    {
+        const menuRect = await this.rectForMenuAction(action);
+        if (menuRect)
+            await this.activateAt(menuRect.left + menuRect.width / 2, menuRect.top + menuRect.height / 2);
+    }
 }

Modified: trunk/Source/WebKit/ChangeLog (244369 => 244370)


--- trunk/Source/WebKit/ChangeLog	2019-04-17 02:56:33 UTC (rev 244369)
+++ trunk/Source/WebKit/ChangeLog	2019-04-17 03:34:10 UTC (rev 244370)
@@ -1,3 +1,42 @@
+2019-04-16  Wenson Hsieh  <[email protected]>
+
+        [iOS] [WebKit2] Add support for honoring -[UIMenuItem dontDismiss]
+        https://bugs.webkit.org/show_bug.cgi?id=196919
+        <rdar://problem/41630459>
+
+        Reviewed by Tim Horton.
+
+        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]):
+
 2019-04-16  Ross Kirsling  <[email protected]>
 
         Unreviewed non-unified build fix after r244307.

Modified: trunk/Source/WebKit/Platform/spi/ios/UIKitSPI.h (244369 => 244370)


--- trunk/Source/WebKit/Platform/spi/ios/UIKitSPI.h	2019-04-17 02:56:33 UTC (rev 244369)
+++ trunk/Source/WebKit/Platform/spi/ios/UIKitSPI.h	2019-04-17 03:34:10 UTC (rev 244370)
@@ -48,6 +48,7 @@
 #import <UIKit/UIKeyboardIntl.h>
 #import <UIKit/UIKeyboard_Private.h>
 #import <UIKit/UILongPressGestureRecognizer_Private.h>
+#import <UIKit/UIMenuController_Private.h>
 #import <UIKit/UIPeripheralHost.h>
 #import <UIKit/UIPeripheralHost_Private.h>
 #import <UIKit/UIPickerContentView_Private.h>
@@ -987,6 +988,10 @@
 
 #endif
 
+@interface UIMenuItem (UIMenuController_SPI)
+@property (nonatomic) BOOL dontDismiss;
+@end
+
 @interface UICalloutBar : UIView
 + (UICalloutBar *)activeCalloutBar;
 + (void)fadeSharedCalloutBar;

Modified: trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm (244369 => 244370)


--- trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm	2019-04-17 02:56:33 UTC (rev 244369)
+++ trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm	2019-04-17 03:34:10 UTC (rev 244370)
@@ -1553,6 +1553,11 @@
     return [super targetForAction:action withSender:sender];
 }
 
+- (void)willFinishIgnoringCalloutBarFadeAfterPerformingAction
+{
+    [_contentView willFinishIgnoringCalloutBarFadeAfterPerformingAction];
+}
+
 static inline CGFloat floorToDevicePixel(CGFloat input, float deviceScaleFactor)
 {
     return CGFloor(input * deviceScaleFactor) / deviceScaleFactor;

Modified: trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.h (244369 => 244370)


--- trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.h	2019-04-17 02:56:33 UTC (rev 244369)
+++ trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.h	2019-04-17 03:34:10 UTC (rev 244370)
@@ -319,6 +319,7 @@
     BOOL _focusRequiresStrongPasswordAssistance;
 
     BOOL _hasSetUpInteractions;
+    NSUInteger _ignoreSelectionCommandFadeCount;
     CompletionHandler<void(WebCore::DOMPasteAccessResponse)> _domPasteRequestHandler;
     BlockPtr<void(UIWKAutocorrectionContext *)> _pendingAutocorrectionContextHandler;
 
@@ -449,6 +450,8 @@
 - (WKFormInputSession *)_formInputSession;
 - (void)_didChangeWebViewEditability;
 
+- (void)willFinishIgnoringCalloutBarFadeAfterPerformingAction;
+
 // UIWebFormAccessoryDelegate protocol
 - (void)accessoryDone;
 

Modified: trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm (244369 => 244370)


--- trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm	2019-04-17 02:56:33 UTC (rev 244369)
+++ trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm	2019-04-17 03:34:10 UTC (rev 244370)
@@ -3880,6 +3880,15 @@
     [self.inputDelegate selectionDidChange:self];
 }
 
+- (void)willFinishIgnoringCalloutBarFadeAfterPerformingAction
+{
+    _ignoreSelectionCommandFadeCount++;
+    _page->callAfterNextPresentationUpdate([weakSelf = WeakObjCPtr<WKContentView>(self)] (auto) {
+        if (auto strongSelf = weakSelf.get())
+            strongSelf->_ignoreSelectionCommandFadeCount--;
+    });
+}
+
 - (void)_didChangeWebViewEditability
 {
     if ([_formAccessoryView respondsToSelector:@selector(setNextPreviousItemsVisible:)])
@@ -5647,8 +5656,7 @@
         // FIXME: We need to figure out what to do if the selection is changed by _javascript_.
         if (_textSelectionAssistant) {
             _markedText = (_page->editorState().hasComposition) ? _page->editorState().markedText : String();
-            if (!_showingTextStyleOptions)
-                [_textSelectionAssistant selectionChanged];
+            [_textSelectionAssistant selectionChanged];
         }
 
         _selectionNeedsUpdate = NO;
@@ -5670,6 +5678,12 @@
     }
 }
 
+- (BOOL)shouldAllowHidingSelectionCommands
+{
+    ASSERT(_ignoreSelectionCommandFadeCount >= 0);
+    return !_ignoreSelectionCommandFadeCount;
+}
+
 - (BOOL)_shouldSuppressSelectionCommands
 {
     return !!_suppressSelectionAssistantReasons;

Modified: trunk/Tools/ChangeLog (244369 => 244370)


--- trunk/Tools/ChangeLog	2019-04-17 02:56:33 UTC (rev 244369)
+++ trunk/Tools/ChangeLog	2019-04-17 03:34:10 UTC (rev 244370)
@@ -1,3 +1,87 @@
+2019-04-16  Wenson Hsieh  <[email protected]>
+
+        [iOS] [WebKit2] Add support for honoring -[UIMenuItem dontDismiss]
+        https://bugs.webkit.org/show_bug.cgi?id=196919
+        <rdar://problem/41630459>
+
+        Reviewed by Tim Horton.
+
+        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):
+
 2019-04-16  Megan Gardner  <[email protected]>
 
         Allow sharing from imageSheet on an image document

Modified: trunk/Tools/DumpRenderTree/ios/UIScriptControllerIOS.mm (244369 => 244370)


--- trunk/Tools/DumpRenderTree/ios/UIScriptControllerIOS.mm	2019-04-17 02:56:33 UTC (rev 244369)
+++ trunk/Tools/DumpRenderTree/ios/UIScriptControllerIOS.mm	2019-04-17 03:34:10 UTC (rev 244370)
@@ -381,6 +381,11 @@
     return false;
 }
 
+bool UIScriptController::isDismissingMenu() const
+{
+    return false;
+}
+
 void UIScriptController::platformSetDidEndScrollingCallback()
 {
 }

Modified: trunk/Tools/TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl (244369 => 244370)


--- trunk/Tools/TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl	2019-04-17 02:56:33 UTC (rev 244369)
+++ trunk/Tools/TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl	2019-04-17 03:34:10 UTC (rev 244370)
@@ -227,6 +227,7 @@
 
     attribute object didShowMenuCallback;
     attribute object didHideMenuCallback;
+    readonly attribute boolean isDismissingMenu;
     readonly attribute boolean isShowingMenu;
     readonly attribute object menuRect;
     object rectForMenuAction(DOMString action);

Modified: trunk/Tools/TestRunnerShared/UIScriptContext/UIScriptController.cpp (244369 => 244370)


--- trunk/Tools/TestRunnerShared/UIScriptContext/UIScriptController.cpp	2019-04-17 02:56:33 UTC (rev 244369)
+++ trunk/Tools/TestRunnerShared/UIScriptContext/UIScriptController.cpp	2019-04-17 03:34:10 UTC (rev 244370)
@@ -598,6 +598,11 @@
     return nullptr;
 }
 
+bool UIScriptController::isDismissingMenu() const
+{
+    return false;
+}
+
 bool UIScriptController::isShowingMenu() const
 {
     return false;

Modified: trunk/Tools/TestRunnerShared/UIScriptContext/UIScriptController.h (244369 => 244370)


--- trunk/Tools/TestRunnerShared/UIScriptContext/UIScriptController.h	2019-04-17 02:56:33 UTC (rev 244369)
+++ trunk/Tools/TestRunnerShared/UIScriptContext/UIScriptController.h	2019-04-17 03:34:10 UTC (rev 244370)
@@ -175,6 +175,7 @@
     void setWillPresentPopoverCallback(JSValueRef);
     JSValueRef willPresentPopoverCallback() const;
 
+    bool isDismissingMenu() const;
     bool isShowingMenu() const;
     JSObjectRef rectForMenuAction(JSStringRef action) const;
     JSObjectRef menuRect() const;

Modified: trunk/Tools/WebKitTestRunner/InjectedBundle/Bindings/TestRunner.idl (244369 => 244370)


--- trunk/Tools/WebKitTestRunner/InjectedBundle/Bindings/TestRunner.idl	2019-04-17 02:56:33 UTC (rev 244369)
+++ trunk/Tools/WebKitTestRunner/InjectedBundle/Bindings/TestRunner.idl	2019-04-17 03:34:10 UTC (rev 244370)
@@ -272,6 +272,10 @@
 
     void accummulateLogsForChannel(DOMString channel);
 
+    // Contextual menu actions
+    void setAllowedMenuActions(object actions);
+    void installCustomMenuAction(DOMString name, boolean dismissesAutomatically, object callback);
+
     // Gamepad
     void setMockGamepadDetails(unsigned long index, DOMString id, unsigned long axisCount, unsigned long buttonCount);
     void setMockGamepadAxisValue(unsigned long index, unsigned long axisIndex, double value);

Modified: trunk/Tools/WebKitTestRunner/InjectedBundle/InjectedBundle.cpp (244369 => 244370)


--- trunk/Tools/WebKitTestRunner/InjectedBundle/InjectedBundle.cpp	2019-04-17 02:56:33 UTC (rev 244369)
+++ trunk/Tools/WebKitTestRunner/InjectedBundle/InjectedBundle.cpp	2019-04-17 03:34:10 UTC (rev 244370)
@@ -445,6 +445,11 @@
         m_testRunner->didGetApplicationManifest();
         return;
     }
+
+    if (WKStringIsEqualToUTF8CString(messageName, "PerformCustomMenuAction")) {
+        m_testRunner->performCustomMenuAction();
+        return;
+    }
     
     WKRetainPtr<WKStringRef> errorMessageName(AdoptWK, WKStringCreateWithUTF8CString("Error"));
     WKRetainPtr<WKStringRef> errorMessageBody(AdoptWK, WKStringCreateWithUTF8CString("Unknown"));

Modified: trunk/Tools/WebKitTestRunner/InjectedBundle/TestRunner.cpp (244369 => 244370)


--- trunk/Tools/WebKitTestRunner/InjectedBundle/TestRunner.cpp	2019-04-17 02:56:33 UTC (rev 244369)
+++ trunk/Tools/WebKitTestRunner/InjectedBundle/TestRunner.cpp	2019-04-17 03:34:10 UTC (rev 244370)
@@ -793,6 +793,7 @@
     TextDidChangeInTextFieldCallbackID,
     TextFieldDidBeginEditingCallbackID,
     TextFieldDidEndEditingCallbackID,
+    CustomMenuActionCallbackID,
     FirstUIScriptCallbackID = 100
 };
 
@@ -1342,6 +1343,52 @@
     callTestRunnerCallback(callbackID, 1, &resultValue);
 }
 
+void TestRunner::setAllowedMenuActions(JSValueRef actions)
+{
+    WKRetainPtr<WKStringRef> messageName(AdoptWK, WKStringCreateWithUTF8CString("SetAllowedMenuActions"));
+    WKRetainPtr<WKMutableArrayRef> messageBody(AdoptWK, WKMutableArrayCreate());
+
+    auto page = InjectedBundle::singleton().page()->page();
+    auto mainFrame = WKBundlePageGetMainFrame(page);
+    auto context = WKBundleFrameGetJavaScriptContext(mainFrame);
+    auto lengthPropertyName = adopt(JSStringCreateWithUTF8CString("length"));
+    auto actionsArray = JSValueToObject(context, actions, nullptr);
+    auto lengthValue = JSObjectGetProperty(context, actionsArray, lengthPropertyName.get(), nullptr);
+    if (!JSValueIsNumber(context, lengthValue))
+        return;
+
+    auto length = static_cast<size_t>(JSValueToNumber(context, lengthValue, 0));
+    for (size_t i = 0; i < length; ++i) {
+        auto value = JSObjectGetPropertyAtIndex(context, actionsArray, i, 0);
+        if (!JSValueIsString(context, value))
+            continue;
+
+        auto actionName = adopt(JSValueToStringCopy(context, value, 0));
+        WKRetainPtr<WKStringRef> action(AdoptWK, WKStringCreateWithJSString(actionName.get()));
+        WKArrayAppendItem(messageBody.get(), action.get());
+    }
+
+    WKBundlePagePostMessage(page, messageName.get(), messageBody.get());
+}
+
+void TestRunner::installCustomMenuAction(JSStringRef name, bool dismissesAutomatically, JSValueRef callback)
+{
+    cacheTestRunnerCallback(CustomMenuActionCallbackID, callback);
+
+    WKRetainPtr<WKStringRef> messageName(AdoptWK, WKStringCreateWithUTF8CString("InstallCustomMenuAction"));
+    WKRetainPtr<WKMutableDictionaryRef> messageBody(AdoptWK, WKMutableDictionaryCreate());
+
+    WKRetainPtr<WKStringRef> nameKey(AdoptWK, WKStringCreateWithUTF8CString("name"));
+    WKRetainPtr<WKStringRef> nameValue(AdoptWK, WKStringCreateWithJSString(name));
+    WKDictionarySetItem(messageBody.get(), nameKey.get(), nameValue.get());
+
+    WKRetainPtr<WKStringRef> dismissesAutomaticallyKey(AdoptWK, WKStringCreateWithUTF8CString("dismissesAutomatically"));
+    WKRetainPtr<WKBooleanRef> dismissesAutomaticallyValue(AdoptWK, WKBooleanCreate(dismissesAutomatically));
+    WKDictionarySetItem(messageBody.get(), dismissesAutomaticallyKey.get(), dismissesAutomaticallyValue.get());
+
+    WKBundlePagePostMessage(InjectedBundle::singleton().page()->page(), messageName.get(), messageBody.get());
+}
+
 void TestRunner::installDidBeginSwipeCallback(JSValueRef callback)
 {
     cacheTestRunnerCallback(DidBeginSwipeCallbackID, callback);
@@ -2465,6 +2512,11 @@
     callTestRunnerCallback(GetApplicationManifestCallbackID);
 }
 
+void TestRunner::performCustomMenuAction()
+{
+    callTestRunnerCallback(CustomMenuActionCallbackID);
+}
+
 size_t TestRunner::userScriptInjectedCount() const
 {
     return InjectedBundle::singleton().userScriptInjectedCount();

Modified: trunk/Tools/WebKitTestRunner/InjectedBundle/TestRunner.h (244369 => 244370)


--- trunk/Tools/WebKitTestRunner/InjectedBundle/TestRunner.h	2019-04-17 02:56:33 UTC (rev 244369)
+++ trunk/Tools/WebKitTestRunner/InjectedBundle/TestRunner.h	2019-04-17 03:34:10 UTC (rev 244370)
@@ -355,6 +355,11 @@
     void runUIScript(JSStringRef script, JSValueRef callback);
     void runUIScriptCallback(unsigned callbackID, JSStringRef result);
 
+    // Contextual menu actions
+    void setAllowedMenuActions(JSValueRef);
+    void installCustomMenuAction(JSStringRef name, bool dismissesAutomatically, JSValueRef callback);
+    void performCustomMenuAction();
+
     void installDidBeginSwipeCallback(JSValueRef);
     void installWillEndSwipeCallback(JSValueRef);
     void installDidEndSwipeCallback(JSValueRef);

Modified: trunk/Tools/WebKitTestRunner/TestController.cpp (244369 => 244370)


--- trunk/Tools/WebKitTestRunner/TestController.cpp	2019-04-17 02:56:33 UTC (rev 244369)
+++ trunk/Tools/WebKitTestRunner/TestController.cpp	2019-04-17 03:34:10 UTC (rev 244370)
@@ -3511,6 +3511,14 @@
     return false;
 }
 
+void TestController::installCustomMenuAction(const String&, bool)
+{
+}
+
+void TestController::setAllowedMenuActions(const Vector<String>&)
+{
+}
+
 #endif
 
 void TestController::sendDisplayConfigurationChangedMessageForTesting()

Modified: trunk/Tools/WebKitTestRunner/TestController.h (244369 => 244370)


--- trunk/Tools/WebKitTestRunner/TestController.h	2019-04-17 02:56:33 UTC (rev 244369)
+++ trunk/Tools/WebKitTestRunner/TestController.h	2019-04-17 03:34:10 UTC (rev 244370)
@@ -296,6 +296,9 @@
     UIKeyboardInputMode *overriddenKeyboardInputMode() const { return m_overriddenKeyboardInputMode.get(); }
 #endif
 
+    void setAllowedMenuActions(const Vector<String>&);
+    void installCustomMenuAction(const String& name, bool dismissesAutomatically);
+
     bool canDoServerTrustEvaluationInNetworkProcess() const;
     uint64_t serverTrustEvaluationCallbackCallsCount() const { return m_serverTrustEvaluationCallbackCallsCount; }
 

Modified: trunk/Tools/WebKitTestRunner/TestInvocation.cpp (244369 => 244370)


--- trunk/Tools/WebKitTestRunner/TestInvocation.cpp	2019-04-17 02:56:33 UTC (rev 244369)
+++ trunk/Tools/WebKitTestRunner/TestInvocation.cpp	2019-04-17 03:34:10 UTC (rev 244370)
@@ -768,6 +768,27 @@
         return;
     }
 
+    if (WKStringIsEqualToUTF8CString(messageName, "InstallCustomMenuAction")) {
+        auto messageBodyDictionary = static_cast<WKDictionaryRef>(messageBody);
+        WKRetainPtr<WKStringRef> nameKey(AdoptWK, WKStringCreateWithUTF8CString("name"));
+        WKRetainPtr<WKStringRef> name = static_cast<WKStringRef>(WKDictionaryGetItemForKey(messageBodyDictionary, nameKey.get()));
+        WKRetainPtr<WKStringRef> dismissesAutomaticallyKey(AdoptWK, WKStringCreateWithUTF8CString("dismissesAutomatically"));
+        auto dismissesAutomatically = static_cast<WKBooleanRef>(WKDictionaryGetItemForKey(messageBodyDictionary, dismissesAutomaticallyKey.get()));
+        TestController::singleton().installCustomMenuAction(toWTFString(name.get()), WKBooleanGetValue(dismissesAutomatically));
+        return;
+    }
+
+    if (WKStringIsEqualToUTF8CString(messageName, "SetAllowedMenuActions")) {
+        auto messageBodyArray = static_cast<WKArrayRef>(messageBody);
+        auto size = WKArrayGetSize(messageBodyArray);
+        Vector<String> actions;
+        actions.reserveInitialCapacity(size);
+        for (size_t index = 0; index < size; ++index)
+            actions.append(toWTFString(static_cast<WKStringRef>(WKArrayGetItemAtIndex(messageBodyArray, index))));
+        TestController::singleton().setAllowedMenuActions(actions);
+        return;
+    }
+
     if (WKStringIsEqualToUTF8CString(messageName, "SetOpenPanelFileURLs")) {
         TestController::singleton().setOpenPanelFileURLs(static_cast<WKArrayRef>(messageBody));
         return;
@@ -1785,4 +1806,10 @@
     m_shouldDumpAdClickAttribution = true;
 }
 
+void TestInvocation::performCustomMenuAction()
+{
+    WKRetainPtr<WKStringRef> messageName = adoptWK(WKStringCreateWithUTF8CString("PerformCustomMenuAction"));
+    WKPagePostMessageToInjectedBundle(TestController::singleton().mainWebView()->page(), messageName.get(), 0);
+}
+
 } // namespace WTR

Modified: trunk/Tools/WebKitTestRunner/TestInvocation.h (244369 => 244370)


--- trunk/Tools/WebKitTestRunner/TestInvocation.h	2019-04-17 02:56:33 UTC (rev 244369)
+++ trunk/Tools/WebKitTestRunner/TestInvocation.h	2019-04-17 03:34:10 UTC (rev 244370)
@@ -88,6 +88,7 @@
     bool canOpenWindows() const { return m_canOpenWindows; }
 
     void dumpAdClickAttribution();
+    void performCustomMenuAction();
 
 private:
     WKRetainPtr<WKMutableDictionaryRef> createTestSettingsDictionary();

Modified: trunk/Tools/WebKitTestRunner/cocoa/TestControllerCocoa.mm (244369 => 244370)


--- trunk/Tools/WebKitTestRunner/cocoa/TestControllerCocoa.mm	2019-04-17 02:56:33 UTC (rev 244369)
+++ trunk/Tools/WebKitTestRunner/cocoa/TestControllerCocoa.mm	2019-04-17 03:34:10 UTC (rev 244370)
@@ -400,6 +400,32 @@
 #endif
 }
 
+void TestController::installCustomMenuAction(const String& name, bool dismissesAutomatically)
+{
+#if PLATFORM(IOS_FAMILY)
+    auto* invocation = m_currentInvocation.get();
+    [m_mainWebView->platformView() installCustomMenuAction:name dismissesAutomatically:dismissesAutomatically callback:[invocation] {
+        if (TestController::singleton().isCurrentInvocation(invocation))
+            invocation->performCustomMenuAction();
+    }];
+#else
+    UNUSED_PARAM(name);
+    UNUSED_PARAM(dismissesAutomatically);
+#endif
+}
+
+void TestController::setAllowedMenuActions(const Vector<String>& actions)
+{
+#if PLATFORM(IOS_FAMILY)
+    auto actionNames = adoptNS([[NSMutableArray<NSString *> alloc] initWithCapacity:actions.size()]);
+    for (auto action : actions)
+        [actionNames addObject:action];
+    [m_mainWebView->platformView() setAllowedMenuActions:actionNames.get()];
+#else
+    UNUSED_PARAM(actions);
+#endif
+}
+
 bool TestController::isDoingMediaCapture() const
 {
     return m_mainWebView->platformView()._mediaCaptureState != _WKMediaCaptureStateNone;

Modified: trunk/Tools/WebKitTestRunner/cocoa/TestRunnerWKWebView.h (244369 => 244370)


--- trunk/Tools/WebKitTestRunner/cocoa/TestRunnerWKWebView.h	2019-04-17 02:56:33 UTC (rev 244369)
+++ trunk/Tools/WebKitTestRunner/cocoa/TestRunnerWKWebView.h	2019-04-17 03:34:10 UTC (rev 244370)
@@ -49,6 +49,11 @@
 @property (nonatomic, copy) void (^rotationDidEndCallback)(void);
 @property (nonatomic, copy) NSString *accessibilitySpeakSelectionContent;
 
+- (void)setAllowedMenuActions:(NSArray<NSString *> *)actions;
+
+- (void)resetCustomMenuAction;
+- (void)installCustomMenuAction:(NSString *)name dismissesAutomatically:(BOOL)dismissesAutomatically callback:(dispatch_block_t)callback;
+
 - (void)resetInteractionCallbacks;
 - (void)zoomToScale:(double)scale animated:(BOOL)animated completionHandler:(void (^)(void))completionHandler;
 - (void)accessibilityRetrieveSpeakSelectionContentWithCompletionHandler:(void (^)(void))completionHandler;
@@ -58,6 +63,7 @@
 
 @property (nonatomic, readonly, getter=isShowingKeyboard) BOOL showingKeyboard;
 @property (nonatomic, readonly, getter=isShowingMenu) BOOL showingMenu;
+@property (nonatomic, readonly, getter=isDismissingMenu) BOOL dismissingMenu;
 @property (nonatomic, readonly, getter=isShowingPopover) BOOL showingPopover;
 @property (nonatomic, assign) BOOL usesSafariLikeRotation;
 @property (nonatomic, readonly, getter=isInteractingWithFormControl) BOOL interactingWithFormControl;

Modified: trunk/Tools/WebKitTestRunner/cocoa/TestRunnerWKWebView.mm (244369 => 244370)


--- trunk/Tools/WebKitTestRunner/cocoa/TestRunnerWKWebView.mm	2019-04-17 02:56:33 UTC (rev 244369)
+++ trunk/Tools/WebKitTestRunner/cocoa/TestRunnerWKWebView.mm	2019-04-17 03:34:10 UTC (rev 244370)
@@ -29,6 +29,8 @@
 #import "WebKitTestRunnerDraggingInfo.h"
 #import <WebKit/WKUIDelegatePrivate.h>
 #import <wtf/Assertions.h>
+#import <wtf/BlockPtr.h>
+#import <wtf/Optional.h>
 #import <wtf/RetainPtr.h>
 
 #if PLATFORM(IOS_FAMILY)
@@ -45,10 +47,18 @@
 @end
 #endif
 
+struct CustomMenuActionInfo {
+    RetainPtr<NSString> name;
+    BOOL dismissesAutomatically { NO };
+    BlockPtr<void()> callback;
+};
+
 @interface TestRunnerWKWebView () <WKUIDelegatePrivate> {
     RetainPtr<NSNumber> m_stableStateOverride;
     BOOL _isInteractingWithFormControl;
     BOOL _scrollingUpdatesDisabled;
+    Optional<CustomMenuActionInfo> _customMenuActionInfo;
+    RetainPtr<NSArray<NSString *>> _allowedMenuActions;
 }
 
 @property (nonatomic, copy) void (^zoomToScaleCompletionHandler)(void);
@@ -55,6 +65,7 @@
 @property (nonatomic, copy) void (^retrieveSpeakSelectionContentCompletionHandler)(void);
 @property (nonatomic, getter=isShowingKeyboard, setter=setIsShowingKeyboard:) BOOL showingKeyboard;
 @property (nonatomic, getter=isShowingMenu, setter=setIsShowingMenu:) BOOL showingMenu;
+@property (nonatomic, getter=isDismissingMenu, setter=setIsDismissingMenu:) BOOL dismissingMenu;
 @property (nonatomic, getter=isShowingPopover, setter=setIsShowingPopover:) BOOL showingPopover;
 
 @end
@@ -79,6 +90,7 @@
         [center addObserver:self selector:@selector(_invokeShowKeyboardCallbackIfNecessary) name:UIKeyboardDidShowNotification object:nil];
         [center addObserver:self selector:@selector(_invokeHideKeyboardCallbackIfNecessary) name:UIKeyboardDidHideNotification object:nil];
         [center addObserver:self selector:@selector(_didShowMenu) name:UIMenuControllerDidShowMenuNotification object:nil];
+        [center addObserver:self selector:@selector(_willHideMenu) name:UIMenuControllerWillHideMenuNotification object:nil];
         [center addObserver:self selector:@selector(_didHideMenu) name:UIMenuControllerDidHideMenuNotification object:nil];
         [center addObserver:self selector:@selector(_willPresentPopover) name:@"UIPopoverControllerWillPresentPopoverNotification" object:nil];
         [center addObserver:self selector:@selector(_didDismissPopover) name:@"UIPopoverControllerDidDismissPopoverNotification" object:nil];
@@ -132,6 +144,88 @@
         self.didDismissForcePressPreviewCallback();
 }
 
+- (BOOL)becomeFirstResponder
+{
+    BOOL wasFirstResponder = self.isFirstResponder;
+    BOOL becameFirstResponder = [super becomeFirstResponder];
+    if (!wasFirstResponder && becameFirstResponder)
+        [self _addCustomItemToMenuControllerIfNecessary];
+    return becameFirstResponder;
+}
+
+- (void)_addCustomItemToMenuControllerIfNecessary
+{
+    if (!_customMenuActionInfo)
+        return;
+
+    auto item = adoptNS([[UIMenuItem alloc] initWithTitle:_customMenuActionInfo->name.get() action:@selector(performCustomAction:)]);
+    [item setDontDismiss:!_customMenuActionInfo->dismissesAutomatically];
+    UIMenuController *controller = UIMenuController.sharedMenuController;
+    controller.menuItems = @[ item.get() ];
+    [controller update];
+}
+
+- (void)installCustomMenuAction:(NSString *)name dismissesAutomatically:(BOOL)dismissesAutomatically callback:(dispatch_block_t)callback
+{
+    _customMenuActionInfo = {{ name, dismissesAutomatically, callback }};
+    [self _addCustomItemToMenuControllerIfNecessary];
+}
+
+- (void)setAllowedMenuActions:(NSArray<NSString *> *)actions
+{
+    _allowedMenuActions = actions;
+}
+
+- (void)resetCustomMenuAction
+{
+    _customMenuActionInfo.reset();
+    UIMenuController.sharedMenuController.menuItems = @[ ];
+}
+
+- (void)performCustomAction:(id)sender
+{
+    if (!_customMenuActionInfo)
+        return;
+
+    if (!_customMenuActionInfo->callback) {
+        ASSERT_NOT_REACHED();
+        return;
+    }
+
+    _customMenuActionInfo->callback();
+}
+
+- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
+{
+    BOOL isCustomAction = action == @selector(performCustomAction:);
+    BOOL canPerformActionByDefault = [super canPerformAction:action withSender:sender];
+    if (isCustomAction)
+        canPerformActionByDefault = _customMenuActionInfo.hasValue();
+
+    if (canPerformActionByDefault && _allowedMenuActions && sender == UIMenuController.sharedMenuController) {
+        BOOL isAllowed = NO;
+        if (isCustomAction) {
+            for (NSString *allowedAction in _allowedMenuActions.get()) {
+                if ([[_customMenuActionInfo->name lowercaseString] isEqualToString:allowedAction.lowercaseString]) {
+                    isAllowed = YES;
+                    break;
+                }
+            }
+        } else {
+            for (NSString *allowedAction in _allowedMenuActions.get()) {
+                NSString *lowercaseSelectorName = [[allowedAction lowercaseString] stringByAppendingString:@":"];
+                if ([NSStringFromSelector(action).lowercaseString isEqualToString:lowercaseSelectorName]) {
+                    isAllowed = YES;
+                    break;
+                }
+            }
+        }
+        if (!isAllowed)
+            return NO;
+    }
+    return canPerformActionByDefault;
+}
+
 - (void)resetInteractionCallbacks
 {
     self.didStartFormControlInteractionCallback = nil;
@@ -195,8 +289,15 @@
         self.didShowMenuCallback();
 }
 
+- (void)_willHideMenu
+{
+    self.dismissingMenu = YES;
+}
+
 - (void)_didHideMenu
 {
+    self.dismissingMenu = NO;
+
     if (!self.showingMenu)
         return;
 

Modified: trunk/Tools/WebKitTestRunner/ios/TestControllerIOS.mm (244369 => 244370)


--- trunk/Tools/WebKitTestRunner/ios/TestControllerIOS.mm	2019-04-17 02:56:33 UTC (rev 244369)
+++ trunk/Tools/WebKitTestRunner/ios/TestControllerIOS.mm	2019-04-17 03:34:10 UTC (rev 244370)
@@ -163,6 +163,8 @@
         [webView _clearOverrideLayoutParameters];
         [webView _clearInterfaceOrientationOverride];
         [webView resetInteractionCallbacks];
+        [webView resetCustomMenuAction];
+        [webView setAllowedMenuActions:nil];
 
         UIScrollView *scrollView = webView.scrollView;
         [scrollView _removeAllAnimations:YES];

Modified: trunk/Tools/WebKitTestRunner/ios/UIScriptControllerIOS.mm (244369 => 244370)


--- trunk/Tools/WebKitTestRunner/ios/UIScriptControllerIOS.mm	2019-04-17 02:56:33 UTC (rev 244369)
+++ trunk/Tools/WebKitTestRunner/ios/UIScriptControllerIOS.mm	2019-04-17 03:34:10 UTC (rev 244370)
@@ -971,6 +971,11 @@
     return m_context->objectFromRect(WebCore::FloatRect(rectInRootViewCoordinates.origin.x, rectInRootViewCoordinates.origin.y, rectInRootViewCoordinates.size.width, rectInRootViewCoordinates.size.height));
 }
 
+bool UIScriptController::isDismissingMenu() const
+{
+    return TestController::singleton().mainWebView()->platformView().dismissingMenu;
+}
+
 bool UIScriptController::isShowingMenu() const
 {
     return TestController::singleton().mainWebView()->platformView().showingMenu;
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to