Title: [241322] trunk
Revision
241322
Author
wenson_hs...@apple.com
Date
2019-02-12 15:18:30 -0800 (Tue, 12 Feb 2019)

Log Message

Allow pages to trigger programmatic paste from script on iOS
https://bugs.webkit.org/show_bug.cgi?id=194271
<rdar://problem/47808810>

Reviewed by Tim Horton.

Source/WebCore:

Tests: editing/pasteboard/ios/dom-paste-confirmation.html
       editing/pasteboard/ios/dom-paste-consecutive-confirmations.html
       editing/pasteboard/ios/dom-paste-rejection.html
       editing/pasteboard/ios/dom-paste-requires-user-gesture.html

* dom/UserGestureIndicator.cpp:
(WebCore::UserGestureIndicator::~UserGestureIndicator):

Reset a gesture token's DOM paste access when exiting the scope of a user gesture. This prevents DOM paste
access permissions from leaking into `setTimeout()` callbacks when we forward user gesture tokens.

* dom/UserGestureIndicator.h:
(WebCore::UserGestureToken::resetDOMPasteAccess):

Source/WebKit:

Cancel the pending DOM paste access handler when the menu is about to hide, rather than when the hiding
animation has completed. This ensures that if the page (on behalf of the user) requests DOM paste again during
user interaction before the callout bar has finished fading after the previous DOM paste, we won't automatically
cancel the incoming DOM paste access request because the callout bar animation finished.

This scenario is exercised in the layout test editing/pasteboard/ios/dom-paste-consecutive-confirmations.html.

* Platform/spi/ios/UIKitSPI.h:
* UIProcess/ios/WKContentViewInteraction.mm:
(-[WKContentView setupInteraction]):
(-[WKContentView _willHideMenu:]):
(-[WKContentView _didHideMenu:]):

Tools:

Add support for interacting with the callout bar on iOS during layout tests. See below for more detail.

* DumpRenderTree/ios/UIScriptControllerIOS.mm:
(WTR::UIScriptController::platformSetDidShowMenuCallback):
(WTR::UIScriptController::platformSetDidHideMenuCallback):
(WTR::UIScriptController::rectForMenuAction const):

Add new mechanisms to make it possible to interact with and query the state of the callout menu on iOS. This
includes determining the rect (in content view coordinates) of the menu's controls, and callbacks to register
for when the menu is shown or hidden.

* TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl:
* TestRunnerShared/UIScriptContext/UIScriptContext.h:
* TestRunnerShared/UIScriptContext/UIScriptController.cpp:
(WTR::UIScriptController::setDidShowMenuCallback):
(WTR::UIScriptController::didShowMenuCallback const):
(WTR::UIScriptController::setDidHideMenuCallback):
(WTR::UIScriptController::didHideMenuCallback const):
(WTR::UIScriptController::platformSetDidShowMenuCallback):
(WTR::UIScriptController::platformSetDidHideMenuCallback):
(WTR::UIScriptController::rectForMenuAction const):
* TestRunnerShared/UIScriptContext/UIScriptController.h:
* WebKitTestRunner/TestController.cpp:
(WTR::TestController::resetPreferencesToConsistentValues):
(WTR::updateTestOptionsFromTestHeader):
* WebKitTestRunner/TestOptions.h:

Add a new test option to determine whether DOM paste is enabled. DOM paste is currently enabled everywhere by
default, but these new programmatic paste tests require it to be disabled in order for confirmation UI to show.

(WTR::TestOptions::hasSameInitializationOptions const):
* WebKitTestRunner/UIScriptControllerCocoa.mm:
(WTR::UIScriptController::calendarType const):
(WTR::UIScriptController::platformUndoManager const):
* WebKitTestRunner/cocoa/TestRunnerWKWebView.h:
* WebKitTestRunner/cocoa/TestRunnerWKWebView.mm:
(-[TestRunnerWKWebView initWithFrame:configuration:]):
(-[TestRunnerWKWebView dealloc]):
(-[TestRunnerWKWebView _didShowMenu]):
(-[TestRunnerWKWebView _didHideMenu]):

Listen to when the callout bar is presented and dismissed, and invoke testing callbacks as needed.

* WebKitTestRunner/ios/TestControllerIOS.mm:
(WTR::handleMenuWillHideNotification):
(WTR::handleMenuDidHideNotification):
(WTR::TestController::platformInitialize):
(WTR::TestController::platformDestroy):
(WTR::TestController::platformResetStateToConsistentValues):

Additionally ensure that any callout menu presented by a previous layout test is dismissed before running the
next test by hiding the callout bar if necessary, and then waiting for the "DidHide" notification.

* WebKitTestRunner/ios/UIScriptControllerIOS.mm:
(WTR::forEachViewInHierarchy):
(WTR::findViewInHierarchyOfType):

Move `forEachViewInHierarchy` so that we can use it throughout the file, and then add some additional helper
functions that dig through a given view's hierarchy in search of a view of a given class.

(WTR::UIScriptController::selectionStartGrabberViewRect const):
(WTR::UIScriptController::selectionEndGrabberViewRect const):
(WTR::UIScriptController::selectionCaretViewRect const):
(WTR::UIScriptController::selectionRangeViewRects const):
(WTR::UIScriptController::platformSetDidShowMenuCallback):
(WTR::UIScriptController::platformSetDidHideMenuCallback):

Tweak these to use `platformContentView` instead of grabbing the content view from WKWebView directly.

(WTR::UIScriptController::rectForMenuAction const):

Add a new UIScriptController method to get the rect of the action in the contextual menu (on iOS, this is the
callout bar) whose label matches the given string.

(WTR::UIScriptController::platformContentView const):

Add a `platformContentView()` helper on UIScriptController so that we can stop grabbing the value for key
"_currentContentView" from various places in this file. Additionally, rewrite `platformUndoManager()` in terms
of this new helper, and move the code out from iOS/macOS-specific files into UIScriptControllerCocoa.

(WTR::UIScriptController::platformUndoManager const): Deleted.
* WebKitTestRunner/mac/UIScriptControllerMac.mm:
(WTR::UIScriptController::platformContentView const):
(WTR::UIScriptController::platformUndoManager const): Deleted.

LayoutTests:

Add new tests to exercise programmatic pasting.

* TestExpectations:
* editing/pasteboard/ios/dom-paste-confirmation-expected.txt: Added.
* editing/pasteboard/ios/dom-paste-confirmation.html: Added.

Verify that the user can tap "Paste" to allow programmatic pasting.

* editing/pasteboard/ios/dom-paste-consecutive-confirmations-expected.txt: Added.
* editing/pasteboard/ios/dom-paste-consecutive-confirmations.html: Added.

Verify that DOM paste access isn't carried over when using `setTimeout` in a user gesture event handler.

* editing/pasteboard/ios/dom-paste-rejection-expected.txt: Added.
* editing/pasteboard/ios/dom-paste-rejection.html: Added.

Verify that resigning first responder dismisses the callout bar and does not allow programmatic pasting.

* editing/pasteboard/ios/dom-paste-requires-user-gesture-expected.txt: Added.
* editing/pasteboard/ios/dom-paste-requires-user-gesture.html: Added.

Verify that user gesture is required to present the callout menu for a programmatic paste request.

* editing/pasteboard/ios/resources/dom-paste-helper.js: Added.
(return.new.Promise.):
(async._waitForOrTriggerPasteMenu):
(async.triggerPasteMenuAfterTapAt):
(async.waitForPasteMenu):

Add helpers to summon, wait for, and interact with the callout bar when the page attempts to trigger a paste.

* platform/ios-wk2/TestExpectations:
* platform/win/TestExpectations:

Skip editing/pasteboard/ios by default, and enable it only in the modern WebKit port of iOS.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (241321 => 241322)


--- trunk/LayoutTests/ChangeLog	2019-02-12 22:55:33 UTC (rev 241321)
+++ trunk/LayoutTests/ChangeLog	2019-02-12 23:18:30 UTC (rev 241322)
@@ -1,3 +1,47 @@
+2019-02-12  Wenson Hsieh  <wenson_hs...@apple.com>
+
+        Allow pages to trigger programmatic paste from script on iOS
+        https://bugs.webkit.org/show_bug.cgi?id=194271
+        <rdar://problem/47808810>
+
+        Reviewed by Tim Horton.
+
+        Add new tests to exercise programmatic pasting.
+
+        * TestExpectations:
+        * editing/pasteboard/ios/dom-paste-confirmation-expected.txt: Added.
+        * editing/pasteboard/ios/dom-paste-confirmation.html: Added.
+
+        Verify that the user can tap "Paste" to allow programmatic pasting.
+
+        * editing/pasteboard/ios/dom-paste-consecutive-confirmations-expected.txt: Added.
+        * editing/pasteboard/ios/dom-paste-consecutive-confirmations.html: Added.
+
+        Verify that DOM paste access isn't carried over when using `setTimeout` in a user gesture event handler.
+
+        * editing/pasteboard/ios/dom-paste-rejection-expected.txt: Added.
+        * editing/pasteboard/ios/dom-paste-rejection.html: Added.
+
+        Verify that resigning first responder dismisses the callout bar and does not allow programmatic pasting.
+
+        * editing/pasteboard/ios/dom-paste-requires-user-gesture-expected.txt: Added.
+        * editing/pasteboard/ios/dom-paste-requires-user-gesture.html: Added.
+
+        Verify that user gesture is required to present the callout menu for a programmatic paste request.
+
+        * editing/pasteboard/ios/resources/dom-paste-helper.js: Added.
+        (return.new.Promise.):
+        (async._waitForOrTriggerPasteMenu):
+        (async.triggerPasteMenuAfterTapAt):
+        (async.waitForPasteMenu):
+
+        Add helpers to summon, wait for, and interact with the callout bar when the page attempts to trigger a paste.
+
+        * platform/ios-wk2/TestExpectations:
+        * platform/win/TestExpectations:
+
+        Skip editing/pasteboard/ios by default, and enable it only in the modern WebKit port of iOS.
+
 2019-02-12  Dean Jackson  <d...@apple.com>
 
         BitmapRenderer should handle existing ImageBuffers

Modified: trunk/LayoutTests/TestExpectations (241321 => 241322)


--- trunk/LayoutTests/TestExpectations	2019-02-12 22:55:33 UTC (rev 241321)
+++ trunk/LayoutTests/TestExpectations	2019-02-12 23:18:30 UTC (rev 241322)
@@ -52,6 +52,7 @@
 system-preview [ Skip ]
 editing/images [ Skip ]
 pointerevents/ios [ Skip ]
+editing/pasteboard/ios [ Skip ]
 editing/pasteboard/mac [ Skip ]
 
 # window.showModalDialog is only tested in DumpRenderTree on Mac.

Added: trunk/LayoutTests/editing/pasteboard/ios/dom-paste-confirmation-expected.txt (0 => 241322)


--- trunk/LayoutTests/editing/pasteboard/ios/dom-paste-confirmation-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/editing/pasteboard/ios/dom-paste-confirmation-expected.txt	2019-02-12 23:18:30 UTC (rev 241322)
@@ -0,0 +1,18 @@
+Click here to copy
+Click here to copy
+Click here to copy
+Verifies that a callout is shown when the page programmatically triggers paste, and that tapping the callout allows the paste to happen. To manually test, tap the text on the bottom, tap the editable area above, and then select 'Paste' in the resulting callout menu. The text 'Click here to copy' should be pasted twice in the editable area.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+PASS document.queryCommandSupported('Paste') is true
+PASS document.queryCommandEnabled('Paste') is true
+PASS event.clipboardData.getData('text/plain') is "Click here to copy"
+PASS document.execCommand('Paste') is true
+PASS event.clipboardData.getData('text/plain') is "Click here to copy"
+PASS document.execCommand('Paste') is true
+PASS editor.textContent is "Click here to copyClick here to copy"
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/editing/pasteboard/ios/dom-paste-confirmation.html (0 => 241322)


--- trunk/LayoutTests/editing/pasteboard/ios/dom-paste-confirmation.html	                        (rev 0)
+++ trunk/LayoutTests/editing/pasteboard/ios/dom-paste-confirmation.html	2019-02-12 23:18:30 UTC (rev 241322)
@@ -0,0 +1,73 @@
+<!DOCTYPE html> <!-- webkit-test-runner [ domPasteAllowed=false useFlexibleViewport=true ] -->
+<html>
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<head>
+<script src=""
+<script src=""
+<script src=""
+<style>
+body {
+    margin: 0;
+}
+
+#copy, #editor {
+    text-align: center;
+}
+
+#copy {
+    font-size: 40px;
+    width: 100%;
+    height: 50px;
+    border: 1px dashed black;
+}
+
+#editor {
+    width: 100%;
+    height: 100px;
+    border: 1px dashed silver;
+}
+</style>
+</head>
+<body>
+<div id="editor" contenteditable></div>
+<div id="copy">Click here to copy</div>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+jsTestIsAsync = true;
+
+const copy = document.getElementById("copy");
+const editor = document.getElementById("editor");
+
+description("Verifies that a callout is shown when the page programmatically triggers paste, and that tapping the callout allows the paste to happen. To manually test, tap the text on the bottom, tap the editable area above, and then select 'Paste' in the resulting callout menu. The text 'Click here to copy' should be pasted <strong><em>twice</em></strong> in the editable area.");
+
+copy.addEventListener("click", () => {
+    getSelection().selectAllChildren(copy);
+    document.execCommand("Copy");
+    getSelection().removeAllRanges();
+});
+
+editor.addEventListener("paste", event => shouldBeEqualToString("event.clipboardData.getData('text/plain')", "Click here to copy"));
+editor.addEventListener("click", event => {
+    getSelection().setPosition(editor);
+    shouldBe("document.queryCommandSupported('Paste')", "true");
+    shouldBe("document.queryCommandEnabled('Paste')", "true");
+    shouldBe("document.execCommand('Paste')", "true");
+    document.execCommand('InsertParagraph');
+    shouldBe("document.execCommand('Paste')", "true");
+    shouldBeEqualToString("editor.textContent", "Click here to copyClick here to copy");
+    event.preventDefault();
+    editor.blur();
+});
+
+(async () => {
+    if (!window.testRunner)
+        return;
+
+    await UIHelper.activateAt(160, 125);
+    await triggerPasteMenuAfterTapAt(160, 50);
+    finishJSTest();
+})();
+</script>
+</body>
+</html>

Added: trunk/LayoutTests/editing/pasteboard/ios/dom-paste-consecutive-confirmations-expected.txt (0 => 241322)


--- trunk/LayoutTests/editing/pasteboard/ios/dom-paste-consecutive-confirmations-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/editing/pasteboard/ios/dom-paste-consecutive-confirmations-expected.txt	2019-02-12 23:18:30 UTC (rev 241322)
@@ -0,0 +1,13 @@
+Click here to copy
+Verifies that no callout is shown when the page programmatically triggers paste on a timer after user interaction. To test manually, click the text on the bottom to copy, and then click the editable area above to trigger two programmatic pastes with the callout bar. Check that permissions for the first programmatic paste do not affect the second programmatic paste, since it is performed on a zero-delay timer.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+PASS document.execCommand('Paste') is true
+PASS editor.textContent is "Click here to copy"
+PASS document.execCommand('Paste') is true
+PASS editor.textContent is "Click here to copy"
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/editing/pasteboard/ios/dom-paste-consecutive-confirmations.html (0 => 241322)


--- trunk/LayoutTests/editing/pasteboard/ios/dom-paste-consecutive-confirmations.html	                        (rev 0)
+++ trunk/LayoutTests/editing/pasteboard/ios/dom-paste-consecutive-confirmations.html	2019-02-12 23:18:30 UTC (rev 241322)
@@ -0,0 +1,93 @@
+<!DOCTYPE html> <!-- webkit-test-runner [ domPasteAllowed=false useFlexibleViewport=true ] -->
+<html>
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<head>
+<script src=""
+<script src=""
+<script src=""
+<style>
+body {
+    margin: 0;
+}
+
+#copy, #editor {
+    text-align: center;
+}
+
+#copy {
+    font-size: 40px;
+    width: 100%;
+    height: 50px;
+    border: 1px dashed black;
+}
+
+#editor {
+    width: 100%;
+    height: 100px;
+    border: 1px dashed silver;
+}
+</style>
+</head>
+<body>
+<div id="editor" contenteditable></div>
+<div id="copy">Click here to copy</div>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+jsTestIsAsync = true;
+
+const copy = document.getElementById("copy");
+const editor = document.getElementById("editor");
+
+description("Verifies that no callout is shown when the page programmatically triggers paste on a timer after user interaction. To test manually, click the text on the bottom to copy, and then click the editable area above to trigger two programmatic pastes with the callout bar. Check that permissions for the first programmatic paste do not affect the second programmatic paste, since it is performed on a zero-delay timer.");
+
+async function waitForAndTapPasteMenuTwice() {
+    return new Promise(resolve => {
+        testRunner.runUIScript(`
+            (() => {
+                doneCount = 0;
+                function incrementProgress() {
+                    if (++doneCount === 4)
+                        uiController.uiScriptComplete();
+                }
+
+                uiController.didHideMenuCallback = incrementProgress;
+                uiController.didShowMenuCallback = () => {
+                    const rect = uiController.rectForMenuAction("Paste");
+                    uiController.singleTapAtPoint(rect.left + rect.width / 2, rect.top + rect.height / 2, incrementProgress);
+                };
+            })()`, resolve);
+    });
+}
+
+copy.addEventListener("click", () => {
+    getSelection().selectAllChildren(copy);
+    document.execCommand("Copy");
+    getSelection().removeAllRanges();
+});
+
+function paste() {
+    getSelection().setPosition(editor);
+    shouldBe("document.execCommand('Paste')", "true");
+    shouldBeEqualToString("editor.textContent", "Click here to copy");
+    editor.textContent = "";
+    getSelection().removeAllRanges(editor);
+}
+
+editor.addEventListener("click", event => {
+    event.preventDefault();
+    paste();
+    setTimeout(paste, 0);
+});
+
+(async () => {
+    if (!window.testRunner || !window.internals)
+        return;
+
+    waitForAndTapPasteMenuTwice().then(finishJSTest);
+    await UIHelper.activateAt(160, 125);
+    await UIHelper.activateAt(160, 50);
+})();
+</script>
+</body>
+</html>

Added: trunk/LayoutTests/editing/pasteboard/ios/dom-paste-rejection-expected.txt (0 => 241322)


--- trunk/LayoutTests/editing/pasteboard/ios/dom-paste-rejection-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/editing/pasteboard/ios/dom-paste-rejection-expected.txt	2019-02-12 23:18:30 UTC (rev 241322)
@@ -0,0 +1,14 @@
+Click here to copy
+Verifies that a callout is shown when the page programmatically triggers paste, and that dismissing the callout prevents the paste from happening. To manually test, tap the text on the bottom, tap the editable area above, and then dismiss the resulting callout menu by scrolling or tapping elsewhere. The text 'Click here to copy' should not be pasted, and the callout bar should disappear.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+PASS document.queryCommandSupported('Paste') is true
+PASS document.queryCommandEnabled('Paste') is true
+PASS document.execCommand('Paste') is false
+PASS document.execCommand('Paste') is false
+PASS editor.textContent is ""
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/editing/pasteboard/ios/dom-paste-rejection.html (0 => 241322)


--- trunk/LayoutTests/editing/pasteboard/ios/dom-paste-rejection.html	                        (rev 0)
+++ trunk/LayoutTests/editing/pasteboard/ios/dom-paste-rejection.html	2019-02-12 23:18:30 UTC (rev 241322)
@@ -0,0 +1,72 @@
+<!DOCTYPE html> <!-- webkit-test-runner [ domPasteAllowed=false useFlexibleViewport=true ] -->
+<html>
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<head>
+<script src=""
+<script src=""
+<script src=""
+<style>
+body {
+    margin: 0;
+}
+
+#copy, #editor {
+    text-align: center;
+}
+
+#copy {
+    font-size: 40px;
+    width: 100%;
+    height: 50px;
+    border: 1px dashed black;
+}
+
+#editor {
+    width: 100%;
+    height: 100px;
+    border: 1px dashed silver;
+}
+</style>
+</head>
+<body>
+<div id="editor" contenteditable></div>
+<div id="copy">Click here to copy</div>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+jsTestIsAsync = true;
+
+const copy = document.getElementById("copy");
+const editor = document.getElementById("editor");
+
+description("Verifies that a callout is shown when the page programmatically triggers paste, and that dismissing the callout prevents the paste from happening. To manually test, tap the text on the bottom, tap the editable area above, and then dismiss the resulting callout menu by scrolling or tapping elsewhere. The text 'Click here to copy' should <strong>not</strong> be pasted, and the callout bar should disappear.");
+
+copy.addEventListener("click", () => {
+    getSelection().selectAllChildren(copy);
+    document.execCommand("Copy");
+    getSelection().removeAllRanges();
+});
+
+editor.addEventListener("paste", event => shouldBeEqualToString("event.clipboardData.getData('text/plain')", "Click here to copy"));
+editor.addEventListener("click", event => {
+    getSelection().setPosition(editor);
+    shouldBe("document.queryCommandSupported('Paste')", "true");
+    shouldBe("document.queryCommandEnabled('Paste')", "true");
+    shouldBe("document.execCommand('Paste')", "false");
+    shouldBe("document.execCommand('Paste')", "false");
+    shouldBeEqualToString("editor.textContent", "");
+    event.preventDefault();
+    editor.blur();
+});
+
+(async () => {
+    if (!window.testRunner)
+        return;
+
+    await UIHelper.activateAt(160, 125);
+    await triggerPasteMenuAfterTapAt(160, 50, false);
+    finishJSTest();
+})();
+</script>
+</body>
+</html>

Added: trunk/LayoutTests/editing/pasteboard/ios/dom-paste-requires-user-gesture-expected.txt (0 => 241322)


--- trunk/LayoutTests/editing/pasteboard/ios/dom-paste-requires-user-gesture-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/editing/pasteboard/ios/dom-paste-requires-user-gesture-expected.txt	2019-02-12 23:18:30 UTC (rev 241322)
@@ -0,0 +1,13 @@
+Click here to copy
+Click here to copy
+Verifies that no callout is shown when the page programmatically triggers paste outside the scope of user interaction. This test requires WebKitTestRunner.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+PASS document.execCommand('Paste') is true
+PASS document.execCommand('Paste') is false
+PASS editor.textContent is "Click here to copy"
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/editing/pasteboard/ios/dom-paste-requires-user-gesture.html (0 => 241322)


--- trunk/LayoutTests/editing/pasteboard/ios/dom-paste-requires-user-gesture.html	                        (rev 0)
+++ trunk/LayoutTests/editing/pasteboard/ios/dom-paste-requires-user-gesture.html	2019-02-12 23:18:30 UTC (rev 241322)
@@ -0,0 +1,62 @@
+<!DOCTYPE html> <!-- webkit-test-runner [ domPasteAllowed=false useFlexibleViewport=true ] -->
+<html>
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<head>
+<script src=""
+<script src=""
+<script src=""
+<style>
+body {
+    margin: 0;
+}
+
+#copy, #editor {
+    text-align: center;
+}
+
+#copy {
+    font-size: 40px;
+    width: 100%;
+    height: 50px;
+    border: 1px dashed black;
+}
+
+#editor {
+    width: 100%;
+    height: 100px;
+    border: 1px dashed silver;
+}
+</style>
+</head>
+<body>
+<div id="editor" contenteditable></div>
+<div id="copy">Click here to copy</div>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+jsTestIsAsync = true;
+
+const copy = document.getElementById("copy");
+const editor = document.getElementById("editor");
+
+description("Verifies that no callout is shown when the page programmatically triggers paste outside the scope of user interaction. This test requires WebKitTestRunner.");
+
+copy.addEventListener("click", () => {
+    getSelection().selectAllChildren(copy);
+    document.execCommand("Copy");
+    getSelection().removeAllRanges();
+});
+
+UIHelper.activateAt(160, 125).then(() => {
+    editor.focus();
+    waitForPasteMenu().then(finishJSTest);
+
+    UIHelper.ensurePresentationUpdate().then(() => {
+        internals.withUserGesture(() => shouldBe("document.execCommand('Paste')", "true"));
+        shouldBe("document.execCommand('Paste')", "false");
+        shouldBeEqualToString("editor.textContent", "Click here to copy");
+    });
+});
+</script>
+</body>
+</html>

Added: trunk/LayoutTests/editing/pasteboard/ios/resources/dom-paste-helper.js (0 => 241322)


--- trunk/LayoutTests/editing/pasteboard/ios/resources/dom-paste-helper.js	                        (rev 0)
+++ trunk/LayoutTests/editing/pasteboard/ios/resources/dom-paste-helper.js	2019-02-12 23:18:30 UTC (rev 241322)
@@ -0,0 +1,35 @@
+
+async function _waitForOrTriggerPasteMenu(x, y, proceedWithPaste, shouldTap) {
+    return new Promise(resolve => {
+        testRunner.runUIScript(`
+            (() => {
+                doneCount = 0;
+                function checkDone() {
+                    if (++doneCount === (${shouldTap} ? 3 : 2))
+                        uiController.uiScriptComplete();
+                }
+
+                uiController.didHideMenuCallback = checkDone;
+                uiController.didShowMenuCallback = () => {
+                    if (${proceedWithPaste}) {
+                        const rect = uiController.rectForMenuAction("Paste");
+                        uiController.singleTapAtPoint(rect.left + rect.width / 2, rect.top + rect.height / 2, checkDone);
+                    } else {
+                        uiController.resignFirstResponder();
+                        checkDone();
+                    }
+                };
+
+                if (${shouldTap})
+                    uiController.singleTapAtPoint(${x}, ${y}, checkDone);
+            })()`, resolve);
+    });
+}
+
+async function triggerPasteMenuAfterTapAt(x, y, proceedWithPaste = true) {
+    return _waitForOrTriggerPasteMenu(x, y, proceedWithPaste, true);
+}
+
+async function waitForPasteMenu(proceedWithPaste = true) {
+    return _waitForOrTriggerPasteMenu(null, null, proceedWithPaste, false);
+}

Modified: trunk/LayoutTests/platform/ios-wk2/TestExpectations (241321 => 241322)


--- trunk/LayoutTests/platform/ios-wk2/TestExpectations	2019-02-12 22:55:33 UTC (rev 241321)
+++ trunk/LayoutTests/platform/ios-wk2/TestExpectations	2019-02-12 23:18:30 UTC (rev 241322)
@@ -15,6 +15,7 @@
 fast/web-share [ Pass ]
 editing/find [ Pass ]
 editing/input/ios [ Pass ]
+editing/pasteboard/ios [ Pass ]
 editing/undo-manager [ Pass ]
 
 editing/selection/character-granularity-rect.html [ Failure ]

Modified: trunk/LayoutTests/platform/win/TestExpectations (241321 => 241322)


--- trunk/LayoutTests/platform/win/TestExpectations	2019-02-12 22:55:33 UTC (rev 241321)
+++ trunk/LayoutTests/platform/win/TestExpectations	2019-02-12 23:18:30 UTC (rev 241322)
@@ -1157,6 +1157,7 @@
 ###### Pasteboard
 ###### These tests are very flaky.
 editing/pasteboard/ [ Pass Failure ]
+editing/pasteboard/ios [ Skip ]
 [ Debug ] editing/pasteboard/copy-crash.html [ Skip ] # Debug Assertion
 [ Debug ] editing/pasteboard/copy-crash-with-extraneous-attribute.html [ Skip ] # Debug Assertion
 [ Debug ] editing/pasteboard/testcase-9507.html [ Skip ] # Debug Assertion

Modified: trunk/Source/WebCore/ChangeLog (241321 => 241322)


--- trunk/Source/WebCore/ChangeLog	2019-02-12 22:55:33 UTC (rev 241321)
+++ trunk/Source/WebCore/ChangeLog	2019-02-12 23:18:30 UTC (rev 241322)
@@ -1,3 +1,25 @@
+2019-02-12  Wenson Hsieh  <wenson_hs...@apple.com>
+
+        Allow pages to trigger programmatic paste from script on iOS
+        https://bugs.webkit.org/show_bug.cgi?id=194271
+        <rdar://problem/47808810>
+
+        Reviewed by Tim Horton.
+
+        Tests: editing/pasteboard/ios/dom-paste-confirmation.html
+               editing/pasteboard/ios/dom-paste-consecutive-confirmations.html
+               editing/pasteboard/ios/dom-paste-rejection.html
+               editing/pasteboard/ios/dom-paste-requires-user-gesture.html
+
+        * dom/UserGestureIndicator.cpp:
+        (WebCore::UserGestureIndicator::~UserGestureIndicator):
+
+        Reset a gesture token's DOM paste access when exiting the scope of a user gesture. This prevents DOM paste
+        access permissions from leaking into `setTimeout()` callbacks when we forward user gesture tokens.
+
+        * dom/UserGestureIndicator.h:
+        (WebCore::UserGestureToken::resetDOMPasteAccess):
+
 2019-02-12  Chris Fleizach  <cfleiz...@apple.com>
 
         AX: IsolatedTree: Implement more attributes

Modified: trunk/Source/WebCore/dom/UserGestureIndicator.cpp (241321 => 241322)


--- trunk/Source/WebCore/dom/UserGestureIndicator.cpp	2019-02-12 22:55:33 UTC (rev 241321)
+++ trunk/Source/WebCore/dom/UserGestureIndicator.cpp	2019-02-12 23:18:30 UTC (rev 241322)
@@ -87,6 +87,9 @@
     if (!isMainThread())
         return;
     
+    if (auto token = currentToken())
+        token->resetDOMPasteAccess();
+
     currentToken() = m_previousToken;
 }
 

Modified: trunk/Source/WebCore/dom/UserGestureIndicator.h (241321 => 241322)


--- trunk/Source/WebCore/dom/UserGestureIndicator.h	2019-02-12 22:55:33 UTC (rev 241321)
+++ trunk/Source/WebCore/dom/UserGestureIndicator.h	2019-02-12 23:18:30 UTC (rev 241322)
@@ -66,6 +66,7 @@
 
     DOMPasteAccessPolicy domPasteAccessPolicy() const { return m_domPasteAccessPolicy; }
     void didRequestDOMPasteAccess(bool granted) { m_domPasteAccessPolicy = granted ? DOMPasteAccessPolicy::Granted : DOMPasteAccessPolicy::Denied; }
+    void resetDOMPasteAccess() { m_domPasteAccessPolicy = DOMPasteAccessPolicy::NotRequestedYet; }
 
 private:
     UserGestureToken(ProcessingUserGestureState state, UserGestureType gestureType)

Modified: trunk/Source/WebKit/ChangeLog (241321 => 241322)


--- trunk/Source/WebKit/ChangeLog	2019-02-12 22:55:33 UTC (rev 241321)
+++ trunk/Source/WebKit/ChangeLog	2019-02-12 23:18:30 UTC (rev 241322)
@@ -1,3 +1,24 @@
+2019-02-12  Wenson Hsieh  <wenson_hs...@apple.com>
+
+        Allow pages to trigger programmatic paste from script on iOS
+        https://bugs.webkit.org/show_bug.cgi?id=194271
+        <rdar://problem/47808810>
+
+        Reviewed by Tim Horton.
+
+        Cancel the pending DOM paste access handler when the menu is about to hide, rather than when the hiding
+        animation has completed. This ensures that if the page (on behalf of the user) requests DOM paste again during
+        user interaction before the callout bar has finished fading after the previous DOM paste, we won't automatically
+        cancel the incoming DOM paste access request because the callout bar animation finished.
+
+        This scenario is exercised in the layout test editing/pasteboard/ios/dom-paste-consecutive-confirmations.html.
+
+        * Platform/spi/ios/UIKitSPI.h:
+        * UIProcess/ios/WKContentViewInteraction.mm:
+        (-[WKContentView setupInteraction]):
+        (-[WKContentView _willHideMenu:]):
+        (-[WKContentView _didHideMenu:]):
+
 2019-02-12  Chris Fleizach  <cfleiz...@apple.com>
 
         AX: IsolatedTree: Implement more attributes

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


--- trunk/Source/WebKit/Platform/spi/ios/UIKitSPI.h	2019-02-12 22:55:33 UTC (rev 241321)
+++ trunk/Source/WebKit/Platform/spi/ios/UIKitSPI.h	2019-02-12 23:18:30 UTC (rev 241322)
@@ -980,6 +980,12 @@
 -(void)remove;
 @end
 
+@interface UIURLDragPreviewView : UIView
++ (instancetype)viewWithTitle:(NSString *)title URL:(NSURL *)url;
+@end
+
+#endif
+
 @interface UICalloutBar : UIView
 + (void)fadeSharedCalloutBar;
 @end
@@ -991,12 +997,6 @@
 + (UITextEffectsWindow *)sharedTextEffectsWindow;
 @end
 
-@interface UIURLDragPreviewView : UIView
-+ (instancetype)viewWithTitle:(NSString *)title URL:(NSURL *)url;
-@end
-
-#endif
-
 @interface _UIVisualEffectLayerConfig : NSObject
 + (instancetype)layerWithFillColor:(UIColor *)fillColor opacity:(CGFloat)opacity filterType:(NSString *)filterType;
 - (void)configureLayerView:(UIView *)view;

Modified: trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm (241321 => 241322)


--- trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm	2019-02-12 22:55:33 UTC (rev 241321)
+++ trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm	2019-02-12 23:18:30 UTC (rev 241322)
@@ -740,6 +740,7 @@
 #endif
 
     NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
+    [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(_keyboardDidRequestDismissal:) name:UIKeyboardPrivateDidRequestDismissalNotification object:nil];
 
@@ -2810,11 +2811,15 @@
     return [super targetForAction:action withSender:sender];
 }
 
+- (void)_willHideMenu:(NSNotification *)notification
+{
+    [self _handleDOMPasteRequestWithResult:NO];
+}
+
 - (void)_didHideMenu:(NSNotification *)notification
 {
     _showingTextStyleOptions = NO;
     [_textSelectionAssistant hideTextStyleOptions];
-    [self _handleDOMPasteRequestWithResult:NO];
 }
 
 - (void)_keyboardDidRequestDismissal:(NSNotification *)notification

Modified: trunk/Tools/ChangeLog (241321 => 241322)


--- trunk/Tools/ChangeLog	2019-02-12 22:55:33 UTC (rev 241321)
+++ trunk/Tools/ChangeLog	2019-02-12 23:18:30 UTC (rev 241322)
@@ -1,3 +1,96 @@
+2019-02-12  Wenson Hsieh  <wenson_hs...@apple.com>
+
+        Allow pages to trigger programmatic paste from script on iOS
+        https://bugs.webkit.org/show_bug.cgi?id=194271
+        <rdar://problem/47808810>
+
+        Reviewed by Tim Horton.
+
+        Add support for interacting with the callout bar on iOS during layout tests. See below for more detail.
+
+        * DumpRenderTree/ios/UIScriptControllerIOS.mm:
+        (WTR::UIScriptController::platformSetDidShowMenuCallback):
+        (WTR::UIScriptController::platformSetDidHideMenuCallback):
+        (WTR::UIScriptController::rectForMenuAction const):
+
+        Add new mechanisms to make it possible to interact with and query the state of the callout menu on iOS. This
+        includes determining the rect (in content view coordinates) of the menu's controls, and callbacks to register
+        for when the menu is shown or hidden.
+
+        * TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl:
+        * TestRunnerShared/UIScriptContext/UIScriptContext.h:
+        * TestRunnerShared/UIScriptContext/UIScriptController.cpp:
+        (WTR::UIScriptController::setDidShowMenuCallback):
+        (WTR::UIScriptController::didShowMenuCallback const):
+        (WTR::UIScriptController::setDidHideMenuCallback):
+        (WTR::UIScriptController::didHideMenuCallback const):
+        (WTR::UIScriptController::platformSetDidShowMenuCallback):
+        (WTR::UIScriptController::platformSetDidHideMenuCallback):
+        (WTR::UIScriptController::rectForMenuAction const):
+        * TestRunnerShared/UIScriptContext/UIScriptController.h:
+        * WebKitTestRunner/TestController.cpp:
+        (WTR::TestController::resetPreferencesToConsistentValues):
+        (WTR::updateTestOptionsFromTestHeader):
+        * WebKitTestRunner/TestOptions.h:
+
+        Add a new test option to determine whether DOM paste is enabled. DOM paste is currently enabled everywhere by
+        default, but these new programmatic paste tests require it to be disabled in order for confirmation UI to show.
+
+        (WTR::TestOptions::hasSameInitializationOptions const):
+        * WebKitTestRunner/UIScriptControllerCocoa.mm:
+        (WTR::UIScriptController::calendarType const):
+        (WTR::UIScriptController::platformUndoManager const):
+        * WebKitTestRunner/cocoa/TestRunnerWKWebView.h:
+        * WebKitTestRunner/cocoa/TestRunnerWKWebView.mm:
+        (-[TestRunnerWKWebView initWithFrame:configuration:]):
+        (-[TestRunnerWKWebView dealloc]):
+        (-[TestRunnerWKWebView _didShowMenu]):
+        (-[TestRunnerWKWebView _didHideMenu]):
+
+        Listen to when the callout bar is presented and dismissed, and invoke testing callbacks as needed.
+
+        * WebKitTestRunner/ios/TestControllerIOS.mm:
+        (WTR::handleMenuWillHideNotification):
+        (WTR::handleMenuDidHideNotification):
+        (WTR::TestController::platformInitialize):
+        (WTR::TestController::platformDestroy):
+        (WTR::TestController::platformResetStateToConsistentValues):
+
+        Additionally ensure that any callout menu presented by a previous layout test is dismissed before running the
+        next test by hiding the callout bar if necessary, and then waiting for the "DidHide" notification.
+
+        * WebKitTestRunner/ios/UIScriptControllerIOS.mm:
+        (WTR::forEachViewInHierarchy):
+        (WTR::findViewInHierarchyOfType):
+
+        Move `forEachViewInHierarchy` so that we can use it throughout the file, and then add some additional helper
+        functions that dig through a given view's hierarchy in search of a view of a given class.
+
+        (WTR::UIScriptController::selectionStartGrabberViewRect const):
+        (WTR::UIScriptController::selectionEndGrabberViewRect const):
+        (WTR::UIScriptController::selectionCaretViewRect const):
+        (WTR::UIScriptController::selectionRangeViewRects const):
+        (WTR::UIScriptController::platformSetDidShowMenuCallback):
+        (WTR::UIScriptController::platformSetDidHideMenuCallback):
+
+        Tweak these to use `platformContentView` instead of grabbing the content view from WKWebView directly.
+
+        (WTR::UIScriptController::rectForMenuAction const):
+
+        Add a new UIScriptController method to get the rect of the action in the contextual menu (on iOS, this is the
+        callout bar) whose label matches the given string.
+
+        (WTR::UIScriptController::platformContentView const):
+
+        Add a `platformContentView()` helper on UIScriptController so that we can stop grabbing the value for key
+        "_currentContentView" from various places in this file. Additionally, rewrite `platformUndoManager()` in terms
+        of this new helper, and move the code out from iOS/macOS-specific files into UIScriptControllerCocoa.
+
+        (WTR::UIScriptController::platformUndoManager const): Deleted.
+        * WebKitTestRunner/mac/UIScriptControllerMac.mm:
+        (WTR::UIScriptController::platformContentView const):
+        (WTR::UIScriptController::platformUndoManager const): Deleted.
+
 2019-02-12  Jonathan Bedard  <jbed...@apple.com>
 
         webkitpy: No option to only show unexpected failures in results.html for iPad

Modified: trunk/Tools/DumpRenderTree/ios/UIScriptControllerIOS.mm (241321 => 241322)


--- trunk/Tools/DumpRenderTree/ios/UIScriptControllerIOS.mm	2019-02-12 22:55:33 UTC (rev 241321)
+++ trunk/Tools/DumpRenderTree/ios/UIScriptControllerIOS.mm	2019-02-12 23:18:30 UTC (rev 241322)
@@ -324,6 +324,19 @@
 {
 }
 
+void UIScriptController::platformSetDidShowMenuCallback()
+{
+}
+
+void UIScriptController::platformSetDidHideMenuCallback()
+{
+}
+
+JSObjectRef UIScriptController::rectForMenuAction(JSStringRef) const
+{
+    return nullptr;
+}
+
 void UIScriptController::platformSetDidEndScrollingCallback()
 {
 }

Modified: trunk/Tools/TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl (241321 => 241322)


--- trunk/Tools/TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl	2019-02-12 22:55:33 UTC (rev 241321)
+++ trunk/Tools/TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl	2019-02-12 23:18:30 UTC (rev 241322)
@@ -223,6 +223,10 @@
     attribute object didHideKeyboardCallback;
     readonly attribute boolean isShowingKeyboard;
 
+    attribute object didShowMenuCallback;
+    attribute object didHideMenuCallback;
+    object rectForMenuAction(DOMString action);
+
     attribute object willBeginZoomingCallback;
     attribute object didEndZoomingCallback;
 

Modified: trunk/Tools/TestRunnerShared/UIScriptContext/UIScriptContext.h (241321 => 241322)


--- trunk/Tools/TestRunnerShared/UIScriptContext/UIScriptContext.h	2019-02-12 22:55:33 UTC (rev 241321)
+++ trunk/Tools/TestRunnerShared/UIScriptContext/UIScriptContext.h	2019-02-12 23:18:30 UTC (rev 241322)
@@ -54,6 +54,8 @@
     CallbackTypeDidEndZooming,
     CallbackTypeDidShowKeyboard,
     CallbackTypeDidHideKeyboard,
+    CallbackTypeDidShowMenu,
+    CallbackTypeDidHideMenu,
     CallbackTypeDidEndScrolling,
     CallbackTypeDidStartFormControlInteraction,
     CallbackTypeDidEndFormControlInteraction,

Modified: trunk/Tools/TestRunnerShared/UIScriptContext/UIScriptController.cpp (241321 => 241322)


--- trunk/Tools/TestRunnerShared/UIScriptContext/UIScriptController.cpp	2019-02-12 22:55:33 UTC (rev 241321)
+++ trunk/Tools/TestRunnerShared/UIScriptContext/UIScriptController.cpp	2019-02-12 23:18:30 UTC (rev 241322)
@@ -205,6 +205,28 @@
     return m_context->callbackWithID(CallbackTypeDidHideKeyboard);
 }
 
+void UIScriptController::setDidShowMenuCallback(JSValueRef callback)
+{
+    m_context->registerCallback(callback, CallbackTypeDidShowMenu);
+    platformSetDidShowMenuCallback();
+}
+
+JSValueRef UIScriptController::didShowMenuCallback() const
+{
+    return m_context->callbackWithID(CallbackTypeDidShowMenu);
+}
+
+void UIScriptController::setDidHideMenuCallback(JSValueRef callback)
+{
+    m_context->registerCallback(callback, CallbackTypeDidHideMenu);
+    platformSetDidHideMenuCallback();
+}
+
+JSValueRef UIScriptController::didHideMenuCallback() const
+{
+    return m_context->callbackWithID(CallbackTypeDidHideMenu);
+}
+
 #if !PLATFORM(COCOA)
 
 void UIScriptController::zoomToScale(double, JSValueRef)
@@ -502,6 +524,19 @@
 {
 }
 
+void UIScriptController::platformSetDidShowMenuCallback()
+{
+}
+
+void UIScriptController::platformSetDidHideMenuCallback()
+{
+}
+
+JSObjectRef UIScriptController::rectForMenuAction(JSStringRef) const
+{
+    return nullptr;
+}
+
 void UIScriptController::platformClearAllCallbacks()
 {
 }

Modified: trunk/Tools/TestRunnerShared/UIScriptContext/UIScriptController.h (241321 => 241322)


--- trunk/Tools/TestRunnerShared/UIScriptContext/UIScriptController.h	2019-02-12 22:55:33 UTC (rev 241321)
+++ trunk/Tools/TestRunnerShared/UIScriptContext/UIScriptController.h	2019-02-12 23:18:30 UTC (rev 241322)
@@ -32,6 +32,8 @@
 #include <wtf/Ref.h>
 
 OBJC_CLASS NSUndoManager;
+OBJC_CLASS NSView;
+OBJC_CLASS UIView;
 
 namespace WebCore {
 class FloatRect;
@@ -156,6 +158,14 @@
 
     bool isShowingKeyboard() const;
 
+    void setDidHideMenuCallback(JSValueRef);
+    JSValueRef didHideMenuCallback() const;
+
+    void setDidShowMenuCallback(JSValueRef);
+    JSValueRef didShowMenuCallback() const;
+
+    JSObjectRef rectForMenuAction(JSStringRef action) const;
+
     void setDidEndScrollingCallback(JSValueRef);
     JSValueRef didEndScrollingCallback() const;
 
@@ -230,6 +240,8 @@
     void platformSetDidEndZoomingCallback();
     void platformSetDidShowKeyboardCallback();
     void platformSetDidHideKeyboardCallback();
+    void platformSetDidShowMenuCallback();
+    void platformSetDidHideMenuCallback();
     void platformSetDidEndScrollingCallback();
     void platformClearAllCallbacks();
     void platformPlayBackEventStream(JSStringRef, JSValueRef);
@@ -238,6 +250,13 @@
     NSUndoManager *platformUndoManager() const;
 #endif
 
+#if PLATFORM(IOS_FAMILY)
+    UIView *platformContentView() const;
+#endif
+#if PLATFORM(MAC)
+    NSView *platformContentView() const;
+#endif
+
     JSClassRef wrapperClass() final;
 
     JSObjectRef objectFromRect(const WebCore::FloatRect&) const;

Modified: trunk/Tools/WebKitTestRunner/TestController.cpp (241321 => 241322)


--- trunk/Tools/WebKitTestRunner/TestController.cpp	2019-02-12 22:55:33 UTC (rev 241321)
+++ trunk/Tools/WebKitTestRunner/TestController.cpp	2019-02-12 23:18:30 UTC (rev 241322)
@@ -748,7 +748,7 @@
     WKPreferencesSetJavaScriptRuntimeFlags(preferences, kWKJavaScriptRuntimeFlagsAllEnabled);
     WKPreferencesSetJavaScriptCanOpenWindowsAutomatically(preferences, true);
     WKPreferencesSetJavaScriptCanAccessClipboard(preferences, true);
-    WKPreferencesSetDOMPasteAllowed(preferences, true);
+    WKPreferencesSetDOMPasteAllowed(preferences, options.domPasteAllowed);
     WKPreferencesSetUniversalAccessFromFileURLsAllowed(preferences, true);
     WKPreferencesSetFileAccessFromFileURLsAllowed(preferences, true);
 #if ENABLE(FULLSCREEN_API)
@@ -1269,6 +1269,8 @@
             testOptions.applicationManifest = parseStringTestHeaderValueAsRelativePath(value, pathOrURL);
         else if (key == "allowCrossOriginSubresourcesToAskForCredentials")
             testOptions.allowCrossOriginSubresourcesToAskForCredentials = parseBooleanTestHeaderValue(value);
+        else if (key == "domPasteAllowed")
+            testOptions.domPasteAllowed = parseBooleanTestHeaderValue(value);
         else if (key == "enableProcessSwapOnNavigation")
             testOptions.enableProcessSwapOnNavigation = parseBooleanTestHeaderValue(value);
         else if (key == "enableProcessSwapOnWindowOpen")

Modified: trunk/Tools/WebKitTestRunner/TestOptions.h (241321 => 241322)


--- trunk/Tools/WebKitTestRunner/TestOptions.h	2019-02-12 22:55:33 UTC (rev 241321)
+++ trunk/Tools/WebKitTestRunner/TestOptions.h	2019-02-12 23:18:30 UTC (rev 241322)
@@ -57,6 +57,7 @@
     bool shouldShowTouches { false };
     bool dumpJSConsoleLogInStdErr { false };
     bool allowCrossOriginSubresourcesToAskForCredentials { false };
+    bool domPasteAllowed { true };
     bool enableProcessSwapOnNavigation { true };
     bool enableProcessSwapOnWindowOpen { false };
     bool enableColorFilter { false };
@@ -105,6 +106,7 @@
             || dumpJSConsoleLogInStdErr != options.dumpJSConsoleLogInStdErr
             || applicationManifest != options.applicationManifest
             || allowCrossOriginSubresourcesToAskForCredentials != options.allowCrossOriginSubresourcesToAskForCredentials
+            || domPasteAllowed != options.domPasteAllowed
             || enableProcessSwapOnNavigation != options.enableProcessSwapOnNavigation
             || enableProcessSwapOnWindowOpen != options.enableProcessSwapOnWindowOpen
             || enableColorFilter != options.enableColorFilter

Modified: trunk/Tools/WebKitTestRunner/UIScriptControllerCocoa.mm (241321 => 241322)


--- trunk/Tools/WebKitTestRunner/UIScriptControllerCocoa.mm	2019-02-12 22:55:33 UTC (rev 241321)
+++ trunk/Tools/WebKitTestRunner/UIScriptControllerCocoa.mm	2019-02-12 23:18:30 UTC (rev 241322)
@@ -190,4 +190,9 @@
     return JSStringCreateWithCFString((__bridge CFStringRef)platformUndoManager().redoActionName);
 }
 
+NSUndoManager *UIScriptController::platformUndoManager() const
+{
+    return platformContentView().undoManager;
+}
+
 } // namespace WTR

Modified: trunk/Tools/WebKitTestRunner/cocoa/TestRunnerWKWebView.h (241321 => 241322)


--- trunk/Tools/WebKitTestRunner/cocoa/TestRunnerWKWebView.h	2019-02-12 22:55:33 UTC (rev 241321)
+++ trunk/Tools/WebKitTestRunner/cocoa/TestRunnerWKWebView.h	2019-02-12 23:18:30 UTC (rev 241322)
@@ -43,6 +43,8 @@
 @property (nonatomic, copy) void (^didEndZoomingCallback)(void);
 @property (nonatomic, copy) void (^didShowKeyboardCallback)(void);
 @property (nonatomic, copy) void (^didHideKeyboardCallback)(void);
+@property (nonatomic, copy) void (^didShowMenuCallback)(void);
+@property (nonatomic, copy) void (^didHideMenuCallback)(void);
 @property (nonatomic, copy) void (^didEndScrollingCallback)(void);
 @property (nonatomic, copy) void (^rotationDidEndCallback)(void);
 @property (nonatomic, copy) NSString *accessibilitySpeakSelectionContent;

Modified: trunk/Tools/WebKitTestRunner/cocoa/TestRunnerWKWebView.mm (241321 => 241322)


--- trunk/Tools/WebKitTestRunner/cocoa/TestRunnerWKWebView.mm	2019-02-12 22:55:33 UTC (rev 241321)
+++ trunk/Tools/WebKitTestRunner/cocoa/TestRunnerWKWebView.mm	2019-02-12 23:18:30 UTC (rev 241322)
@@ -55,6 +55,7 @@
 @property (nonatomic, copy) void (^zoomToScaleCompletionHandler)(void);
 @property (nonatomic, copy) void (^retrieveSpeakSelectionContentCompletionHandler)(void);
 @property (nonatomic, getter=isShowingKeyboard, setter=setIsShowingKeyboard:) BOOL showingKeyboard;
+@property (nonatomic, getter=isShowingMenu, setter=setIsShowingMenu:) BOOL showingMenu;
 
 @end
 
@@ -77,7 +78,8 @@
         NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
         [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(_didHideMenu) name:UIMenuControllerDidHideMenuNotification object:nil];
         self.UIDelegate = self;
     }
     return self;
@@ -95,6 +97,8 @@
     self.didEndZoomingCallback = nil;
     self.didShowKeyboardCallback = nil;
     self.didHideKeyboardCallback = nil;
+    self.didShowMenuCallback = nil;
+    self.didHideMenuCallback = nil;
     self.didEndScrollingCallback = nil;
     self.rotationDidEndCallback = nil;
 
@@ -172,6 +176,26 @@
         self.didHideKeyboardCallback();
 }
 
+- (void)_didShowMenu
+{
+    if (self.showingMenu)
+        return;
+
+    self.showingMenu = YES;
+    if (self.didShowMenuCallback)
+        self.didShowMenuCallback();
+}
+
+- (void)_didHideMenu
+{
+    if (!self.showingMenu)
+        return;
+
+    self.showingMenu = NO;
+    if (self.didHideMenuCallback)
+        self.didHideMenuCallback();
+}
+
 - (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view
 {
     [super scrollViewWillBeginZooming:scrollView withView:view];

Modified: trunk/Tools/WebKitTestRunner/ios/TestControllerIOS.mm (241321 => 241322)


--- trunk/Tools/WebKitTestRunner/ios/TestControllerIOS.mm	2019-02-12 22:55:33 UTC (rev 241321)
+++ trunk/Tools/WebKitTestRunner/ios/TestControllerIOS.mm	2019-02-12 23:18:30 UTC (rev 241322)
@@ -53,6 +53,7 @@
 namespace WTR {
 
 static bool isDoneWaitingForKeyboardToDismiss = true;
+static bool isDoneWaitingForMenuToDismiss = true;
 
 static void handleKeyboardWillHideNotification(CFNotificationCenterRef, void*, CFStringRef, const void*, CFDictionaryRef)
 {
@@ -64,6 +65,16 @@
     isDoneWaitingForKeyboardToDismiss = true;
 }
 
+static void handleMenuWillHideNotification(CFNotificationCenterRef, void*, CFStringRef, const void*, CFDictionaryRef)
+{
+    isDoneWaitingForMenuToDismiss = false;
+}
+
+static void handleMenuDidHideNotification(CFNotificationCenterRef, void*, CFStringRef, const void*, CFDictionaryRef)
+{
+    isDoneWaitingForMenuToDismiss = true;
+}
+
 void TestController::notifyDone()
 {
 }
@@ -79,6 +90,8 @@
     auto center = CFNotificationCenterGetLocalCenter();
     CFNotificationCenterAddObserver(center, this, handleKeyboardWillHideNotification, (CFStringRef)UIKeyboardWillHideNotification, nullptr, CFNotificationSuspensionBehaviorDeliverImmediately);
     CFNotificationCenterAddObserver(center, this, handleKeyboardDidHideNotification, (CFStringRef)UIKeyboardDidHideNotification, nullptr, CFNotificationSuspensionBehaviorDeliverImmediately);
+    CFNotificationCenterAddObserver(center, this, handleMenuWillHideNotification, (CFStringRef)UIMenuControllerWillHideMenuNotification, nullptr, CFNotificationSuspensionBehaviorDeliverImmediately);
+    CFNotificationCenterAddObserver(center, this, handleMenuDidHideNotification, (CFStringRef)UIMenuControllerDidHideMenuNotification, nullptr, CFNotificationSuspensionBehaviorDeliverImmediately);
 
     // Override the implementation of +[UIKeyboard isInHardwareKeyboardMode] to ensure that test runs are deterministic
     // regardless of whether a hardware keyboard is attached. We intentionally never restore the original implementation.
@@ -92,6 +105,8 @@
     auto center = CFNotificationCenterGetLocalCenter();
     CFNotificationCenterRemoveObserver(center, this, (CFStringRef)UIKeyboardWillHideNotification, nullptr);
     CFNotificationCenterRemoveObserver(center, this, (CFStringRef)UIKeyboardDidHideNotification, nullptr);
+    CFNotificationCenterRemoveObserver(center, this, (CFStringRef)UIMenuControllerWillHideMenuNotification, nullptr);
+    CFNotificationCenterRemoveObserver(center, this, (CFStringRef)UIMenuControllerDidHideMenuNotification, nullptr);
 }
 
 void TestController::initializeInjectedBundlePath()
@@ -119,6 +134,7 @@
 {
     cocoaResetStateToConsistentValues(options);
 
+    UIMenuController.sharedMenuController.menuVisible = NO;
     [[UIApplication sharedApplication] _cancelAllTouches];
     [[UIDevice currentDevice] setOrientation:UIDeviceOrientationPortrait animated:NO];
 
@@ -145,6 +161,7 @@
     }
 
     runUntil(isDoneWaitingForKeyboardToDismiss, m_currentInvocation->shortTimeout());
+    runUntil(isDoneWaitingForMenuToDismiss, m_currentInvocation->shortTimeout());
 
     if (shouldRestoreFirstResponder)
         [mainWebView()->platformView() becomeFirstResponder];

Modified: trunk/Tools/WebKitTestRunner/ios/UIScriptControllerIOS.mm (241321 => 241322)


--- trunk/Tools/WebKitTestRunner/ios/UIScriptControllerIOS.mm	2019-02-12 22:55:33 UTC (rev 241321)
+++ trunk/Tools/WebKitTestRunner/ios/UIScriptControllerIOS.mm	2019-02-12 23:18:30 UTC (rev 241322)
@@ -102,6 +102,44 @@
     return modifiers;
 }
 
+static BOOL forEachViewInHierarchy(UIView *view, void(^mapFunction)(UIView *subview, BOOL *stop))
+{
+    BOOL stop = NO;
+    mapFunction(view, &stop);
+    if (stop)
+        return YES;
+
+    for (UIView *subview in view.subviews) {
+        stop = forEachViewInHierarchy(subview, mapFunction);
+        if (stop)
+            break;
+    }
+    return stop;
+}
+
+static UIView *findViewInHierarchyOfType(UIView *view, Class viewClass)
+{
+    __block RetainPtr<UIView> foundView;
+    forEachViewInHierarchy(view, ^(UIView *subview, BOOL *stop) {
+        if (![subview isKindOfClass:viewClass])
+            return;
+
+        foundView = subview;
+        *stop = YES;
+    });
+    return foundView.autorelease();
+}
+
+static NSArray<UIView *> *findAllViewsInHierarchyOfType(UIView *view, Class viewClass)
+{
+    __block RetainPtr<NSMutableArray> views = adoptNS([[NSMutableArray alloc] init]);
+    forEachViewInHierarchy(view, ^(UIView *subview, BOOL *stop) {
+        if ([subview isKindOfClass:viewClass])
+            [views addObject:subview];
+    });
+    return views.autorelease();
+}
+
 void UIScriptController::checkForOutstandingCallbacks()
 {
     if (![[HIDEventGenerator sharedHIDEventGenerator] checkForOutstandingCallbacks])
@@ -629,8 +667,7 @@
 
 JSObjectRef UIScriptController::selectionStartGrabberViewRect() const
 {
-    WKWebView *webView = TestController::singleton().mainWebView()->platformView();
-    UIView *contentView = [webView valueForKeyPath:@"_currentContentView"];
+    UIView *contentView = platformContentView();
     UIView *selectionRangeView = [contentView valueForKeyPath:@"interactionAssistant.selectionView.rangeView"];
     auto frameInContentCoordinates = [selectionRangeView convertRect:[[selectionRangeView valueForKeyPath:@"startGrabber"] frame] toView:contentView];
     frameInContentCoordinates = CGRectIntersection(contentView.bounds, frameInContentCoordinates);
@@ -640,8 +677,7 @@
 
 JSObjectRef UIScriptController::selectionEndGrabberViewRect() const
 {
-    WKWebView *webView = TestController::singleton().mainWebView()->platformView();
-    UIView *contentView = [webView valueForKeyPath:@"_currentContentView"];
+    UIView *contentView = platformContentView();
     UIView *selectionRangeView = [contentView valueForKeyPath:@"interactionAssistant.selectionView.rangeView"];
     auto frameInContentCoordinates = [selectionRangeView convertRect:[[selectionRangeView valueForKeyPath:@"endGrabber"] frame] toView:contentView];
     frameInContentCoordinates = CGRectIntersection(contentView.bounds, frameInContentCoordinates);
@@ -651,8 +687,7 @@
 
 JSObjectRef UIScriptController::selectionCaretViewRect() const
 {
-    WKWebView *webView = TestController::singleton().mainWebView()->platformView();
-    UIView *contentView = [webView valueForKeyPath:@"_currentContentView"];
+    UIView *contentView = platformContentView();
     UIView *caretView = [contentView valueForKeyPath:@"interactionAssistant.selectionView.caretView"];
     auto rectInContentViewCoordinates = CGRectIntersection([caretView convertRect:caretView.bounds toView:contentView], contentView.bounds);
     return JSValueToObject(m_context->jsContext(), [JSValue valueWithObject:toNSDictionary(rectInContentViewCoordinates) inContext:[JSContext contextWithJSGlobalContextRef:m_context->jsContext()]].JSValueRef, nullptr);
@@ -660,8 +695,7 @@
 
 JSObjectRef UIScriptController::selectionRangeViewRects() const
 {
-    WKWebView *webView = TestController::singleton().mainWebView()->platformView();
-    UIView *contentView = [webView valueForKeyPath:@"_currentContentView"];
+    UIView *contentView = platformContentView();
     UIView *rangeView = [contentView valueForKeyPath:@"interactionAssistant.selectionView.rangeView"];
     auto rectsAsDictionaries = adoptNS([[NSMutableArray alloc] init]);
     NSArray *textRectInfoArray = [rangeView valueForKeyPath:@"rects"];
@@ -825,6 +859,58 @@
     };
 }
 
+void UIScriptController::platformSetDidShowMenuCallback()
+{
+    TestController::singleton().mainWebView()->platformView().didShowMenuCallback = ^{
+        if (!m_context)
+            return;
+        m_context->fireCallback(CallbackTypeDidShowMenu);
+    };
+}
+
+void UIScriptController::platformSetDidHideMenuCallback()
+{
+    TestController::singleton().mainWebView()->platformView().didHideMenuCallback = ^{
+        if (!m_context)
+            return;
+        m_context->fireCallback(CallbackTypeDidHideMenu);
+    };
+}
+
+JSObjectRef UIScriptController::rectForMenuAction(JSStringRef jsAction) const
+{
+    auto action = "" jsAction));
+
+    UIWindow *windowForButton = nil;
+    UIButton *buttonForAction = nil;
+    for (UIWindow *window in UIApplication.sharedApplication.windows) {
+        if (![window isKindOfClass:UITextEffectsWindow.class])
+            continue;
+
+        UIView *calloutBar = findViewInHierarchyOfType(window, UICalloutBar.class);
+        if (!calloutBar)
+            continue;
+
+        for (UIButton *button in findAllViewsInHierarchyOfType(calloutBar, UIButton.class)) {
+            NSString *buttonTitle = [button titleForState:UIControlStateNormal];
+            if (!buttonTitle.length)
+                continue;
+
+            if (![buttonTitle isEqualToString:(__bridge NSString *)action.get()])
+                continue;
+
+            buttonForAction = button;
+            windowForButton = window;
+        }
+    }
+
+    if (!buttonForAction)
+        return nullptr;
+
+    CGRect rectInRootViewCoordinates = [buttonForAction convertRect:buttonForAction.bounds toView:platformContentView()];
+    return m_context->objectFromRect(WebCore::FloatRect(rectInRootViewCoordinates.origin.x, rectInRootViewCoordinates.origin.y, rectInRootViewCoordinates.size.width, rectInRootViewCoordinates.size.height));
+}
+
 void UIScriptController::platformSetDidEndScrollingCallback()
 {
     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
@@ -870,21 +956,6 @@
     [webView _completeBackSwipeForTesting];
 }
 
-static BOOL forEachViewInHierarchy(UIView *view, void(^mapFunction)(UIView *subview, BOOL *stop))
-{
-    BOOL stop = NO;
-    mapFunction(view, &stop);
-    if (stop)
-        return YES;
-
-    for (UIView *subview in view.subviews) {
-        stop = forEachViewInHierarchy(subview, mapFunction);
-        if (stop)
-            break;
-    }
-    return stop;
-}
-
 bool UIScriptController::isShowingDataListSuggestions() const
 {
     Class remoteKeyboardWindowClass = NSClassFromString(@"UIRemoteKeyboardWindow");
@@ -989,9 +1060,9 @@
     return JSValueToObject(m_context->jsContext(), [JSValue valueWithObject:attachmentInfoDictionary inContext:[JSContext contextWithJSGlobalContextRef:m_context->jsContext()]].JSValueRef, nullptr);
 }
 
-NSUndoManager *UIScriptController::platformUndoManager() const
+UIView *UIScriptController::platformContentView() const
 {
-    return [(UIView *)[TestController::singleton().mainWebView()->platformView() valueForKeyPath:@"_currentContentView"] undoManager];
+    return [TestController::singleton().mainWebView()->platformView() valueForKeyPath:@"_currentContentView"];
 }
 
 JSObjectRef UIScriptController::calendarType() const

Modified: trunk/Tools/WebKitTestRunner/mac/UIScriptControllerMac.mm (241321 => 241322)


--- trunk/Tools/WebKitTestRunner/mac/UIScriptControllerMac.mm	2019-02-12 22:55:33 UTC (rev 241321)
+++ trunk/Tools/WebKitTestRunner/mac/UIScriptControllerMac.mm	2019-02-12 23:18:30 UTC (rev 241322)
@@ -209,9 +209,9 @@
     doAsyncTask(callback);
 }
 
-NSUndoManager *UIScriptController::platformUndoManager() const
+NSView *UIScriptController::platformContentView() const
 {
-    return TestController::singleton().mainWebView()->platformView().undoManager;
+    return TestController::singleton().mainWebView()->platformView();
 }
 
 JSObjectRef UIScriptController::calendarType() const
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to