[go: up one dir, main page]

Support form submission from spatial navigation.

When we get to the last non-submit-button element of a form the IME
action should change from Next to Submit.

(cherry picked from commit 18b921a22c85e88c3b7314b3ab8885281f48e5ca)

Bug: 968557
Change-Id: I5e61f184d30e6088fa80f3cdf95bbed1c344f46c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1648464
Reviewed-by: David Bokan <bokan@chromium.org>
Reviewed-by: Ken Buchanan <kenrb@chromium.org>
Commit-Queue: Michael Thiessen <mthiesse@chromium.org>
Cr-Original-Commit-Position: refs/heads/master@{#667156}
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1655347
Reviewed-by: Michael Thiessen <mthiesse@chromium.org>
Cr-Commit-Position: refs/branch-heads/3809@{#254}
Cr-Branched-From: d82dec1a818f378c464ba307ddd9c92133eac355-refs/heads/master@{#665002}
diff --git a/third_party/blink/public/mojom/page/spatial_navigation.mojom b/third_party/blink/public/mojom/page/spatial_navigation.mojom
index 51538fc..8358e7e 100644
--- a/third_party/blink/public/mojom/page/spatial_navigation.mojom
+++ b/third_party/blink/public/mojom/page/spatial_navigation.mojom
@@ -11,6 +11,8 @@
   bool can_exit_focus;
   // True if Spatial Navigation has a target that can be selected.
   bool can_select_element;
+  // True if the currently focused element is a form element.
+  bool is_form_focused;
   // True if the currently focused element is a form element, and there is a
   // next form element available to move to.
   bool has_next_form_element;
diff --git a/third_party/blink/renderer/core/input/keyboard_event_manager.cc b/third_party/blink/renderer/core/input/keyboard_event_manager.cc
index 9c4c4fb..06c313c3 100644
--- a/third_party/blink/renderer/core/input/keyboard_event_manager.cc
+++ b/third_party/blink/renderer/core/input/keyboard_event_manager.cc
@@ -340,6 +340,9 @@
       DefaultEscapeEventHandler(event);
     } else if (event->key() == "Enter") {
       DefaultEnterEventHandler(event);
+    } else if (static_cast<int>(event->KeyEvent()->dom_key) == 0x00200310) {
+      // TODO(bokan): Cleanup magic numbers once https://crbug.com/949766 lands.
+      DefaultImeSubmitHandler(event);
     } else {
       // TODO(bokan): Seems odd to call the default _arrow_ event handler on
       // events that aren't necessarily arrow keys.
@@ -511,6 +514,17 @@
   }
 }
 
+void KeyboardEventManager::DefaultImeSubmitHandler(KeyboardEvent* event) {
+  Page* page = frame_->GetPage();
+  if (!page)
+    return;
+
+  if (IsSpatialNavigationEnabled(frame_) &&
+      !frame_->GetDocument()->InDesignMode()) {
+    page->GetSpatialNavigationController().HandleImeSubmitKeyboardEvent(event);
+  }
+}
+
 static OverrideCapsLockState g_override_caps_lock_state;
 
 void KeyboardEventManager::SetCurrentCapsLockState(
diff --git a/third_party/blink/renderer/core/input/keyboard_event_manager.h b/third_party/blink/renderer/core/input/keyboard_event_manager.h
index 3d9afb6..3f1b4c4 100644
--- a/third_party/blink/renderer/core/input/keyboard_event_manager.h
+++ b/third_party/blink/renderer/core/input/keyboard_event_manager.h
@@ -56,6 +56,7 @@
   void DefaultTabEventHandler(KeyboardEvent*);
   void DefaultEscapeEventHandler(KeyboardEvent*);
   void DefaultEnterEventHandler(KeyboardEvent*);
+  void DefaultImeSubmitHandler(KeyboardEvent*);
   void DefaultArrowEventHandler(KeyboardEvent*, Node*);
   bool DefaultSpatNavBackEventHandler(KeyboardEvent*);
 
diff --git a/third_party/blink/renderer/core/page/focus_controller.cc b/third_party/blink/renderer/core/page/focus_controller.cc
index 856562e..6a10241 100644
--- a/third_party/blink/renderer/core/page/focus_controller.cc
+++ b/third_party/blink/renderer/core/page/focus_controller.cc
@@ -1160,9 +1160,12 @@
     if (form_element->formOwner() != form_owner ||
         form_element->IsDisabledOrReadOnly())
       continue;
-    // Focusless spatial navigation supports all form types.
+    // Focusless spatial navigation supports all form types. However, submit
+    // buttons are explicitly excluded as moving to them isn't necessary - the
+    // IME should just submit instead.
     if (RuntimeEnabledFeatures::FocuslessSpatialNavigationEnabled() &&
-        page_->GetSettings().GetSpatialNavigationEnabled()) {
+        page_->GetSettings().GetSpatialNavigationEnabled() &&
+        !form_element->CanBeSuccessfulSubmitButton()) {
       return next_element;
     }
     LayoutObject* layout = next_element->GetLayoutObject();
diff --git a/third_party/blink/renderer/core/page/spatial_navigation_controller.cc b/third_party/blink/renderer/core/page/spatial_navigation_controller.cc
index 02d9677a..7a2921c 100644
--- a/third_party/blink/renderer/core/page/spatial_navigation_controller.cc
+++ b/third_party/blink/renderer/core/page/spatial_navigation_controller.cc
@@ -17,6 +17,8 @@
 #include "third_party/blink/renderer/core/frame/local_frame.h"
 #include "third_party/blink/renderer/core/frame/settings.h"
 #include "third_party/blink/renderer/core/frame/visual_viewport.h"
+#include "third_party/blink/renderer/core/html/forms/html_form_control_element.h"
+#include "third_party/blink/renderer/core/html/forms/html_form_element.h"
 #include "third_party/blink/renderer/core/html/html_frame_owner_element.h"
 #include "third_party/blink/renderer/core/html/media/html_video_element.h"
 #include "third_party/blink/renderer/core/input/event_handler.h"
@@ -177,6 +179,19 @@
   return true;
 }
 
+bool SpatialNavigationController::HandleImeSubmitKeyboardEvent(
+    KeyboardEvent* event) {
+  DCHECK(page_->GetSettings().GetSpatialNavigationEnabled());
+
+  if (!IsHTMLFormControlElement(GetFocusedElement()))
+    return false;
+
+  HTMLFormControlElement* element =
+      ToHTMLFormControlElement(GetFocusedElement());
+  element->formOwner()->SubmitImplicitly(*event, true);
+  return true;
+}
+
 bool SpatialNavigationController::HandleEscapeKeyboardEvent(
     KeyboardEvent* event) {
   DCHECK(page_->GetSettings().GetSpatialNavigationEnabled());
@@ -519,6 +534,7 @@
   bool change = false;
   change |= UpdateCanExitFocus(element);
   change |= UpdateCanSelectInterestedElement(element);
+  change |= UpdateIsFormFocused(element);
   change |= UpdateHasNextFormElement(element);
   change |= UpdateHasDefaultVideoControls(element);
   if (change)
@@ -563,6 +579,15 @@
   return true;
 }
 
+bool SpatialNavigationController::UpdateIsFormFocused(Element* element) {
+  bool is_form_focused = IsFocused(element) && element->IsFormControlElement();
+
+  if (is_form_focused == spatial_navigation_state_->is_form_focused)
+    return false;
+  spatial_navigation_state_->is_form_focused = is_form_focused;
+  return true;
+}
+
 bool SpatialNavigationController::UpdateHasDefaultVideoControls(
     Element* element) {
   bool has_default_video_controls =
diff --git a/third_party/blink/renderer/core/page/spatial_navigation_controller.h b/third_party/blink/renderer/core/page/spatial_navigation_controller.h
index 12d6555..faee8c69 100644
--- a/third_party/blink/renderer/core/page/spatial_navigation_controller.h
+++ b/third_party/blink/renderer/core/page/spatial_navigation_controller.h
@@ -30,6 +30,7 @@
   bool HandleArrowKeyboardEvent(KeyboardEvent* event);
   bool HandleEnterKeyboardEvent(KeyboardEvent* event);
   bool HandleEscapeKeyboardEvent(KeyboardEvent* event);
+  bool HandleImeSubmitKeyboardEvent(KeyboardEvent* event);
 
   // Returns the element that's currently interested. i.e. the Element that's
   // currently indicated to the user.
@@ -95,6 +96,7 @@
   bool UpdateCanExitFocus(Element* element);
   bool UpdateCanSelectInterestedElement(Element* element);
   bool UpdateHasNextFormElement(Element* element);
+  bool UpdateIsFormFocused(Element* element);
   bool UpdateHasDefaultVideoControls(Element* element);
 
   const mojom::blink::SpatialNavigationHostPtr& GetSpatialNavigationHost();
diff --git a/third_party/blink/web_tests/fast/spatial-navigation/resources/mock-snav-service.js b/third_party/blink/web_tests/fast/spatial-navigation/resources/mock-snav-service.js
index d475a8e7..4808163 100644
--- a/third_party/blink/web_tests/fast/spatial-navigation/resources/mock-snav-service.js
+++ b/third_party/blink/web_tests/fast/spatial-navigation/resources/mock-snav-service.js
@@ -4,6 +4,7 @@
   constructor() {
     this.canExitFocus = false;
     this.canSelectElement = false;
+    this.isFormFocused = false;
     this.hasNextFormElement = false;
     this.hasDefaultVideoControls = false;
     this.callback = null;
@@ -18,6 +19,7 @@
   spatialNavigationStateChanged(state) {
     this.canExitFocus = state.canExitFocus;
     this.canSelectElement = state.canSelectElement;
+    this.isFormFocused = state.isFormFocused;
     this.hasNextFormElement = state.hasNextFormElement;
     this.hasDefaultVideoControls = state.hasDefaultVideoControls;
     if (this.callback) {
diff --git a/third_party/blink/web_tests/virtual/focusless-spat-nav/fast/spatial-navigation/focusless/snav-focusless-form-does-not-submit-a11y.html b/third_party/blink/web_tests/virtual/focusless-spat-nav/fast/spatial-navigation/focusless/snav-focusless-form-does-not-submit-a11y.html
index 213abef..c5c66e3c 100644
--- a/third_party/blink/web_tests/virtual/focusless-spat-nav/fast/spatial-navigation/focusless/snav-focusless-form-does-not-submit-a11y.html
+++ b/third_party/blink/web_tests/virtual/focusless-spat-nav/fast/spatial-navigation/focusless/snav-focusless-form-does-not-submit-a11y.html
@@ -25,8 +25,8 @@
                 "Should not be able to exit focus.");
     assert_true(mockSnavService.canSelectElement,
                 "Should be able to select element.");
-    assert_true(mockSnavService.hasNextFormElement,
-                "Should not be able to move to next form element.");
+    assert_false(mockSnavService.hasNextFormElement,
+                 "Should not be able to move to next form element.");
 
     assert_equals(document.activeElement,
                   first,
@@ -38,7 +38,7 @@
                 "Should be able to exit focus.");
     assert_true(mockSnavService.canSelectElement,
                 "Should be able to select element.");
-    assert_true(mockSnavService.hasNextFormElement,
-                 "Should be able to move to next form element.");
+    assert_false(mockSnavService.hasNextFormElement,
+                "Should not be able to move to next form element.");
   }, 'Form does not submit when passing spat nav focus.');
 </script>
diff --git a/third_party/blink/web_tests/virtual/focusless-spat-nav/fast/spatial-navigation/focusless/snav-focusless-form-does-not-submit.html b/third_party/blink/web_tests/virtual/focusless-spat-nav/fast/spatial-navigation/focusless/snav-focusless-form-does-not-submit.html
index 36887c0..90865a5 100644
--- a/third_party/blink/web_tests/virtual/focusless-spat-nav/fast/spatial-navigation/focusless/snav-focusless-form-does-not-submit.html
+++ b/third_party/blink/web_tests/virtual/focusless-spat-nav/fast/spatial-navigation/focusless/snav-focusless-form-does-not-submit.html
@@ -30,7 +30,7 @@
     assert_true(mockSnavService.canSelectElement,
                 "Should be able to select element.");
     assert_false(mockSnavService.hasNextFormElement,
-                "Should not be able to move to next form element.");
+                 "Should not be able to move to next form element.");
 
     eventSender.keyDown('Enter');
     assert_equals(document.activeElement,
@@ -43,7 +43,7 @@
                 "Should be able to exit focus.");
     assert_true(mockSnavService.canSelectElement,
                 "Should be able to select element.");
-    assert_true(mockSnavService.hasNextFormElement,
-                 "Should be able to move to next form element.");
+    assert_false(mockSnavService.hasNextFormElement,
+                 "Should not be able to move to next form element.");
   }, 'Form does not submit when passing spat nav focus.');
 </script>
diff --git a/third_party/blink/web_tests/virtual/focusless-spat-nav/fast/spatial-navigation/focusless/snav-focusless-form-state.html b/third_party/blink/web_tests/virtual/focusless-spat-nav/fast/spatial-navigation/focusless/snav-focusless-form-state.html
index 6501c24..3f7fc8da 100644
--- a/third_party/blink/web_tests/virtual/focusless-spat-nav/fast/spatial-navigation/focusless/snav-focusless-form-state.html
+++ b/third_party/blink/web_tests/virtual/focusless-spat-nav/fast/spatial-navigation/focusless/snav-focusless-form-state.html
@@ -10,6 +10,7 @@
 <input type="text" id="first" autofocus></input>
 <input type="text" id="second"></input>
 <input type="checkbox" id="third"></input>
+<input type="submit" id="submit">Submit</input>
 </form>
 
 <script>
@@ -38,6 +39,8 @@
                 "Should be able to exit focus.");
     assert_true(mockSnavService.canSelectElement,
                 "Should be able to select element.");
+    assert_true(mockSnavService.isFormFocused,
+                "Form should be focused.");
     assert_true(mockSnavService.hasNextFormElement,
                 "Should be able to move to next form element.");
 
@@ -59,6 +62,8 @@
                 "Should be able to exit focus.");
     assert_true(mockSnavService.canSelectElement,
                 "Should be able to select element.");
+    assert_true(mockSnavService.isFormFocused,
+                "Form should be focused.");
     assert_false(mockSnavService.hasNextFormElement,
                  "Should be able to move to next form element.");
 
@@ -69,6 +74,8 @@
                  "Should be able to exit focus.");
     assert_true(mockSnavService.canSelectElement,
                 "Should be able to select element.");
+    assert_false(mockSnavService.isFormFocused,
+                 "Form should not be focused.");
     assert_false(mockSnavService.hasNextFormElement,
                  "Should be able to move to next form element.");