commit 5d7a4cf9ed9c80f1ba4a3b846fd9661fd7d1cd14
parent 427097407647f4af6fb5526a802e6cd6273d665d
Author: Vincent Hilla <vhilla@mozilla.com>
Date: Mon, 27 Oct 2025 07:31:29 +0000
Bug 1767228 - Add showPicker Support for Input with Data List. r=emilio,credential-management-reviewers,dimi
Differential Revision: https://phabricator.services.mozilla.com/D269180
Diffstat:
8 files changed, 133 insertions(+), 21 deletions(-)
diff --git a/dom/html/HTMLInputElement.cpp b/dom/html/HTMLInputElement.cpp
@@ -65,6 +65,7 @@
#include "nsIEditor.h"
#include "nsIFilePicker.h"
#include "nsIFormControl.h"
+#include "nsIFormFillController.h"
#include "nsIFrame.h"
#include "nsIMutationObserver.h"
#include "nsIPromptCollection.h"
@@ -5899,7 +5900,7 @@ void HTMLInputElement::ShowPicker(ErrorResult& aRv) {
// InitFilePicker() and InitColorPicker() consume it themselves,
// so only consume in this function if not those.
- // Step 4. If element's type attribute is in the File Upload state, then run
+ // Step 5. If element's type attribute is in the File Upload state, then run
// these steps in parallel:
if (mType == FormControlType::InputFile) {
FilePickerType type = FILE_PICKER_FILE;
@@ -5911,9 +5912,11 @@ void HTMLInputElement::ShowPicker(ErrorResult& aRv) {
return;
}
- // Step 5. Otherwise, the user agent should show any relevant user interface
+ // Step 6. Otherwise, the user agent should show any relevant user interface
// for selecting a value for element, in the way it normally would when the
// user interacts with the control
+
+ // Step 6 for color
if (mType == FormControlType::InputColor) {
InitColorPicker();
return;
@@ -5926,6 +5929,7 @@ void HTMLInputElement::ShowPicker(ErrorResult& aRv) {
return;
}
+ // Step 6 for date and time types
if (IsDateTimeTypeSupported(mType)) {
if (CreatesDateTimeWidget()) {
if (RefPtr<Element> dateTimeBoxElement = GetDateTimeBoxElement()) {
@@ -5940,6 +5944,18 @@ void HTMLInputElement::ShowPicker(ErrorResult& aRv) {
GetDateTimeInputBoxValue(value);
OpenDateTimePicker(value);
}
+ return;
+ }
+
+ // Step 6 for input elements with a suggestions source element.
+ // I.e. show the autocomplete dropdown based on the list attribute.
+ // XXX Form-fill support on android is bug 1535985.
+ if (IsSingleLineTextControl(true) && GetList()) {
+ if (nsCOMPtr<nsIFormFillController> controller =
+ do_GetService("@mozilla.org/satchel/form-fill-controller;1")) {
+ controller->SetControlledElement(this);
+ controller->ShowPopup();
+ }
}
}
diff --git a/dom/html/HTMLInputElement.h b/dom/html/HTMLInputElement.h
@@ -720,7 +720,7 @@ class HTMLInputElement final : public TextControlElement,
SelectionMode aSelectMode,
ErrorResult& aRv);
- void ShowPicker(ErrorResult& aRv);
+ MOZ_CAN_RUN_SCRIPT void ShowPicker(ErrorResult& aRv);
bool WebkitDirectoryAttr() const {
return HasAttr(nsGkAtoms::webkitdirectory);
diff --git a/dom/html/test/forms/mochitest.toml b/dom/html/test/forms/mochitest.toml
@@ -135,6 +135,9 @@ skip-if = ["os == 'mac' && os_version == '10.15' && processor == 'x86_64' && deb
["test_input_setting_value.html"]
+["test_input_text_show_picker.html"]
+skip-if = ["os == 'android'"] # Bug 1535985
+
["test_input_textarea_set_value_no_scroll.html"]
["test_input_time_key_events.html"]
diff --git a/dom/html/test/forms/test_input_text_show_picker.html b/dom/html/test/forms/test_input_text_show_picker.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>input.showPicker() with datalist shows the autocomplete popup</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ const controller = SpecialPowers.Cc["@mozilla.org/autocomplete/controller;1"].getService(
+ SpecialPowers.Ci.nsIAutoCompleteController
+ );
+
+ async function testWithInputType(aType) {
+ const inp = document.querySelector("input");
+ inp.type = aType;
+
+ ok(!controller.input?.popupOpen, `Initially no input popup`);
+
+ SpecialPowers.wrap(document).notifyUserGestureActivation();
+ inp.showPicker();
+
+ await SimpleTest.promiseWaitForCondition(
+ () => controller.input?.popupOpen,
+ `input[type=${aType}] popup was not opened`
+ );
+ ok(controller.input?.popupOpen, `input[type=${aType}] popup open`);
+
+ inp.type = "date";
+ await SimpleTest.promiseWaitForCondition(
+ () => !controller.input?.popupOpen,
+ `input[type=${aType}] popup was closed`
+ );
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ window.onload = async function() {
+ for (const type of ["text", "search", "email", "url", "tel", "number"]) {
+ await testWithInputType(type);
+ }
+ SimpleTest.finish();
+ };
+ </script>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+
+<input list="id-list" />
+<datalist id="id-list">
+ <option value="Chocolate"></option>
+ <option value="Coconut"></option>
+</datalist>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/show-picker-does-not-focus.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/show-picker-does-not-focus.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<title>Test showPicker() does not focus the element</title>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<body>
+<script type=module>
+import inputTypes from "./input-types.js";
+
+function createElement(t, type) {
+ const input = document.createElement("input");
+ input.type = type;
+ document.body.append(input);
+ t.add_cleanup(() => input.remove());
+ return input;
+}
+
+for (const inputType of inputTypes) {
+ promise_test(async (t) => {
+ const input = createElement(t, inputType);
+ await test_driver.bless('show picker');
+ input.showPicker();
+ assert_not_equals(input, document.activeElement, "Input element was not focused");
+ }, `input[type=${inputType}].showPicker() does not focus the element`);
+}
+</script>
diff --git a/toolkit/components/satchel/nsFormFillController.cpp b/toolkit/components/satchel/nsFormFillController.cpp
@@ -271,6 +271,27 @@ nsFormFillController::MarkAsAutoCompletableField(Element* aElement) {
}
NS_IMETHODIMP
+nsFormFillController::SetControlledElement(Element* aElement) {
+ if (!aElement ||
+ !aElement->IsAnyOfHTMLElements(nsGkAtoms::input, nsGkAtoms::textarea)) {
+ return NS_OK;
+ }
+
+ MaybeStartControllingInput(aElement);
+
+ // Bail if we didn't start controlling the input.
+ if (!mControlledElement) {
+ return NS_OK;
+ }
+
+ // if there is a delayed task to restart the controller after an attribute
+ // change, cancel it to prevent it overriding the controlled input
+ MaybeCancelAttributeChangeTask();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
nsFormFillController::GetControlledElement(Element** aElement) {
*aElement = mControlledElement;
NS_IF_ADDREF(*aElement);
@@ -864,21 +885,7 @@ void nsFormFillController::MaybeStartControllingInput(Element* aElement) {
}
nsresult nsFormFillController::HandleFocus(Element* aElement) {
- if (!aElement ||
- !aElement->IsAnyOfHTMLElements(nsGkAtoms::input, nsGkAtoms::textarea)) {
- return NS_OK;
- }
-
- MaybeStartControllingInput(aElement);
-
- // Bail if we didn't start controlling the input.
- if (!mControlledElement) {
- return NS_OK;
- }
-
- // if there is a delayed task to restart the controller after an attribute
- // change, cancel it to prevent it overriding the focused input
- MaybeCancelAttributeChangeTask();
+ MOZ_TRY(SetControlledElement(aElement));
// If this focus doesn't follow a right click within our specified
// threshold then show the autocomplete popup for all password fields.
@@ -1135,7 +1142,8 @@ void nsFormFillController::StartControllingInput(Element* aElement) {
}
bool nsFormFillController::IsFocusedInputControlled() const {
- return mControlledElement && mController && !ReadOnly(mControlledElement);
+ return mControlledElement && mController && !ReadOnly(mControlledElement) &&
+ nsFocusManager::GetFocusedElementStatic() == mControlledElement;
}
void nsFormFillController::StopControllingInput() {
diff --git a/toolkit/components/satchel/nsFormFillController.h b/toolkit/components/satchel/nsFormFillController.h
@@ -72,7 +72,7 @@ class nsFormFillController final : public nsIFormFillController,
bool IsFocusedInputControlled() const;
MOZ_CAN_RUN_SCRIPT
- nsresult HandleFocus(mozilla::dom::Element* aInput);
+ nsresult HandleFocus(mozilla::dom::Element* aElement);
void AttachListeners(mozilla::dom::EventTarget* aEventTarget);
diff --git a/toolkit/components/satchel/nsIFormFillController.idl b/toolkit/components/satchel/nsIFormFillController.idl
@@ -26,7 +26,7 @@ interface nsIFormFillController : nsISupports
/*
* The input or textarea element the form fill controller is currently bound to.
*/
- readonly attribute Element controlledElement;
+ [setter_can_run_script] attribute Element controlledElement;
/*
* Whether the autocomplete popup on a password field was automatically opened