Bug 1745005 - Add showPicker() to <input> elements. r=emilio

Differential Revision: https://phabricator.services.mozilla.com/D142754
This commit is contained in:
Tom Schuster 2022-04-22 18:43:48 +00:00
parent 0303e6facd
commit eed1aead2d
14 changed files with 173 additions and 348 deletions

View File

@ -24,6 +24,7 @@
#include "mozilla/dom/UserActivation.h"
#include "mozilla/dom/MutationEventBinding.h"
#include "mozilla/dom/WheelEventBinding.h"
#include "mozilla/dom/WindowGlobalChild.h"
#include "mozilla/PresShell.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/TextUtils.h"
@ -5522,6 +5523,74 @@ void HTMLInputElement::SetSelectionDirection(const nsAString& aDirection,
state->SetSelectionDirection(aDirection, aRv);
}
// https://html.spec.whatwg.org/multipage/input.html#dom-input-showpicker
void HTMLInputElement::ShowPicker(ErrorResult& aRv) {
// Step 1. If this is not mutable, then throw an "InvalidStateError"
// DOMException.
if (!IsMutable()) {
return aRv.ThrowInvalidStateError(
"This input is either disabled or readonly.");
}
// Step 2. If this's relevant settings object's origin is not same origin with
// this's relevant settings object's top-level origin, and this's type
// attribute is not in the File Upload state or Color state, then throw a
// "SecurityError" DOMException.
if (mType != FormControlType::InputFile &&
mType != FormControlType::InputColor) {
nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow();
WindowGlobalChild* windowGlobalChild =
window ? window->GetWindowGlobalChild() : nullptr;
if (!windowGlobalChild || !windowGlobalChild->SameOriginWithTop()) {
return aRv.ThrowSecurityError(
"Call was blocked because the current origin isn't same-origin with "
"top.");
}
}
// Step 3. If this's relevant global object does not have transient
// activation, then throw a "NotAllowedError" DOMException.
if (!OwnerDoc()->HasValidTransientUserGestureActivation()) {
return aRv.ThrowNotAllowedError(
"Call was blocked due to lack of user activation.");
}
// Step 4. Show the picker, if applicable, for this.
//
// https://html.spec.whatwg.org/multipage/input.html#show-the-picker,-if-applicable
// To show the picker, if applicable for an input element element:
// Step 1. Assert: element's relevant global object has transient activation.
// Step 2. If element is not mutable, then return.
// (See above.)
// Step 3. 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;
if (StaticPrefs::dom_webkitBlink_dirPicker_enabled() &&
HasAttr(nsGkAtoms::webkitdirectory)) {
type = FILE_PICKER_DIRECTORY;
}
InitFilePicker(type);
return;
}
// Step 4. 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
if (mType == FormControlType::InputColor) {
InitColorPicker();
return;
}
if (IsDateTimeInputType(mType) && IsInComposedDoc()) {
DateTimeValue value;
GetDateTimeInputBoxValue(value);
OpenDateTimePicker(value);
}
}
#ifdef ACCESSIBILITY
/*static*/ nsresult FireEventForAccessibility(HTMLInputElement* aTarget,
EventMessage aEventMessage) {

View File

@ -699,6 +699,8 @@ class HTMLInputElement final : public TextControlElement,
SelectionMode aSelectMode,
ErrorResult& aRv);
void ShowPicker(ErrorResult& aRv);
bool WebkitDirectoryAttr() const {
return HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory);
}

View File

@ -27,6 +27,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=885996
{ id: 'normal', type: 'prevent-default-1', result: false },
{ id: 'normal', type: 'prevent-default-2', result: false },
{ id: 'normal', type: 'click-method', result: true },
{ id: 'normal', type: 'show-picker', result: true },
{ id: 'normal', type: 'right-click', result: false },
{ id: 'normal', type: 'middle-click', result: false },
{ id: 'label-1', result: true },
@ -87,6 +88,10 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=885996
case 'click-method':
element.click();
break;
case 'show-picker':
SpecialPowers.wrap(document).notifyUserGestureActivation();
element.showPicker();
break;
case 'right-click':
synthesizeMouseAtCenter(element, { button: 2 });
break;

View File

@ -62,6 +62,7 @@
<div id='div-click-on-demand' onclick="var i=document.createElement('input'); i.type='file'; i.click();" tabindex='1'>foo</div>
<div id='div-keydown' onkeydown="document.getElementById('by-button').click();" tabindex='1'>foo</div>
<a id='link-click' href="javascript:document.getElementById('by-button').click();" tabindex='1'>foo</a>
<input id='show-picker' type='file'>
</div>
<pre id="test">
<script type="application/javascript">
@ -135,6 +136,7 @@ var testData = [["a", 1, MockFilePicker.filterImages, 1],
["div-click-on-demand", 0, undefined, 0],
["div-keydown", 0, undefined, 0],
["link-click", 0, undefined, 0],
["show-picker", 0, undefined, 0],
];
var currentTest = 0;
@ -202,6 +204,9 @@ function launchNextTest() {
synthesizeMouseAtCenter(document.getElementById(testData[currentTest][0]), {});
} else if (testData[currentTest][0] == 'div-keydown') {
sendString("a");
} else if (testData[currentTest][0] == 'show-picker') {
SpecialPowers.wrap(document).notifyUserGestureActivation();
document.getElementById(testData[currentTest][0]).showPicker();
} else {
document.getElementById(testData[currentTest][0]).click();
}

View File

@ -134,6 +134,9 @@ interface HTMLInputElement : HTMLElement {
[Throws]
void setSelectionRange(unsigned long start, unsigned long end, optional DOMString direction);
[Throws, Pref="dom.input.showPicker"]
void showPicker();
// also has obsolete members
};

View File

@ -2670,6 +2670,12 @@
value: true
mirror: always
# Is support for HTMLInputElement.showPicker enabled?
- name: dom.input.showPicker
type: bool
value: true
mirror: always
# Whether to allow or disallow web apps to cancel `beforeinput` events caused
# by MozEditableElement#setUserInput() which is used by autocomplete, autofill
# and password manager.

View File

@ -740,78 +740,6 @@ prefs: [dom.security.featurePolicy.experimental.enabled:true, dom.security.featu
[HTMLMetaElement interface: document.createElement("meta") must inherit property "media" with the proper type]
expected: FAIL
[HTMLInputElement interface: operation showPicker()]
expected: FAIL
[HTMLInputElement interface: document.createElement("input") must inherit property "showPicker()" with the proper type]
expected: FAIL
[HTMLInputElement interface: createInput("text") must inherit property "showPicker()" with the proper type]
expected: FAIL
[HTMLInputElement interface: createInput("hidden") must inherit property "showPicker()" with the proper type]
expected: FAIL
[HTMLInputElement interface: createInput("search") must inherit property "showPicker()" with the proper type]
expected: FAIL
[HTMLInputElement interface: createInput("tel") must inherit property "showPicker()" with the proper type]
expected: FAIL
[HTMLInputElement interface: createInput("url") must inherit property "showPicker()" with the proper type]
expected: FAIL
[HTMLInputElement interface: createInput("email") must inherit property "showPicker()" with the proper type]
expected: FAIL
[HTMLInputElement interface: createInput("password") must inherit property "showPicker()" with the proper type]
expected: FAIL
[HTMLInputElement interface: createInput("date") must inherit property "showPicker()" with the proper type]
expected: FAIL
[HTMLInputElement interface: createInput("month") must inherit property "showPicker()" with the proper type]
expected: FAIL
[HTMLInputElement interface: createInput("week") must inherit property "showPicker()" with the proper type]
expected: FAIL
[HTMLInputElement interface: createInput("time") must inherit property "showPicker()" with the proper type]
expected: FAIL
[HTMLInputElement interface: createInput("datetime-local") must inherit property "showPicker()" with the proper type]
expected: FAIL
[HTMLInputElement interface: createInput("number") must inherit property "showPicker()" with the proper type]
expected: FAIL
[HTMLInputElement interface: createInput("range") must inherit property "showPicker()" with the proper type]
expected: FAIL
[HTMLInputElement interface: createInput("color") must inherit property "showPicker()" with the proper type]
expected: FAIL
[HTMLInputElement interface: createInput("checkbox") must inherit property "showPicker()" with the proper type]
expected: FAIL
[HTMLInputElement interface: createInput("radio") must inherit property "showPicker()" with the proper type]
expected: FAIL
[HTMLInputElement interface: createInput("file") must inherit property "showPicker()" with the proper type]
expected: FAIL
[HTMLInputElement interface: createInput("submit") must inherit property "showPicker()" with the proper type]
expected: FAIL
[HTMLInputElement interface: createInput("image") must inherit property "showPicker()" with the proper type]
expected: FAIL
[HTMLInputElement interface: createInput("reset") must inherit property "showPicker()" with the proper type]
expected: FAIL
[HTMLInputElement interface: createInput("button") must inherit property "showPicker()" with the proper type]
expected: FAIL
[HTMLLinkElement interface: attribute blocking]
expected: FAIL

View File

@ -1,6 +1,4 @@
[show-picker-cross-origin-iframe.html]
[Test showPicker() called from cross-origin iframe 1]
expected: FAIL
[Test showPicker() called from cross-origin iframe 3]
expected: FAIL
disabled:
if tsan: https://bugzilla.mozilla.org/show_bug.cgi?id=1745005
if asan: https://bugzilla.mozilla.org/show_bug.cgi?id=1745005

View File

@ -1,132 +0,0 @@
[show-picker-disabled-readonly.html]
[input[type=button\] showPicker() throws when disabled]
expected: FAIL
[input[type=checkbox\] showPicker() throws when disabled]
expected: FAIL
[input[type=color\] showPicker() throws when disabled]
expected: FAIL
[input[type=date\] showPicker() throws when disabled]
expected: FAIL
[input[type=datetime-local\] showPicker() throws when disabled]
expected: FAIL
[input[type=email\] showPicker() throws when disabled]
expected: FAIL
[input[type=file\] showPicker() throws when disabled]
expected: FAIL
[input[type=hidden\] showPicker() throws when disabled]
expected: FAIL
[input[type=image\] showPicker() throws when disabled]
expected: FAIL
[input[type=month\] showPicker() throws when disabled]
expected: FAIL
[input[type=number\] showPicker() throws when disabled]
expected: FAIL
[input[type=password\] showPicker() throws when disabled]
expected: FAIL
[input[type=radio\] showPicker() throws when disabled]
expected: FAIL
[input[type=range\] showPicker() throws when disabled]
expected: FAIL
[input[type=reset\] showPicker() throws when disabled]
expected: FAIL
[input[type=search\] showPicker() throws when disabled]
expected: FAIL
[input[type=submit\] showPicker() throws when disabled]
expected: FAIL
[input[type=tel\] showPicker() throws when disabled]
expected: FAIL
[input[type=text\] showPicker() throws when disabled]
expected: FAIL
[input[type=time\] showPicker() throws when disabled]
expected: FAIL
[input[type=url\] showPicker() throws when disabled]
expected: FAIL
[input[type=week\] showPicker() throws when disabled]
expected: FAIL
[input[type=button\] showPicker() throws when readonly]
expected: FAIL
[input[type=checkbox\] showPicker() throws when readonly]
expected: FAIL
[input[type=color\] showPicker() throws when readonly]
expected: FAIL
[input[type=date\] showPicker() throws when readonly]
expected: FAIL
[input[type=datetime-local\] showPicker() throws when readonly]
expected: FAIL
[input[type=email\] showPicker() throws when readonly]
expected: FAIL
[input[type=file\] showPicker() throws when readonly]
expected: FAIL
[input[type=hidden\] showPicker() throws when readonly]
expected: FAIL
[input[type=image\] showPicker() throws when readonly]
expected: FAIL
[input[type=month\] showPicker() throws when readonly]
expected: FAIL
[input[type=number\] showPicker() throws when readonly]
expected: FAIL
[input[type=password\] showPicker() throws when readonly]
expected: FAIL
[input[type=radio\] showPicker() throws when readonly]
expected: FAIL
[input[type=range\] showPicker() throws when readonly]
expected: FAIL
[input[type=reset\] showPicker() throws when readonly]
expected: FAIL
[input[type=search\] showPicker() throws when readonly]
expected: FAIL
[input[type=submit\] showPicker() throws when readonly]
expected: FAIL
[input[type=tel\] showPicker() throws when readonly]
expected: FAIL
[input[type=text\] showPicker() throws when readonly]
expected: FAIL
[input[type=time\] showPicker() throws when readonly]
expected: FAIL
[input[type=url\] showPicker() throws when readonly]
expected: FAIL
[input[type=week\] showPicker() throws when readonly]
expected: FAIL

View File

@ -1,132 +1,4 @@
[show-picker-user-gesture.html]
[input[type=button\] showPicker() requires a user gesture]
expected: FAIL
[input[type=checkbox\] showPicker() requires a user gesture]
expected: FAIL
[input[type=color\] showPicker() requires a user gesture]
expected: FAIL
[input[type=date\] showPicker() requires a user gesture]
expected: FAIL
[input[type=datetime-local\] showPicker() requires a user gesture]
expected: FAIL
[input[type=email\] showPicker() requires a user gesture]
expected: FAIL
[input[type=file\] showPicker() requires a user gesture]
expected: FAIL
[input[type=hidden\] showPicker() requires a user gesture]
expected: FAIL
[input[type=image\] showPicker() requires a user gesture]
expected: FAIL
[input[type=month\] showPicker() requires a user gesture]
expected: FAIL
[input[type=number\] showPicker() requires a user gesture]
expected: FAIL
[input[type=password\] showPicker() requires a user gesture]
expected: FAIL
[input[type=radio\] showPicker() requires a user gesture]
expected: FAIL
[input[type=range\] showPicker() requires a user gesture]
expected: FAIL
[input[type=reset\] showPicker() requires a user gesture]
expected: FAIL
[input[type=search\] showPicker() requires a user gesture]
expected: FAIL
[input[type=submit\] showPicker() requires a user gesture]
expected: FAIL
[input[type=tel\] showPicker() requires a user gesture]
expected: FAIL
[input[type=text\] showPicker() requires a user gesture]
expected: FAIL
[input[type=time\] showPicker() requires a user gesture]
expected: FAIL
[input[type=url\] showPicker() requires a user gesture]
expected: FAIL
[input[type=week\] showPicker() requires a user gesture]
expected: FAIL
[input[type=button\] showPicker() does not throw when user activation is active]
expected: FAIL
[input[type=checkbox\] showPicker() does not throw when user activation is active]
expected: FAIL
[input[type=color\] showPicker() does not throw when user activation is active]
expected: FAIL
[input[type=date\] showPicker() does not throw when user activation is active]
expected: FAIL
[input[type=datetime-local\] showPicker() does not throw when user activation is active]
expected: FAIL
[input[type=email\] showPicker() does not throw when user activation is active]
expected: FAIL
[input[type=file\] showPicker() does not throw when user activation is active]
expected: FAIL
[input[type=hidden\] showPicker() does not throw when user activation is active]
expected: FAIL
[input[type=image\] showPicker() does not throw when user activation is active]
expected: FAIL
[input[type=month\] showPicker() does not throw when user activation is active]
expected: FAIL
[input[type=number\] showPicker() does not throw when user activation is active]
expected: FAIL
[input[type=password\] showPicker() does not throw when user activation is active]
expected: FAIL
[input[type=radio\] showPicker() does not throw when user activation is active]
expected: FAIL
[input[type=range\] showPicker() does not throw when user activation is active]
expected: FAIL
[input[type=reset\] showPicker() does not throw when user activation is active]
expected: FAIL
[input[type=search\] showPicker() does not throw when user activation is active]
expected: FAIL
[input[type=submit\] showPicker() does not throw when user activation is active]
expected: FAIL
[input[type=tel\] showPicker() does not throw when user activation is active]
expected: FAIL
[input[type=text\] showPicker() does not throw when user activation is active]
expected: FAIL
[input[type=time\] showPicker() does not throw when user activation is active]
expected: FAIL
[input[type=url\] showPicker() does not throw when user activation is active]
expected: FAIL
[input[type=week\] showPicker() does not throw when user activation is active]
expected: FAIL
disabled:
if tsan: https://bugzilla.mozilla.org/show_bug.cgi?id=1745005
if asan: https://bugzilla.mozilla.org/show_bug.cgi?id=1745005

View File

@ -18,13 +18,26 @@ for (const inputType of inputTypes) {
}, `input[type=${inputType}] showPicker() throws when disabled`);
}
const noReadonlySupport = ['button', 'checkbox', 'color', 'file',
'hidden', 'image', 'radio', 'range', 'reset', 'submit'];
for (const inputType of inputTypes) {
test(() => {
const input = document.createElement("input");
input.setAttribute("type", inputType);
input.setAttribute("readonly", "");
if (!noReadonlySupport.includes(inputType)) {
test(() => {
const input = document.createElement("input");
input.setAttribute("type", inputType);
input.setAttribute("readonly", "");
assert_throws_dom('InvalidStateError', () => { input.showPicker(); });
}, `input[type=${inputType}] showPicker() throws when readonly`);
assert_throws_dom('InvalidStateError', () => { input.showPicker(); });
}, `input[type=${inputType}] showPicker() throws when readonly`);
} else {
test(() => {
const input = document.createElement("input");
input.setAttribute("type", inputType);
input.setAttribute("readonly", "");
// Missing user gesture activation throws.
assert_throws_dom('NotAllowedError', () => { input.showPicker(); });
}, `input[type=${inputType}] showPicker() doesn't throw when readonly`);
}
}
</script>

View File

@ -80,6 +80,7 @@ skip-if = !e10s || !crashreporter
[browser_contentTitle.js]
[browser_crash_previous_frameloader.js]
run-if = e10s && crashreporter
[browser_datetime_showPicker.js]
[browser_datetime_datepicker.js]
skip-if =
tsan # Frequently times out on TSan

View File

@ -0,0 +1,47 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const MONTH_YEAR = ".month-year",
DAYS_VIEW = ".days-view",
BTN_PREV_MONTH = ".prev",
BTN_NEXT_MONTH = ".next";
const DATE_FORMAT_LOCAL = new Intl.DateTimeFormat("en-US", {
year: "numeric",
month: "long",
}).format;
let helper = new DateTimeTestHelper();
registerCleanupFunction(() => {
helper.cleanup();
});
/**
* Test that date picker opens with showPicker.
*/
add_task(async function test_datepicker_showPicker() {
const date = new Date();
await helper.openPicker(
"data:text/html, <input type='date'>",
false,
"showPicker"
);
if (date.getMonth() === new Date().getMonth()) {
Assert.equal(
helper.getElement(MONTH_YEAR).textContent,
DATE_FORMAT_LOCAL(date)
);
} else {
Assert.ok(
true,
"Skipping datepicker today test if month changes when opening picker."
);
}
await helper.tearDown();
});

View File

@ -186,8 +186,9 @@ class DateTimeTestHelper {
*
* @param {String} pageUrl
* @param {bool} inFrame true if input is in the first child frame
* @param {String} openMethod "click" or "showPicker"
*/
async openPicker(pageUrl, inFrame) {
async openPicker(pageUrl, inFrame, openMethod = "click") {
this.tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
let bc = gBrowser.selectedBrowser;
if (inFrame) {
@ -199,7 +200,14 @@ class DateTimeTestHelper {
});
bc = bc.browsingContext.children[0];
}
await BrowserTestUtils.synthesizeMouseAtCenter("input", {}, bc);
if (openMethod === "click") {
await BrowserTestUtils.synthesizeMouseAtCenter("input", {}, bc);
} else if (openMethod === "showPicker") {
await SpecialPowers.spawn(bc, [], function() {
content.document.notifyUserGestureActivation();
content.document.querySelector("input").showPicker();
});
}
this.frame = this.panel.querySelector("#dateTimePopupFrame");
await this.waitForPickerReady();
}