[WK2] EditorState updates should be rolled into the layer update lifecycle when possible
authorwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 23 Aug 2017 03:15:38 +0000 (03:15 +0000)
committerwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 23 Aug 2017 03:15:38 +0000 (03:15 +0000)
https://bugs.webkit.org/show_bug.cgi?id=175370
<rdar://problem/33799806>

Reviewed by Ryosuke Niwa.

Source/WebCore:

Remove didChangeSelectionAndUpdateLayout -- EditorState updates that are scheduled due to missing post-layout
data will now be scheduled for the next presentation update. Additionally, add editor client hooks to notify the
WebKit layer when we've updated the current composition. See WebKit ChangeLog for more details. This patch
adjusts and rebaselines existing layout tests.

* editing/Editor.cpp:
(WebCore::SetCompositionScope::SetCompositionScope):
(WebCore::SetCompositionScope::~SetCompositionScope):

Introduce a helper RAII class to ensure that we ignore selection changes during the scope of
Editor::setComposition and call out to the client with WebEditorClient::didUpdateComposition afterwards. This
also maintains a UserTypingGestureIndicator over its lifetime, so we don't additionally need to create a
UserTypingGestureIndicator in Editor::setComposition.

(WebCore::Editor::setComposition):
* editing/FrameSelection.cpp:
(WebCore::FrameSelection::setSelection):
(WebCore::FrameSelection::updateAndRevealSelection):
(WebCore::FrameSelection::setSelectedRange):
* editing/FrameSelection.h:
(WebCore::FrameSelection::defaultSetSelectionOptions):

Plumb state about whether or not the selection change was triggered by the user to FrameSelection::setSelection,
and if so, notify the editing client. A separate SetSelectionOptions flag is used here instead of
RevealSelection to avoid calling out to the client in places where we want to reveal the selection regardless of
whether or not the selection is user triggered.

* loader/EmptyClients.cpp:
* page/EditorClient.h:

Source/WebKit:

See per-method comments for more detail. WebPage::didChangeSelection now schedules EditorState updates to be sent
during the next layer tree transaction rather than sending them synchronously. To ensure that iOS and Mac continue
to behave correctly w.r.t. EditorState updates, we immediately dispatch EditorStates in the following cases:
- After the composition changes, is confirmed, or is canceled.
- After an edit command is executed.
- After ending user-triggered selection changes.

* Shared/mac/RemoteLayerTreeTransaction.h:
(WebKit::RemoteLayerTreeTransaction::hasEditorState const):
(WebKit::RemoteLayerTreeTransaction::editorState const):
(WebKit::RemoteLayerTreeTransaction::setEditorState):

Attaches an optional EditorState to the RemoteLayerTreeTransaction. This EditorState is computed and sent over
when setting up the transaction in WebPage, if something previously scheduled an EditorState update.

* Shared/mac/RemoteLayerTreeTransaction.mm:
(WebKit::RemoteLayerTreeTransaction::encode const):
(WebKit::RemoteLayerTreeTransaction::decode):

Add coder support for sending over a layer tree transaction's EditorState.

* UIProcess/API/Cocoa/WKViewPrivate.h:
* UIProcess/API/mac/WKView.mm:
(-[WKView _doAfterNextPresentationUpdate:]):

Add _doAfterNextPresentationUpdate to WKView (used in TestWebKitAPI -- refer to
WebKitAgnosticTest::waitForNextPresentationUpdate).

* UIProcess/DrawingAreaProxy.h:
(WebKit::DrawingAreaProxy::dispatchPresentationCallbacksAfterFlushingLayers):
* UIProcess/DrawingAreaProxy.messages.in:

Add a new IPC messages, DispatchPresentationCallbacksAfterFlushingLayers, to invoke in-flight presentation
callbacks in the UI process following a layer flush in the web process.

* UIProcess/WebPageProxy.h:
* UIProcess/mac/RemoteLayerTreeDrawingAreaProxy.mm:
(WebKit::RemoteLayerTreeDrawingAreaProxy::commitLayerTree):
* UIProcess/mac/TiledCoreAnimationDrawingAreaProxy.h:
* UIProcess/mac/TiledCoreAnimationDrawingAreaProxy.mm:
(WebKit::TiledCoreAnimationDrawingAreaProxy::~TiledCoreAnimationDrawingAreaProxy):
(WebKit::TiledCoreAnimationDrawingAreaProxy::dispatchAfterEnsuringDrawing):
(WebKit::TiledCoreAnimationDrawingAreaProxy::dispatchPresentationCallbacksAfterFlushingLayers):

Run all pending _doAfterNextPresentationUpdate callbacks.

* WebProcess/WebCoreSupport/WebEditorClient.cpp:
(WebKit::WebEditorClient::didApplyStyle):
(WebKit::WebEditorClient::respondToChangedContents):
(WebKit::WebEditorClient::didEndUserTriggeredSelectionChanges):
(WebKit::WebEditorClient::didUpdateComposition):

Forward editor client calls to the WebPage.

(WebKit::WebEditorClient::didChangeSelectionAndUpdateLayout): Deleted.
* WebProcess/WebCoreSupport/WebEditorClient.h:
* WebProcess/WebPage/WebPage.cpp:
(WebKit::WebPage::editorState const):
(WebKit::WebPage::updateEditorStateAfterLayoutIfEditabilityChanged):
(WebKit::WebPage::willCommitLayerTree):
(WebKit::WebPage::didApplyStyle):

Allow style application to immediately trigger EditorState updates, if we're not currently ignoring selection
changes in the Editor.

(WebKit::WebPage::didChangeContents):

Allow applying top-level edit commands to immediately trigger EditorState updates, if we're not currently
ignoring selection changes in the Editor.

(WebKit::WebPage::didChangeSelection):
(WebKit::WebPage::didUpdateComposition):
(WebKit::WebPage::didEndUserTriggeredSelectionChanges):
(WebKit::WebPage::discardedComposition):
(WebKit::WebPage::canceledComposition):

When handling composition updates, always send an EditorState to the UI process. Unlike other cases, IME
requires immediate EditorState data, so we need to be explicit here in sending updates right away.

(WebKit::WebPage::sendEditorStateUpdate):
(WebKit::WebPage::sendPartialEditorStateAndSchedulePostLayoutUpdate):
(WebKit::WebPage::flushPendingEditorStateUpdate):

Helper methods to schedule an EditorState update to be sent upon the next layer tree update, or flush any
pending EditorState update that has been scheduled. The private, more aggressive variant of this is
sendEditorStateUpdate, which ignores whether or not there was already an EditorState update scheduled, and sends
one anyways (this still fulfills any EditorState update that was previously scheduled).

These helper methods are treated as no-ops when invoked while ignoring selection changes. This is to prevent
temporary selection state and editor commands during operations such as text indicator snapshotting from pushing
bogus information about transient editor states to the UI process.

(WebKit::WebPage::sendPostLayoutEditorStateIfNeeded): Deleted.
* WebProcess/WebPage/WebPage.h:
* WebProcess/WebPage/ios/WebPageIOS.mm:
(WebKit::WebPage::platformEditorState const):
(WebKit::WebPage::executeEditCommandWithCallback):
(WebKit::selectionIsInsideFixedPositionContainer):
(WebKit::WebPage::updateVisibleContentRects):

Fix a hack that was computing an EditorState to figure out whether the current selection starts or ends in a
fixed position container. Factors out relevant logic into a separate helper, and also schedules an EditorState
update instead of immediately computing it.

* WebProcess/WebPage/mac/TiledCoreAnimationDrawingArea.h:
* WebProcess/WebPage/mac/TiledCoreAnimationDrawingArea.mm:
(WebKit::TiledCoreAnimationDrawingArea::addTransactionCallbackID):

Add support for registering and dispatching presentation callbacks that hook into the layer flush lifecycle,
using the tiled CA drawing area. These are used by Mac LayoutTests and API tests that need to wait until the
next flush before checking for state that depends on EditorState updates in the UI process.

(WebKit::TiledCoreAnimationDrawingArea::flushLayers):

Tell the WebPage to flush any pending EditorState updates.

* WebProcess/WebPage/mac/WebPageMac.mm:
(WebKit::WebPage::platformEditorState const):

Source/WebKitLegacy/mac:

Adjust WebEditorClient for interface changes.

* WebCoreSupport/WebEditorClient.h:

Source/WebKitLegacy/win:

Adjust WebEditorClient for interface changes.

* WebCoreSupport/WebEditorClient.h:

Tools:

Tweaks API tests that involve editing to wait for a presentation update before checking against UI process-side
information sent via EditorState updates. This allows any EditorState update scheduled by the test to propagate
to the UI process.

* TestWebKitAPI/Tests/WebKit2Cocoa/WKWebViewCandidateTests.mm:
(-[CandidateTestWebView typeString:inputMessage:]):
(+[CandidateTestWebView setUpWithFrame:testPage:]):
* TestWebKitAPI/Tests/WebKit2Cocoa/WKWebViewTextInput.mm:
* TestWebKitAPI/Tests/mac/AcceptsFirstMouse.mm:
(TestWebKitAPI::AcceptsFirstMouse::runTest):
* TestWebKitAPI/Tests/mac/WKWebViewMacEditingTests.mm:
* TestWebKitAPI/cocoa/TestWKWebView.h:
* TestWebKitAPI/cocoa/TestWKWebView.mm:
(-[TestWKWebView waitForNextPresentationUpdate]):

Add a new helper method to spin until the next presentation update.

* TestWebKitAPI/mac/WebKitAgnosticTest.h:
* TestWebKitAPI/mac/WebKitAgnosticTest.mm:
(TestWebKitAPI::WebKitAgnosticTest::waitForNextPresentationUpdate):

LayoutTests:

Rebaseline and adjust LayoutTests.

* editing/caret/ios/absolute-caret-position-after-scroll-expected.txt:
* editing/caret/ios/absolute-caret-position-after-scroll.html:
* editing/caret/ios/fixed-caret-position-after-scroll-expected.txt:
* editing/caret/ios/fixed-caret-position-after-scroll.html:
* editing/secure-input/password-input-changed-type.html:
* editing/secure-input/password-input-focusing.html:
* editing/secure-input/removed-password-input.html:
* editing/secure-input/reset-state-on-navigation.html:
* editing/selection/character-granularity-rect.html:

Delay checking for secure input state and caret rects until after the next presentation update.

* editing/selection/ios/absolute-selection-after-scroll-expected.txt:
* editing/selection/ios/absolute-selection-after-scroll.html:
* editing/selection/ios/fixed-selection-after-scroll-expected.txt:
* editing/selection/ios/fixed-selection-after-scroll.html:

Refactor and simplify these tests. These tests are not run on the OpenSource bots, since they depend on long
press and tap gestures.

* platform/ios-wk2/editing/inserting/insert-div-024-expected.txt:
* platform/ios-wk2/editing/inserting/insert-div-026-expected.txt:
* platform/ios-wk2/editing/style/5084241-expected.txt:

Rebaselines these tests, removing an anonymous RenderBlock inserted as a result of inserting and removing a
dummy span in order to compute a RenderStyle in WebPage::editorState. This is because editorState is no longer
invoked immediately on page load; https://bugs.webkit.org/show_bug.cgi?id=175116 tracks preventing this render
tree thrashing altogether.

* platform/mac-wk2/TestExpectations:
* platform/mac-wk2/editing/style/unbold-in-bold-expected.txt:
* resources/ui-helper.js:

Introduce new UIHelper functions.

(window.UIHelper.ensurePresentationUpdate.return.new.Promise):
(window.UIHelper.ensurePresentationUpdate):

Returns a Promise, resolved after the next presentation update.

(window.UIHelper.activateAndWaitForInputSessionAt.return.new.Promise.):
(window.UIHelper.activateAndWaitForInputSessionAt.return.new.Promise):
(window.UIHelper.activateAndWaitForInputSessionAt):

Returns a Promise, resolved after tapping at the given location and waiting for the keyboard to appear on iOS.

(window.UIHelper.getUICaretRect.return.new.Promise.):
(window.UIHelper.getUICaretRect.return.new.Promise):
(window.UIHelper.getUICaretRect):
(window.UIHelper.getUISelectionRects.return.new.Promise.):
(window.UIHelper.getUISelectionRects.return.new.Promise):
(window.UIHelper.getUISelectionRects):

Helpers to fetch selection and caret rect information in the UI process.

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

57 files changed:
LayoutTests/ChangeLog
LayoutTests/editing/caret/ios/absolute-caret-position-after-scroll-expected.txt
LayoutTests/editing/caret/ios/absolute-caret-position-after-scroll.html
LayoutTests/editing/caret/ios/fixed-caret-position-after-scroll-expected.txt
LayoutTests/editing/caret/ios/fixed-caret-position-after-scroll.html
LayoutTests/editing/secure-input/password-input-changed-type.html
LayoutTests/editing/secure-input/password-input-focusing.html
LayoutTests/editing/secure-input/removed-password-input.html
LayoutTests/editing/secure-input/reset-state-on-navigation.html
LayoutTests/editing/selection/ios/absolute-selection-after-scroll-expected.txt
LayoutTests/editing/selection/ios/absolute-selection-after-scroll.html
LayoutTests/editing/selection/ios/fixed-selection-after-scroll-expected.txt
LayoutTests/editing/selection/ios/fixed-selection-after-scroll.html
LayoutTests/platform/ios-wk2/editing/inserting/insert-div-024-expected.txt
LayoutTests/platform/ios-wk2/editing/inserting/insert-div-026-expected.txt
LayoutTests/platform/ios-wk2/editing/style/5084241-expected.txt
LayoutTests/platform/mac-wk2/TestExpectations
LayoutTests/platform/mac-wk2/editing/style/unbold-in-bold-expected.txt
LayoutTests/resources/ui-helper.js
Source/WebCore/ChangeLog
Source/WebCore/editing/Editor.cpp
Source/WebCore/editing/FrameSelection.cpp
Source/WebCore/editing/FrameSelection.h
Source/WebCore/loader/EmptyClients.cpp
Source/WebCore/page/EditorClient.h
Source/WebKit/ChangeLog
Source/WebKit/Shared/mac/RemoteLayerTreeTransaction.h
Source/WebKit/Shared/mac/RemoteLayerTreeTransaction.mm
Source/WebKit/UIProcess/API/Cocoa/WKViewPrivate.h
Source/WebKit/UIProcess/API/mac/WKView.mm
Source/WebKit/UIProcess/DrawingAreaProxy.h
Source/WebKit/UIProcess/DrawingAreaProxy.messages.in
Source/WebKit/UIProcess/WebPageProxy.h
Source/WebKit/UIProcess/mac/RemoteLayerTreeDrawingAreaProxy.mm
Source/WebKit/UIProcess/mac/TiledCoreAnimationDrawingAreaProxy.h
Source/WebKit/UIProcess/mac/TiledCoreAnimationDrawingAreaProxy.mm
Source/WebKit/WebProcess/WebCoreSupport/WebEditorClient.cpp
Source/WebKit/WebProcess/WebCoreSupport/WebEditorClient.h
Source/WebKit/WebProcess/WebPage/WebPage.cpp
Source/WebKit/WebProcess/WebPage/WebPage.h
Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm
Source/WebKit/WebProcess/WebPage/mac/TiledCoreAnimationDrawingArea.h
Source/WebKit/WebProcess/WebPage/mac/TiledCoreAnimationDrawingArea.mm
Source/WebKit/WebProcess/WebPage/mac/WebPageMac.mm
Source/WebKitLegacy/mac/ChangeLog
Source/WebKitLegacy/mac/WebCoreSupport/WebEditorClient.h
Source/WebKitLegacy/win/ChangeLog
Source/WebKitLegacy/win/WebCoreSupport/WebEditorClient.h
Tools/ChangeLog
Tools/TestWebKitAPI/Tests/WebKit2Cocoa/WKWebViewCandidateTests.mm
Tools/TestWebKitAPI/Tests/WebKit2Cocoa/WKWebViewTextInput.mm
Tools/TestWebKitAPI/Tests/mac/AcceptsFirstMouse.mm
Tools/TestWebKitAPI/Tests/mac/WKWebViewMacEditingTests.mm
Tools/TestWebKitAPI/cocoa/TestWKWebView.h
Tools/TestWebKitAPI/cocoa/TestWKWebView.mm
Tools/TestWebKitAPI/mac/WebKitAgnosticTest.h
Tools/TestWebKitAPI/mac/WebKitAgnosticTest.mm

index c1b21338df47e21597dda3e68f4e5416ce09d066..ff89abf770e78cdc35e33dbee94efd393c0da129 100644 (file)
@@ -1,3 +1,68 @@
+2017-08-22  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        [WK2] EditorState updates should be rolled into the layer update lifecycle when possible
+        https://bugs.webkit.org/show_bug.cgi?id=175370
+        <rdar://problem/33799806>
+
+        Reviewed by Ryosuke Niwa.
+
+        Rebaseline and adjust LayoutTests.
+
+        * editing/caret/ios/absolute-caret-position-after-scroll-expected.txt:
+        * editing/caret/ios/absolute-caret-position-after-scroll.html:
+        * editing/caret/ios/fixed-caret-position-after-scroll-expected.txt:
+        * editing/caret/ios/fixed-caret-position-after-scroll.html:
+        * editing/secure-input/password-input-changed-type.html:
+        * editing/secure-input/password-input-focusing.html:
+        * editing/secure-input/removed-password-input.html:
+        * editing/secure-input/reset-state-on-navigation.html:
+        * editing/selection/character-granularity-rect.html:
+
+        Delay checking for secure input state and caret rects until after the next presentation update.
+
+        * editing/selection/ios/absolute-selection-after-scroll-expected.txt:
+        * editing/selection/ios/absolute-selection-after-scroll.html:
+        * editing/selection/ios/fixed-selection-after-scroll-expected.txt:
+        * editing/selection/ios/fixed-selection-after-scroll.html:
+
+        Refactor and simplify these tests. These tests are not run on the OpenSource bots, since they depend on long
+        press and tap gestures.
+
+        * platform/ios-wk2/editing/inserting/insert-div-024-expected.txt:
+        * platform/ios-wk2/editing/inserting/insert-div-026-expected.txt:
+        * platform/ios-wk2/editing/style/5084241-expected.txt:
+
+        Rebaselines these tests, removing an anonymous RenderBlock inserted as a result of inserting and removing a
+        dummy span in order to compute a RenderStyle in WebPage::editorState. This is because editorState is no longer
+        invoked immediately on page load; https://bugs.webkit.org/show_bug.cgi?id=175116 tracks preventing this render
+        tree thrashing altogether.
+
+        * platform/mac-wk2/TestExpectations:
+        * platform/mac-wk2/editing/style/unbold-in-bold-expected.txt:
+        * resources/ui-helper.js:
+
+        Introduce new UIHelper functions.
+
+        (window.UIHelper.ensurePresentationUpdate.return.new.Promise):
+        (window.UIHelper.ensurePresentationUpdate):
+
+        Returns a Promise, resolved after the next presentation update.
+
+        (window.UIHelper.activateAndWaitForInputSessionAt.return.new.Promise.):
+        (window.UIHelper.activateAndWaitForInputSessionAt.return.new.Promise):
+        (window.UIHelper.activateAndWaitForInputSessionAt):
+
+        Returns a Promise, resolved after tapping at the given location and waiting for the keyboard to appear on iOS.
+
+        (window.UIHelper.getUICaretRect.return.new.Promise.):
+        (window.UIHelper.getUICaretRect.return.new.Promise):
+        (window.UIHelper.getUICaretRect):
+        (window.UIHelper.getUISelectionRects.return.new.Promise.):
+        (window.UIHelper.getUISelectionRects.return.new.Promise):
+        (window.UIHelper.getUISelectionRects):
+
+        Helpers to fetch selection and caret rect information in the UI process.
+
 2017-08-21  Ryosuke Niwa  <rniwa@webkit.org>
 
         Consolidate the code to normalize MIME type in DataTransfer
index cbfe1ab3015ba5b9905d874819f7369541893f6f..cac0ccc5494fc897a2d18867bee1ac65bdf44548 100644 (file)
@@ -1,10 +1,12 @@
+PASS initialCaretRect.left is 6
+PASS initialCaretRect.top is 21
+PASS initialCaretRect.width is 3
+PASS initialCaretRect.height is 15
+PASS finalCaretRect.left is 6
+PASS finalCaretRect.top is 21
+PASS finalCaretRect.width is 3
+PASS finalCaretRect.height is 15
 PASS successfullyParsed is true
 
 TEST COMPLETE
-The initial caret rect is: [6 21 ; 3 15]
-The caret rect after scrolling 1000px down is: [6 21 ; 3 15]
-PASS finalCaretRect.top is initialCaretRect.top
-PASS finalCaretRect.left is initialCaretRect.left
-PASS finalCaretRect.width is initialCaretRect.width
-PASS finalCaretRect.height is initialCaretRect.height
 
index 4f927a11ab78c4a570115b2d32a7b70c6326ee33..a0ec9d0ca7362a40c223a56595339420987989ee 100644 (file)
@@ -2,6 +2,7 @@
 <html>
 <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
 <head>
+    <script src="../../../resources/ui-helper.js"></script>
     <script src="../../../resources/js-test.js"></script>
     <style>
         body {
 
         div {
             background-image: linear-gradient(0deg, blue, red);
-            height: 4000px;
+            height: 10000px;
         }
     </style>
-    <script>
-    jsTestIsAsync = true;
-
-    function tapInInputScript(tapX, tapY)
-    {
-        return `(function() {
-            uiController.didShowKeyboardCallback = function() {
-                uiController.doAfterNextStablePresentationUpdate(function() {
-                    uiController.uiScriptComplete(JSON.stringify(uiController.textSelectionCaretRect));
-                });
-            };
-            uiController.singleTapAtPoint(${tapX}, ${tapY}, function() { });
-        })()`;
-    }
-
-    function simulateScrollingScript(distance)
-    {
-        return `(function() {
-            uiController.stableStateOverride = false;
-            uiController.immediateScrollToOffset(0, ${distance});
-            uiController.stableStateOverride = true;
-            uiController.doAfterNextStablePresentationUpdate(function() {
-                uiController.uiScriptComplete(JSON.stringify(uiController.textSelectionCaretRect));
-            });
-        })()`;
-    }
-
-    function toString(rect)
-    {
-        return `[${rect.left} ${rect.top} ; ${rect.width} ${rect.height}]`;
-    }
-
-    function run()
-    {
-        if (!window.testRunner || !testRunner.runUIScript) {
-            description("To manually test, tap this input field and scroll up. The text caret should not end up outside of the input.");
-            return;
-        }
-
-        testRunner.runUIScript(tapInInputScript(window.innerWidth / 2, 30), initialCaretRect => {
-            initialCaretRect = JSON.parse(initialCaretRect);
-            window.initialCaretRect = initialCaretRect;
-            debug(`The initial caret rect is: ${toString(initialCaretRect)}`);
-            testRunner.runUIScript(simulateScrollingScript(1000), finalCaretRect => {
-                finalCaretRect = JSON.parse(finalCaretRect);
-                window.finalCaretRect = finalCaretRect;
-                debug(`The caret rect after scrolling 1000px down is: ${toString(finalCaretRect)}`);
-                shouldBe("finalCaretRect.top", "initialCaretRect.top");
-                shouldBe("finalCaretRect.left", "initialCaretRect.left");
-                shouldBe("finalCaretRect.width", "initialCaretRect.width");
-                shouldBe("finalCaretRect.height", "initialCaretRect.height");
-                finishJSTest();
-            });
-        });
-    }
-    </script>
 </head>
-<body onload=run()>
-    <input></input>
+<body>
+<div>
+<input id="input"></input>
+</div>
 </body>
+<script>
+jsTestIsAsync = true;
+
+(() => {
+    if (!window.testRunner || !testRunner.runUIScript) {
+        description("To manually test, tap this input field and scroll up. The text caret should not end up outside of the input.");
+        return;
+    }
 
+    UIHelper.activateAndWaitForInputSessionAt(innerWidth / 2, 30)
+    .then(() => UIHelper.getUICaretRect())
+    .then((rect) => {
+        initialCaretRect = rect;
+        shouldBe("initialCaretRect.left", "6");
+        shouldBe("initialCaretRect.top", "21");
+        shouldBe("initialCaretRect.width", "3");
+        shouldBe("initialCaretRect.height", "15");
+        document.body.scrollTop += 5000;
+        return UIHelper.getUICaretRect();
+    })
+    .then((rect) => {
+        finalCaretRect = rect;
+        shouldBe("finalCaretRect.left", "6");
+        shouldBe("finalCaretRect.top", "21");
+        shouldBe("finalCaretRect.width", "3");
+        shouldBe("finalCaretRect.height", "15");
+        finishJSTest();
+    });
+})();
+</script>
 </html>
index f7898254a2d8de956477a49b7231dda45565d2bb..d473645359054062449852dd6c7e0ec0ddf238f6 100644 (file)
@@ -1,5 +1,12 @@
+PASS initialCaretRect.left is 6
+PASS initialCaretRect.top is 21
+PASS initialCaretRect.width is 3
+PASS initialCaretRect.height is 15
+PASS finalCaretRect.left is 6
+PASS finalCaretRect.top is 5021
+PASS finalCaretRect.width is 3
+PASS finalCaretRect.height is 15
 PASS successfullyParsed is true
 
 TEST COMPLETE
-PASS finalCaretRect.top - initialCaretRect.top is 500
 
index 42f0fa02e18b9efd935ed2de2d29e3de4a44f2a1..80ae21c9687748ec7235a5e12b7e375df8f859e2 100644 (file)
@@ -2,6 +2,7 @@
 <html>
 <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
 <head>
+    <script src="../../../resources/ui-helper.js"></script>
     <script src="../../../resources/js-test.js"></script>
     <style>
         body {
 
         div {
             background-image: linear-gradient(blue, red);
-            height: 4000px;
+            height: 10000px;
         }
     </style>
-    <script>
-    jsTestIsAsync = true;
-
-    function getInputViewBoundsAfterTappingInInputScript(tapX, tapY)
-    {
-        return `(function() {
-            uiController.didShowKeyboardCallback = function() {
-                uiController.doAfterNextStablePresentationUpdate(function() {
-                    uiController.uiScriptComplete(JSON.stringify(uiController.inputViewBounds));
-                });
-            };
-            uiController.singleTapAtPoint(${tapX}, ${tapY}, function() { });
-        })()`;
-    }
-
-    function getCaretRectAfterScrollToOffsetScript(distance)
-    {
-        return `(function() {
-            uiController.stableStateOverride = false;
-            uiController.immediateScrollToOffset(0, ${distance});
-            uiController.stableStateOverride = true;
-            uiController.doAfterNextStablePresentationUpdate(function() {
-                uiController.uiScriptComplete(JSON.stringify(uiController.textSelectionCaretRect));
-            });
-        })()`;
-    }
-
-    function toString(rect)
-    {
-        return `[${rect.left} ${rect.top} ; ${rect.width} ${rect.height}]`;
-    }
-
-    function run()
-    {
-        if (!window.testRunner || !testRunner.runUIScript) {
-            description("To manually test, tap this input field and scroll up. The text caret should not end up outside of the input.");
-            return;
-        }
-
-        testRunner.runUIScript(getInputViewBoundsAfterTappingInInputScript(window.innerWidth / 2, 30), (inputViewBounds) => {
-            window.inputViewBounds = inputViewBounds = JSON.parse(inputViewBounds);
-            testRunner.runUIScript(getCaretRectAfterScrollToOffsetScript(inputViewBounds.height), initialCaretRect => {
-                window.initialCaretRect = initialCaretRect = JSON.parse(initialCaretRect);
-                testRunner.runUIScript(getCaretRectAfterScrollToOffsetScript(inputViewBounds.height + 500), finalCaretRect => {
-                    window.finalCaretRect = finalCaretRect = JSON.parse(finalCaretRect);
-                    shouldBe("finalCaretRect.top - initialCaretRect.top", "500");
-                    finishJSTest();
-                });
-            });
-        });
-    }
-    </script>
 </head>
-<body onload=run()>
-    <input id="input"></input>
+<body>
+<div>
+<input id="input"></input>
+</div>
 </body>
+<script>
+jsTestIsAsync = true;
+
+(() => {
+    if (!window.testRunner || !testRunner.runUIScript) {
+        description("To manually test, tap this input field and scroll up. The text caret should not end up outside of the input.");
+        return;
+    }
 
+    UIHelper.activateAndWaitForInputSessionAt(innerWidth / 2, 30)
+    .then(() => UIHelper.getUICaretRect())
+    .then((rect) => {
+        initialCaretRect = rect;
+        shouldBe("initialCaretRect.left", "6");
+        shouldBe("initialCaretRect.top", "21");
+        shouldBe("initialCaretRect.width", "3");
+        shouldBe("initialCaretRect.height", "15");
+        document.body.scrollTop += 5000;
+        return UIHelper.getUICaretRect();
+    })
+    .then((rect) => {
+        finalCaretRect = rect;
+        shouldBe("finalCaretRect.left", "6");
+        shouldBe("finalCaretRect.top", "5021");
+        shouldBe("finalCaretRect.width", "3");
+        shouldBe("finalCaretRect.height", "15");
+        finishJSTest();
+    });
+})();
+</script>
 </html>
index 13d05917664e072262ae73fc6cc14d8863807ce8..d0c7692133a84088918fc37169f09c6cb0ab4a6d 100644 (file)
@@ -2,27 +2,40 @@
 <html>
 <head>
 <meta charset="utf-8">
+<script src="../../resources/ui-helper.js"></script>
 <script src="../../resources/js-test-pre.js"></script>
 </head>
 <body>
 <input type=password>
 <script>
 
+jsTestIsAsync = true;
+
 description("Verify that changing a password input's type updates secure input state.");
 
 var passwordInput = document.getElementsByTagName("input")[0];
 
 debug("A password input is focused:");
 passwordInput.focus();
-shouldBe("testRunner.secureEventInputIsEnabled", "true");
 
-debug("\nAfter changing the type to text:");
-passwordInput.type = "text";
-shouldBe("testRunner.secureEventInputIsEnabled", "false");
+UIHelper.ensurePresentationUpdate().then(() => {
+    shouldBe("testRunner.secureEventInputIsEnabled", "true");
+    debug("\nAfter changing the type to text:");
+    passwordInput.type = "text";
+})
+
+.then(() => UIHelper.ensurePresentationUpdate())
+.then(() => {
+    shouldBe("testRunner.secureEventInputIsEnabled", "false");
+    debug("\nAfter changing the type back to password:");
+    passwordInput.type = "password";
+})
 
-debug("\nAfter changing the type back to password:");
-passwordInput.type = "password";
-shouldBe("testRunner.secureEventInputIsEnabled", "true");
+.then(() => UIHelper.ensurePresentationUpdate())
+.then(() => {
+    shouldBe("testRunner.secureEventInputIsEnabled", "true");
+    finishJSTest();
+});
 
 </script>
 <script src="../../resources/js-test-post.js"></script>
index d8279c4a819e866a14cf53e71d623de20300cc5d..0523efa4a2b5d18f2cd065a6c3f681da94e2d85f 100644 (file)
@@ -2,12 +2,14 @@
 <html>
 <head>
 <meta charset="utf-8">
+<script src="../../resources/ui-helper.js"></script>
 <script src="../../resources/js-test-pre.js"></script>
 </head>
 <body>
 <input type=password>
 <input type=text>
 <script>
+jsTestIsAsync = true;
 
 description("Verify that basic focusing/unfocusing updates secure input state.");
 
@@ -19,16 +21,25 @@ shouldBe("testRunner.secureEventInputIsEnabled", "false");
 
 debug("\nA password input is focused:");
 passwordInput.focus();
-shouldBe("testRunner.secureEventInputIsEnabled", "true");
 
-debug("\nA regular text input is focused:");
-textInput.focus();
-shouldBe("testRunner.secureEventInputIsEnabled", "false");
+UIHelper.ensurePresentationUpdate().then(() => {
+    shouldBe("testRunner.secureEventInputIsEnabled", "true");
+    debug("\nA regular text input is focused:");
+    textInput.focus();
+})
 
-debug("\nA password input is focused again:");
-passwordInput.focus();
-shouldBe("testRunner.secureEventInputIsEnabled", "true");
+.then(() => UIHelper.ensurePresentationUpdate())
+.then(() => {
+    shouldBe("testRunner.secureEventInputIsEnabled", "false");
+    debug("\nA password input is focused again:");
+    passwordInput.focus();
+})
 
+.then(() => UIHelper.ensurePresentationUpdate())
+.then(() => {
+    shouldBe("testRunner.secureEventInputIsEnabled", "true");
+    finishJSTest();
+});
 </script>
 <script src="../../resources/js-test-post.js"></script>
 </body>
index 2380844ea07e2514e836ee096142bdb6d857b302..333618bd8b936fc2128d660e8e20f9aa470bcd12 100644 (file)
@@ -2,23 +2,36 @@
 <html>
 <head>
 <meta charset="utf-8">
+<script src="../../resources/ui-helper.js"></script>
 <script src="../../resources/js-test-pre.js"></script>
 </head>
 <body>
 <input type=password>
 <script>
 
+jsTestIsAsync = true;
+
 description("Verify that removing a password input from DOM tree disables secure input state.");
 
 var passwordInput = document.getElementsByTagName("input")[0];
 
 debug("A password input is focused:");
 passwordInput.focus();
-shouldBe("testRunner.secureEventInputIsEnabled", "true");
 
-debug("\nAfter deleting the input:");
-document.body.removeChild(passwordInput);
-shouldBe("testRunner.secureEventInputIsEnabled", "false");
+UIHelper.ensurePresentationUpdate().then(() => {
+    shouldBe("testRunner.secureEventInputIsEnabled", "true");
+    debug("\nAfter deleting the input:");
+    document.body.removeChild(passwordInput);
+})
+
+.then(() => {
+    return UIHelper.ensurePresentationUpdate()
+})
+
+.then(() => {
+    shouldBe("testRunner.secureEventInputIsEnabled", "false");
+    finishJSTest();
+})
 
 </script>
 <script src="../../resources/js-test-post.js"></script>
index e9b98eed8138d39751d269a623ec677e9321ea3c..2dfa775d393489bac6314e50358e1f0d0f7ebee1 100644 (file)
@@ -1,5 +1,8 @@
 <!DOCTYPE html>
 <html>
+<head>
+<script src="../../resources/ui-helper.js"></script>
+</head>
 <body>
 <input type=password>
 <script>
@@ -10,10 +13,12 @@ if (window.testRunner)
 var passwordInput = document.getElementsByTagName("input")[0];
 
 passwordInput.focus();
-if (!testRunner.secureEventInputIsEnabled)
-    alert("FAIL: Secure event input is not enabled after focusing a password input");
+UIHelper.ensurePresentationUpdate().then(() => {
+    if (!testRunner.secureEventInputIsEnabled)
+        alert("FAIL: Secure event input is not enabled after focusing a password input");
 
-location = "resources/reset-state-on-navigation-target.html";
+    location = "resources/reset-state-on-navigation-target.html";
+})
 
 </script>
 </body>
index a0761dbc17c2048784a2a29c16d1700fffc058ad..b9c9df3db2980f4f31a934c7db3b13e445816efe 100644 (file)
@@ -1,11 +1,15 @@
+PASS initialSelectionRects.length is 1
+PASS initialSelectionRects[0].left is 0
+PASS initialSelectionRects[0].top is 0
+PASS initialSelectionRects[0].width is 309
+PASS initialSelectionRects[0].height is 114
+PASS finalSelectionRects.length is 1
+PASS finalSelectionRects[0].left is 0
+PASS finalSelectionRects[0].top is 0
+PASS finalSelectionRects[0].width is 309
+PASS finalSelectionRects[0].height is 114
 PASS successfullyParsed is true
 
 TEST COMPLETE
-After long pressing, the selection rects are: [0 0 ; 309 114]
-After scrolling 1000px down, the selection rects are: [0 0 ; 309 114]
-PASS finalSelectionRects[0].top is initialSelectionRects[0].top
-PASS finalSelectionRects[0].left is initialSelectionRects[0].left
-PASS finalSelectionRects[0].width is initialSelectionRects[0].width
-PASS finalSelectionRects[0].height is initialSelectionRects[0].height
 WebKit
 
index aab1a0783ba6acc0ba02ac3202a3d3790e8aaa66..a7c2bba9060208808dd755d67350146c9ebf1a5b 100644 (file)
@@ -2,13 +2,14 @@
 <html>
 <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
 <head>
+    <script src="../../../resources/ui-helper.js"></script>
     <script src="../../../resources/js-test.js"></script>
     <style>
         body {
             margin: 0;
         }
 
-        #fixed {
+        #absolute {
             width: 100vw;
             height: 50px;
             position: absolute;
 
         #content {
             background-image: linear-gradient(blue, red);
-            height: 4000px;
+            height: 10000px;
         }
     </style>
     <script>
     jsTestIsAsync = true;
 
-    function toString(rect)
+    function selectTextAt(tapX, tapY)
     {
-        return `[${rect.left} ${rect.top} ; ${rect.width} ${rect.height}]`;
-    }
-
-    function selectFixedTextScript(tapX, tapY)
-    {
-        return `
-        (function() {
-            uiController.longPressAtPoint(${tapX}, ${tapY}, function() {
-                uiController.doAfterNextStablePresentationUpdate(function() {
-                    uiController.uiScriptComplete(JSON.stringify(uiController.selectionRangeViewRects));
-                });
-            });
-        })();`;
-    }
-
-    function simulateScrollingScript(distance)
-    {
-        return `(function() {
-            uiController.stableStateOverride = false;
-            uiController.immediateScrollToOffset(0, ${distance});
-            uiController.stableStateOverride = true;
-            uiController.doAfterNextStablePresentationUpdate(function() {
-                uiController.uiScriptComplete(JSON.stringify(uiController.selectionRangeViewRects));
-            });
-        })()`;
-    }
-
-    function toString(rect)
-    {
-        return `[${rect.left} ${rect.top} ; ${rect.width} ${rect.height}]`;
+        return new Promise(resolve => {
+            testRunner.runUIScript(`
+                (function() {
+                    uiController.longPressAtPoint(${tapX}, ${tapY}, function() {
+                        uiController.uiScriptComplete("Done");
+                    });
+                })();`, resolve);
+        });
     }
 
     function run()
     {
         if (!window.testRunner || !testRunner.runUIScript) {
-            description("To manually test, long press this text and scroll up. The selection rect should be in the expected place (at the very top of the page) after scrolling.");
+            description("To manually test, long press this text and scroll up. The selection rect should be in the expected place (in the absolutely positioned div) after scrolling.");
             return;
         }
 
-        testRunner.runUIScript(selectFixedTextScript(50, 50), initialSelectionRects => {
-            initialSelectionRects = JSON.parse(initialSelectionRects);
-            window.initialSelectionRects = initialSelectionRects;
-            debug(`After long pressing, the selection rects are: ${initialSelectionRects.map(toString)}`);
-            testRunner.runUIScript(simulateScrollingScript(1000), finalSelectionRects => {
-                finalSelectionRects = JSON.parse(finalSelectionRects);
-                window.finalSelectionRects = finalSelectionRects;
-                debug(`After scrolling 1000px down, the selection rects are: ${finalSelectionRects.map(toString)}`);
-                shouldBe("finalSelectionRects[0].top", "initialSelectionRects[0].top");
-                shouldBe("finalSelectionRects[0].left", "initialSelectionRects[0].left");
-                shouldBe("finalSelectionRects[0].width", "initialSelectionRects[0].width");
-                shouldBe("finalSelectionRects[0].height", "initialSelectionRects[0].height");
-                finishJSTest();
-            });
+        selectTextAt(50, 50)
+        .then(() => UIHelper.getUISelectionRects())
+        .then((rects) => {
+            initialSelectionRects = rects;
+            shouldBe("initialSelectionRects.length", "1");
+            shouldBe("initialSelectionRects[0].left", "0");
+            shouldBe("initialSelectionRects[0].top", "0");
+            shouldBe("initialSelectionRects[0].width", "309");
+            shouldBe("initialSelectionRects[0].height", "114");
+            document.body.scrollTop += 5000;
+            return UIHelper.getUISelectionRects();
+        })
+        .then((rects) => {
+            finalSelectionRects = rects;
+            shouldBe("finalSelectionRects.length", "1");
+            shouldBe("finalSelectionRects[0].left", "0");
+            shouldBe("finalSelectionRects[0].top", "0");
+            shouldBe("finalSelectionRects[0].width", "309");
+            shouldBe("finalSelectionRects[0].height", "114");
+            finishJSTest();
         });
     }
     </script>
 </head>
 <body onload=run()>
-    <div id="fixed">WebKit</div>
+    <div id="absolute">WebKit</div>
     <div id="content"></div>
 </body>
-</html>
\ No newline at end of file
+</html>
index 23e02319b38800a8d4cee9a446cd148123ed3a93..9917b9649c4b96f7a62d6f70f8799989da49625e 100644 (file)
@@ -1,10 +1,15 @@
+PASS initialSelectionRects.length is 1
+PASS initialSelectionRects[0].left is 0
+PASS initialSelectionRects[0].top is 0
+PASS initialSelectionRects[0].width is 309
+PASS initialSelectionRects[0].height is 114
+PASS finalSelectionRects.length is 1
+PASS finalSelectionRects[0].left is 0
+PASS finalSelectionRects[0].top is 5000
+PASS finalSelectionRects[0].width is 309
+PASS finalSelectionRects[0].height is 114
 PASS successfullyParsed is true
 
 TEST COMPLETE
-After long pressing, the selection rects are: [0 0 ; 309 114]
-After scrolling 1000px down, the selection rects are: [0 1000 ; 309 114]
-PASS initialSelectionRects.length is 1
-PASS finalSelectionRects.length is 1
-PASS finalSelectionRects[0].top - initialSelectionRects[0].top is 1000
 WebKit
 
index 248be1d6bb6ee62ea8aaf2f7907518914544a304..8da8b39850c1c093bf06b21b8cf4aaa1bdbbcaa5 100644 (file)
@@ -2,6 +2,7 @@
 <html>
 <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
 <head>
+    <script src="../../../resources/ui-helper.js"></script>
     <script src="../../../resources/js-test.js"></script>
     <style>
         body {
 
         #content {
             background-image: linear-gradient(blue, red);
-            height: 4000px;
+            height: 10000px;
         }
     </style>
     <script>
     jsTestIsAsync = true;
 
-    function toString(rect)
+    function selectTextAt(tapX, tapY)
     {
-        return `[${rect.left} ${rect.top} ; ${rect.width} ${rect.height}]`;
-    }
-
-    function selectFixedTextScript(tapX, tapY)
-    {
-        return `
-        (function() {
-            uiController.longPressAtPoint(${tapX}, ${tapY}, function() {
-                uiController.doAfterNextStablePresentationUpdate(function() {
-                    uiController.uiScriptComplete(JSON.stringify(uiController.selectionRangeViewRects));
-                });
-            });
-        })();`;
-    }
-
-    function simulateScrollingScript(distance)
-    {
-        return `(function() {
-            uiController.stableStateOverride = false;
-            uiController.immediateScrollToOffset(0, ${distance});
-            uiController.stableStateOverride = true;
-            uiController.doAfterNextStablePresentationUpdate(function() {
-                uiController.uiScriptComplete(JSON.stringify(uiController.selectionRangeViewRects));
-            });
-        })()`;
-    }
-
-    function toString(rect)
-    {
-        return `[${rect.left} ${rect.top} ; ${rect.width} ${rect.height}]`;
+        return new Promise(resolve => {
+            testRunner.runUIScript(`
+                (function() {
+                    uiController.longPressAtPoint(${tapX}, ${tapY}, function() {
+                        uiController.uiScriptComplete("Done");
+                    });
+                })();`, resolve);
+        });
     }
 
     function run()
             return;
         }
 
-        testRunner.runUIScript(selectFixedTextScript(50, 50), initialSelectionRects => {
-            initialSelectionRects = JSON.parse(initialSelectionRects);
-            window.initialSelectionRects = initialSelectionRects;
-            debug(`After long pressing, the selection rects are: ${initialSelectionRects.map(toString)}`);
-            testRunner.runUIScript(simulateScrollingScript(1000), finalSelectionRects => {
-                finalSelectionRects = JSON.parse(finalSelectionRects);
-                window.finalSelectionRects = finalSelectionRects;
-                debug(`After scrolling 1000px down, the selection rects are: ${finalSelectionRects.map(toString)}`);
-                shouldBe("initialSelectionRects.length", "1");
-                shouldBe("finalSelectionRects.length", "1");
-                shouldBe("finalSelectionRects[0].top - initialSelectionRects[0].top", "1000");
-                finishJSTest();
-            });
+        selectTextAt(50, 50)
+        .then(() => UIHelper.getUISelectionRects())
+        .then((rects) => {
+            initialSelectionRects = rects;
+            shouldBe("initialSelectionRects.length", "1");
+            shouldBe("initialSelectionRects[0].left", "0");
+            shouldBe("initialSelectionRects[0].top", "0");
+            shouldBe("initialSelectionRects[0].width", "309");
+            shouldBe("initialSelectionRects[0].height", "114");
+            document.body.scrollTop += 5000;
+            return UIHelper.getUISelectionRects();
+        })
+        .then((rects) => {
+            finalSelectionRects = rects;
+            shouldBe("finalSelectionRects.length", "1");
+            shouldBe("finalSelectionRects[0].left", "0");
+            shouldBe("finalSelectionRects[0].top", "5000");
+            shouldBe("finalSelectionRects[0].width", "309");
+            shouldBe("finalSelectionRects[0].height", "114");
+            finishJSTest();
         });
     }
     </script>
@@ -87,4 +73,4 @@
     <div id="fixed">WebKit</div>
     <div id="content"></div>
 </body>
-</html>
\ No newline at end of file
+</html>
index 1e41ca5bdb98a2f0f86d0df58118945b7840c11c..491323d9c269112103dc3954bd8f687574f6aba2 100644 (file)
@@ -64,7 +64,6 @@ layer at (0,0) size 800x600
       RenderBlock {P} at (0,238) size 784x58 [border: (2px solid #0000FF)]
         RenderText {#text} at (14,15) size 36x28
           text run at (14,15) width 36: "xxx"
-      RenderBlock (anonymous) at (0,320) size 784x0
       RenderBlock {P} at (0,320) size 784x58 [border: (2px solid #0000FF)]
         RenderBR {BR} at (14,15) size 0x28 [bgcolor=#008000]
       RenderBlock {P} at (0,402) size 784x58 [border: (2px solid #0000FF)]
index a61cffed4f780fc839d93d6e801e9e690959b9cc..d00e802060142aa239a3112fabd84127caa50a5b 100644 (file)
@@ -54,5 +54,4 @@ layer at (0,0) size 800x600
               text run at (2,3) width 20: "fo"
           RenderText {#text} at (21,3) size 13x28
             text run at (21,3) width 13: "x"
-        RenderBlock (anonymous) at (0,34) size 784x0
 caret: position 3 of child 0 {#text} of child 0 {B} of child 1 {DIV} of child 3 {DIV} of body
index 950ad328cbab3d2e63350a52dac31a087895ed3e..e963cd311307ef48ef9ac1e1477ebef8651c5b86 100644 (file)
@@ -15,5 +15,4 @@ layer at (0,0) size 800x600
           RenderInline {FONT} at (0,0) size 159x19 [color=#0000FF]
             RenderText {#text} at (150,0) size 159x19
               text run at (150,0) width 159: "This text should be blue."
-      RenderBlock (anonymous) at (0,76) size 784x0
 caret: position 25 of child 0 {#text} of child 1 {FONT} of child 0 {SPAN} of child 2 {DIV} of body
index 9dec42863e930fc0034676ddfa031a9d2b7a36de..bf3513416acaebc35d6a2b58811f05b07e2ca2f1 100644 (file)
@@ -678,8 +678,6 @@ fast/events/context-activated-by-key-event.html [ Skip ]
 
 webkit.org/b/168933 fast/regions/inline-block-inside-anonymous-overflow-with-covered-controls.html [ Pass ImageOnlyFailure ]
 
-webkit.org/b/168346 [ Sierra Debug ] editing/selection/move-by-word-visually-multi-space.html [ Pass Timeout ]
-
 webkit.org/b/169621 imported/w3c/web-platform-tests/IndexedDB/fire-error-event-exception.html [ Pass Failure ]
 webkit.org/b/169760 imported/w3c/web-platform-tests/IndexedDB/fire-success-event-exception.html [ Pass Failure ]
 
index 768b8e976d8aa67a6b067028c5aa227ceb151c64..a3c8f76f8033702179c70964b0c9f4a8d8eef28e 100644 (file)
@@ -86,5 +86,6 @@ layer at (0,0) size 800x600
           RenderText {#text} at (164,14) size 78x28
             text run at (164,14) width 78: " xxxxxx"
         RenderInline {SPAN} at (0,0) size 0x28
+      RenderBlock (anonymous) at (0,56) size 784x0
 selection start: position 0 of child 1 {#text} of child 1 {DIV} of body
 selection end:   position 6 of child 1 {#text} of child 1 {DIV} of body
index 068f7012254b906b1a88d126e8760e4f103ec0d0..788fdf8cc59302aa5017dc2797a0a8fb73d66ee2 100644 (file)
@@ -47,6 +47,69 @@ window.UIHelper = class UIHelper {
         });
     }
 
+    static ensurePresentationUpdate()
+    {
+        if (!this.isWebKit2()) {
+            testRunner.display();
+            return Promise.resolve();
+        }
+
+        return new Promise(resolve => {
+            testRunner.runUIScript(`
+                uiController.doAfterPresentationUpdate(function() {
+                    uiController.uiScriptComplete('Done');
+                });`, resolve);
+        });
+    }
+
+    static activateAndWaitForInputSessionAt(x, y)
+    {
+        if (!this.isWebKit2() || !this.isIOS())
+            return this.activateAt(x, y);
+
+        return new Promise(resolve => {
+            testRunner.runUIScript(`
+                (function() {
+                    uiController.didShowKeyboardCallback = function() {
+                        uiController.uiScriptComplete("Done");
+                    };
+                    uiController.singleTapAtPoint(${x}, ${y}, function() { });
+                })()`, resolve);
+        });
+    }
+
+    static getUICaretRect()
+    {
+        if (!this.isWebKit2() || !this.isIOS())
+            return Promise.resolve();
+
+        return new Promise(resolve => {
+            testRunner.runUIScript(`(function() {
+                uiController.doAfterNextStablePresentationUpdate(function() {
+                    uiController.uiScriptComplete(JSON.stringify(uiController.textSelectionCaretRect));
+                });
+            })()`, jsonString => {
+                resolve(JSON.parse(jsonString));
+            });
+        });
+    }
+
+    static getUISelectionRects()
+    {
+        if (!this.isWebKit2() || !this.isIOS())
+            return Promise.resolve();
+
+        return new Promise(resolve => {
+            testRunner.runUIScript(`(function() {
+                uiController.doAfterNextStablePresentationUpdate(function() {
+                    uiController.uiScriptComplete(JSON.stringify(uiController.selectionRangeViewRects));
+                });
+            })()`, jsonString => {
+                resolve(JSON.parse(jsonString));
+            });
+        });
+    }
+
     static wait(promise)
     {
         testRunner.waitUntilDone();
index 8d1880889abcbcc1ed7df7278c1fd7804abd0391..56fb54d0db33c19836259c256d07de4e3e31f38a 100644 (file)
@@ -1,3 +1,41 @@
+2017-08-22  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        [WK2] EditorState updates should be rolled into the layer update lifecycle when possible
+        https://bugs.webkit.org/show_bug.cgi?id=175370
+        <rdar://problem/33799806>
+
+        Reviewed by Ryosuke Niwa.
+
+        Remove didChangeSelectionAndUpdateLayout -- EditorState updates that are scheduled due to missing post-layout
+        data will now be scheduled for the next presentation update. Additionally, add editor client hooks to notify the
+        WebKit layer when we've updated the current composition. See WebKit ChangeLog for more details. This patch
+        adjusts and rebaselines existing layout tests.
+
+        * editing/Editor.cpp:
+        (WebCore::SetCompositionScope::SetCompositionScope):
+        (WebCore::SetCompositionScope::~SetCompositionScope):
+
+        Introduce a helper RAII class to ensure that we ignore selection changes during the scope of
+        Editor::setComposition and call out to the client with WebEditorClient::didUpdateComposition afterwards. This
+        also maintains a UserTypingGestureIndicator over its lifetime, so we don't additionally need to create a
+        UserTypingGestureIndicator in Editor::setComposition.
+
+        (WebCore::Editor::setComposition):
+        * editing/FrameSelection.cpp:
+        (WebCore::FrameSelection::setSelection):
+        (WebCore::FrameSelection::updateAndRevealSelection):
+        (WebCore::FrameSelection::setSelectedRange):
+        * editing/FrameSelection.h:
+        (WebCore::FrameSelection::defaultSetSelectionOptions):
+
+        Plumb state about whether or not the selection change was triggered by the user to FrameSelection::setSelection,
+        and if so, notify the editing client. A separate SetSelectionOptions flag is used here instead of
+        RevealSelection to avoid calling out to the client in places where we want to reveal the selection regardless of
+        whether or not the selection is user triggered.
+
+        * loader/EmptyClients.cpp:
+        * page/EditorClient.h:
+
 2017-08-21  Ryosuke Niwa  <rniwa@webkit.org>
 
         Consolidate the code to normalize MIME type in DataTransfer
index 47a7405f3afe9f44f92a07c43e67d29e266a2d2c..18545052a539f72109b357ebe4290aaa6fceec82 100644 (file)
@@ -1749,12 +1749,30 @@ void Editor::confirmComposition(const String& text)
     setComposition(text, ConfirmComposition);
 }
 
+class SetCompositionScope {
+public:
+    SetCompositionScope(Frame& frame)
+        : m_frame(frame)
+        , m_typingGestureIndicator(frame)
+    {
+        m_frame->editor().setIgnoreSelectionChanges(true);
+    }
+
+    ~SetCompositionScope()
+    {
+        m_frame->editor().setIgnoreSelectionChanges(false);
+        if (auto* editorClient = m_frame->editor().client())
+            editorClient->didUpdateComposition();
+    }
+
+    Ref<Frame> m_frame;
+    UserTypingGestureIndicator m_typingGestureIndicator;
+};
+
 void Editor::setComposition(const String& text, SetCompositionMode mode)
 {
     ASSERT(mode == ConfirmComposition || mode == CancelComposition);
-    UserTypingGestureIndicator typingGestureIndicator(m_frame);
-
-    setIgnoreSelectionChanges(true);
+    SetCompositionScope setCompositionScope(m_frame);
 
     if (mode == CancelComposition)
         ASSERT(text == emptyString());
@@ -1764,10 +1782,8 @@ void Editor::setComposition(const String& text, SetCompositionMode mode)
     m_compositionNode = nullptr;
     m_customCompositionUnderlines.clear();
 
-    if (m_frame.selection().isNone()) {
-        setIgnoreSelectionChanges(false);
+    if (m_frame.selection().isNone())
         return;
-    }
 
     // Always delete the current composition before inserting the finalized composition text if we're confirming our composition.
     // Our default behavior (if the beforeinput event is not prevented) is to insert the finalized composition text back in.
@@ -1784,17 +1800,11 @@ void Editor::setComposition(const String& text, SetCompositionMode mode)
         // An open typing command that disagrees about current selection would cause issues with typing later on.
         TypingCommand::closeTyping(&m_frame);
     }
-
-    setIgnoreSelectionChanges(false);
 }
 
 void Editor::setComposition(const String& text, const Vector<CompositionUnderline>& underlines, unsigned selectionStart, unsigned selectionEnd)
 {
-    Ref<Frame> protection(m_frame);
-
-    UserTypingGestureIndicator typingGestureIndicator(m_frame);
-
-    setIgnoreSelectionChanges(true);
+    SetCompositionScope setCompositionScope(m_frame);
 
     // Updates styles before setting selection for composition to prevent
     // inserting the previous composition text into text nodes oddly.
@@ -1803,10 +1813,8 @@ void Editor::setComposition(const String& text, const Vector<CompositionUnderlin
 
     selectComposition();
 
-    if (m_frame.selection().isNone()) {
-        setIgnoreSelectionChanges(false);
+    if (m_frame.selection().isNone())
         return;
-    }
 
     String originalText = selectedText();
     bool isStartingToRecomposeExistingRange = !text.isEmpty() && selectionStart < selectionEnd && !hasComposition();
@@ -1897,8 +1905,6 @@ void Editor::setComposition(const String& text, const Vector<CompositionUnderlin
         }
     }
 
-    setIgnoreSelectionChanges(false);
-
 #if PLATFORM(IOS)        
     client()->stopDelayingAndCoalescingContentChangeNotifications();
 #endif
index 071eb44391bdd176c4dea5add3458b2f5cd50959..a857edede6b23777c6e84ccfbd3144117181b778 100644 (file)
@@ -367,6 +367,11 @@ void FrameSelection::setSelection(const VisibleSelection& selection, SetSelectio
         return;
 
     updateAndRevealSelection(intent);
+
+    if (options & IsUserTriggered) {
+        if (auto* client = m_frame->editor().client())
+            client->didEndUserTriggeredSelectionChanges();
+    }
 }
 
 static void updateSelectionByUpdatingLayoutOrStyle(Frame& frame)
@@ -406,9 +411,6 @@ void FrameSelection::updateAndRevealSelection(const AXTextStateChangeIntent& int
     }
 
     notifyAccessibilityForSelectionChange(intent);
-
-    if (auto* client = m_frame->editor().client())
-        client->didChangeSelectionAndUpdateLayout();
 }
 
 void FrameSelection::updateDataDetectorsForSelection()
@@ -1972,7 +1974,7 @@ bool FrameSelection::setSelectedRange(Range* range, EAffinity affinity, bool clo
             return false;
     }
 
-    setSelection(newSelection, ClearTypingStyle | (closeTyping ? CloseTyping : 0));
+    setSelection(newSelection, ClearTypingStyle | (closeTyping ? CloseTyping : 0) | (userTriggered == UserTriggered ? IsUserTriggered : 0));
     return true;
 }
 
index d35ef7c9e31ea75a947fd0729a35061470a78a36..c6ae93db2a8df40c1be6b79092c11289b6381e44 100644 (file)
@@ -126,11 +126,12 @@ public:
         DoNotSetFocus = 1 << 4,
         DictationTriggered = 1 << 5,
         RevealSelection = 1 << 6,
+        IsUserTriggered = 1 << 7,
     };
     typedef unsigned SetSelectionOptions; // Union of values in SetSelectionOption and EUserTriggered
     static inline SetSelectionOptions defaultSetSelectionOptions(EUserTriggered userTriggered = NotUserTriggered)
     {
-        return CloseTyping | ClearTypingStyle | (userTriggered ? (RevealSelection | FireSelectEvent) : 0);
+        return CloseTyping | ClearTypingStyle | (userTriggered ? (RevealSelection | FireSelectEvent | IsUserTriggered) : 0);
     }
 
     WEBCORE_EXPORT explicit FrameSelection(Frame* = nullptr);
index 7539e6beb12c5226fa96db2434964818cb339db4..781c4b02cde74542843082a82fca8c3d46f8a2f8 100644 (file)
@@ -166,11 +166,12 @@ private:
     void didBeginEditing() final { }
     void respondToChangedContents() final { }
     void respondToChangedSelection(Frame*) final { }
-    void didChangeSelectionAndUpdateLayout() final { }
     void updateEditorStateAfterLayoutIfEditabilityChanged() final { }
     void discardedComposition(Frame*) final { }
     void canceledComposition() final { }
+    void didUpdateComposition() final { }
     void didEndEditing() final { }
+    void didEndUserTriggeredSelectionChanges() final { }
     void willWriteSelectionToPasteboard(Range*) final { }
     void didWriteSelectionToPasteboard() final { }
     void getClientPasteboardDataForRange(Range*, Vector<String>&, Vector<RefPtr<SharedBuffer>>&) final { }
index 069587787e30862923ce47a1fe12a58390fef239..e135ed657960e4c2bfeb9c3a3805e535596e4c4b 100644 (file)
@@ -90,7 +90,7 @@ public:
     virtual void didBeginEditing() = 0;
     virtual void respondToChangedContents() = 0;
     virtual void respondToChangedSelection(Frame*) = 0;
-    virtual void didChangeSelectionAndUpdateLayout() = 0;
+    virtual void didEndUserTriggeredSelectionChanges() = 0;
     virtual void updateEditorStateAfterLayoutIfEditabilityChanged() = 0;
     virtual void didEndEditing() = 0;
     virtual void willWriteSelectionToPasteboard(Range*) = 0;
@@ -103,6 +103,7 @@ public:
     // This function is not called when a composition is closed per a request from an input method.
     virtual void discardedComposition(Frame*) = 0;
     virtual void canceledComposition() = 0;
+    virtual void didUpdateComposition() = 0;
 
     virtual void registerUndoStep(UndoStep&) = 0;
     virtual void registerRedoStep(UndoStep&) = 0;
index 10c7d753302948edc6b86ce89a4a2856f8828895..f9bb0d04b945bb77b4a33d1b0365b605bf90d51f 100644 (file)
@@ -1,3 +1,130 @@
+2017-08-22  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        [WK2] EditorState updates should be rolled into the layer update lifecycle when possible
+        https://bugs.webkit.org/show_bug.cgi?id=175370
+        <rdar://problem/33799806>
+
+        Reviewed by Ryosuke Niwa.
+
+        See per-method comments for more detail. WebPage::didChangeSelection now schedules EditorState updates to be sent
+        during the next layer tree transaction rather than sending them synchronously. To ensure that iOS and Mac continue
+        to behave correctly w.r.t. EditorState updates, we immediately dispatch EditorStates in the following cases:
+        - After the composition changes, is confirmed, or is canceled.
+        - After an edit command is executed.
+        - After ending user-triggered selection changes.
+
+        * Shared/mac/RemoteLayerTreeTransaction.h:
+        (WebKit::RemoteLayerTreeTransaction::hasEditorState const):
+        (WebKit::RemoteLayerTreeTransaction::editorState const):
+        (WebKit::RemoteLayerTreeTransaction::setEditorState):
+
+        Attaches an optional EditorState to the RemoteLayerTreeTransaction. This EditorState is computed and sent over
+        when setting up the transaction in WebPage, if something previously scheduled an EditorState update.
+
+        * Shared/mac/RemoteLayerTreeTransaction.mm:
+        (WebKit::RemoteLayerTreeTransaction::encode const):
+        (WebKit::RemoteLayerTreeTransaction::decode):
+
+        Add coder support for sending over a layer tree transaction's EditorState.
+
+        * UIProcess/API/Cocoa/WKViewPrivate.h:
+        * UIProcess/API/mac/WKView.mm:
+        (-[WKView _doAfterNextPresentationUpdate:]):
+
+        Add _doAfterNextPresentationUpdate to WKView (used in TestWebKitAPI -- refer to
+        WebKitAgnosticTest::waitForNextPresentationUpdate).
+
+        * UIProcess/DrawingAreaProxy.h:
+        (WebKit::DrawingAreaProxy::dispatchPresentationCallbacksAfterFlushingLayers):
+        * UIProcess/DrawingAreaProxy.messages.in:
+
+        Add a new IPC messages, DispatchPresentationCallbacksAfterFlushingLayers, to invoke in-flight presentation
+        callbacks in the UI process following a layer flush in the web process.
+
+        * UIProcess/WebPageProxy.h:
+        * UIProcess/mac/RemoteLayerTreeDrawingAreaProxy.mm:
+        (WebKit::RemoteLayerTreeDrawingAreaProxy::commitLayerTree):
+        * UIProcess/mac/TiledCoreAnimationDrawingAreaProxy.h:
+        * UIProcess/mac/TiledCoreAnimationDrawingAreaProxy.mm:
+        (WebKit::TiledCoreAnimationDrawingAreaProxy::~TiledCoreAnimationDrawingAreaProxy):
+        (WebKit::TiledCoreAnimationDrawingAreaProxy::dispatchAfterEnsuringDrawing):
+        (WebKit::TiledCoreAnimationDrawingAreaProxy::dispatchPresentationCallbacksAfterFlushingLayers):
+
+        Run all pending _doAfterNextPresentationUpdate callbacks.
+
+        * WebProcess/WebCoreSupport/WebEditorClient.cpp:
+        (WebKit::WebEditorClient::didApplyStyle):
+        (WebKit::WebEditorClient::respondToChangedContents):
+        (WebKit::WebEditorClient::didEndUserTriggeredSelectionChanges):
+        (WebKit::WebEditorClient::didUpdateComposition):
+
+        Forward editor client calls to the WebPage.
+
+        (WebKit::WebEditorClient::didChangeSelectionAndUpdateLayout): Deleted.
+        * WebProcess/WebCoreSupport/WebEditorClient.h:
+        * WebProcess/WebPage/WebPage.cpp:
+        (WebKit::WebPage::editorState const):
+        (WebKit::WebPage::updateEditorStateAfterLayoutIfEditabilityChanged):
+        (WebKit::WebPage::willCommitLayerTree):
+        (WebKit::WebPage::didApplyStyle):
+
+        Allow style application to immediately trigger EditorState updates, if we're not currently ignoring selection
+        changes in the Editor.
+
+        (WebKit::WebPage::didChangeContents):
+
+        Allow applying top-level edit commands to immediately trigger EditorState updates, if we're not currently
+        ignoring selection changes in the Editor.
+
+        (WebKit::WebPage::didChangeSelection):
+        (WebKit::WebPage::didUpdateComposition):
+        (WebKit::WebPage::didEndUserTriggeredSelectionChanges):
+        (WebKit::WebPage::discardedComposition):
+        (WebKit::WebPage::canceledComposition):
+
+        When handling composition updates, always send an EditorState to the UI process. Unlike other cases, IME
+        requires immediate EditorState data, so we need to be explicit here in sending updates right away.
+
+        (WebKit::WebPage::sendEditorStateUpdate):
+        (WebKit::WebPage::sendPartialEditorStateAndSchedulePostLayoutUpdate):
+        (WebKit::WebPage::flushPendingEditorStateUpdate):
+
+        Helper methods to schedule an EditorState update to be sent upon the next layer tree update, or flush any
+        pending EditorState update that has been scheduled. The private, more aggressive variant of this is
+        sendEditorStateUpdate, which ignores whether or not there was already an EditorState update scheduled, and sends
+        one anyways (this still fulfills any EditorState update that was previously scheduled).
+
+        These helper methods are treated as no-ops when invoked while ignoring selection changes. This is to prevent
+        temporary selection state and editor commands during operations such as text indicator snapshotting from pushing
+        bogus information about transient editor states to the UI process.
+
+        (WebKit::WebPage::sendPostLayoutEditorStateIfNeeded): Deleted.
+        * WebProcess/WebPage/WebPage.h:
+        * WebProcess/WebPage/ios/WebPageIOS.mm:
+        (WebKit::WebPage::platformEditorState const):
+        (WebKit::WebPage::executeEditCommandWithCallback):
+        (WebKit::selectionIsInsideFixedPositionContainer):
+        (WebKit::WebPage::updateVisibleContentRects):
+
+        Fix a hack that was computing an EditorState to figure out whether the current selection starts or ends in a
+        fixed position container. Factors out relevant logic into a separate helper, and also schedules an EditorState
+        update instead of immediately computing it.
+
+        * WebProcess/WebPage/mac/TiledCoreAnimationDrawingArea.h:
+        * WebProcess/WebPage/mac/TiledCoreAnimationDrawingArea.mm:
+        (WebKit::TiledCoreAnimationDrawingArea::addTransactionCallbackID):
+
+        Add support for registering and dispatching presentation callbacks that hook into the layer flush lifecycle,
+        using the tiled CA drawing area. These are used by Mac LayoutTests and API tests that need to wait until the
+        next flush before checking for state that depends on EditorState updates in the UI process.
+
+        (WebKit::TiledCoreAnimationDrawingArea::flushLayers):
+
+        Tell the WebPage to flush any pending EditorState updates.
+
+        * WebProcess/WebPage/mac/WebPageMac.mm:
+        (WebKit::WebPage::platformEditorState const):
+
 2017-08-22  Brent Fulgham  <bfulgham@apple.com>
 
         Relax keychain access to permit users to permanently allow client certificates
index 2a45fa260a8e503126f13d9192907f0ee35d4ac9..9bf2f088c8270adace3b24270aa6e4438007fb4c 100644 (file)
@@ -26,6 +26,7 @@
 #ifndef RemoteLayerTreeTransaction_h
 #define RemoteLayerTreeTransaction_h
 
+#include "EditorState.h"
 #include "GenericCallback.h"
 #include "PlatformCAAnimationRemote.h"
 #include "RemoteLayerBackingStore.h"
@@ -268,6 +269,10 @@ public:
 
     WebCore::LayoutMilestones newlyReachedLayoutMilestones() const { return m_newlyReachedLayoutMilestones; }
     void setNewlyReachedLayoutMilestones(WebCore::LayoutMilestones milestones) { m_newlyReachedLayoutMilestones = milestones; }
+
+    bool hasEditorState() const { return !!m_editorState; }
+    const EditorState& editorState() const { return m_editorState.value(); }
+    void setEditorState(const EditorState& editorState) { m_editorState = editorState; }
     
 private:
     WebCore::GraphicsLayer::PlatformLayerID m_rootLayerID;
@@ -304,6 +309,8 @@ private:
     bool m_viewportMetaTagWidthWasExplicit { false };
     bool m_viewportMetaTagCameFromImageDocument { false };
     bool m_isInStableState { false };
+
+    std::optional<EditorState> m_editorState { std::nullopt };
 };
 
 } // namespace WebKit
index 84f8c5e53f1498bf2fab41abfd26e0c37c61a39d..e94f4e9b5403ff7b644dfbfac2efd287674eaa04 100644 (file)
@@ -558,6 +558,10 @@ void RemoteLayerTreeTransaction::encode(IPC::Encoder& encoder) const
     encoder << m_isInStableState;
 
     encoder << m_callbackIDs;
+
+    encoder << hasEditorState();
+    if (m_editorState)
+        encoder << *m_editorState;
 }
 
 bool RemoteLayerTreeTransaction::decode(IPC::Decoder& decoder, RemoteLayerTreeTransaction& result)
@@ -673,6 +677,17 @@ bool RemoteLayerTreeTransaction::decode(IPC::Decoder& decoder, RemoteLayerTreeTr
     if (!decoder.decode(result.m_callbackIDs))
         return false;
 
+    bool hasEditorState;
+    if (!decoder.decode(hasEditorState))
+        return false;
+
+    if (hasEditorState) {
+        EditorState editorState;
+        if (!decoder.decode(editorState))
+            return false;
+        result.setEditorState(editorState);
+    }
+
     return true;
 }
 
index bdd9f6ae53ac463486a329db2c54a35dfd68a883..cb6ee1dc6159521138c3ab124a674ccbdc094636 100644 (file)
 - (void)_addMediaPlaybackControlsView:(id)mediaPlaybackControlsView;
 - (void)_removeMediaPlaybackControlsView;
 
+- (void)_doAfterNextPresentationUpdate:(void (^)(void))updateBlock WK_API_AVAILABLE(macosx(WK_MAC_TBA));
+
 @end
 
 #endif // !TARGET_OS_IPHONE
index 30374791747e220bd3009b3e33b95ab2dd7d42be..9d1268b934f970bd4e6e941ef1d5ceea9ee7c3bc 100644 (file)
@@ -1592,6 +1592,15 @@ static _WKOverlayScrollbarStyle toAPIScrollbarStyle(std::optional<WebCore::Scrol
 {
 }
 
+- (void)_doAfterNextPresentationUpdate:(void (^)(void))updateBlock
+{
+    auto updateBlockCopy = makeBlockPtr(updateBlock);
+    _data->_impl->page().callAfterNextPresentationUpdate([updateBlockCopy](WebKit::CallbackBase::Error error) {
+        if (updateBlockCopy)
+            updateBlockCopy();
+    });
+}
+
 @end
 
 #endif // PLATFORM(MAC)
index e96cf73fc99a320377db63e3e7da342e6a9a9373..50a297c7fb7c3cc8d125254ac588a15e154c4125 100644 (file)
@@ -106,6 +106,8 @@ public:
     virtual WebCore::MachSendRight createFence();
 #endif
 
+    virtual void dispatchPresentationCallbacksAfterFlushingLayers(const Vector<CallbackID>&) { }
+
 protected:
     explicit DrawingAreaProxy(DrawingAreaType, WebPageProxy&);
 
index 47360e37d637de6e1fe1bcc6c7b2bae35c16b495..3e58de595ddc61688e6ac1e16c5efc197d0814bf 100644 (file)
@@ -26,6 +26,7 @@ messages -> DrawingAreaProxy {
     EnterAcceleratedCompositingMode(uint64_t backingStoreStateID, WebKit::LayerTreeContext context)
     ExitAcceleratedCompositingMode(uint64_t backingStoreStateID, WebKit::UpdateInfo updateInfo)
     UpdateAcceleratedCompositingMode(uint64_t backingStoreStateID, WebKit::LayerTreeContext context)
+    DispatchPresentationCallbacksAfterFlushingLayers(Vector<WebKit::CallbackID> callbackIDs);
 
 #if PLATFORM(COCOA)
     // Used by TiledCoreAnimationDrawingAreaProxy.
index 09f4968edfbb8c15992f4d40d95e119539d41dfd..9fa015fc59f6ac9a38a7a2a6ab8bcb2fab5cff86 100644 (file)
@@ -1200,6 +1200,7 @@ public:
 #if PLATFORM(COCOA)
     void createSandboxExtensionsIfNeeded(const Vector<String>& files, SandboxExtension::Handle& fileReadHandle, SandboxExtension::HandleArray& fileUploadHandles);
 #endif
+    void editorStateChanged(const EditorState&);
 
 private:
     WebPageProxy(PageClient&, WebProcessProxy&, uint64_t pageID, Ref<API::PageConfiguration>&&);
@@ -1363,7 +1364,6 @@ private:
     void didEndColorPicker() override;
 #endif
 
-    void editorStateChanged(const EditorState&);
     void compositionWasCanceled();
     void setHasHadSelectionChangesFromUserInteraction(bool);
     void setNeedsHiddenContentEditableQuirk(bool);
index fcc435ea84f38d6af31670dc79f07c01e74e02d3..54b17d1f0d69a3f14a3b54ea3b298c933a5325b9 100644 (file)
@@ -185,6 +185,9 @@ void RemoteLayerTreeDrawingAreaProxy::commitLayerTree(const RemoteLayerTreeTrans
     ASSERT(layerTreeTransaction.transactionID() == m_lastVisibleTransactionID + 1);
     m_transactionIDForPendingCACommit = layerTreeTransaction.transactionID();
 
+    if (layerTreeTransaction.hasEditorState())
+        m_webPageProxy.editorStateChanged(layerTreeTransaction.editorState());
+
     if (m_remoteLayerTreeHost.updateLayerTree(layerTreeTransaction)) {
         if (layerTreeTransaction.transactionID() >= m_transactionIDForUnhidingContent)
             m_webPageProxy.setAcceleratedCompositingRootLayer(m_remoteLayerTreeHost.rootLayer());
index 4ccf564fbd7f0feaa232c24ab7925776ed4bd662..f6eb62e0ec9bd832bc0c3638882f5d3d0642436e 100644 (file)
@@ -52,6 +52,7 @@ private:
 
     void waitForDidUpdateActivityState() override;
     void dispatchAfterEnsuringDrawing(WTF::Function<void (CallbackBase::Error)>&&) override;
+    void dispatchPresentationCallbacksAfterFlushingLayers(const Vector<CallbackID>&) final;
 
     void willSendUpdateGeometry() override;
 
@@ -71,6 +72,8 @@ private:
 
     // The last minimum layout size we sent to the web process.
     WebCore::IntSize m_lastSentMinimumLayoutSize;
+
+    CallbackMap m_callbacks;
 };
 
 } // namespace WebKit
index 4a6f9ea090a5e9bc590c027e387a2c02e4ceb875..b9e00735539f8edaaac978973cabbaf44d1fb40c 100644 (file)
@@ -51,6 +51,7 @@ TiledCoreAnimationDrawingAreaProxy::TiledCoreAnimationDrawingAreaProxy(WebPagePr
 
 TiledCoreAnimationDrawingAreaProxy::~TiledCoreAnimationDrawingAreaProxy()
 {
+    m_callbacks.invalidate(CallbackBase::Error::OwnerWasInvalidated);
 }
 
 void TiledCoreAnimationDrawingAreaProxy::deviceScaleFactorDidChange()
@@ -197,10 +198,20 @@ void TiledCoreAnimationDrawingAreaProxy::commitTransientZoom(double scale, Float
 
 void TiledCoreAnimationDrawingAreaProxy::dispatchAfterEnsuringDrawing(WTF::Function<void (CallbackBase::Error)>&& callback)
 {
-    // This callback is primarily used for testing in RemoteLayerTreeDrawingArea. We could in theory wait for a CA commit here.
-    dispatch_async(dispatch_get_main_queue(), BlockPtr<void ()>::fromCallable([callback = WTFMove(callback)] {
-        callback(CallbackBase::Error::None);
-    }).get());
+    if (!m_webPageProxy.isValid()) {
+        callback(CallbackBase::Error::OwnerWasInvalidated);
+        return;
+    }
+
+    m_webPageProxy.process().send(Messages::DrawingArea::AddTransactionCallbackID(m_callbacks.put(WTFMove(callback), nullptr)), m_webPageProxy.pageID());
+}
+
+void TiledCoreAnimationDrawingAreaProxy::dispatchPresentationCallbacksAfterFlushingLayers(const Vector<CallbackID>& callbackIDs)
+{
+    for (auto& callbackID : callbackIDs) {
+        if (auto callback = m_callbacks.take<VoidCallback>(callbackID))
+            callback->performCallback();
+    }
 }
 
 } // namespace WebKit
index a4a79847640ee7073a8cae04f68143c3d673318c..00fa7a3139e373b13776fe75df775222fe343077 100644 (file)
@@ -159,7 +159,7 @@ bool WebEditorClient::shouldApplyStyle(StyleProperties* style, Range* range)
 
 void WebEditorClient::didApplyStyle()
 {
-    notImplemented();
+    m_page->didApplyStyle();
 }
 
 bool WebEditorClient::shouldMoveRangeAfterDelete(Range*, Range*)
@@ -180,7 +180,7 @@ void WebEditorClient::respondToChangedContents()
 {
     static NeverDestroyed<String> WebViewDidChangeNotification(MAKE_STATIC_STRING_IMPL("WebViewDidChangeNotification"));
     m_page->injectedBundleEditorClient().didChange(*m_page, WebViewDidChangeNotification.get().impl());
-    notImplemented();
+    m_page->didChangeContents();
 }
 
 void WebEditorClient::respondToChangedSelection(Frame* frame)
@@ -197,9 +197,9 @@ void WebEditorClient::respondToChangedSelection(Frame* frame)
 #endif
 }
 
-void WebEditorClient::didChangeSelectionAndUpdateLayout()
+void WebEditorClient::didEndUserTriggeredSelectionChanges()
 {
-    m_page->sendPostLayoutEditorStateIfNeeded();
+    m_page->didEndUserTriggeredSelectionChanges();
 }
 
 void WebEditorClient::updateEditorStateAfterLayoutIfEditabilityChanged()
@@ -207,6 +207,11 @@ void WebEditorClient::updateEditorStateAfterLayoutIfEditabilityChanged()
     m_page->updateEditorStateAfterLayoutIfEditabilityChanged();
 }
 
+void WebEditorClient::didUpdateComposition()
+{
+    m_page->didUpdateComposition();
+}
+
 void WebEditorClient::discardedComposition(Frame*)
 {
     m_page->discardedComposition();
index 613dde294db54c84b8cda6fc8e225b769aae40f5..a607d8ef716fa1e6af5e41be71b08150a5fe5fb6 100644 (file)
@@ -62,10 +62,11 @@ private:
     void didBeginEditing() final;
     void respondToChangedContents() final;
     void respondToChangedSelection(WebCore::Frame*) final;
-    void didChangeSelectionAndUpdateLayout() final;
+    void didEndUserTriggeredSelectionChanges() final;
     void updateEditorStateAfterLayoutIfEditabilityChanged() final;
     void discardedComposition(WebCore::Frame*) final;
     void canceledComposition() final;
+    void didUpdateComposition() final;
     void didEndEditing() final;
     void willWriteSelectionToPasteboard(WebCore::Range*) final;
     void didWriteSelectionToPasteboard() final;
index 699e7b1195923f6d5bca3d9dd530f218efe7f7c2..e5d072aca789f9fccd88c596c76be1e395bc5df0 100644 (file)
@@ -856,7 +856,8 @@ EditorState WebPage::editorState(IncludePostLayoutDataHint shouldIncludePostLayo
     result.shouldIgnoreSelectionChanges = frame.editor().ignoreSelectionChanges();
 
 #if PLATFORM(COCOA)
-    if (shouldIncludePostLayoutData == IncludePostLayoutDataHint::Yes && result.isContentEditable) {
+    bool canIncludePostLayoutData = frame.view() && !frame.view()->needsLayout();
+    if (shouldIncludePostLayoutData == IncludePostLayoutDataHint::Yes && canIncludePostLayoutData && result.isContentEditable) {
         auto& postLayoutData = result.postLayoutData();
         if (!selection.isNone()) {
             Node* nodeToRemove;
@@ -938,7 +939,7 @@ void WebPage::updateEditorStateAfterLayoutIfEditabilityChanged()
     Frame& frame = m_page->focusController().focusedOrMainFrame();
     EditorStateIsContentEditable editorStateIsContentEditable = frame.selection().selection().isContentEditable() ? EditorStateIsContentEditable::Yes : EditorStateIsContentEditable::No;
     if (m_lastEditorStateWasContentEditable != editorStateIsContentEditable)
-        send(Messages::WebPageProxy::EditorStateChanged(editorState()));
+        sendPartialEditorStateAndSchedulePostLayoutUpdate();
 }
 
 String WebPage::renderTreeExternalRepresentation() const
@@ -3437,6 +3438,11 @@ void WebPage::willCommitLayerTree(RemoteLayerTreeTransaction& layerTransaction)
 #if PLATFORM(MAC)
     layerTransaction.setScrollPosition(frameView->scrollPosition());
 #endif
+
+    if (m_hasPendingEditorStateUpdate) {
+        layerTransaction.setEditorState(editorState());
+        m_hasPendingEditorStateUpdate = false;
+    }
 }
 
 void WebPage::didFlushLayerTreeAtTime(MonotonicTime timestamp)
@@ -5049,6 +5055,16 @@ static bool needsPlainTextQuirk(bool needsQuirks, const URL& url)
 }
 #endif
 
+void WebPage::didApplyStyle()
+{
+    sendEditorStateUpdate();
+}
+
+void WebPage::didChangeContents()
+{
+    sendEditorStateUpdate();
+}
+
 void WebPage::didChangeSelection()
 {
     Frame& frame = m_page->focusController().focusedOrMainFrame();
@@ -5063,14 +5079,6 @@ void WebPage::didChangeSelection()
     if (m_isSelectingTextWhileInsertingAsynchronously)
         return;
 
-    FrameView* view = frame.view();
-
-    // If there is a layout pending, we should avoid populating EditorState that require layout to be done or it will
-    // trigger a synchronous layout every time the selection changes. sendPostLayoutEditorStateIfNeeded() will be called
-    // to send the full editor state after layout is done if we send a partial editor state here.
-    auto editorState = this->editorState(view && view->needsLayout() ? IncludePostLayoutDataHint::No : IncludePostLayoutDataHint::Yes);
-    m_isEditorStateMissingPostLayoutData = editorState.isMissingPostLayoutData;
-
 #if PLATFORM(MAC)
     bool hasPreviouslyFocusedDueToUserInteraction = m_hasEverFocusedElementDueToUserInteractionSincePageTransition;
     m_hasEverFocusedElementDueToUserInteractionSincePageTransition |= m_userIsInteracting;
@@ -5096,15 +5104,11 @@ void WebPage::didChangeSelection()
     if (frame.editor().hasComposition() && !frame.editor().ignoreSelectionChanges() && !frame.selection().isNone()) {
         frame.editor().cancelComposition();
         discardedComposition();
-    } else
-        send(Messages::WebPageProxy::EditorStateChanged(editorState));
-#else
-    send(Messages::WebPageProxy::EditorStateChanged(editorState), pageID(), IPC::SendOption::DispatchMessageEvenWhenWaitingForSyncReply);
+        return;
+    }
 #endif
 
-#if PLATFORM(IOS)
-    m_drawingArea->scheduleCompositingLayerFlush();
-#endif
+    sendPartialEditorStateAndSchedulePostLayoutUpdate();
 }
 
 void WebPage::resetAssistedNodeForFrame(WebFrame* frame)
@@ -5169,24 +5173,28 @@ void WebPage::elementDidBlur(WebCore::Node* node)
     }
 }
 
-void WebPage::sendPostLayoutEditorStateIfNeeded()
+void WebPage::didUpdateComposition()
 {
-    if (!m_isEditorStateMissingPostLayoutData)
-        return;
+    sendEditorStateUpdate();
+}
 
-    send(Messages::WebPageProxy::EditorStateChanged(editorState(IncludePostLayoutDataHint::Yes)), pageID(), IPC::SendOption::DispatchMessageEvenWhenWaitingForSyncReply);
-    m_isEditorStateMissingPostLayoutData = false;
+void WebPage::didEndUserTriggeredSelectionChanges()
+{
+    Frame& frame = m_page->focusController().focusedOrMainFrame();
+    if (!frame.editor().ignoreSelectionChanges())
+        sendEditorStateUpdate();
 }
 
 void WebPage::discardedComposition()
 {
     send(Messages::WebPageProxy::CompositionWasCanceled());
-    send(Messages::WebPageProxy::EditorStateChanged(editorState()));
+    sendEditorStateUpdate();
 }
 
 void WebPage::canceledComposition()
 {
     send(Messages::WebPageProxy::CompositionWasCanceled());
+    sendEditorStateUpdate();
 }
 
 void WebPage::setMinimumLayoutSize(const IntSize& minimumLayoutSize)
@@ -5604,6 +5612,54 @@ void WebPage::reportUsedFeatures()
     m_loaderClient->featuresUsedInPage(*this, namedFeatures);
 }
 
+void WebPage::sendEditorStateUpdate()
+{
+    Frame& frame = m_page->focusController().focusedOrMainFrame();
+    if (frame.editor().ignoreSelectionChanges())
+        return;
+
+    m_hasPendingEditorStateUpdate = false;
+
+    // If we immediately dispatch an EditorState update to the UI process, layout may not be up to date yet.
+    // If that is the case, just send what we have (i.e. don't include post-layout data) and wait until the
+    // next layer tree commit to compute and send the complete EditorState over.
+    auto state = editorState();
+    send(Messages::WebPageProxy::EditorStateChanged(state), pageID(), IPC::SendOption::DispatchMessageEvenWhenWaitingForSyncReply);
+
+    if (state.isMissingPostLayoutData) {
+        m_hasPendingEditorStateUpdate = true;
+        m_drawingArea->scheduleCompositingLayerFlush();
+    }
+}
+
+void WebPage::sendPartialEditorStateAndSchedulePostLayoutUpdate()
+{
+    Frame& frame = m_page->focusController().focusedOrMainFrame();
+    if (frame.editor().ignoreSelectionChanges())
+        return;
+
+    send(Messages::WebPageProxy::EditorStateChanged(editorState(IncludePostLayoutDataHint::No)), pageID(), IPC::SendOption::DispatchMessageEvenWhenWaitingForSyncReply);
+
+    if (m_hasPendingEditorStateUpdate)
+        return;
+
+    // Flag the next layer flush to compute and propagate an EditorState to the UI process.
+    m_hasPendingEditorStateUpdate = true;
+    m_drawingArea->scheduleCompositingLayerFlush();
+}
+
+void WebPage::flushPendingEditorStateUpdate()
+{
+    if (!m_hasPendingEditorStateUpdate)
+        return;
+
+    Frame& frame = m_page->focusController().focusedOrMainFrame();
+    if (frame.editor().ignoreSelectionChanges())
+        return;
+
+    sendEditorStateUpdate();
+}
+
 void WebPage::updateWebsitePolicies(const WebsitePolicies& websitePolicies)
 {
     if (!m_page)
index 4a6bd9e7e9de3fecc18ba251513ce85dd38ba887..2ba29168c732b677aa044e4986aa8a64168144f8 100644 (file)
@@ -373,7 +373,6 @@ public:
     
     enum class IncludePostLayoutDataHint { No, Yes };
     EditorState editorState(IncludePostLayoutDataHint = IncludePostLayoutDataHint::Yes) const;
-    void sendPostLayoutEditorStateIfNeeded();
     void updateEditorStateAfterLayoutIfEditabilityChanged();
 
     String renderTreeExternalRepresentation() const;
@@ -663,8 +662,11 @@ public:
 
     void didApplyStyle();
     void didChangeSelection();
+    void didChangeContents();
     void discardedComposition();
     void canceledComposition();
+    void didUpdateComposition();
+    void didEndUserTriggeredSelectionChanges();
 
 #if PLATFORM(COCOA)
     void registerUIProcessAccessibilityTokens(const IPC::DataReference& elemenToken, const IPC::DataReference& windowToken);
@@ -985,6 +987,9 @@ public:
 
     static PluginView* pluginViewForFrame(WebCore::Frame*);
 
+    void sendPartialEditorStateAndSchedulePostLayoutUpdate();
+    void flushPendingEditorStateUpdate();
+
 private:
     WebPage(uint64_t pageID, WebPageCreationParameters&&);
 
@@ -998,6 +1003,7 @@ private:
     void platformInitialize();
     void platformDetach();
     void platformEditorState(WebCore::Frame&, EditorState& result, IncludePostLayoutDataHint) const;
+    void sendEditorStateUpdate();
 
     void didReceiveWebPageMessage(IPC::Connection&, IPC::Decoder&);
     void didReceiveSyncWebPageMessage(IPC::Connection&, IPC::Decoder&, std::unique_ptr<IPC::Encoder>&);
@@ -1479,6 +1485,7 @@ private:
 
     RefPtr<WebCore::Node> m_assistedNode;
     bool m_hasPendingBlurNotification { false };
+    bool m_hasPendingEditorStateUpdate { false };
     
 #if PLATFORM(IOS)
     RefPtr<WebCore::Range> m_currentWordRange;
index f641e07dd3883f2dc4d2632265a10ef901f45cca..854268193399be978df5b4e2c0fbac1a0cd9ce48 100644 (file)
@@ -163,7 +163,8 @@ void WebPage::platformEditorState(Frame& frame, EditorState& result, IncludePost
     // entries, we need the layout to be done and we don't want to trigger a synchronous
     // layout as this would be bad for performance. If we have a composition, we send everything
     // right away as the UIProcess needs the caretRects ASAP for marked text.
-    if (shouldIncludePostLayoutData == IncludePostLayoutDataHint::No && !frame.editor().hasComposition()) {
+    bool frameViewHasFinishedLayout = frame.view() && !frame.view()->needsLayout();
+    if (shouldIncludePostLayoutData == IncludePostLayoutDataHint::No && !frameViewHasFinishedLayout && !frame.editor().hasComposition()) {
         result.isMissingPostLayoutData = true;
         return;
     }
@@ -2247,8 +2248,6 @@ void WebPage::applyAutocorrection(const String& correction, const String& origin
 void WebPage::executeEditCommandWithCallback(const String& commandName, CallbackID callbackID)
 {
     executeEditCommand(commandName, String());
-    if (commandName == "toggleBold" || commandName == "toggleItalic" || commandName == "toggleUnderline")
-        send(Messages::WebPageProxy::EditorStateChanged(editorState()));
     send(Messages::WebPageProxy::VoidCallback(callbackID));
 }
 
@@ -3190,6 +3189,26 @@ std::optional<float> WebPage::scaleFromUIProcess(const VisibleContentRectUpdateI
     return scaleFromUIProcess;
 }
 
+static bool selectionIsInsideFixedPositionContainer(Frame& frame)
+{
+    auto& selection = frame.selection().selection();
+    if (selection.isNone())
+        return false;
+
+    bool isInsideFixedPosition = false;
+    if (selection.isCaret()) {
+        frame.selection().absoluteCaretBounds(&isInsideFixedPosition);
+        return isInsideFixedPosition;
+    }
+
+    selection.visibleStart().absoluteCaretBounds(&isInsideFixedPosition);
+    if (isInsideFixedPosition)
+        return true;
+
+    selection.visibleEnd().absoluteCaretBounds(&isInsideFixedPosition);
+    return isInsideFixedPosition;
+}
+
 void WebPage::updateVisibleContentRects(const VisibleContentRectUpdateInfo& visibleContentRectUpdateInfo, MonotonicTime oldestTimestamp)
 {
     LOG_WITH_STREAM(VisibleRects, stream << "\nWebPage::updateVisibleContentRects " << visibleContentRectUpdateInfo);
@@ -3238,7 +3257,8 @@ void WebPage::updateVisibleContentRects(const VisibleContentRectUpdateInfo& visi
         hasSetPageScale = true;
     }
 
-    FrameView& frameView = *m_page->mainFrame().view();
+    auto& frame = m_page->mainFrame();
+    FrameView& frameView = *frame.view();
     if (scrollPosition != frameView.scrollPosition())
         m_dynamicSizeUpdateHistory.clear();
 
@@ -3260,10 +3280,10 @@ void WebPage::updateVisibleContentRects(const VisibleContentRectUpdateInfo& visi
     if (m_isInStableState) {
         if (frameView.frame().settings().visualViewportEnabled()) {
             frameView.setLayoutViewportOverrideRect(LayoutRect(visibleContentRectUpdateInfo.customFixedPositionRect()));
-            const auto& state = editorState();
-            if (!state.isMissingPostLayoutData && state.postLayoutData().insideFixedPosition) {
+            if (selectionIsInsideFixedPositionContainer(frame)) {
+                // Ensure that the next layer tree commit contains up-to-date caret/selection rects.
                 frameView.frame().selection().setCaretRectNeedsUpdate();
-                send(Messages::WebPageProxy::EditorStateChanged(state));
+                sendPartialEditorStateAndSchedulePostLayoutUpdate();
             }
         } else
             frameView.setCustomFixedPositionLayoutRect(enclosingIntRect(visibleContentRectUpdateInfo.customFixedPositionRect()));
index f0e6c55041a51a02a67a1190176fa26e1841afc4..50c41264cf3e5aa68aa6f2c649f9735a602bc374 100644 (file)
@@ -28,6 +28,7 @@
 
 #if !PLATFORM(IOS)
 
+#include "CallbackID.h"
 #include "DrawingArea.h"
 #include "LayerTreeContext.h"
 #include <WebCore/FloatRect.h>
@@ -100,6 +101,7 @@ private:
     void setColorSpace(const ColorSpaceData&) override;
     void addFence(const WebCore::MachSendRight&) override;
 
+    void addTransactionCallbackID(CallbackID) override;
     void setShouldScaleViewToFitDocument(bool) override;
 
     void adjustTransientZoom(double scale, WebCore::FloatPoint origin) override;
@@ -160,6 +162,7 @@ private:
     WebCore::IntSize m_lastDocumentSizeForScaleToFit;
 
     WebCore::LayoutMilestones m_pendingNewlyReachedLayoutMilestones { 0 };
+    Vector<CallbackID> m_pendingCallbackIDs;
 };
 
 } // namespace WebKit
index 702dd7f95c8734ecc02faefc987e6f1e9bfadbdd..d1ab44232f9e002beeab5bcf6fcc68180d34e0e0 100644 (file)
@@ -403,6 +403,12 @@ void TiledCoreAnimationDrawingArea::sendPendingNewlyReachedLayoutMilestones()
     m_pendingNewlyReachedLayoutMilestones = 0;
 }
 
+void TiledCoreAnimationDrawingArea::addTransactionCallbackID(CallbackID callbackID)
+{
+    m_pendingCallbackIDs.append(callbackID);
+    scheduleCompositingLayerFlush();
+}
+
 bool TiledCoreAnimationDrawingArea::flushLayers()
 {
     ASSERT(!m_layerTreeStateIsFrozen);
@@ -411,6 +417,7 @@ bool TiledCoreAnimationDrawingArea::flushLayers()
         scaleViewToFitDocumentIfNeeded();
 
         m_webPage.layoutIfNeeded();
+        m_webPage.flushPendingEditorStateUpdate();
 
         updateIntrinsicContentSizeIfNeeded();
 
@@ -448,6 +455,11 @@ bool TiledCoreAnimationDrawingArea::flushLayers()
         if (m_transientZoomScale != 1)
             applyTransientZoomToLayers(m_transientZoomScale, m_transientZoomOrigin);
 
+        if (!m_pendingCallbackIDs.isEmpty()) {
+            m_webPage.send(Messages::DrawingAreaProxy::DispatchPresentationCallbacksAfterFlushingLayers(m_pendingCallbackIDs));
+            m_pendingCallbackIDs.clear();
+        }
+
         return returnValue;
     }
 }
index 0192ff1c15730ea957cdda7a45b80b2980fc6230..3f8d5aff32eb7d5875f7434d51960e25707da395 100644 (file)
@@ -125,7 +125,7 @@ void WebPage::platformDetach()
 
 void WebPage::platformEditorState(Frame& frame, EditorState& result, IncludePostLayoutDataHint shouldIncludePostLayoutData) const
 {
-    if (shouldIncludePostLayoutData == IncludePostLayoutDataHint::No || !result.isContentEditable) {
+    if (shouldIncludePostLayoutData == IncludePostLayoutDataHint::No || !frame.view() || frame.view()->needsLayout() || !result.isContentEditable) {
         result.isMissingPostLayoutData = true;
         return;
     }
index b16ccee877f81a3aa12d29347756cc988b569925..ce17605610d536ec6a3ee0fb8ae8abbff1700ba6 100644 (file)
@@ -1,3 +1,15 @@
+2017-08-22  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        [WK2] EditorState updates should be rolled into the layer update lifecycle when possible
+        https://bugs.webkit.org/show_bug.cgi?id=175370
+        <rdar://problem/33799806>
+
+        Reviewed by Ryosuke Niwa.
+
+        Adjust WebEditorClient for interface changes.
+
+        * WebCoreSupport/WebEditorClient.h:
+
 2017-08-22  Alex Christensen  <achristensen@webkit.org>
 
         Remove ChromeClient::scrollbarsModeDidChange
index dcc950ebc69277c4d5d6fcfd40da4449a78716a2..328394dd1afd5967f9d92360cec5f359beb01f13 100644 (file)
@@ -110,10 +110,11 @@ private:
 
     void respondToChangedContents() final;
     void respondToChangedSelection(WebCore::Frame*) final;
-    void didChangeSelectionAndUpdateLayout() final { }
+    void didEndUserTriggeredSelectionChanges() final { }
     void updateEditorStateAfterLayoutIfEditabilityChanged() final;
     void discardedComposition(WebCore::Frame*) final;
     void canceledComposition() final;
+    void didUpdateComposition() final { }
 
     void registerUndoStep(WebCore::UndoStep&) final;
     void registerRedoStep(WebCore::UndoStep&) final;
index 97b5c803be3bb2adbc3351a9351589e213217371..3dfa2a8439153159f6a3e3c33cd1aa4bce433e7c 100644 (file)
@@ -1,3 +1,15 @@
+2017-08-22  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        [WK2] EditorState updates should be rolled into the layer update lifecycle when possible
+        https://bugs.webkit.org/show_bug.cgi?id=175370
+        <rdar://problem/33799806>
+
+        Reviewed by Ryosuke Niwa.
+
+        Adjust WebEditorClient for interface changes.
+
+        * WebCoreSupport/WebEditorClient.h:
+
 2017-08-22  Brent Fulgham  <bfulgham@apple.com>
 
         Unreviewed build fix after r221017.
index 673db5f43a7f8fcdcc62381b98582ac98999ceff..d516ceb04d1172db7af786438e5cbf4840e9e8b0 100644 (file)
@@ -57,10 +57,10 @@ private:
 
     void respondToChangedContents() final;
     void respondToChangedSelection(WebCore::Frame*) final;
-    void didChangeSelectionAndUpdateLayout() final { }
     void updateEditorStateAfterLayoutIfEditabilityChanged() final { } 
     void canceledComposition() final;
     void discardedComposition(WebCore::Frame*) final;
+    void didUpdateComposition() final { }
 
     bool shouldDeleteRange(WebCore::Range*) final;
 
index 740a43a69a859d15914d174c577398048c7d8f65..bd9ad0ff6ee1d39b7fa1d79a83fd9e52489a77cc 100644 (file)
@@ -1,3 +1,32 @@
+2017-08-22  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        [WK2] EditorState updates should be rolled into the layer update lifecycle when possible
+        https://bugs.webkit.org/show_bug.cgi?id=175370
+        <rdar://problem/33799806>
+
+        Reviewed by Ryosuke Niwa.
+
+        Tweaks API tests that involve editing to wait for a presentation update before checking against UI process-side
+        information sent via EditorState updates. This allows any EditorState update scheduled by the test to propagate
+        to the UI process.
+
+        * TestWebKitAPI/Tests/WebKit2Cocoa/WKWebViewCandidateTests.mm:
+        (-[CandidateTestWebView typeString:inputMessage:]):
+        (+[CandidateTestWebView setUpWithFrame:testPage:]):
+        * TestWebKitAPI/Tests/WebKit2Cocoa/WKWebViewTextInput.mm:
+        * TestWebKitAPI/Tests/mac/AcceptsFirstMouse.mm:
+        (TestWebKitAPI::AcceptsFirstMouse::runTest):
+        * TestWebKitAPI/Tests/mac/WKWebViewMacEditingTests.mm:
+        * TestWebKitAPI/cocoa/TestWKWebView.h:
+        * TestWebKitAPI/cocoa/TestWKWebView.mm:
+        (-[TestWKWebView waitForNextPresentationUpdate]):
+
+        Add a new helper method to spin until the next presentation update.
+
+        * TestWebKitAPI/mac/WebKitAgnosticTest.h:
+        * TestWebKitAPI/mac/WebKitAgnosticTest.mm:
+        (TestWebKitAPI::WebKitAgnosticTest::waitForNextPresentationUpdate):
+
 2017-08-22  Alex Christensen  <achristensen@webkit.org>
 
         Add UIDelegatePrivate SPI corresponding to WKPageUIClient.showPage
index 57b5145d800d1932c342403d3963b9eccb7e1f9a..b18cc94423efb5d442b69d74b1bdb508c8bc9a9c 100644 (file)
@@ -121,6 +121,7 @@ static NSString *GetDocumentScrollTopJSExpression = @"document.body.scrollTop";
             [self typeCharacter:[string characterAtIndex:i]];
         });
         [self waitForMessage:inputMessage];
+        [self waitForNextPresentationUpdate];
     }
 }
 
@@ -130,6 +131,7 @@ static NSString *GetDocumentScrollTopJSExpression = @"document.body.scrollTop";
 
     [wkWebView loadTestPageNamed:testPageName];
     [wkWebView waitForMessage:@"focused"];
+    [wkWebView waitForNextPresentationUpdate];
     [wkWebView _forceRequestCandidates];
 
     return wkWebView;
@@ -142,13 +144,13 @@ TEST(WKWebViewCandidateTests, SoftSpaceReplacementAfterCandidateInsertionWithout
     CandidateTestWebView *wkWebView = [CandidateTestWebView setUpWithFrame:NSMakeRect(0, 0, 800, 600) testPage:@"input-field-in-scrollable-document"];
 
     [wkWebView insertCandidatesAndWaitForResponse:@"apple " range:NSMakeRange(0, 0)];
-    EXPECT_TRUE([[wkWebView stringByEvaluatingJavaScript:GetInputValueJSExpression] isEqualToString:@"apple "]);
+    EXPECT_WK_STREQ("apple ", [wkWebView stringByEvaluatingJavaScript:GetInputValueJSExpression]);
 
     [wkWebView expectCandidateListVisibilityUpdates:0 whenPerformingActions:^()
     {
         [wkWebView typeString:@" " inputMessage:@"input"];
     }];
-    EXPECT_TRUE([[wkWebView stringByEvaluatingJavaScript:GetInputValueJSExpression] isEqualToString:@"apple "]);
+    EXPECT_WK_STREQ("apple ", [wkWebView stringByEvaluatingJavaScript:GetInputValueJSExpression]);
     EXPECT_EQ([[wkWebView stringByEvaluatingJavaScript:GetDocumentScrollTopJSExpression] doubleValue], 0);
 }
 
@@ -157,10 +159,10 @@ TEST(WKWebViewCandidateTests, InsertCharactersAfterCandidateInsertionWithSoftSpa
     CandidateTestWebView *wkWebView = [CandidateTestWebView setUpWithFrame:NSMakeRect(0, 0, 800, 600) testPage:@"input-field-in-scrollable-document"];
 
     [wkWebView insertCandidatesAndWaitForResponse:@"foo " range:NSMakeRange(0, 0)];
-    EXPECT_TRUE([[wkWebView stringByEvaluatingJavaScript:GetInputValueJSExpression] isEqualToString:@"foo "]);
+    EXPECT_WK_STREQ("foo ", [wkWebView stringByEvaluatingJavaScript:GetInputValueJSExpression]);
 
     [wkWebView typeString:@"a" inputMessage:@"input"];
-    EXPECT_TRUE([[wkWebView stringByEvaluatingJavaScript:GetInputValueJSExpression] isEqualToString:@"foo a"]);
+    EXPECT_WK_STREQ("foo a", [wkWebView stringByEvaluatingJavaScript:GetInputValueJSExpression]);
 }
 
 TEST(WKWebViewCandidateTests, InsertCandidateFromPartiallyTypedPhraseWithSoftSpace)
@@ -169,16 +171,16 @@ TEST(WKWebViewCandidateTests, InsertCandidateFromPartiallyTypedPhraseWithSoftSpa
 
     [wkWebView typeString:@"hel" inputMessage:@"input"];
     [wkWebView insertCandidatesAndWaitForResponse:@"hello " range:NSMakeRange(0, 3)];
-    EXPECT_TRUE([[wkWebView stringByEvaluatingJavaScript:GetInputValueJSExpression] isEqualToString:@"hello "]);
+    EXPECT_WK_STREQ("hello ", [wkWebView stringByEvaluatingJavaScript:GetInputValueJSExpression]);
 
     [wkWebView expectCandidateListVisibilityUpdates:0 whenPerformingActions:^()
     {
         [wkWebView typeString:@" " inputMessage:@"input"];
-        EXPECT_TRUE([[wkWebView stringByEvaluatingJavaScript:GetInputValueJSExpression] isEqualToString:@"hello "]);
+        EXPECT_WK_STREQ("hello ", [wkWebView stringByEvaluatingJavaScript:GetInputValueJSExpression]);
         EXPECT_EQ([[wkWebView stringByEvaluatingJavaScript:GetDocumentScrollTopJSExpression] doubleValue], 0);
 
         [wkWebView typeString:@" " inputMessage:@"input"];
-        EXPECT_TRUE([[wkWebView stringByEvaluatingJavaScript:GetInputValueJSExpression] isEqualToString:@"hello  "]);
+        EXPECT_WK_STREQ("hello  ", [wkWebView stringByEvaluatingJavaScript:GetInputValueJSExpression]);
         EXPECT_EQ([[wkWebView stringByEvaluatingJavaScript:GetDocumentScrollTopJSExpression] doubleValue], 0);
     }];
 }
@@ -196,7 +198,7 @@ TEST(WKWebViewCandidateTests, ClickingInTextFieldDoesNotThrashCandidateVisibilit
         });
         [wkWebView waitForMessage:@"mousedown"];
     }];
-    EXPECT_TRUE([[wkWebView stringByEvaluatingJavaScript:GetInputValueJSExpression] isEqualToString:@"test"]);
+    EXPECT_WK_STREQ("test", [wkWebView stringByEvaluatingJavaScript:GetInputValueJSExpression]);
 }
 
 TEST(WKWebViewCandidateTests, ShouldNotRequestCandidatesInPasswordField)
index 53ab2297efc897d5b217c47e18c072c4f83e578d..288a455f6c76ea3bcab6d884421268cafd480b6d 100644 (file)
 #import "PlatformUtilities.h"
 #import "Test.h"
 #import "TestNavigationDelegate.h"
+#import "TestWKWebView.h"
 #import <WebKit/WebKit.h>
 #import <wtf/RetainPtr.h>
 
 TEST(WKWebView, ShouldHaveInputContextForEditableContent)
 {
-    RetainPtr<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600)]);
-
-    NSURLRequest *request = [NSURLRequest requestWithURL:[[NSBundle mainBundle] URLForResource:@"editable-body" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"]];
-    [webView loadRequest:request];
-    [webView _test_waitForDidFinishNavigation];
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600)]);
+    [webView synchronouslyLoadTestPageNamed:@"editable-body"];
+    [webView waitForNextPresentationUpdate];
 
     EXPECT_NOT_NULL([webView inputContext]);
 }
index 7c34494550f7245b10e073b48718d119a0db4d40..fa61985935780bc6f7d643da49a6aec1d40517c8 100644 (file)
@@ -50,6 +50,7 @@ void AcceptsFirstMouse::runTest(View view)
 {
     RetainPtr<NSWindow> window = adoptNS([[NSWindow alloc] initWithContentRect:view.frame styleMask:NSWindowStyleMaskBorderless backing:NSBackingStoreBuffered defer:YES]);
     [[window.get() contentView] addSubview:view];
+    waitForNextPresentationUpdate(view);
 
     CGFloat viewHeight = view.bounds.size.height;
 
index 00607c3a651f96fef78fffc0a1a012d4576e728b..342564f2617ef1a67cfdae3c3199e06b9b845546 100644 (file)
@@ -124,6 +124,7 @@ TEST(WKWebViewMacEditingTests, DoNotCrashWhenInterpretingKeyEventWhileDeallocati
         SlowInputWebView *webView = [[[SlowInputWebView alloc] initWithFrame:NSMakeRect(0, 0, 400, 400)] autorelease];
         [webView synchronouslyLoadHTMLString:[NSString stringWithFormat:@"<body contenteditable>Hello world</body>"]];
         [webView stringByEvaluatingJavaScript:@"document.body.focus()"];
+        [webView waitForNextPresentationUpdate];
         [webView removeFromSuperview];
         [webView typeCharacter:'a'];
 
index 2497ebd708a89a3d96a4f2e63e6f4603c7e84551..51da3dbc9ea2969394d91da6585567fff0aec12a 100644 (file)
@@ -48,6 +48,7 @@
 - (NSString *)stringByEvaluatingJavaScript:(NSString *)script;
 - (void)waitForMessage:(NSString *)message;
 - (void)performAfterLoading:(dispatch_block_t)actions;
+- (void)waitForNextPresentationUpdate;
 @end
 
 #if PLATFORM(IOS)
index 8d4adc70404d820302400556b674f9dc97ee626b..b756ca185793f7d3fc251339cba2d5c774ecefd5 100644 (file)
@@ -274,6 +274,16 @@ NSEventMask __simulated_forceClickAssociatedEventsMask(id self, SEL _cmd)
     [contentController addScriptMessageHandler:handler name:@"onloadHandler"];
 }
 
+- (void)waitForNextPresentationUpdate
+{
+    __block bool done = false;
+    [self _doAfterNextPresentationUpdate:^() {
+        done = true;
+    }];
+
+    TestWebKitAPI::Util::run(&done);
+}
+
 @end
 
 #if PLATFORM(IOS)
index c25c4b4cffa34f016bd7eb58ecca498b11de69dc..f6c963a7d303f4868dbbfcd9a18481fe831e3f9c 100644 (file)
@@ -46,6 +46,9 @@ public:
     void goBack(WebView *);
     void goBack(WKView *);
 
+    void waitForNextPresentationUpdate(WebView *);
+    void waitForNextPresentationUpdate(WKView *);
+
     void waitForLoadToFinish();
 
     NSRect viewFrame;
index 127fe51d38211db22cb68fbf63b440c655c930c3..2dcd32db218d26d89580aeb9ba543c4776e53e29 100644 (file)
@@ -139,4 +139,23 @@ void WebKitAgnosticTest::waitForLoadToFinish()
     didFinishLoad = false;
 }
 
+void WebKitAgnosticTest::waitForNextPresentationUpdate(WebView *)
+{
+    // FIXME: This isn't currently required anywhere. Just dispatch to the next runloop for now.
+    __block bool done = false;
+    dispatch_async(dispatch_get_main_queue(), ^() {
+        done = true;
+    });
+    Util::run(&done);
+}
+
+void WebKitAgnosticTest::waitForNextPresentationUpdate(WKView *view)
+{
+    __block bool done = false;
+    [view _doAfterNextPresentationUpdate:^() {
+        done = true;
+    }];
+    Util::run(&done);
+}
+
 } // namespace TestWebKitAPI