Keep scroll by keyboard after child focused node is removed

So far, scroll stops if focused child node is removed
while scrolling parent node.
This is because the event handler passes the |MousePressNode|.
If focused child node(|MousePressNode|) is removed,
it will be null and scrollable area changed to document.

|LogicalScroll| finds scrollable area from |MousePressNode|.
If we save this scrollable area node,
we can get one more chance to find proper node to scroll.


Bug: 493078
Change-Id: I6e81ca0a0d15e0def66c8457840ca9e1343e8dba
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1611620
Commit-Queue: Lan Wei <lanwei@chromium.org>
Reviewed-by: Lan Wei <lanwei@chromium.org>
Reviewed-by: David Bokan <bokan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#663040}
diff --git a/third_party/blink/renderer/core/input/scroll_manager.cc b/third_party/blink/renderer/core/input/scroll_manager.cc
index ad72acd8..ca9b494 100644
--- a/third_party/blink/renderer/core/input/scroll_manager.cc
+++ b/third_party/blink/renderer/core/input/scroll_manager.cc
@@ -94,6 +94,7 @@
   visitor->Trace(previous_gesture_scrolled_node_);
   visitor->Trace(scrollbar_handling_scroll_gesture_);
   visitor->Trace(resize_scrollable_area_);
+  visitor->Trace(last_logical_scrolled_node_);
 }
 
 void ScrollManager::ClearGestureScrollState() {
@@ -236,6 +237,11 @@
   if (!node)
     node = mouse_press_node;
 
+  if ((!node || !node->GetLayoutObject()) &&
+      (last_logical_scrolled_node_ &&
+       last_logical_scrolled_node_->GetLayoutObject()))
+    node = last_logical_scrolled_node_;
+
   if ((!node || !node->GetLayoutObject()) && frame_->View() &&
       frame_->View()->GetLayoutView())
     node = frame_->View()->GetLayoutView()->GetNode();
@@ -319,8 +325,10 @@
     ScrollResult result = scrollable_area->UserScroll(
         granularity, ToScrollDelta(physical_direction, 1));
 
-    if (result.DidScroll())
+    if (result.DidScroll()) {
+      last_logical_scrolled_node_ = scroll_chain_node;
       return true;
+    }
   }
 
   return false;
diff --git a/third_party/blink/renderer/core/input/scroll_manager.h b/third_party/blink/renderer/core/input/scroll_manager.h
index 8c83aeb..a7205235 100644
--- a/third_party/blink/renderer/core/input/scroll_manager.h
+++ b/third_party/blink/renderer/core/input/scroll_manager.h
@@ -153,6 +153,8 @@
 
   Member<Node> scroll_gesture_handling_node_;
 
+  Member<Node> last_logical_scrolled_node_;
+
   bool last_gesture_scroll_over_embedded_content_view_;
 
   // The most recent Node to scroll natively during this scroll
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 4969f2c..0fbbf33 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -2748,6 +2748,7 @@
 crbug.com/893480 external/wpt/infrastructure/testdriver/actions/elementTiming.html [ Timeout ]
 crbug.com/893480 external/wpt/infrastructure/testdriver/actions/multiDevice.html [ Failure Timeout ]
 crbug.com/893480 external/wpt/infrastructure/testdriver/actions/actionsWithKeyPressed.html [ Failure Timeout ]
+crbug.com/493078 external/wpt/dom/nodes/keyboard-scroll-removed-node.html [ Failure Timeout ]
 
 # Hit a DCHECK
 crbug.com/918664 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/sizing/block-size-with-min-or-max-content-table-1a.html [ Failure Pass ]
diff --git a/third_party/blink/web_tests/external/wpt/dom/metadata/dom/nodes/keyboard-scroll-removed-node.html.ini b/third_party/blink/web_tests/external/wpt/dom/metadata/dom/nodes/keyboard-scroll-removed-node.html.ini
new file mode 100644
index 0000000..21c9b48
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/dom/metadata/dom/nodes/keyboard-scroll-removed-node.html.ini
@@ -0,0 +1,3 @@
+[scrollchain.html]
+  expected:
+    if product == "safari": ERROR
diff --git a/third_party/blink/web_tests/external/wpt/dom/nodes/keyboard-scroll-removed-node.html b/third_party/blink/web_tests/external/wpt/dom/nodes/keyboard-scroll-removed-node.html
new file mode 100644
index 0000000..8defac4
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/dom/nodes/keyboard-scroll-removed-node.html
@@ -0,0 +1,69 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Keyboard scroll removed node</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<style>
+  ul {
+    width: 30vw;
+    height: 40vh;
+    overflow-y: scroll;
+  }
+  li {
+    width: 95%;
+    height: 10vh;
+    border: 1px solid black;
+  }
+  #target {
+    background-color: grey;
+  }
+</style>
+</head>
+
+<body>
+  <ul id="list">
+    <li>ITEM 1</li>
+    <li>ITEM 2</li>
+    <li id="target">TARGET ITEM 3</li>
+    <li>ITEM 4</li>
+    <li>ITEM 5</li>
+    <li>ITEM 6</li>
+    <li>ITEM 7</li>
+  </ul>
+</body>
+
+<script>
+  async_test(t => {
+    let listElement = document.getElementById("list");
+    let targetElement = document.getElementById("target");
+    let firstScrollTop, secondScrollTop;
+    let ArrowDownKey = "\uE015";
+    let firstActions = new test_driver.Actions()
+      .pointerMove(10, 10, { origin: targetElement })
+      .pointerDown()
+      .pointerUp()
+      .keyDown(ArrowDownKey)
+      .keyUp(ArrowDownKey)
+      .send()
+      .then(() => {
+        firstScrollTop = listElement.scrollTop;
+        targetElement.remove();
+        let secondAction = new test_driver.Actions()
+          .keyDown(ArrowDownKey)
+          .keyUp(ArrowDownKey)
+          .send()
+          .then(() => {
+            secondScrollTop = listElement.scrollTop;
+            assert_greater_than(secondScrollTop, firstScrollTop);
+            t.done();
+          })
+          .catch(t.unreached_func("Actions sequence failed"));
+      })
+      .catch(t.unreached_func("Actions sequence failed"));
+  }, "Keyboard scrolls, after clicked element is removed, continue to affect previous scroller");
+</script>
+
+</html>
\ No newline at end of file