Backed out 7 changesets (bug 1676068) for causing geckoview junit failures. CLOSED TREE

Backed out changeset d3c9e777a050 (bug 1676068)
Backed out changeset 639c9661c850 (bug 1676068)
Backed out changeset d06b6aa3b9a3 (bug 1676068)
Backed out changeset 50bb7e9c6bcf (bug 1676068)
Backed out changeset 234acd14548e (bug 1676068)
Backed out changeset 04050cfd5e3f (bug 1676068)
Backed out changeset a06081c85646 (bug 1676068)
This commit is contained in:
Marian-Vasile Laza 2022-12-06 07:20:40 +02:00
Родитель 88bd4f8124
Коммит ff26a6d976
31 изменённых файлов: 576 добавлений и 2618 удалений

Просмотреть файл

@ -887,6 +887,7 @@
{ role: ROLE_SPINBUTTON },
{ role: ROLE_TEXT_LEAF },
{ role: ROLE_ENTRY },
{ role: ROLE_PUSHBUTTON },
],
};
testElm("input_time", obj);

Просмотреть файл

@ -142,6 +142,7 @@
<panel id="DateTimePickerPanel"
type="arrow"
orient="vertical"
noautofocus="true"
norolluponanchor="true"
consumeoutsideclicks="never"
level="parent"

Просмотреть файл

@ -78,8 +78,6 @@
synthesizeKey("KEY_Tab");
opener.is(lastFocusTarget, shadowDate, "Should have focused date element in shadow DOM. (3)");
synthesizeKey("KEY_Tab");
opener.is(lastFocusTarget, shadowDate, "Should have focused date element with a calendar button in shadow DOM. (3)");
synthesizeKey("KEY_Tab");
opener.is(shadowIframe.contentDocument.activeElement,
shadowIframe.contentDocument.documentElement,
"Should have focused document element in shadow iframe. (3)");
@ -104,8 +102,6 @@
shadowIframe.contentDocument.documentElement,
"Should have focused document element in shadow iframe. (4)");
synthesizeKey("KEY_Tab", {shiftKey: true});
opener.is(lastFocusTarget, shadowDate, "Should have focused date element with a calendar button in shadow DOM. (4)");
synthesizeKey("KEY_Tab", {shiftKey: true});
opener.is(lastFocusTarget, shadowDate, "Should have focused date element in shadow DOM. (4)");
synthesizeKey("KEY_Tab", {shiftKey: true});
opener.is(lastFocusTarget, shadowDate, "Should have focused date element in shadow DOM. (4)");

Просмотреть файл

@ -2,6 +2,7 @@
support-files =
save_restore_radio_groups.sjs
test_input_number_data.js
utils.js
!/dom/html/test/reflect.js
FAIL.html
PASS.html
@ -42,7 +43,7 @@ support-files = file_double_submit.html
[test_input_datetime_focus_state.html]
[test_input_datetime_hidden.html]
[test_input_datetime_readonly.html]
[test_input_datetime_calendar_button.html]
[test_input_datetime_reset_button.html]
[test_input_datetime_reset_default_value_input_change_event.html]
[test_input_datetime_tabindex.html]
[test_input_defaultValue.html]

Просмотреть файл

@ -49,7 +49,7 @@ function checkValidity(aElement, aIsBadInput) {
is(window.getComputedStyle(aElement).getPropertyValue('background-color'),
aIsBadInput ? "rgb(255, 0, 0)" : "rgb(0, 255, 0)",
(aIsBadInput ? ":invalid" : "valid") + " pseudo-class should apply");
(aIsBadInput ? ":invalid" : "valid") + " pseudo-classs should apply");
}
function sendKeys(aKey) {
@ -62,6 +62,11 @@ function sendKeys(aKey) {
function test() {
var elem = document.getElementById("input");
var inputRect = input.getBoundingClientRect();
// Points over the input's reset button
var resetButton_X = inputRect.width - 15;
var resetButton_Y = inputRect.height / 2;
elem.focus();
sendKeys("02312017");
@ -97,6 +102,13 @@ function test() {
sendKeys("02292017");
elem.blur();
checkValidity(elem, true);
// Reset button is desktop only.
if (isDesktop) {
// Clearing all fields should clear bad input validity state as well.
synthesizeMouse(input, resetButton_X, resetButton_Y, {});
checkValidity(elem, false);
}
}
</script>

Просмотреть файл

@ -45,6 +45,11 @@ SimpleTest.waitForFocus(function() {
function test() {
for (var i = 0; i < inputTypes.length; i++) {
var input = document.getElementById("input_" + inputTypes[i]);
var inputRect = input.getBoundingClientRect();
// Points over the input's reset button
var resetButton_X = inputRect.width - 15;
var resetButton_Y = inputRect.height / 2;
is(changeEvents[i], 0, "Number of change events should be 0 at start.");
is(inputEvents[i], 0, "Number of input events should be 0 at start.");
@ -71,6 +76,16 @@ function test() {
is(input.value, expectedValues[i][1], "Check that value was set correctly (2).");
is(changeEvents[i], 3, "Change event should be dispatched (2).");
is(inputEvents[i], 3, "Input event should be dispatched (2).");
// Reset button is desktop only.
if (isDesktop) {
// Test that change and input events are fired when clearing the value using
// the reset button.
synthesizeMouse(input, resetButton_X, resetButton_Y, {});
is(input.value, "", "Check that value was set correctly (3).");
is(changeEvents[i], 4, "Change event should be dispatched (3).");
is(inputEvents[i], 4, "Input event should be dispatched (3).");
}
}
}

Просмотреть файл

@ -4,13 +4,13 @@
https://bugzilla.mozilla.org/show_bug.cgi?id=1479708
-->
<head>
<title>Test required date/datetime-local input's Calendar button</title>
<title>Test required date/time input can't be reset</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="/tests/SimpleTest/EventUtils.js"></script>
<link rel="stylesheet" href="/tests/SimpleTest/test.css" />
</head>
<body>
Created for <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1479708">Mozilla Bug 1479708</a> and updated by <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1676068">Mozilla Bug 1676068</a>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1479708">Mozilla Bug 1479708</a>
<p id="display"></p>
<div id="content">
<input type="date" id="id_date" value="2017-06-08">
@ -29,6 +29,7 @@ Created for <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?i
<pre id="test">
<script class="testbody">
const isDesktop = !/Mobile|Tablet/.test(navigator.userAgent);
const kTypes = ["date", "time", "datetime-local"];
function id_for_type(type, kind) {
@ -37,15 +38,20 @@ function id_for_type(type, kind) {
SimpleTest.waitForExplicitFinish();
SimpleTest.waitForFocus(function() {
if (!isDesktop) {
ok(true, "Mobile and tablet dont show reset button");
return SimpleTest.finish();
}
// Initial load.
assert_calendar_visible_all("");
assert_calendar_visible_all("required");
assert_calendar_hidden_all("readonly");
assert_calendar_hidden_all("disabled");
assert_reset_visible_all("");
assert_reset_hidden_all("required");
// Dynamic toggling.
test_make_readonly("");
test_make_editable("readonly");
test_make_required("");
test_make_optional("required");
test_readonly_field_disabled();
test_disabled_field_disabled();
// Now toggle the inputs to the initial state, but while being
@ -55,8 +61,8 @@ SimpleTest.waitForFocus(function() {
is(input.getBoundingClientRect().width, 0, "Should be undisplayed");
}
test_make_readonly("readonly");
test_make_editable("");
test_make_required("required");
test_make_optional("");
// And test other toggling as well.
test_readonly_field_disabled();
@ -70,22 +76,12 @@ function test_disabled_field_disabled() {
const id = id_for_type(type, "disabled");
const input = document.getElementById(id);
ok(input.disabled, `#${id} Should be disabled`);
ok(
get_calendar_button(id).hidden,
`disabled's Calendar button is hidden (${id})`
);
ok(input.disabled, "Should be disabled");
ok(get_reset_button(id).disabled, `disabled's reset button is disabled (${id})`);
input.disabled = false;
ok(!input.disabled, `#${id} Should not be disabled anymore`);
if (type === "time") {
assert_calendar_hidden(id);
} else {
ok(
!get_calendar_button(id).hidden,
`enabled field's Calendar button is not hidden (${id})`
);
}
ok(!input.disabled, "Should not be disabled anymore");
ok(!get_reset_button(id).disabled, `enabled field's reset button is not disabled (${id})`);
input.disabled = true; // reset to the original state.
}
@ -96,79 +92,64 @@ function test_readonly_field_disabled() {
const id = id_for_type(type, "readonly");
const input = document.getElementById(id);
ok(input.readOnly, `#${id} Should be read-only`);
ok(get_calendar_button(id).hidden, `readonly field's Calendar button is hidden (${id})`);
ok(input.readOnly, "Should be read-only");
ok(get_reset_button(id).disabled, `readonly field's reset button is disabled (${id})`);
input.readOnly = false;
ok(!input.readOnly, `#${id} Should not be read-only anymore`);
if (type === "time") {
assert_calendar_hidden(id);
} else {
ok(
!get_calendar_button(id).hidden,
`non-readonly field's Calendar button is not hidden (${id})`
);
}
ok(!input.readOnly, "Should not be read-only anymore");
ok(!get_reset_button(id).disabled, `non-readonly field's reset button is not disabled (${id})`);
input.readOnly = true; // reset to the original state.
}
}
function test_make_readonly(kind) {
function test_make_required(kind) {
for (let type of kTypes) {
const id = id_for_type(type, kind);
const input = document.getElementById(id);
is(input.readOnly, false, `Precondition: input #${id} is editable`);
is(input.required, false, `Precondition: input #${id} is optional`);
input.readOnly = true;
assert_calendar_hidden(id);
input.required = true;
assert_reset_hidden(id);
}
}
function test_make_editable(kind) {
function test_make_optional(kind) {
for (let type of kTypes) {
const id = id_for_type(type, kind);
const input = document.getElementById(id);
is(input.readOnly, true, `Precondition: input #${id} is read-only`);
is(input.required, true, `Precondition: input #${id} is required`);
input.readOnly = false;
if (type === "time") {
assert_calendar_hidden(id);
} else {
assert_calendar_visible(id);
}
input.required = false;
assert_reset_visible(id);
}
}
function assert_calendar_visible_all(kind) {
function assert_reset_visible_all(kind) {
for (let type of kTypes) {
if (type === "time") {
assert_calendar_hidden(id_for_type(type, kind));
} else {
assert_calendar_visible(id_for_type(type, kind));
}
assert_reset_visible(id_for_type(type, kind));
}
}
function assert_calendar_visible(id) {
const calendarButton = get_calendar_button(id);
is(calendarButton.hidden, false, `Calendar button is not hidden on #${id}`);
function assert_reset_visible(id) {
const resetButton = get_reset_button(id);
is(resetButton.style.visibility, "", `Reset button is visible on #${id}`);
}
function assert_calendar_hidden_all(kind) {
function assert_reset_hidden_all(kind) {
for (let type of kTypes) {
assert_calendar_hidden(id_for_type(type, kind));
assert_reset_hidden(id_for_type(type, kind));
}
}
function assert_calendar_hidden(id) {
const calendarButton = get_calendar_button(id);
is(calendarButton.hidden, true, `Calendar button is hidden on #${id}`);
function assert_reset_hidden(id) {
const resetButton = get_reset_button(id);
is(resetButton.style.visibility, "hidden", `Reset button is hidden on #${id}`);
}
function get_calendar_button(id) {
function get_reset_button(id) {
const input = document.getElementById(id);
const shadowRoot = SpecialPowers.wrap(input).openOrClosedShadowRoot;
return shadowRoot.getElementById("calendar-button");
return shadowRoot.getElementById("reset-button");
}
</script>

Просмотреть файл

@ -51,6 +51,13 @@ var numberInputEvents = 0;
SimpleTest.waitForExplicitFinish();
SimpleTest.waitForFocus(function() {
if (isDesktopUserAgent(navigator)) {
test_reset_in_ui_triggers_change_and_input_event(
"time", numberChangeEvents, numberInputEvents);
test_reset_in_ui_triggers_change_and_input_event(
"date", numberChangeEvents, numberInputEvents);
}
test_reset_in_script_does_not_trigger_change_and_input_event(
"time2", numberChangeEvents, numberInputEvents);
test_reset_in_script_does_not_trigger_change_and_input_event(
@ -64,6 +71,30 @@ SimpleTest.waitForFocus(function() {
SimpleTest.finish();
});
function test_reset_in_ui_triggers_change_and_input_event(
inputFieldIdSuffix, oldNumberChangeEvents, oldNumberInputEvents) {
const inputFieldName = INPUT_FIELD_ID_PREFIX + inputFieldIdSuffix;
var input = document.getElementById(inputFieldName);
is(input.value, input.defaultValue,
"Check " + inputFieldName + "'s default value is initialized correctly.");
is(numberChangeEvents, oldNumberChangeEvents,
"Check numberChangeEvents is initialized correctly for " + inputFieldName +
".");
is(numberInputEvents, oldNumberInputEvents,
"Check numberInputEvents is initialized correctly for " + inputFieldName +
".");
simulateUserClicksResetButton(input);
is(input.value, "",
"Check " + inputFieldName + "'s value was set correctly.");
is(numberChangeEvents, oldNumberChangeEvents + 1,
"Change event should be dispatched for " + inputFieldName + ".");
is(numberInputEvents, oldNumberInputEvents + 1,
"Input event should be dispatched for " + inputFieldName + ".");
}
function test_reset_in_script_does_not_trigger_change_and_input_event(
inputFieldIdSuffix, oldNumberChangeEvents, oldNumberInputEvents) {
const inputFieldName = INPUT_FIELD_ID_PREFIX + inputFieldIdSuffix;

Просмотреть файл

@ -58,15 +58,7 @@ function testTabindex(type) {
is(document.activeElement, input1,
"input element with tabindex=0 is focusable");
// Time input does not include a Calendar button
let fieldCount;
if (type == "datetime-local") {
fieldCount = 7;
} else if (type == "date") {
fieldCount = 4;
} else {
fieldCount = 3;
};
let fieldCount = type == "datetime-local" ? 6 : 3;
// Advance through inner fields.
for (let i = 0; i < fieldCount - 1; ++i) {

Просмотреть файл

@ -0,0 +1,20 @@
/**
* Simulate the user clicks the reset button of the given date or time element.
*
* @param inputElement A date or time input element of default size.
*/
function simulateUserClicksResetButton(inputElement) {
var inputRectangle = inputElement.getBoundingClientRect();
const offsetX = inputRectangle.width - 15;
const offsetY = inputRectangle.height / 2;
synthesizeMouse(inputElement, offsetX, offsetY, {});
}
/**
* @param navigator https://www.w3schools.com/jsref/obj_navigator.asp.
* @return true, iff it's a desktop user agent.
*/
function isDesktopUserAgent(navigator) {
return !/Mobile|Tablet/.test(navigator.userAgent);
}

Просмотреть файл

@ -0,0 +1,4 @@
[transform-input-013.html]
expected:
if (os == "mac") and not debug: [FAIL, PASS]
if (os == "mac") and debug: FAIL

Просмотреть файл

@ -89,10 +89,6 @@ class DateTimePickerChild extends JSWindowActorChild {
receiveMessage(aMessage) {
switch (aMessage.name) {
case "FormDateTime:PickerClosed": {
if (!this._inputElement) {
return;
}
this.close();
break;
}

Просмотреть файл

@ -101,7 +101,6 @@ class DateTimePickerParent extends JSWindowActorParent {
debug("aBrowser.dateTimePicker not found, exiting now.");
return;
}
this.oldFocus = window.document.activeElement;
this._picker = new lazy.DateTimePickerPanel(panel);
this._picker.openPicker(type, rect, detail);
@ -110,11 +109,6 @@ class DateTimePickerParent extends JSWindowActorParent {
// Picker is closed, do some cleanup.
close() {
if (this.oldFocus) {
// Restore focus to where it was before the picker opened.
this.oldFocus.focus();
this.oldFocus = null;
}
this.removePickerListeners();
this._picker = null;
}

Просмотреть файл

@ -9,37 +9,37 @@
]>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<head>
<title>Date Picker</title>
<link rel="stylesheet" href="chrome://global/skin/datetimeinputpickers.css"/>
<link rel="localization" href="toolkit/global/datepicker.ftl" />
<script src="chrome://global/content/bindings/datekeeper.js"></script>
<script src="chrome://global/content/bindings/spinner.js"></script>
<script src="chrome://global/content/bindings/calendar.js"></script>
<script src="chrome://global/content/bindings/datepicker.js"></script>
</head>
<body>
<div id="date-picker" role="dialog" data-l10n-id="date-picker-label" aria-modal="true">
<div id="date-picker">
<div class="calendar-container">
<div class="month-year-nav" data-l10n-id="date-spinner-label">
<button class="prev" data-l10n-id="date-picker-previous" />
<div class="month-year-container">
<button class="month-year" id="month-year-label" aria-live="polite" />
</div>
<template id="spinner-template">
<div class="spinner-container">
<button class="up"/>
<div class="spinner"></div>
<button class="down"/>
</div>
</template>
<div class="month-year-view"></div>
<button class="next" data-l10n-id="date-picker-next" />
<div class="nav">
<button class="prev"/>
<button class="next"/>
</div>
<div class="week-header"></div>
<div class="days-viewport">
<div class="days-view"></div>
</div>
<table role="grid" aria-labelledby="month-year-label">
<thead class="week-header"></thead>
<tbody class="days-view"></tbody>
</table>
</div>
<div class="month-year-container">
<button class="month-year"/>
</div>
<div class="month-year-view"></div>
</div>
<template id="spinner-template">
<div class="spinner-container">
<button class="up"/>
<div class="spinner"></div>
<button class="down"/>
</div>
</template>
<script>
/* import-globals-from widgets/datepicker.js */
// Create a DatePicker instance and prepare to be

Просмотреть файл

@ -81,11 +81,6 @@ skip-if = !crashreporter
run-if = crashreporter
[browser_datetime_showPicker.js]
[browser_datetime_datepicker.js]
[browser_datetime_datepicker_markup.js]
[browser_datetime_datepicker_keynav.js]
[browser_datetime_datepicker_mousenav.js]
[browser_spinner.js]
[browser_spinner_keynav.js]
skip-if =
tsan # Frequently times out on TSan
os == "win" && asan && fission # fails on asan/fission

Просмотреть файл

@ -6,9 +6,8 @@
const MONTH_YEAR = ".month-year",
DAYS_VIEW = ".days-view",
BTN_NEXT_MONTH = ".next",
DAY_TODAY = ".today",
DAY_SELECTED = ".selection";
BTN_PREV_MONTH = ".prev",
BTN_NEXT_MONTH = ".next";
const DATE_FORMAT = new Intl.DateTimeFormat("en-US", {
year: "numeric",
month: "long",
@ -74,23 +73,13 @@ const calendarClasslist_201612 = [
];
function getCalendarText() {
let calendarCells = [];
for (const tr of helper.getChildren(DAYS_VIEW)) {
for (const td of tr.children) {
calendarCells.push(td.textContent);
}
}
return calendarCells;
return helper.getChildren(DAYS_VIEW).map(child => child.textContent);
}
function getCalendarClassList() {
let calendarCellsClasses = [];
for (const tr of helper.getChildren(DAYS_VIEW)) {
for (const td of tr.children) {
calendarCellsClasses.push(td.classList);
}
}
return calendarCellsClasses;
return helper
.getChildren(DAYS_VIEW)
.map(child => Array.from(child.classList));
}
function mergeArrays(a, b) {
@ -115,7 +104,7 @@ async function verifyPickerPosition(browsingContext, inputId) {
function is_close(got, exp, msg) {
// on some platforms we see differences of a fraction of a pixel - so
// allow any difference of < 1 pixels as being OK.
Assert.ok(
ok(
Math.abs(got - exp) < 1,
msg + ": " + got + " should be equal(-ish) to " + exp
);
@ -144,8 +133,6 @@ registerCleanupFunction(() => {
* Test that date picker opens to today's date when input field is blank
*/
add_task(async function test_datepicker_today() {
info("Test that date picker opens to today's date when input field is blank");
const date = new Date();
await helper.openPicker("data:text/html, <input type='date'>");
@ -153,18 +140,7 @@ add_task(async function test_datepicker_today() {
if (date.getMonth() === new Date().getMonth()) {
Assert.equal(
helper.getElement(MONTH_YEAR).textContent,
DATE_FORMAT_LOCAL(date),
"Today's date is opened"
);
Assert.equal(
helper.getElement(DAY_TODAY).getAttribute("aria-current"),
"date",
"Today's date is programmatically current"
);
Assert.equal(
helper.getElement(DAY_TODAY).getAttribute("tabindex"),
"0",
"Today's date is included in the focus order, when nothing is selected"
DATE_FORMAT_LOCAL(date)
);
} else {
Assert.ok(
@ -181,8 +157,6 @@ add_task(async function test_datepicker_today() {
* displayed correctly, given a date value is set.
*/
add_task(async function test_datepicker_open() {
info("Test the date picker markup with a set input date value");
const inputValue = "2016-12-15";
await helper.openPicker(
@ -191,10 +165,8 @@ add_task(async function test_datepicker_open() {
Assert.equal(
helper.getElement(MONTH_YEAR).textContent,
DATE_FORMAT(new Date(inputValue)),
"2016-12-15 date is opened"
DATE_FORMAT(new Date(inputValue))
);
Assert.deepEqual(
getCalendarText(),
[
@ -241,29 +213,258 @@ add_task(async function test_datepicker_open() {
"6",
"7",
],
"Calendar text for 2016-12 is correct"
"2016-12"
);
Assert.deepEqual(
getCalendarClassList(),
calendarClasslist_201612,
"2016-12 classNames of the picker are correct"
);
Assert.equal(
helper.getElement(DAY_SELECTED).getAttribute("aria-selected"),
"true",
"Chosen date is programmatically selected"
);
Assert.equal(
helper.getElement(DAY_SELECTED).getAttribute("tabindex"),
"0",
"Selected date is included in the focus order"
"2016-12 classNames"
);
await helper.tearDown();
});
/**
* Ensure that the datepicker popup appears correctly positioned when
* Ensure picker closes when focus moves to a different input.
*/
add_task(async function test_datepicker_focus_change() {
await helper.openPicker(
`data:text/html,<input id=date type=date><input id=other>`
);
let browser = helper.tab.linkedBrowser;
await verifyPickerPosition(browser, "date");
isnot(helper.panel.state, "closed", "Panel should be visible");
let closed = helper.promisePickerClosed();
await SpecialPowers.spawn(browser, [], () => {
content.document.querySelector("#other").focus();
});
await closed;
ok(true, "Panel should be closed now");
await helper.tearDown();
});
/**
* Ensure picker opens and closes with key bindings appropriately.
*/
add_task(async function test_datepicker_keyboard_open() {
const inputValue = "2016-12-15";
const prevMonth = "2016-11-01";
await helper.openPicker(
`data:text/html,<input id=date type=date value=${inputValue}>`
);
let browser = helper.tab.linkedBrowser;
await verifyPickerPosition(browser, "date");
let closed = helper.promisePickerClosed();
BrowserTestUtils.synthesizeKey(" ", {}, browser);
await closed;
let ready = helper.waitForPickerReady();
BrowserTestUtils.synthesizeKey(" ", {}, browser);
await ready;
// NOTE: After the click, the first input field (the month one) is focused,
// so down arrow will change the selected month.
//
// This assumes en-US locale, which seems fine for testing purposes (as
// DATE_FORMAT and other bits around do the same).
BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
// It'd be good to use something else than waitForCondition for this but
// there's no exposed event atm when the value changes from the child.
await BrowserTestUtils.waitForCondition(() => {
return (
helper.getElement(MONTH_YEAR).textContent ==
DATE_FORMAT(new Date(prevMonth))
);
}, "Should update date when updating months");
await helper.tearDown();
});
/**
* When the prev month button is clicked, calendar should display the dates for
* the previous month.
*/
add_task(async function test_datepicker_prev_month_btn() {
const inputValue = "2016-12-15";
const prevMonth = "2016-11-01";
await helper.openPicker(
`data:text/html, <input type="date" value="${inputValue}">`
);
helper.click(helper.getElement(BTN_PREV_MONTH));
Assert.equal(
helper.getElement(MONTH_YEAR).textContent,
DATE_FORMAT(new Date(prevMonth))
);
Assert.deepEqual(
getCalendarText(),
[
"30",
"31",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"10",
"11",
"12",
"13",
"14",
"15",
"16",
"17",
"18",
"19",
"20",
"21",
"22",
"23",
"24",
"25",
"26",
"27",
"28",
"29",
"30",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"10",
],
"2016-11"
);
await helper.tearDown();
});
/**
* When the next month button is clicked, calendar should display the dates for
* the next month.
*/
add_task(async function test_datepicker_next_month_btn() {
const inputValue = "2016-12-15";
const nextMonth = "2017-01-01";
await helper.openPicker(
`data:text/html, <input type="date" value="${inputValue}">`
);
helper.click(helper.getElement(BTN_NEXT_MONTH));
Assert.equal(
helper.getElement(MONTH_YEAR).textContent,
DATE_FORMAT(new Date(nextMonth))
);
Assert.deepEqual(
getCalendarText(),
[
"25",
"26",
"27",
"28",
"29",
"30",
"31",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"10",
"11",
"12",
"13",
"14",
"15",
"16",
"17",
"18",
"19",
"20",
"21",
"22",
"23",
"24",
"25",
"26",
"27",
"28",
"29",
"30",
"31",
"1",
"2",
"3",
"4",
],
"2017-01"
);
await helper.tearDown();
});
/**
* When a date on the calendar is clicked, date picker should close and set
* value to the input box.
*/
add_task(async function test_datepicker_clicked() {
const inputValue = "2016-12-15";
const firstDayOnCalendar = "2016-11-27";
await helper.openPicker(
`data:text/html, <input id="date" type="date" value="${inputValue}">`
);
let browser = helper.tab.linkedBrowser;
await verifyPickerPosition(browser, "date");
// Click the first item (top-left corner) of the calendar
let promise = BrowserTestUtils.waitForContentEvent(
helper.tab.linkedBrowser,
"input"
);
helper.click(helper.getElement(DAYS_VIEW).children[0]);
await promise;
let value = await SpecialPowers.spawn(
browser,
[],
() => content.document.querySelector("input").value
);
Assert.equal(value, firstDayOnCalendar);
await helper.tearDown();
});
/**
* Ensure that the datepicker popop appears correctly positioned when
* the input field has been transformed.
*/
add_task(async function test_datepicker_transformed_position() {
@ -295,36 +496,25 @@ add_task(async function test_datepicker_reopen_state() {
await helper.openPicker(
`data:text/html, <input type="date" value="${inputValue}">`
);
// Navigate to the next month but does not commit the change
Assert.equal(
helper.getElement(MONTH_YEAR).textContent,
DATE_FORMAT(new Date(inputValue))
);
helper.click(helper.getElement(BTN_NEXT_MONTH));
Assert.equal(
helper.getElement(MONTH_YEAR).textContent,
DATE_FORMAT(new Date(nextMonth))
);
EventUtils.synthesizeKey("VK_ESCAPE", {}, window);
Assert.equal(helper.panel.state, "closed", "Panel should be closed");
// Ensures the picker opens to the month of the input value
await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
let input = content.document.querySelector("input");
function getCalendarButton(input) {
const shadowRoot = SpecialPowers.wrap(input).openOrClosedShadowRoot;
return shadowRoot.getElementById("calendar-button");
}
getCalendarButton(input).click();
});
await BrowserTestUtils.synthesizeMouseAtCenter(
"input",
{},
gBrowser.selectedBrowser
);
await helper.waitForPickerReady();
Assert.equal(
helper.getElement(MONTH_YEAR).textContent,
DATE_FORMAT(new Date(inputValue))
@ -396,18 +586,6 @@ add_task(async function test_datepicker_min_max() {
"2016-12 with min & max"
);
Assert.ok(
helper
.getElement(DAYS_VIEW)
.firstChild.firstChild.getAttribute("aria-disabled"),
"An out-of-range date is programmatically disabled"
);
Assert.ok(
!helper.getElement(DAY_SELECTED).hasAttribute("aria-disabled"),
"An in-range date is not programmatically disabled"
);
await helper.tearDown();
});
@ -605,27 +783,34 @@ add_task(async function test_datepicker_abs_max() {
});
/**
* Ensure datetime-local picker closes when selection is made.
* Ensure datetime-local picker closes when focus moves to a time input.
*/
add_task(async function test_datetime_focus_to_input() {
info("Ensure datetime-local picker closes when focus moves to a time input");
await helper.openPicker(
`data:text/html,<input id=datetime type=datetime-local>`
);
let browser = helper.tab.linkedBrowser;
await verifyPickerPosition(browser, "datetime");
Assert.equal(helper.panel.state, "open", "Panel should be visible");
// Make selection to close the date dialog
await EventUtils.synthesizeKey(" ", {});
isnot(helper.panel.state, "closed", "Panel should be visible");
let closed = helper.promisePickerClosed();
// Move to the time section by pressing tab.
for (let i = 0; i < 3; ++i) {
await BrowserTestUtils.synthesizeKey("KEY_Tab", {}, browser);
}
await closed;
Assert.equal(helper.panel.state, "closed", "Panel should be closed now");
ok(true, "Panel should be closed now");
// The input should still be focused.
let isFocused = await SpecialPowers.spawn(browser, [], () => {
return content.document.querySelector("#datetime").matches(":focus");
});
ok(isFocused, "<input> should still be focused");
await helper.tearDown();
});
@ -705,9 +890,9 @@ add_task(async function test_datetime_local_min_select_invalid() {
let changePromise = helper.promiseChange();
// Select the minimum day (the 5th, which is the 2nd child of 2nd row).
// Select the minimum day (the 5th, which is the 9th child).
// The date becomes invalid (we select 2016-12-05T05:00).
helper.click(helper.getElement(DAYS_VIEW).children[1].children[1]);
helper.click(helper.getElement(DAYS_VIEW).children[8]);
await changePromise;
@ -720,8 +905,8 @@ add_task(async function test_datetime_local_min_select_invalid() {
}
);
Assert.equal(value, "2016-12-05T05:00", "Value should've changed");
Assert.ok(invalid, "input should be now invalid");
is(value, "2016-12-05T05:00", "Value should've changed");
ok(invalid, "input should be now invalid");
await helper.tearDown();
});

Просмотреть файл

@ -1,479 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const MONTH_YEAR = ".month-year",
DAY_SELECTED = ".selection";
const DATE_FORMAT = new Intl.DateTimeFormat("en-US", {
year: "numeric",
month: "long",
timeZone: "UTC",
}).format;
/**
* Helper function to check the value of a Calendar button's specific attribute
*
* @param {String} attr: The name of the attribute to be tested
* @param {String} val: Value that is expected to be assigned to the attribute
*/
async function testCalendarBtnAttribute(attr, val) {
let browser = helper.tab.linkedBrowser;
await SpecialPowers.spawn(browser, [attr, val], (attr, val) => {
const input = content.document.querySelector("input");
const shadowRoot = SpecialPowers.wrap(input).openOrClosedShadowRoot;
const calendarBtn = shadowRoot.getElementById("calendar-button");
Assert.equal(
calendarBtn.getAttribute(attr),
val,
`Calendar button has ${attr} attribute set to ${val}`
);
});
}
let helper = new DateTimeTestHelper();
registerCleanupFunction(() => {
helper.cleanup();
});
/**
* Ensure picker opens, closes, and updates its value with key bindings appropriately.
*/
add_task(async function test_datepicker_keyboard_nav() {
info(
"Ensure picker opens, closes, and updates its value with key bindings appropriately."
);
const inputValue = "2016-12-15";
const prevMonth = "2016-11-01";
await helper.openPicker(
`data:text/html,<input id=date type=date value=${inputValue}>`
);
let browser = helper.tab.linkedBrowser;
Assert.equal(helper.panel.state, "open", "Panel should be opened");
await testCalendarBtnAttribute("aria-expanded", "true");
let closed = helper.promisePickerClosed();
// Close on Escape anywhere
EventUtils.synthesizeKey("VK_ESCAPE", {});
await closed;
Assert.equal(
helper.panel.state,
"closed",
"Panel should be closed after Escape from anywhere on the window"
);
await testCalendarBtnAttribute("aria-expanded", "false");
let ready = helper.waitForPickerReady();
// Ensure focus is on the input field
await SpecialPowers.spawn(browser, [], () => {
content.document.querySelector("#date").focus();
});
info("Test that input updates with the keyboard update the picker");
// NOTE: After a Tab, the first input field (the month one) is focused,
// so down arrow will change the selected month.
//
// This assumes en-US locale, which seems fine for testing purposes (as
// DATE_FORMAT and other bits around do the same).
BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
// Toggle the picker on Space anywhere within the input
BrowserTestUtils.synthesizeKey(" ", {}, browser);
await ready;
await testCalendarBtnAttribute("aria-expanded", "true");
Assert.equal(
helper.panel.state,
"open",
"Panel should be opened on Space from anywhere within the input field"
);
Assert.equal(
helper.panel.querySelector("#dateTimePopupFrame").contentDocument
.activeElement.textContent,
"15",
"Picker is opened with a focus set to the currently selected date"
);
// It'd be good to use something else than waitForCondition for this but
// there's no exposed event atm when the value changes from the child.
await BrowserTestUtils.waitForCondition(() => {
return (
helper.getElement(MONTH_YEAR).textContent ==
DATE_FORMAT(new Date(prevMonth))
);
}, `Should change to November 2016, instead got ${helper.getElement(MONTH_YEAR).textContent}`);
Assert.ok(
true,
"The date on both the Calendar and Month-Year button was updated when updating months with Down arrow key"
);
closed = helper.promisePickerClosed();
// Close on Escape and return the focus to the input field (the month input in en-US locale)
EventUtils.synthesizeKey("VK_ESCAPE", {}, window);
await closed;
Assert.equal(
helper.panel.state,
"closed",
"Panel should be closed on Escape"
);
// The focus should return to the input field.
let isFocused = await SpecialPowers.spawn(browser, [], () => {
return (
content.document.querySelector("#date") === content.document.activeElement
);
});
Assert.ok(isFocused, "<input> should again be focused");
// Move focus to the second field (the day input in en-US locale)
BrowserTestUtils.synthesizeKey("VK_RIGHT", {}, browser);
// Change the day to 2016-12-16
BrowserTestUtils.synthesizeKey("VK_UP", {}, browser);
ready = helper.waitForPickerReady();
// Open the picker on Space within the input to check the date update
await BrowserTestUtils.synthesizeKey(" ", {}, browser);
await ready;
await testCalendarBtnAttribute("aria-expanded", "true");
Assert.equal(helper.panel.state, "open", "Panel should be opened on Space");
await BrowserTestUtils.waitForCondition(() => {
return helper.getElement(DAY_SELECTED).textContent === "16";
}, `Should change to the 16th, instead got ${helper.getElement(DAY_SELECTED).textContent}`);
Assert.ok(
true,
"The date on the Calendar was updated when updating days with Up arrow key"
);
closed = helper.promisePickerClosed();
// Close on Escape and return the focus to the input field (the day input in en-US locale)
EventUtils.synthesizeKey("VK_ESCAPE", {}, window);
await closed;
Assert.equal(
helper.panel.state,
"closed",
"Panel should be closed on Escape"
);
await testCalendarBtnAttribute("aria-expanded", "false");
info("Test the Calendar button can toggle the picker with Enter/Space");
// Move focus to the Calendar button
BrowserTestUtils.synthesizeKey("KEY_Tab", {}, browser);
BrowserTestUtils.synthesizeKey("KEY_Tab", {}, browser);
// Toggle the picker on Enter on Calendar button
await BrowserTestUtils.synthesizeKey("KEY_Enter", {}, browser);
await helper.waitForPickerReady();
Assert.equal(
helper.panel.state,
"open",
"Panel should be opened on Enter from the Calendar button"
);
await testCalendarBtnAttribute("aria-expanded", "true");
// Move focus from 2016-11-16 to 2016-11-17
EventUtils.synthesizeKey("VK_RIGHT", {});
// Make a selection by pressing Space on date gridcell
await EventUtils.synthesizeKey(" ", {});
await helper.promisePickerClosed();
Assert.equal(
helper.panel.state,
"closed",
"Panel should be closed on Space from the date gridcell"
);
await testCalendarBtnAttribute("aria-expanded", "false");
// Toggle the picker on Space on Calendar button
await EventUtils.synthesizeKey(" ", {});
await helper.waitForPickerReady();
Assert.equal(
helper.panel.state,
"open",
"Panel should be opened on Space from the Calendar button"
);
await testCalendarBtnAttribute("aria-expanded", "true");
await helper.tearDown();
});
/**
* Ensure calendar follows Arrow key bindings appropriately.
*/
add_task(async function test_datepicker_keyboard_arrows() {
info("Ensure calendar follows Arrow key bindings appropriately.");
const inputValue = "2016-12-10";
const prevMonth = "2016-11-01";
await helper.openPicker(
`data:text/html,<input id=date type=date value=${inputValue}>`
);
let pickerDoc = helper.panel.querySelector("#dateTimePopupFrame")
.contentDocument;
Assert.equal(helper.panel.state, "open", "Panel should be opened");
// Move focus from 2016-12-10 to 2016-12-11:
EventUtils.synthesizeKey("VK_RIGHT", {});
Assert.equal(
pickerDoc.activeElement.textContent,
"11",
"Arrow Right moves focus to the next day"
);
// Move focus from 2016-12-11 to 2016-12-04:
EventUtils.synthesizeKey("VK_UP", {});
Assert.equal(
pickerDoc.activeElement.textContent,
"4",
"Arrow Up moves focus to the same weekday of the previous week"
);
// Move focus from 2016-12-04 to 2016-12-03:
EventUtils.synthesizeKey("VK_LEFT", {});
Assert.equal(
pickerDoc.activeElement.textContent,
"3",
"Arrow Left moves focus to the previous day"
);
// Move focus from 2016-12-03 to 2016-11-26:
EventUtils.synthesizeKey("VK_UP", {});
Assert.equal(
pickerDoc.activeElement.textContent,
"26",
"Arrow Up updates the view to be on the previous month, if needed"
);
Assert.equal(
helper.getElement(MONTH_YEAR).textContent,
DATE_FORMAT(new Date(prevMonth)),
"Arrow Up updates the spinner to show the previous month, if needed"
);
// Move focus from 2016-11-26 to 2016-12-03:
EventUtils.synthesizeKey("VK_DOWN", {});
Assert.equal(
pickerDoc.activeElement.textContent,
"3",
"Arrow Down updates the view to be on the next month, if needed"
);
Assert.equal(
helper.getElement(MONTH_YEAR).textContent,
DATE_FORMAT(new Date(inputValue)),
"Arrow Down updates the spinner to show the next month, if needed"
);
// Move focus from 2016-12-03 to 2016-12-10:
EventUtils.synthesizeKey("VK_DOWN", {});
Assert.equal(
pickerDoc.activeElement.textContent,
"10",
"Arrow Down moves focus to the same day of the next week"
);
await helper.tearDown();
});
/**
* Ensure calendar follows Home/End key bindings appropriately.
*/
add_task(async function test_datepicker_keyboard_home_end() {
info("Ensure calendar follows Home/End key bindings appropriately.");
const inputValue = "2016-12-15";
const prevMonth = "2016-11-01";
await helper.openPicker(
`data:text/html,<input id=date type=date value=${inputValue}>`
);
let pickerDoc = helper.panel.querySelector("#dateTimePopupFrame")
.contentDocument;
Assert.equal(helper.panel.state, "open", "Panel should be opened");
// Move focus from 2016-12-15 to 2016-12-11 (in the en-US locale):
EventUtils.synthesizeKey("VK_HOME", {});
Assert.equal(
pickerDoc.activeElement.textContent,
"11",
"Home key moves focus to the first day/Sunday of the current week"
);
// Move focus from 2016-12-11 to 2016-12-17 (in the en-US locale):
EventUtils.synthesizeKey("VK_END", {});
Assert.equal(
pickerDoc.activeElement.textContent,
"17",
"End key moves focus to the last day/Saturday of the current week"
);
// Move focus from 2016-12-17 to 2016-12-31:
EventUtils.synthesizeKey("VK_END", { ctrlKey: true });
Assert.equal(
pickerDoc.activeElement.textContent,
"31",
"Ctrl + End keys move focus to the last day of the current month"
);
// Move focus from 2016-12-31 to 2016-12-01:
EventUtils.synthesizeKey("VK_HOME", { ctrlKey: true });
Assert.equal(
pickerDoc.activeElement.textContent,
"1",
"Ctrl + Home keys move focus to the first day of the current month"
);
// Move focus from 2016-12-01 to 2016-11-27 (in the en-US locale):
EventUtils.synthesizeKey("VK_HOME", {});
Assert.equal(
pickerDoc.activeElement.textContent,
"27",
"Home key updates the view to be on the previous month, if needed"
);
Assert.equal(
helper.getElement(MONTH_YEAR).textContent,
DATE_FORMAT(new Date(prevMonth)),
"Home key updates the spinner to show the previous month, if needed"
);
// Move focus from 2016-11-27 to 2016-12-03 (in the en-US locale):
EventUtils.synthesizeKey("VK_END", {});
Assert.equal(
pickerDoc.activeElement.textContent,
"3",
"End key updates the view to be on the next month, if needed"
);
Assert.equal(
helper.getElement(MONTH_YEAR).textContent,
DATE_FORMAT(new Date(inputValue)),
"End key updates the spinner to show the next month, if needed"
);
await helper.tearDown();
});
/**
* Ensure calendar follows Page Up/Down key bindings appropriately.
*/
add_task(async function test_datepicker_keyboard_home_end() {
info("Ensure calendar follows Page Up/Down key bindings appropriately.");
const inputValue = "2023-01-31";
const prevMonth = "2022-12-31";
const nextMonth = "2023-01-31";
const nextShortMonth = "2023-03-03";
await helper.openPicker(
`data:text/html,<input id=date type=date value=${inputValue}>`
);
let pickerDoc = helper.panel.querySelector("#dateTimePopupFrame")
.contentDocument;
Assert.equal(helper.panel.state, "open", "Panel should be opened");
// Move focus from 2023-01-31 to 2022-12-31:
EventUtils.synthesizeKey("KEY_PageUp", {});
Assert.equal(
pickerDoc.activeElement.textContent,
"31",
"Page Up key moves focus to the same day of the previous month"
);
Assert.equal(
helper.getElement(MONTH_YEAR).textContent,
DATE_FORMAT(new Date(prevMonth)),
"Page Up key updates the spinner to show the previous month"
);
// Move focus from 2022-12-31 to 2022-12-01:
EventUtils.synthesizeKey("KEY_PageUp", {});
Assert.equal(
pickerDoc.activeElement.textContent,
"1",
`When the same day does not exists in the previous month Page Up key moves
focus to the same day of the same week of the current month`
);
Assert.equal(
helper.getElement(MONTH_YEAR).textContent,
DATE_FORMAT(new Date(prevMonth)),
"Page Up key keeps the spinner to show the current month"
);
// Move focus from 2016-12-01 to 2016-12-31:
EventUtils.synthesizeKey("VK_END", { ctrlKey: true });
// Move focus from 2022-12-31 to 2023-01-31:
EventUtils.synthesizeKey("KEY_PageDown", {});
Assert.equal(
pickerDoc.activeElement.textContent,
"31",
"Page Down key moves focus to the same day of the next month"
);
Assert.equal(
helper.getElement(MONTH_YEAR).textContent,
DATE_FORMAT(new Date(nextMonth)),
"Page Down key updates the spinner to show the next month"
);
// Move focus from 2023-01-31 to 2023-03-03:
EventUtils.synthesizeKey("KEY_PageDown", {});
Assert.equal(
pickerDoc.activeElement.textContent,
"3",
`When the same day does not exists in the next month, Page Down key moves
focus to the same day of the same week of the month after`
);
Assert.equal(
helper.getElement(MONTH_YEAR).textContent,
DATE_FORMAT(new Date(nextShortMonth)),
"Page Down key updates the spinner to show the month after"
);
await helper.tearDown();
});

Просмотреть файл

@ -1,480 +0,0 @@
/* 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",
WEEK_HEADER = ".week-header",
DAYS_VIEW = ".days-view",
DAY_TODAY = ".today",
DAY_SELECTED = ".selection",
BTN_PREV_MONTH = ".prev",
BTN_NEXT_MONTH = ".next",
DIALOG_PICKER = "#date-picker",
MONTH_YEAR_NAV = ".month-year-nav",
MONTH_YEAR_VIEW = ".month-year-view",
SPINNER_MONTH = "#spinner-month",
SPINNER_YEAR = "#spinner-year";
/**
* Helper function to check the value of a Calendar button's specific attribute
*
* @param {String} attr: The name of the attribute to be tested
* @param {String} val: Value that is expected to be assigned to the attribute.
* @param {Boolean} presenceOnly: If "true", test only the presence of the attribute
*/
async function testCalendarBtnAttribute(attr, val, presenceOnly = false) {
let browser = helper.tab.linkedBrowser;
await SpecialPowers.spawn(
browser,
[attr, val, presenceOnly],
(attr, val, presenceOnly) => {
const input = content.document.querySelector("input");
const shadowRoot = SpecialPowers.wrap(input).openOrClosedShadowRoot;
const calendarBtn = shadowRoot.getElementById("calendar-button");
if (presenceOnly) {
Assert.ok(
calendarBtn.hasAttribute(attr),
`Calendar button has ${attr} attribute`
);
} else {
Assert.equal(
calendarBtn.getAttribute(attr),
val,
`Calendar button has ${attr} attribute set to ${val}`
);
}
}
);
}
let helper = new DateTimeTestHelper();
registerCleanupFunction(() => {
helper.cleanup();
});
/**
* Test that date picker opens with accessible markup
*/
add_task(async function test_datepicker_markup() {
info("Test that the date picker opens with accessible markup");
await helper.openPicker("data:text/html, <input type='date'>");
Assert.equal(
helper.getElement(DIALOG_PICKER).getAttribute("role"),
"dialog",
"Datepicker dialog has an appropriate ARIA role"
);
Assert.ok(
helper.getElement(DIALOG_PICKER).getAttribute("aria-modal"),
"Datepicker dialog is a modal"
);
Assert.equal(
helper.getElement(BTN_PREV_MONTH).tagName,
"button",
"Previous Month control is a button"
);
Assert.equal(
helper.getElement(MONTH_YEAR).tagName,
"button",
"Month picker view toggle is a button"
);
Assert.equal(
helper.getElement(MONTH_YEAR).getAttribute("aria-expanded"),
"false",
"Month picker view toggle is collapsed when the dialog is hidden"
);
Assert.equal(
helper.getElement(MONTH_YEAR).getAttribute("aria-live"),
"polite",
"Month picker view toggle is a live region when it's not expanded"
);
Assert.ok(
BrowserTestUtils.is_hidden(helper.getElement(MONTH_YEAR_VIEW)),
"Month-year selection spinner is not visible"
);
Assert.ok(
BrowserTestUtils.is_hidden(helper.getElement(MONTH_YEAR_VIEW)),
"Month-year selection spinner is programmatically hidden"
);
Assert.equal(
helper.getElement(BTN_NEXT_MONTH).tagName,
"button",
"Next Month control is a button"
);
Assert.equal(
helper.getElement(DAYS_VIEW).parentNode.tagName,
"table",
"Calendar view is marked up as a table"
);
Assert.equal(
helper.getElement(DAYS_VIEW).parentNode.getAttribute("role"),
"grid",
"Calendar view is a grid"
);
Assert.ok(
helper.getElement(
`#${helper
.getElement(DAYS_VIEW)
.parentNode.getAttribute("aria-labelledby")}`
),
"Calendar view has a valid accessible name"
);
Assert.equal(
helper.getElement(WEEK_HEADER).firstChild.tagName,
"tr",
"Week headers within the Calendar view are marked up as table rows"
);
Assert.equal(
helper.getElement(WEEK_HEADER).firstChild.firstChild.tagName,
"th",
"Weekdays within the Calendar view are marked up as header cells"
);
Assert.equal(
helper.getElement(WEEK_HEADER).firstChild.firstChild.getAttribute("role"),
"columnheader",
"Weekdays within the Calendar view are grid column headers"
);
Assert.equal(
helper.getElement(DAYS_VIEW).firstChild.tagName,
"tr",
"Weeks within the Calendar view are marked up as table rows"
);
Assert.equal(
helper.getElement(DAYS_VIEW).firstChild.firstChild.tagName,
"td",
"Days within the Calendar view are marked up as table cells"
);
Assert.equal(
helper.getElement(DAYS_VIEW).firstChild.firstChild.getAttribute("role"),
"gridcell",
"Days within the Calendar view are gridcells"
);
await helper.tearDown();
});
/**
* Test that date picker has localizable labels
*/
add_task(async function test_datepicker_l10n() {
info("Test that the date picker has localizable labels");
await helper.openPicker("data:text/html, <input type='date'>");
const testcases = [
{
selector: DIALOG_PICKER,
id: "date-picker-label",
args: null,
},
{
selector: MONTH_YEAR_NAV,
id: "date-spinner-label",
args: null,
},
{
selector: BTN_PREV_MONTH,
id: "date-picker-previous",
args: null,
},
{
selector: BTN_NEXT_MONTH,
id: "date-picker-next",
args: null,
},
];
// Check "aria-label" attributes
for (let { selector, id, args } of testcases) {
const el = helper.getElement(selector);
const l10nAttrs = document.l10n.getAttributes(el);
Assert.ok(
helper.getElement(selector).hasAttribute("aria-label"),
`Datepicker "${selector}" element has accessible name`
);
Assert.deepEqual(
l10nAttrs,
{
id,
args,
},
`Datepicker "${selector}" element's accessible name is localizable`
);
}
await helper.tearDown();
});
/**
* Test that date picker opens to today's date, with today's and selected days
* marked up correctly, given a date value is set.
*/
add_task(async function test_datepicker_today_and_selected() {
info("Test today's and selected days' markup when a date value is set");
const date = new Date();
let inputValue = new Date();
// Both 2 and 10 dates are used as an example only to test that
// the current date and selected dates are marked up differently.
if (date.getDate() === 2) {
inputValue.setDate(10);
} else {
inputValue.setDate(2);
}
inputValue = inputValue.toISOString().split("T")[0];
await helper.openPicker(
`data:text/html, <input type="date" value="${inputValue}"> `
);
if (date.getMonth() === new Date().getMonth()) {
Assert.notEqual(
helper.getElement(DAY_TODAY),
helper.getElement(DAY_SELECTED),
"Today and selected dates are different"
);
Assert.equal(
helper.getElement(DAY_TODAY).getAttribute("aria-current"),
"date",
"Today's date is programmatically current"
);
Assert.equal(
helper.getElement(DAY_SELECTED).getAttribute("aria-selected"),
"true",
"Chosen date is programmatically selected"
);
Assert.ok(
!helper.getElement(DAY_TODAY).hasAttribute("tabindex"),
"Today is not included in the focus order, when another day is selected"
);
Assert.equal(
helper.getElement(DAY_SELECTED).getAttribute("tabindex"),
"0",
"Selected date is included in the focus order"
);
} else {
Assert.ok(
true,
"Skipping datepicker today test if month changes when opening picker."
);
}
await helper.tearDown();
});
/**
* Test that date picker refreshes ARIA properties
* after the other month was displayed.
*/
add_task(async function test_datepicker_markup_refresh() {
const inputValue = "2016-12-05";
const minValue = "2016-12-05";
await helper.openPicker(
`data:text/html, <input type="date" value="${inputValue}" min="${minValue}">`
);
const secondRowDec = helper.getChildren(DAYS_VIEW)[1].children;
// 2016-12-05 Monday is selected (in en_US locale)
if (secondRowDec[1] === helper.getElement(DAY_SELECTED)) {
Assert.equal(
secondRowDec[1].getAttribute("aria-selected"),
"true",
"Chosen date is programmatically selected"
);
Assert.ok(
!secondRowDec[1].classList.contains("out-of-range"),
"Chosen date is not styled as out-of-range"
);
Assert.ok(
!secondRowDec[1].hasAttribute("aria-disabled"),
"Chosen date is not programmatically disabled"
);
// I.e. 2016-12-04 Sunday is out-of-range (in en_US locale)
Assert.ok(
secondRowDec[0].classList.contains("out-of-range"),
"Less than min date is styled as out-of-range"
);
Assert.equal(
secondRowDec[0].getAttribute("aria-disabled"),
"true",
"Less than min date is programmatically disabled"
);
// Change month view to check an updated markup
helper.click(helper.getElement(BTN_NEXT_MONTH));
const secondRowJan = helper.getChildren(DAYS_VIEW)[1].children;
// 2017-01-02 Monday is not selected and in-range (in en_US locale)
Assert.equal(
secondRowJan[1].getAttribute("aria-selected"),
"false",
"Day with the same position as selected is not programmatically selected"
);
Assert.ok(
!secondRowJan[1].classList.contains("out-of-range"),
"Day with the same position as selected is not styled as out-of-range"
);
Assert.ok(
!secondRowJan[1].hasAttribute("aria-disabled"),
"Day with the same position as selected is not programmatically disabled"
);
// I.e. 2017-01-01 Sunday is in-range (in en_US locale)
Assert.ok(
!secondRowJan[0].classList.contains("out-of-range"),
"Day with the same as less than min date is not styled as out-of-range"
);
Assert.ok(
!secondRowJan[0].hasAttribute("aria-disabled"),
"Day with the same as less than min date is not programmatically disabled"
);
Assert.equal(
secondRowJan[0].getAttribute("tabindex"),
"0",
"The first day of the month is made focusable"
);
Assert.ok(
!secondRowJan[1].hasAttribute("tabindex"),
"Day with the same position as selected is not focusable"
);
Assert.ok(!helper.getElement(DAY_TODAY), "No date is marked up as today");
Assert.ok(
!helper.getElement(DAY_SELECTED),
"No date is marked up as selected"
);
} else {
Assert.ok(
true,
"Skipping datepicker attributes flushing test if the week/locale is different from the en_US used for the test"
);
}
await helper.tearDown();
});
/**
* Test that date input field has a Calendar button with an accessible markup
*/
add_task(async function test_calendar_button_markup_date() {
info(
"Test that type=date input field has a Calendar button with an accessible markup"
);
await helper.openPicker("data:text/html, <input type='date'>");
let browser = helper.tab.linkedBrowser;
Assert.equal(helper.panel.state, "open", "Panel is visible");
let closed = helper.promisePickerClosed();
await testCalendarBtnAttribute("aria-expanded", "true");
await testCalendarBtnAttribute("aria-label", null, true);
await testCalendarBtnAttribute("data-l10n-id", "datetime-calendar");
await SpecialPowers.spawn(browser, [], () => {
const input = content.document.querySelector("input");
const shadowRoot = SpecialPowers.wrap(input).openOrClosedShadowRoot;
const calendarBtn = shadowRoot.getElementById("calendar-button");
Assert.equal(calendarBtn.tagName, "BUTTON", "Calendar control is a button");
Assert.ok(
ContentTaskUtils.is_visible(calendarBtn),
"The Calendar button is visible"
);
calendarBtn.click();
});
await closed;
Assert.equal(
helper.panel.state,
"closed",
"Panel should be closed on click on the Calendar button"
);
await testCalendarBtnAttribute("aria-expanded", "false");
await helper.tearDown();
});
/**
* Test that datetime-local input field has a Calendar button
* with an accessible markup
*/
add_task(async function test_calendar_button_markup_datetime() {
info(
"Test that type=datetime-local input field has a Calendar button with an accessible markup"
);
await helper.openPicker("data:text/html, <input type='datetime-local'>");
let browser = helper.tab.linkedBrowser;
Assert.equal(helper.panel.state, "open", "Panel is visible");
let closed = helper.promisePickerClosed();
await testCalendarBtnAttribute("aria-expanded", "true");
await testCalendarBtnAttribute("aria-label", null, true);
await testCalendarBtnAttribute("data-l10n-id", "datetime-calendar");
await SpecialPowers.spawn(browser, [], () => {
const input = content.document.querySelector("input");
const shadowRoot = SpecialPowers.wrap(input).openOrClosedShadowRoot;
const calendarBtn = shadowRoot.getElementById("calendar-button");
Assert.equal(calendarBtn.tagName, "BUTTON", "Calendar control is a button");
Assert.ok(
ContentTaskUtils.is_visible(calendarBtn),
"The Calendar button is visible"
);
calendarBtn.click();
});
await closed;
Assert.equal(
helper.panel.state,
"closed",
"Panel should be closed on click on the Calendar button"
);
await testCalendarBtnAttribute("aria-expanded", "false");
await helper.tearDown();
});
/**
* Test that time input field does not include a Calendar button
*/
add_task(async function test_calendar_button_markup_time() {
info("Test that type=time input field does not include a Calendar button");
let testTab = await BrowserTestUtils.openNewForegroundTab(
gBrowser,
"data:text/html, <input type='time'>"
);
await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
const input = content.document.querySelector("input");
const shadowRoot = SpecialPowers.wrap(input).openOrClosedShadowRoot;
const calendarBtn = shadowRoot.getElementById("calendar-button");
Assert.ok(
ContentTaskUtils.is_hidden(calendarBtn),
"The Calendar control within a type=time input field is programmatically hidden"
);
});
BrowserTestUtils.removeTab(testTab);
});

Просмотреть файл

@ -1,199 +0,0 @@
/* 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 = new Intl.DateTimeFormat("en-US", {
year: "numeric",
month: "long",
timeZone: "UTC",
}).format;
function getCalendarText() {
let calendarCells = [];
for (const tr of helper.getChildren(DAYS_VIEW)) {
for (const td of tr.children) {
calendarCells.push(td.textContent);
}
}
return calendarCells;
}
let helper = new DateTimeTestHelper();
registerCleanupFunction(() => {
helper.cleanup();
});
/**
* When the prev month button is clicked, calendar should display the dates for
* the previous month.
*/
add_task(async function test_datepicker_prev_month_btn() {
const inputValue = "2016-12-15";
const prevMonth = "2016-11-01";
await helper.openPicker(
`data:text/html, <input type="date" value="${inputValue}">`
);
helper.click(helper.getElement(BTN_PREV_MONTH));
Assert.equal(
helper.getElement(MONTH_YEAR).textContent,
DATE_FORMAT(new Date(prevMonth))
);
Assert.deepEqual(
getCalendarText(),
[
"30",
"31",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"10",
"11",
"12",
"13",
"14",
"15",
"16",
"17",
"18",
"19",
"20",
"21",
"22",
"23",
"24",
"25",
"26",
"27",
"28",
"29",
"30",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"10",
],
"2016-11"
);
await helper.tearDown();
});
/**
* When the next month button is clicked, calendar should display the dates for
* the next month.
*/
add_task(async function test_datepicker_next_month_btn() {
const inputValue = "2016-12-15";
const nextMonth = "2017-01-01";
await helper.openPicker(
`data:text/html, <input type="date" value="${inputValue}">`
);
helper.click(helper.getElement(BTN_NEXT_MONTH));
Assert.equal(
helper.getElement(MONTH_YEAR).textContent,
DATE_FORMAT(new Date(nextMonth))
);
Assert.deepEqual(
getCalendarText(),
[
"25",
"26",
"27",
"28",
"29",
"30",
"31",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"10",
"11",
"12",
"13",
"14",
"15",
"16",
"17",
"18",
"19",
"20",
"21",
"22",
"23",
"24",
"25",
"26",
"27",
"28",
"29",
"30",
"31",
"1",
"2",
"3",
"4",
],
"2017-01"
);
await helper.tearDown();
});
/**
* When a date on the calendar is clicked, date picker should close and set
* value to the input box.
*/
add_task(async function test_datepicker_clicked() {
info("When a calendar day is clicked, the picker closes, the value is set");
const inputValue = "2016-12-15";
const firstDayOnCalendar = "2016-11-27";
await helper.openPicker(
`data:text/html, <input id="date" type="date" value="${inputValue}">`
);
let browser = helper.tab.linkedBrowser;
Assert.equal(helper.panel.state, "open", "Panel should be opened");
// Click the first item (top-left corner) of the calendar
let promise = BrowserTestUtils.waitForContentEvent(browser, "input");
helper.click(helper.getElement(DAYS_VIEW).querySelector("td"));
await promise;
let value = await SpecialPowers.spawn(browser, [], () => {
return content.document.querySelector("input").value;
});
Assert.equal(value, firstDayOnCalendar);
await helper.tearDown();
});

Просмотреть файл

@ -1,228 +0,0 @@
/* 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",
BTN_MONTH_YEAR = "#month-year-label",
SPINNER_MONTH = "#spinner-month",
SPINNER_YEAR = "#spinner-year";
const DATE_FORMAT = new Intl.DateTimeFormat("en-US", {
year: "numeric",
month: "long",
timeZone: "UTC",
}).format;
/**
* Helper function to check if a DOM element has a specific attribute
*
* @param {DOMElement} el: DOM Element to be tested
* @param {String} attr: The name of the attribute to be tested
*/
function testAttribute(el, attr) {
Assert.ok(
el.hasAttribute(attr),
`The "${el}" element has a "${attr}" attribute`
);
}
/**
* Helper function to check for localization attributes of a DOM element
*
* @param {DOMElement} el: DOM Element to be tested
* @param {String} id: Value of the "data-l10n-id" attribute of the element
* @param {Object} args: Args provided by the l10n object of the element
*/
function testLocalization(el, id, args = null) {
const l10nAttrs = document.l10n.getAttributes(el);
Assert.deepEqual(
l10nAttrs,
{
id,
args,
},
`The "${id}" element is localizable`
);
}
/**
* Helper function to check for l10n of an element's attribute
*
* @param {DOMElement} el: DOM Element to be tested
* @param {String} attr: The name of the attribute to be tested
* @param {String} id: Value of the "data-l10n-id" attribute of the element
* @param {Object} args: Args provided by the l10n object of the element
*/
function testAttributeL10n(el, attr, id, args = null) {
testAttribute(el, attr);
testLocalization(el, id, args);
}
let helper = new DateTimeTestHelper();
registerCleanupFunction(() => {
helper.cleanup();
});
/**
* Test that the Month spinner opens with an accessible markup
*/
add_task(async function test_spinner_month_markup() {
info("Test that the Month spinner opens with an accessible markup");
const inputValue = "2022-09-09";
await helper.openPicker(
`data:text/html, <input type="date" value="${inputValue}">`
);
helper.click(helper.getElement(MONTH_YEAR));
const spinnerMonth = helper.getElement(SPINNER_MONTH);
const spinnerMonthPrev = spinnerMonth.children[0];
const spinnerMonthBtn = spinnerMonth.children[1];
const spinnerMonthNext = spinnerMonth.children[2];
Assert.equal(
spinnerMonthPrev.tagName,
"button",
"Spinner's Previous Month control is a button"
);
Assert.equal(
spinnerMonthBtn.getAttribute("role"),
"spinbutton",
"Spinner control is a spinbutton"
);
Assert.equal(
spinnerMonthBtn.getAttribute("tabindex"),
"0",
"Spinner control is included in the focus order"
);
Assert.equal(
spinnerMonthBtn.getAttribute("aria-valuemin"),
"0",
"Spinner control has a min value set"
);
Assert.equal(
spinnerMonthBtn.getAttribute("aria-valuemax"),
"11",
"Spinner control has a max value set"
);
// September 2022 as an example
Assert.equal(
spinnerMonthBtn.getAttribute("aria-valuenow"),
"8",
"Spinner control has a current value set"
);
Assert.equal(
spinnerMonthNext.tagName,
"button",
"Spinner's Next Month control is a button"
);
testAttribute(spinnerMonthBtn, "aria-valuetext");
let visibleEls = spinnerMonthBtn.querySelectorAll(
":scope > :not([aria-hidden])"
);
Assert.equal(
visibleEls.length,
0,
"There should be no children of the spinner without aria-hidden"
);
info("Test that the month spinner has localizable labels");
testAttributeL10n(
spinnerMonthPrev,
"aria-label",
"date-spinner-month-previous"
);
testAttributeL10n(spinnerMonthBtn, "aria-label", "date-spinner-month");
testAttributeL10n(spinnerMonthNext, "aria-label", "date-spinner-month-next");
await helper.tearDown();
});
/**
* Test that the Year spinner opens with an accessible markup
*/
add_task(async function test_spinner_year_markup() {
info("Test that the year spinner opens with an accessible markup");
const inputValue = "2022-06-06";
const inputMin = "2020-06-01";
const inputMax = "2030-12-31";
await helper.openPicker(
`data:text/html, <input type="date" value="${inputValue}" min="${inputMin}" max="${inputMax}">`
);
helper.click(helper.getElement(MONTH_YEAR));
const spinnerYear = helper.getElement(SPINNER_YEAR);
const spinnerYearPrev = spinnerYear.children[0];
const spinnerYearBtn = spinnerYear.children[1];
const spinnerYearNext = spinnerYear.children[2];
Assert.equal(
spinnerYearPrev.tagName,
"button",
"Spinner's Previous Year control is a button"
);
Assert.equal(
spinnerYearBtn.getAttribute("role"),
"spinbutton",
"Spinner control is a spinbutton"
);
Assert.equal(
spinnerYearBtn.getAttribute("tabindex"),
"0",
"Spinner control is included in the focus order"
);
Assert.equal(
spinnerYearBtn.getAttribute("aria-valuemin"),
"2020",
"Spinner control has a min value set, when the range is provided"
);
// 2020-2030 range is an example
Assert.equal(
spinnerYearBtn.getAttribute("aria-valuemax"),
"2030",
"Spinner control has a max value set, when the range is provided"
);
// June 2022 is an example
Assert.equal(
spinnerYearBtn.getAttribute("aria-valuenow"),
"2022",
"Spinner control has a current value set"
);
Assert.equal(
spinnerYearNext.tagName,
"button",
"Spinner's Next Year control is a button"
);
testAttribute(spinnerYearBtn, "aria-valuetext");
let visibleEls = spinnerYearBtn.querySelectorAll(
":scope > :not([aria-hidden])"
);
Assert.equal(
visibleEls.length,
0,
"There should be no children of the spinner without aria-hidden"
);
info("Test that the year spinner has localizable labels");
testAttributeL10n(
spinnerYearPrev,
"aria-label",
"date-spinner-year-previous"
);
testAttributeL10n(spinnerYearBtn, "aria-label", "date-spinner-year");
testAttributeL10n(spinnerYearNext, "aria-label", "date-spinner-year-next");
await helper.tearDown();
});

Просмотреть файл

@ -1,223 +0,0 @@
/* 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 BTN_MONTH_YEAR = "#month-year-label",
SPINNER_MONTH = "#spinner-month",
SPINNER_YEAR = "#spinner-year",
MONTH_YEAR = ".month-year";
const DATE_FORMAT = new Intl.DateTimeFormat("en-US", {
year: "numeric",
month: "long",
timeZone: "UTC",
}).format;
let helper = new DateTimeTestHelper();
registerCleanupFunction(() => {
helper.cleanup();
});
/**
* Ensure the month spinner follows arrow key bindings appropriately.
*/
add_task(async function test_spinner_month_keyboard_arrows() {
info("Ensure the month spinner follows arrow key bindings appropriately.");
const inputValue = "2022-12-10";
const nextMonth = "2022-01-01";
await helper.openPicker(
`data:text/html, <input type="date" value="${inputValue}">`
);
let browser = helper.tab.linkedBrowser;
let pickerDoc = helper.panel.querySelector("#dateTimePopupFrame")
.contentDocument;
info("Testing general keyboard navigation");
Assert.equal(
helper.getElement(BTN_MONTH_YEAR).getAttribute("aria-expanded"),
"false",
"Month-year button is collapsed when a picker is opened (by default)"
);
// Move focus from the selection to the month-year toggle button:
EventUtils.synthesizeKey("KEY_Tab", {});
EventUtils.synthesizeKey("KEY_Tab", {});
// Open month-year selection panel with spinners:
EventUtils.synthesizeKey(" ", {});
const spinnerMonthBtn = helper.getElement(SPINNER_MONTH).children[1];
const spinnerYearBtn = helper.getElement(SPINNER_YEAR).children[1];
Assert.equal(
helper.getElement(BTN_MONTH_YEAR).getAttribute("aria-expanded"),
"true",
"Month-year button is expanded when the spinners are shown"
);
// December 2022 is an example:
Assert.equal(
pickerDoc.activeElement.textContent,
DATE_FORMAT(new Date(inputValue)),
"Month-year toggle button is focused"
);
Assert.equal(
spinnerMonthBtn.getAttribute("aria-valuenow"),
"11",
"Month Spinner control is ready"
);
Assert.equal(
spinnerYearBtn.getAttribute("aria-valuenow"),
"2022",
"Year Spinner control is ready"
);
// Move focus from the month-year toggle button to the month spinner:
EventUtils.synthesizeKey("KEY_Tab", {});
Assert.equal(
pickerDoc.activeElement.getAttribute("aria-valuenow"),
"11",
"Tab moves focus to the month spinner"
);
info("Testing Up/Down Arrow keys behavior of the Month Spinner");
// Change the month-year from December 2022 to January 2022:
EventUtils.synthesizeKey("VK_DOWN", {});
await BrowserTestUtils.waitForContentEvent(browser, "input");
Assert.equal(
spinnerMonthBtn.getAttribute("aria-valuenow"),
"0",
"Down Arrow selects the next month"
);
Assert.equal(
spinnerYearBtn.getAttribute("aria-valuenow"),
"2022",
"Down Arrow on a month spinner does not update the year"
);
Assert.equal(
helper.getElement(BTN_MONTH_YEAR).textContent,
DATE_FORMAT(new Date(nextMonth)),
"Down Arrow updates the month-year button to the next month"
);
// Change the month-year from January 2022 to December 2022:
await EventUtils.synthesizeKey("VK_UP", {});
await BrowserTestUtils.waitForContentEvent(browser, "input");
Assert.equal(
spinnerMonthBtn.getAttribute("aria-valuenow"),
"11",
"Up Arrow selects the previous month"
);
Assert.equal(
spinnerYearBtn.getAttribute("aria-valuenow"),
"2022",
"Up Arrow on a month spinner does not update the year"
);
Assert.equal(
helper.getElement(BTN_MONTH_YEAR).textContent,
DATE_FORMAT(new Date(inputValue)),
"Up Arrow updates the month-year button to the previous month"
);
await helper.tearDown();
});
// PageUp/PageDown and Home/End Tests are to be added per bug 1803664
/**
* Ensure the year spinner follows arrow key bindings appropriately.
*/
add_task(async function test_spinner_year_keyboard_arrows() {
info("Ensure the year spinner follows arrow key bindings appropriately.");
const inputValue = "2022-12-10";
const nextYear = "2023-12-01";
await helper.openPicker(
`data:text/html, <input type="date" value="${inputValue}">`
);
let browser = helper.tab.linkedBrowser;
let pickerDoc = helper.panel.querySelector("#dateTimePopupFrame")
.contentDocument;
info("Testing general keyboard navigation");
// Move focus from the selection to the month-year toggle button:
EventUtils.synthesizeKey("KEY_Tab", {});
EventUtils.synthesizeKey("KEY_Tab", {});
// Open month-year selection panel with spinners:
EventUtils.synthesizeKey(" ", {});
const spinnerMonthBtn = helper.getElement(SPINNER_MONTH).children[1];
const spinnerYearBtn = helper.getElement(SPINNER_YEAR).children[1];
// December 2022 is an example:
Assert.equal(
spinnerYearBtn.getAttribute("aria-valuenow"),
"2022",
"Year Spinner control is ready"
);
// Move focus from the month-year toggle button to the year spinner:
EventUtils.synthesizeKey("KEY_Tab", {});
EventUtils.synthesizeKey("KEY_Tab", {});
Assert.equal(
pickerDoc.activeElement.getAttribute("aria-valuenow"),
"2022",
"Tab can move the focus to the year spinner"
);
info("Testing Up/Down Arrow keys behavior of the Year Spinner");
// Change the month-year from December 2022 to December 2023:
EventUtils.synthesizeKey("VK_DOWN", {});
await BrowserTestUtils.waitForContentEvent(browser, "input");
Assert.equal(
spinnerMonthBtn.getAttribute("aria-valuenow"),
"11",
"Down Arrow on the year spinner does not change the month"
);
Assert.equal(
spinnerYearBtn.getAttribute("aria-valuenow"),
"2023",
"Down Arrow updates the year to the next"
);
Assert.equal(
helper.getElement(BTN_MONTH_YEAR).textContent,
DATE_FORMAT(new Date(nextYear)),
"Down Arrow updates the month-year button to the next year"
);
// Change the month-year from December 2023 to December 2022:
EventUtils.synthesizeKey("VK_UP", {});
await BrowserTestUtils.waitForContentEvent(browser, "input");
Assert.equal(
spinnerMonthBtn.getAttribute("aria-valuenow"),
"11",
"Up Arrow on the year spinner does not change the month"
);
Assert.equal(
spinnerYearBtn.getAttribute("aria-valuenow"),
"2022",
"Up Arrow updates the year to the previous"
);
Assert.equal(
helper.getElement(BTN_MONTH_YEAR).textContent,
DATE_FORMAT(new Date(inputValue)),
"Up Arrow updates the month-year button to the previous year"
);
await helper.tearDown();
});
// PageUp/PageDown and Home/End Tests are to be added per bug 1803664

Просмотреть файл

@ -206,11 +206,7 @@ class DateTimeTestHelper {
});
if (openMethod === "click") {
await SpecialPowers.spawn(bc, [], () => {
const input = content.document.querySelector("input");
const shadowRoot = SpecialPowers.wrap(input).openOrClosedShadowRoot;
shadowRoot.getElementById("calendar-button").click();
});
await BrowserTestUtils.synthesizeMouseAtCenter("input", {}, bc);
} else if (openMethod === "showPicker") {
await SpecialPowers.spawn(bc, [], function() {
content.document.notifyUserGestureActivation();

Просмотреть файл

@ -14,7 +14,6 @@
* {Function} getDayString: Transform day number to string
* {Function} getWeekHeaderString: Transform day of week number to string
* {Function} setSelection: Set selection for dateKeeper
* {Function} setMonthByOffset: Update the month shown by the dateView
* }
* @param {Object} context
* {
@ -23,21 +22,18 @@
* }
*/
function Calendar(options, context) {
const DAYS_IN_A_WEEK = 7;
this.context = context;
this.context.DAYS_IN_A_WEEK = 7;
this.state = {
days: [],
weekHeaders: [],
setSelection: options.setSelection,
setMonthByOffset: options.setMonthByOffset,
getDayString: options.getDayString,
getWeekHeaderString: options.getWeekHeaderString,
};
this.elements = {
weekHeaders: this._generateNodes(
this.context.DAYS_IN_A_WEEK,
context.weekHeader
),
weekHeaders: this._generateNodes(DAYS_IN_A_WEEK, context.weekHeader),
daysView: this._generateNodes(options.calViewSize, context.daysView),
};
@ -111,10 +107,6 @@ Calendar.prototype = {
* }
*/
_render({ elements, items, prevState }) {
let selectedEl;
let todayEl;
let firstDayEl;
for (let i = 0, l = items.length; i < l; i++) {
let el = elements[i];
@ -125,61 +117,11 @@ Calendar.prototype = {
if (!prevState[i] || prevState[i].className != items[i].className) {
el.className = items[i].className;
}
if (el.tagName === "td") {
el.setAttribute("role", "gridcell");
// Flush states from the previous view
el.removeAttribute("tabindex");
el.removeAttribute("aria-disabled");
el.removeAttribute("aria-selected");
el.removeAttribute("aria-current");
// Set new states and properties
if (el.classList.contains("today")) {
// Current date/today is communicated to assistive technology
el.setAttribute("aria-current", "date");
todayEl = el;
}
if (el.classList.contains("selection")) {
// Selection is included in the focus order, if from the current month
el.setAttribute("aria-selected", "true");
if (!el.classList.contains("outside")) {
selectedEl = el;
}
} else if (el.classList.contains("out-of-range")) {
// Dates that are outside of the range are not selected and cannot be
el.setAttribute("aria-disabled", "true");
el.removeAttribute("aria-selected");
} else {
// Other dates are not selected, but could be
el.setAttribute("aria-selected", "false");
}
// When no selection or current day/today is present, make the first
// of the month focusable
if (el.textContent === "1" && !firstDayEl) {
let firstDay = new Date(items[i].dateObj);
firstDay.setUTCDate("1");
if (this._isSameDay(items[i].dateObj, firstDay)) {
firstDayEl = el;
}
}
}
}
// Selected date is always focusable on init, otherwise make focusable
// the current day/today or the first day of the month
if (selectedEl) {
selectedEl.setAttribute("tabindex", "0");
} else if (todayEl) {
todayEl.setAttribute("tabindex", "0");
} else if (firstDayEl) {
firstDayEl.setAttribute("tabindex", "0");
}
},
/**
* Generate DOM nodes with HTML table markup
* Generate DOM nodes
*
* @param {Number} size: Number of nodes to generate
* @param {DOMElement} context: Element to append the nodes to
@ -189,30 +131,11 @@ Calendar.prototype = {
let frag = document.createDocumentFragment();
let refs = [];
// Create table row to present a week:
let rowEl = document.createElement("tr");
for (let i = 0; i < size; i++) {
// Create table cell for a table header (weekday) or body (date)
let el;
if (context.classList.contains("week-header")) {
el = document.createElement("th");
el.setAttribute("scope", "col");
// Explicitly assigning the role as a workaround for the bug 1711273:
el.setAttribute("role", "columnheader");
} else {
el = document.createElement("td");
}
let el = document.createElement("div");
el.dataset.id = i;
refs.push(el);
rowEl.appendChild(el);
// Ensure each table row (week) has only
// seven table cells (days) for a Gregorian calendar
if ((i + 1) % this.context.DAYS_IN_A_WEEK === 0) {
frag.appendChild(rowEl);
rowEl = document.createElement("tr");
}
frag.appendChild(el);
}
context.appendChild(frag);
@ -226,7 +149,7 @@ Calendar.prototype = {
handleEvent(event) {
switch (event.type) {
case "click": {
if (this.context.daysView.contains(event.target)) {
if (event.target.parentNode == this.context.daysView) {
let targetId = event.target.dataset.id;
let targetObj = this.state.days[targetId];
if (targetObj.enabled) {
@ -235,149 +158,6 @@ Calendar.prototype = {
}
break;
}
case "keydown": {
// Providing keyboard navigation support in accordance with
// the ARIA Grid and Dialog design patterns
if (this.context.daysView.contains(event.target)) {
// If RTL, the offset direction for Right/Left needs to be reversed
const direction = Services.locale.isAppLocaleRTL ? -1 : 1;
switch (event.key) {
case "Enter":
case " ": {
let targetId = event.target.dataset.id;
let targetObj = this.state.days[targetId];
if (targetObj.enabled) {
this.state.setSelection(targetObj.dateObj);
}
break;
}
case "ArrowRight": {
// Moves focus to the next day. If the next day is
// out-of-range, update the view to show the next month
this._handleKeydownEvent(event.target, 1 * direction);
break;
}
case "ArrowLeft": {
// Moves focus to the previous day. If the next day is
// out-of-range, update the view to show the previous month
this._handleKeydownEvent(event.target, -1 * direction);
break;
}
case "ArrowUp": {
// Moves focus to the same day of the previous week. If the next
// day is out-of-range, update the view to show the previous month
this._handleKeydownEvent(
event.target,
-1,
this.context.DAYS_IN_A_WEEK
);
break;
}
case "ArrowDown": {
// Moves focus to the same day of the next week. If the next
// day is out-of-range, update the view to show the previous month
this._handleKeydownEvent(
event.target,
1,
this.context.DAYS_IN_A_WEEK
);
break;
}
case "Home": {
// Moves focus to the first day (ie. Sunday) of the current week
let nextId;
if (event.ctrlKey) {
// Moves focus to the first day of the current month
for (let i = 0; i < this.state.days.length; i++) {
if (this.state.days[i].dateObj.getUTCDate() == 1) {
nextId = i;
break;
}
}
} else {
nextId =
Number(event.target.dataset.id) -
(Number(event.target.dataset.id) %
this.context.DAYS_IN_A_WEEK);
nextId = this._updateViewIfOutside(nextId, -1);
}
this._updateKeyboardFocus(event.target, nextId);
break;
}
case "End": {
// Moves focus to the last day (ie. Saturday) of the current week
let nextId;
if (event.ctrlKey) {
// Moves focus to the last day of the current month
for (let i = this.state.days.length - 1; i >= 0; i--) {
if (this.state.days[i].dateObj.getUTCDate() == 1) {
nextId = i - 1;
break;
}
}
} else {
nextId =
Number(event.target.dataset.id) +
(this.context.DAYS_IN_A_WEEK - 1) -
(Number(event.target.dataset.id) %
this.context.DAYS_IN_A_WEEK);
nextId = this._updateViewIfOutside(nextId, 1);
}
this._updateKeyboardFocus(event.target, nextId);
break;
}
case "PageUp": {
// Changes the view to the previous month/year
// and sets focus on the same day.
// If that day does not exist, then moves focus
// to the same day of the same week.
let targetId = event.target.dataset.id;
let nextDate = this.state.days[targetId].dateObj;
if (event.shiftKey) {
// Previous year
this.state.setMonthByOffset(-12);
nextDate.setYear(nextDate.getFullYear() - 1);
} else {
// Previous month
this.state.setMonthByOffset(-1);
nextDate.setMonth(nextDate.getMonth() - 1);
}
let nextId = this._calculateNextId(nextDate);
// Outside dates for the previous month (ie. when moving from
// the March 30th back to February where 30th does not exist)
// occur at the end of the month, thus month offset is 1
nextId = this._updateViewIfOutside(nextId, 1);
this._updateKeyboardFocus(event.target, nextId);
break;
}
case "PageDown": {
// Changes the view to the next month/year
// and sets focus on the same day.
// If that day does not exist, then moves focus
// to the same day of the same week.
let targetId = event.target.dataset.id;
let nextDate = this.state.days[targetId].dateObj;
if (event.shiftKey) {
// Next year
this.state.setMonthByOffset(12);
nextDate.setYear(nextDate.getFullYear() + 1);
} else {
// Next month
this.state.setMonthByOffset(1);
nextDate.setMonth(nextDate.getMonth() + 1);
}
let nextId = this._calculateNextId(nextDate);
nextId = this._updateViewIfOutside(nextId, 1);
this._updateKeyboardFocus(event.target, nextId);
break;
}
}
}
break;
}
}
},
@ -386,113 +166,5 @@ Calendar.prototype = {
*/
_attachEventListeners() {
this.context.daysView.addEventListener("click", this);
this.context.daysView.addEventListener("keydown", this);
},
/**
* Find Data-id of the next element to focus on the daysView grid
* @param {Object} nextDate: Data object of the next element to focus
*/
_calculateNextId(nextDate) {
for (let i = 0; i < this.state.days.length; i++) {
if (this._isSameDay(this.state.days[i].dateObj, nextDate)) {
return i;
}
}
return null;
},
/**
* Comparing two date objects to ensure they produce the same date
* @param {Date} dateObj1: Date object from the updated state
* @param {Date} dateObj2: Date object from the previous state
* @return {Boolean} If two date objects are the same day
*/
_isSameDay(dateObj1, dateObj2) {
return (
dateObj1.getUTCFullYear() == dateObj2.getUTCFullYear() &&
dateObj1.getUTCMonth() == dateObj2.getUTCMonth() &&
dateObj1.getUTCDate() == dateObj2.getUTCDate()
);
},
/**
* Manage focus for the keyboard navigation for the daysView grid
* @param {DOMElement} eTarget: The event.target day element
* @param {Number} offsetDir: The direction where the focus should move,
* where a negative number (-1) moves backwards
* @param {Number} offsetSize: The number of days to move the focus by.
*/
_handleKeydownEvent(eTarget, offsetDir, offsetSize = 1) {
let offset = offsetDir * offsetSize;
let nextId = Number(eTarget.dataset.id) + offset;
if (!this.state.days[nextId]) {
nextId = this._updateViewIfUndefined(nextId, offset, eTarget.dataset.id);
}
nextId = this._updateViewIfOutside(nextId, offsetDir);
this._updateKeyboardFocus(eTarget, nextId);
},
/**
* Add gridcell attributes and move focus to the next dayView element
* @param {DOMElement} targetEl: Day element as an event.target
* @param {Number} nextId: The data-id of the next HTML day element to focus
*/
_updateKeyboardFocus(targetEl, nextId) {
const nextEl = this.elements.daysView[nextId];
targetEl.removeAttribute("tabindex");
nextEl.setAttribute("tabindex", "0");
nextEl.focus();
},
/**
* Find Data-id of the next element to focus on the daysView grid if
* the next element has "outside" class and belongs to another month
* @param {Number} nextId: The data-id of the next HTML day element to focus
* @param {Number} offset: The direction for the month view offset
* @return {Number} The data-id of the next HTML day element to focus
*/
_updateViewIfOutside(nextId, offset) {
if (this.elements.daysView[nextId].classList.contains("outside")) {
let nextDate = this.state.days[nextId].dateObj;
this.state.setMonthByOffset(offset);
nextId = this._calculateNextId(nextDate);
}
return nextId;
},
/**
* Find Data-id of the next element to focus on the daysView grid if
* the next element is outside of the current daysView calendar
* @param {Number} nextId: The data-id of the next HTML day element to focus
* @param {Number} offset: The number of days to move by,
* where a negative number moves backwards.
* @param {Number} targetId: The data-id for the event target day element
* @return {Number} The data-id of the next HTML day element to focus
*/
_updateViewIfUndefined(nextId, offset, targetId) {
let targetDate = this.state.days[targetId].dateObj;
let nextDate = targetDate;
// Get the date that needs to be focused next:
nextDate.setDate(targetDate.getDate() + offset);
// Update the month view to include this date:
this.state.setMonthByOffset(Math.sign(offset));
// Find the "data-id" of the date element:
nextId = this._calculateNextId(nextDate);
return nextId;
},
/**
* Place keyboard focus on the calendar grid, when the datepicker is initiated.
* The selected day is what gets focused, if such a day exists. If it does not,
* today's date will be focused.
*/
focus() {
const focus = this.context.daysView.querySelector('[tabindex="0"]');
if (focus) {
focus.focus();
}
},
};

Просмотреть файл

@ -40,7 +40,6 @@ function DatePicker(context) {
this._setDefaultState();
this._createComponents();
this._update();
this.components.calendar.focus();
document.dispatchEvent(new CustomEvent("PickerReady"));
},
@ -99,11 +98,6 @@ function DatePicker(context) {
this._dispatchState();
this._closePopup();
},
setMonthByOffset: offset => {
dateKeeper.setMonthByOffset(offset);
this._update();
this._dispatchState();
},
setYear: year => {
dateKeeper.setYear(year);
dateKeeper.setSelection({
@ -141,7 +135,6 @@ function DatePicker(context) {
calViewSize: CAL_VIEW_SIZE,
locale: this.state.locale,
setSelection: this.state.setSelection,
setMonthByOffset: this.state.setMonthByOffset,
getDayString: this.state.getDayString,
getWeekHeaderString: this.state.getWeekHeaderString,
},
@ -172,26 +165,11 @@ function DatePicker(context) {
_update(options = {}) {
const { dateKeeper, isMonthPickerVisible } = this.state;
const calendarEls = [
this.context.buttonPrev,
this.context.buttonNext,
this.context.weekHeader.parentNode,
];
// Update MonthYear state and toggle visibility for sighted users
// and for assistive technology:
if (isMonthPickerVisible) {
this.state.months = dateKeeper.getMonths();
this.state.years = dateKeeper.getYears();
this.context.monthYearView.hidden = false;
for (let el of calendarEls) {
el.hidden = true;
}
} else {
this.state.days = dateKeeper.getDays();
this.context.monthYearView.hidden = true;
for (let el of calendarEls) {
el.hidden = false;
}
}
this.components.monthYear.setProps({
@ -207,6 +185,10 @@ function DatePicker(context) {
days: this.state.days,
weekHeaders: dateKeeper.state.weekHeaders,
});
isMonthPickerVisible
? this.context.monthYearView.classList.remove("hidden")
: this.context.monthYearView.classList.add("hidden");
},
/**
@ -226,7 +208,6 @@ function DatePicker(context) {
*/
_dispatchState() {
const { year, month, day } = this.state.dateKeeper.selection;
// The panel is listening to window for postMessage event, so we
// do postMessage to itself to send data to input boxes.
window.postMessage(
@ -249,7 +230,6 @@ function DatePicker(context) {
window.addEventListener("message", this);
document.addEventListener("mouseup", this, { passive: true });
document.addEventListener("mousedown", this);
document.addEventListener("keydown", this);
},
/**
@ -263,41 +243,10 @@ function DatePicker(context) {
this.handleMessage(event);
break;
}
case "keydown": {
switch (event.key) {
case "Enter":
case " ": {
if (event.target == this.context.buttonPrev) {
event.target.classList.add("active");
this.state.dateKeeper.setMonthByOffset(-1);
this._update();
} else if (event.target == this.context.buttonNext) {
event.target.classList.add("active");
this.state.dateKeeper.setMonthByOffset(1);
this._update();
}
break;
}
case "Tab": {
// Manage tab order of a daysView to prevent keyboard trap
if (event.target.tagName === "td") {
if (event.shiftKey) {
this.context.buttonNext.focus();
} else if (!event.shiftKey) {
this.context.buttonPrev.focus();
}
event.stopPropagation();
event.preventDefault();
}
break;
}
}
break;
}
case "mousedown": {
// Use preventDefault to keep focus on input boxes
event.preventDefault();
event.target.setPointerCapture(event.pointerId);
event.target.setCapture();
if (event.target == this.context.buttonPrev) {
event.target.classList.add("active");
@ -311,15 +260,12 @@ function DatePicker(context) {
break;
}
case "mouseup": {
event.target.releasePointerCapture(event.pointerId);
if (
event.target == this.context.buttonPrev ||
event.target == this.context.buttonNext
) {
event.target.classList.remove("active");
}
break;
}
}
},
@ -435,7 +381,6 @@ function DatePicker(context) {
),
};
this._updateButtonLabels();
this._attachEventListeners();
}
@ -454,13 +399,9 @@ function DatePicker(context) {
*/
setProps(props) {
this.context.monthYear.textContent = this.state.dateFormat(props.dateObj);
const spinnerDialog = this.context.monthYearView.parentNode;
if (props.isVisible) {
this.context.monthYear.classList.add("active");
this.context.monthYear.setAttribute("aria-expanded", "true");
// To prevent redundancy, as spinners will announce their value on change
this.context.monthYear.setAttribute("aria-live", "off");
this.components.month.setState({
value: props.dateObj.getUTCMonth(),
items: props.months,
@ -476,21 +417,8 @@ function DatePicker(context) {
smoothScroll: !(this.state.firstOpened || props.noSmoothScroll),
});
this.state.firstOpened = false;
// Set up spinner dialog container properties for assistive technology:
spinnerDialog.setAttribute("role", "dialog");
spinnerDialog.setAttribute("aria-modal", "true");
} else {
this.context.monthYear.classList.remove("active");
this.context.monthYear.setAttribute("aria-expanded", "false");
// To ensure calendar month's changes are announced:
this.context.monthYear.setAttribute("aria-live", "polite");
// Remove spinner dialog container properties to ensure this hidden
// modal will be ignored by assistive technology, because even though
// the dialog is hidden, the toggle button is a visible descendant,
// so we must not treat its container as a dialog:
spinnerDialog.removeAttribute("role");
spinnerDialog.removeAttribute("aria-modal");
this.state.isMonthSet = false;
this.state.isYearSet = false;
this.state.firstOpened = true;
@ -509,54 +437,14 @@ function DatePicker(context) {
this.props.toggleMonthPicker();
break;
}
case "keydown": {
if (event.key === "Enter" || event.key === " ") {
event.stopPropagation();
event.preventDefault();
this.props.toggleMonthPicker();
}
break;
}
}
},
/**
* Update localizable IDs of the spinner and its Prev/Next buttons
*/
_updateButtonLabels() {
document.l10n.setAttributes(
this.components.month.elements.spinner,
"date-spinner-month"
);
document.l10n.setAttributes(
this.components.year.elements.spinner,
"date-spinner-year"
);
document.l10n.setAttributes(
this.components.month.elements.up,
"date-spinner-month-previous"
);
document.l10n.setAttributes(
this.components.month.elements.down,
"date-spinner-month-next"
);
document.l10n.setAttributes(
this.components.year.elements.up,
"date-spinner-year-previous"
);
document.l10n.setAttributes(
this.components.year.elements.down,
"date-spinner-year-next"
);
document.l10n.translateRoots();
},
/**
* Attach event listener to monthYear button
*/
_attachEventListeners() {
this.context.monthYear.addEventListener("click", this);
this.context.monthYear.addEventListener("keydown", this);
},
};
}

Просмотреть файл

@ -45,58 +45,23 @@
user-select: none;
}
.datetime-calendar-button {
-moz-context-properties: fill;
.datetime-reset-button {
color: inherit;
font-size: inherit;
fill: currentColor;
opacity: .65;
opacity: .5;
background-color: transparent;
border: none;
border-radius: 0.2em;
flex: none;
margin-block: 0;
margin-inline: 0.075em 0.15em;
padding: 0 0.15em;
padding-inline: 2px;
padding-block: 0;
line-height: 1;
}
.datetime-calendar-button:focus-visible,
.datetime-calendar-button:hover {
opacity: 1;
outline: 0.15em solid SelectedItem;
}
.datetime-calendar-button-svg {
.datetime-reset-button-svg {
pointer-events: none;
/* When using a very small font-size, we don't want the button to take extra
* space (which will affect the baseline of the form control) */
max-width: 1em;
max-height: 1em;
}
@media (prefers-contrast) {
.datetime-calendar-button {
opacity: 1;
background-color: ButtonFace;
color: ButtonText;
}
.datetime-calendar-button:focus-visible,
.datetime-calendar-button:hover {
background-color: SelectedItem;
}
.datetime-calendar-button:focus-visible > .datetime-calendar-button-svg,
.datetime-calendar-button:hover > .datetime-calendar-button-svg {
background-color: SelectedItem;
-moz-context-properties: fill;
fill: SelectedItemText;
}
.datetime-calendar-button-svg {
background-color: ButtonFace;
-moz-context-properties: fill;
fill: ButtonText;
}
}

Просмотреть файл

@ -58,7 +58,11 @@ this.DateTimeBoxWidget = class {
}
teardown() {
this.mInputElement.removeEventListener("keydown", this, {
this.mResetButton.removeEventListener("mousedown", this, {
mozSystemGroup: true,
});
this.mInputElement.removeEventListener("keypress", this, {
capture: true,
mozSystemGroup: true,
});
@ -73,7 +77,6 @@ this.DateTimeBoxWidget = class {
this.l10n.disconnectRoot(this.shadowRoot);
this.removeEditFields();
this.removeEventListenersToField(this.mCalendarButton);
this.mInputElement = null;
@ -131,7 +134,6 @@ this.DateTimeBoxWidget = class {
this.generateContent();
this.mDateTimeBoxElement = this.shadowRoot.firstChild;
this.mCalendarButton = this.shadowRoot.getElementById("calendar-button");
this.mInputElement = this.element;
this.mLocales = this.window.getWebExposedLocales();
@ -187,8 +189,14 @@ this.DateTimeBoxWidget = class {
this.mHourPageUpDownInterval = 3;
this.mMinSecPageUpDownInterval = 10;
this.mResetButton = this.shadowRoot.getElementById("reset-button");
this.mResetButton.style.visibility = "hidden";
this.mResetButton.addEventListener("mousedown", this, {
mozSystemGroup: true,
});
this.mInputElement.addEventListener(
"keydown",
"keypress",
this,
{
capture: true,
@ -196,17 +204,14 @@ this.DateTimeBoxWidget = class {
},
false
);
// This is to open the picker when input element is tapped on Android
// (this includes padding area).
this.isAndroid = this.window.navigator.appVersion.includes("Android");
if (this.isAndroid) {
this.mInputElement.addEventListener(
"click",
this,
{ mozSystemGroup: true },
false
);
}
// This is to open the picker when input element is clicked (this
// includes padding area).
this.mInputElement.addEventListener(
"click",
this,
{ mozSystemGroup: true },
false
);
// Those events are dispatched to <div class="datetimebox"> with bubble set
// to false. They are trapped inside UA Widget Shadow DOM and are not
@ -216,7 +221,6 @@ this.DateTimeBoxWidget = class {
});
this.buildEditFields();
this.buildCalendarBtn();
this.updateEditAttributes();
if (this.mInputElement.value) {
@ -239,9 +243,10 @@ this.DateTimeBoxWidget = class {
<!-- Each of the date/time input types will append their input child
- elements here -->
</span>
<button data-l10n-id="datetime-calendar" class="datetime-calendar-button" id="calendar-button" aria-expanded="false">
<svg role="none" class="datetime-calendar-button-svg" xmlns="http://www.w3.org/2000/svg" id="calendar-16" viewBox="0 0 16 16" width="16" height="16" fill="context-fill">
<path d="M13.5 2H13V1c0-.6-.4-1-1-1s-1 .4-1 1v1H5V1c0-.6-.4-1-1-1S3 .4 3 1v1h-.5C1.1 2 0 3.1 0 4.5v9C0 14.9 1.1 16 2.5 16h11c1.4 0 2.5-1.1 2.5-2.5v-9C16 3.1 14.9 2 13.5 2zm0 12.5h-11c-.6 0-1-.4-1-1V6h13v7.5c0 .6-.4 1-1 1z"/>
<button class="datetime-reset-button" id="reset-button" tabindex="-1" data-l10n-id="datetime-reset">
<svg xmlns="http://www.w3.org/2000/svg" class="datetime-reset-button-svg" width="12" height="12" viewBox="0 0 12 12">
<path d="M 3.9,3 3,3.9 5.1,6 3,8.1 3.9,9 6,6.9 8.1,9 9,8.1 6.9,6 9,3.9 8.1,3 6,5.1 Z M 12,6 A 6,6 0 0 1 6,12 6,6 0 0 1 0,6 6,6 0 0 1 6,0 6,6 0 0 1 12,6 Z"/>
</svg>
</button>
</div>
@ -369,8 +374,12 @@ this.DateTimeBoxWidget = class {
return field;
}
updateCalendarButtonState(isExpanded) {
this.mCalendarButton.setAttribute("aria-expanded", isExpanded);
updateResetButtonVisibility() {
if (this.isAnyFieldAvailable(false) && !this.isRequired()) {
this.mResetButton.style.visibility = "";
} else {
this.mResetButton.style.visibility = "hidden";
}
}
notifyInputElementValueChanged() {
@ -414,8 +423,6 @@ this.DateTimeBoxWidget = class {
setPickerState(aIsOpen) {
this.log("picker is now " + (aIsOpen ? "opened" : "closed"));
this.mIsPickerOpen = aIsOpen;
// Calendar button's expanded state mirrors this.mIsPickerOpen
this.updateCalendarButtonState(this.mIsPickerOpen);
}
setFieldTabIndexAttribute(field) {
@ -447,10 +454,9 @@ this.DateTimeBoxWidget = class {
this.setFieldTabIndexAttribute(child);
}
this.mCalendarButton.hidden =
this.mInputElement.disabled ||
this.mInputElement.readOnly ||
this.mInputElement.type === "time";
this.mResetButton.disabled =
this.mInputElement.disabled || this.mInputElement.readOnly;
this.updateResetButtonVisibility();
}
isEmpty(aValue) {
@ -474,6 +480,7 @@ this.DateTimeBoxWidget = class {
if (aField.classList.contains("numeric")) {
aField.setAttribute("typeBuffer", "");
}
this.updateResetButtonVisibility();
}
openDateTimePicker() {
@ -537,13 +544,11 @@ this.DateTimeBoxWidget = class {
break;
}
case "MozSetDateTimePickerState": {
// To handle cases when an input is within a shadow DOM:
this.oldFocus = this.window.document.activeElement;
this.setPickerState(aEvent.detail);
break;
}
case "keydown": {
this.onKeyDown(aEvent);
case "keypress": {
this.onKeyPress(aEvent);
break;
}
case "click": {
@ -599,13 +604,6 @@ this.DateTimeBoxWidget = class {
aEvent.relatedTarget
);
// Ignore when the focus moves to the datepicker panel
// while the input remains focused (even in another shadow DOM)
if (this.document.activeElement === this.oldFocus) {
return;
}
this.oldFocus = null;
let target = aEvent.originalTarget;
target.setAttribute("typeBuffer", "");
this.setInputValueFromFields();
@ -628,7 +626,7 @@ this.DateTimeBoxWidget = class {
);
}
shouldOpenDateTimePickerOnKeyDown() {
shouldOpenDateTimePickerOnKeyPress() {
if (!this.mLastFocusedField) {
return true;
}
@ -649,34 +647,20 @@ this.DateTimeBoxWidget = class {
return this.isTimeField(field);
}
onKeyDown(aEvent) {
this.log("onKeyDown key: " + aEvent.key);
onKeyPress(aEvent) {
this.log("onKeyPress key: " + aEvent.key);
switch (aEvent.key) {
// Toggle the picker on Space/Enter on Calendar button or Space on input,
// close on Escape anywhere.
case "Escape": {
if (this.mIsPickerOpen) {
this.closeDateTimePicker();
aEvent.preventDefault();
}
break;
}
// Toggle the picker on space/enter, close on Escape.
case "Enter":
case "Escape":
case " ": {
// always close, if opened
if (this.mIsPickerOpen) {
this.closeDateTimePicker();
} else if (
// open on Space from anywhere within the input
aEvent.key == " " &&
this.shouldOpenDateTimePickerOnKeyDown()
) {
this.openDateTimePicker();
} else if (
// open from the Calendar button on either keydown
aEvent.originalTarget == this.mCalendarButton &&
this.shouldOpenDateTimePickerOnKeyDown()
aEvent.key != "Escape" &&
aEvent.key != "Enter" &&
this.shouldOpenDateTimePickerOnKeyPress()
) {
this.openDateTimePicker();
} else {
@ -714,16 +698,12 @@ this.DateTimeBoxWidget = class {
break;
}
default: {
// digits and printable characters
const regex = new RegExp("Digit\\d");
const isDigit = regex.test(aEvent.code);
// printable characters
if (
isDigit ||
(aEvent.key == 0 &&
!(aEvent.ctrlKey || aEvent.altKey || aEvent.metaKey))
aEvent.keyCode == 0 &&
!(aEvent.ctrlKey || aEvent.altKey || aEvent.metaKey)
) {
this.handleKeydown(aEvent);
this.handleKeypress(aEvent);
aEvent.preventDefault();
}
break;
@ -743,20 +723,13 @@ this.DateTimeBoxWidget = class {
return;
}
// Toggle the picker on click on the Calendar button on any platform,
// and, while on Android, on anywhere within an input field
if (
aEvent.originalTarget == this.mCalendarButton ||
(this.isAndroid && aEvent.originalTarget == this.mInputElement)
if (aEvent.originalTarget == this.mResetButton) {
this.clearInputFields(false);
} else if (
!this.mIsPickerOpen &&
this.shouldOpenDateTimePickerOnClick(aEvent.originalTarget)
) {
if (
!this.mIsPickerOpen &&
this.shouldOpenDateTimePickerOnClick(aEvent.originalTarget)
) {
this.openDateTimePicker();
} else {
this.closeDateTimePicker();
}
this.openDateTimePicker();
}
}
@ -900,18 +873,6 @@ this.DateTimeBoxWidget = class {
});
}
buildCalendarBtn() {
this.addEventListenersToField(this.mCalendarButton);
// This is to open the picker when a Calendar button is clicked (this
// includes padding area).
this.mCalendarButton.addEventListener(
"click",
this,
{ mozSystemGroup: true },
false
);
}
clearInputFields(aFromInputElement) {
this.log("clearInputFields");
@ -1124,7 +1085,7 @@ this.DateTimeBoxWidget = class {
this.setInputValueFromFields();
}
handleKeydown(aEvent) {
handleKeypress(aEvent) {
if (!this.isEditable()) {
return;
}
@ -1267,6 +1228,7 @@ this.DateTimeBoxWidget = class {
aField.textContent = formatted;
aField.setAttribute("aria-valuetext", formatted);
this.updateResetButtonVisibility();
}
isAnyFieldAvailable(aForPicker = false) {
@ -1560,5 +1522,6 @@ this.DateTimeBoxWidget = class {
this.mDayPeriodField.textContent = aValue;
this.mDayPeriodField.setAttribute("value", aValue);
this.updateResetButtonVisibility();
}
};

Просмотреть файл

@ -80,16 +80,6 @@ function Spinner(props, context) {
this.elements.spinner.style.height = ITEM_HEIGHT * viewportSize + "rem";
// Prepares the spinner container to function as a spinbutton and expose
// its properties to assistive technology
this.elements.spinner.setAttribute("role", "spinbutton");
this.elements.spinner.setAttribute("tabindex", "0");
// Remove up/down buttons from the focus order, because a keyboard-only
// user can adjust values by pressing Up/Down arrow keys on a spinbutton,
// otherwise it creates extra, redundant tab order stops for users
this.elements.up.setAttribute("tabindex", "-1");
this.elements.down.setAttribute("tabindex", "-1");
if (id) {
this.elements.container.id = id;
}
@ -138,24 +128,7 @@ function Spinner(props, context) {
}
}
this.elements.spinner.setAttribute(
"aria-valuemin",
this.state.items[0].value
);
this.elements.spinner.setAttribute(
"aria-valuemax",
this.state.items.at(-1).value
);
this.elements.spinner.setAttribute("aria-valuenow", this.state.value);
if (!this.elements.spinner.getAttribute("aria-valuetext")) {
this.elements.spinner.setAttribute(
"aria-valuetext",
this.props.getDisplayString(this.state.value)
);
}
// Show selection even if it's passed down from the parent
if ((isValueSet && !isInvalid) || this.state.index) {
if (isValueSet && !isInvalid) {
this._updateSelection();
} else {
this._removeSelection();
@ -204,10 +177,6 @@ function Spinner(props, context) {
*/
_onScrollend() {
this.elements.spinner.classList.remove("scrolling");
this.elements.spinner.setAttribute(
"aria-valuetext",
this.props.getDisplayString(this.state.value)
);
},
/**
@ -271,8 +240,6 @@ function Spinner(props, context) {
for (let i = 0; i < diff; i++) {
let el = document.createElement("div");
// Spinbutton elements should be hidden from assistive technology:
el.setAttribute("aria-hidden", "true");
frag.appendChild(el);
this.elements.itemsViewElements.push(el);
}
@ -317,10 +284,8 @@ function Spinner(props, context) {
spinner.addEventListener("scroll", this, { passive: true });
spinner.addEventListener("scrollend", this, { passive: true });
spinner.addEventListener("keydown", this);
container.addEventListener("mouseup", this, { passive: true });
container.addEventListener("mousedown", this, { passive: true });
container.addEventListener("keydown", this);
},
/**
@ -414,62 +379,6 @@ function Spinner(props, context) {
spinner.scrollTop -= event.movementY;
break;
}
case "keydown": {
// Providing keyboard navigation support in accordance with
// the ARIA Spinbutton design pattern
if (event.target === spinner) {
switch (event.key) {
case "ArrowUp": {
// While the spinner is focused, selects previous value and centers it
this._setValueForSpinner(event, index - 1);
break;
}
case "ArrowDown": {
// While the spinner is focused, selects next value and centers it
this._setValueForSpinner(event, index + 1);
break;
}
case "PageUp": {
// While the spinner is focused, selects 5th value above and centers it
this._setValueForSpinner(event, index - 5);
break;
}
case "PageDown": {
// While the spinner is focused, selects 5th value below and centers it
this._setValueForSpinner(event, index + 5);
break;
}
case "Home": {
// While the spinner is focused, selects the min value and centers it
let targetValue;
for (let i = 0; i < this.state.items.length - 1; i++) {
if (this.state.items[i].enabled) {
targetValue = this.state.items[i].value;
break;
}
}
this._smoothScrollTo(targetValue);
event.stopPropagation();
event.preventDefault();
break;
}
case "End": {
// While the spinner is focused, selects the max value and centers it
let targetValue;
for (let i = this.state.items.length - 1; i >= 0; i--) {
if (this.state.items[i].enabled) {
targetValue = this.state.items[i].value;
break;
}
}
this._smoothScrollTo(targetValue);
event.stopPropagation();
event.preventDefault();
break;
}
}
}
}
}
},
@ -593,7 +502,6 @@ function Spinner(props, context) {
*/
_removeSelection() {
const { selected } = this.elements;
if (selected) {
selected.classList.remove("selection");
}
@ -626,19 +534,5 @@ function Spinner(props, context) {
}
return false;
},
/**
* While the spinner is focused and keyboard command is used, selects an
* appropriate index and centers it, while preventing default behavior and
* stopping event propagation.
*
* @param {Object} event: Keyboard event
* @param {Number} index: The index of the expected next item
*/
_setValueForSpinner(event, index) {
this._smoothScrollToIndex(index);
event.stopPropagation();
event.preventDefault();
},
};
}

Просмотреть файл

@ -1,44 +0,0 @@
# 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/.
### Datepicker - Dialog for default HTML's <input type="date">
## These labels are used by screenreaders and other assistive technology
## to indicate the purpose of a date picker calendar and a month-year selection
## spinner dialogs for HTML's <input type="date">
date-picker-label =
.aria-label = Choose a date
date-spinner-label =
.aria-label = Choose a month and a year
## These labels are used by screenreaders and other assistive technology
## to indicate the purpose of buttons that leaf through months of a calendar
date-picker-previous =
.aria-label = Previous month
date-picker-next =
.aria-label = Next month
## These labels are used by screenreaders and other assistive technology
## to indicate the type of a value/unit that is being selected within a
## Month/Year date spinner dialogs on a datepicker calendar dialog
date-spinner-month =
.aria-label = Month
date-spinner-year =
.aria-label = Year
## These labels are used by screenreaders and other assistive technology
## to indicate the purpose of buttons that leaf through either months
## or years of a Month/Year date spinner on a datepicker calendar dialog
date-spinner-month-previous =
.aria-label = Previous month
date-spinner-month-next =
.aria-label = Next month
date-spinner-year-previous =
.aria-label = Previous year
date-spinner-year-next =
.aria-label = Next year

Просмотреть файл

@ -2,6 +2,10 @@
# 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/.
# Date/time clear button
datetime-reset =
.aria-label = Clear
## Placeholders for date and time inputs
datetime-year-placeholder = yyyy
@ -30,12 +34,3 @@ datetime-millisecond =
.aria-label = Milliseconds
datetime-dayperiod =
.aria-label = AM/PM
## Calendar button for input type=date
# This label is used by screenreaders and other assistive technology
# to indicate the purpose of a toggle button inside of the <input type="date">
# field that opens/closes a date picker calendar dialog
datetime-calendar =
.aria-label = Calendar

Просмотреть файл

@ -67,15 +67,15 @@ button {
border: none;
}
.month-year-nav {
.nav {
display: flex;
width: var(--calendar-width);
height: 2.4rem;
margin-bottom: 0.8rem;
justify-content: space-evenly;
justify-content: space-between;
}
.month-year-nav > button {
.nav > button {
width: 3rem;
height: var(--date-picker-item-height);
-moz-context-properties: fill, fill-opacity;
@ -83,37 +83,36 @@ button {
fill-opacity: .5;
}
.month-year-nav > button:hover {
.nav > button:hover {
fill-opacity: .8;
}
.month-year-nav > button.active {
.nav > button.active {
fill-opacity: 1;
}
.month-year-nav > button.prev:dir(ltr),
.month-year-nav > button.next:dir(rtl) {
.nav > button.prev,
.nav > button.next {
background: url("chrome://global/skin/icons/arrow-left.svg") no-repeat center;
}
.month-year-nav > button.prev:dir(rtl),
.month-year-nav > button.next:dir(ltr) {
background: url("chrome://global/skin/icons/arrow-right.svg") no-repeat center;
.nav > button.prev:dir(rtl),
.nav > button.next:dir(ltr) {
transform: scaleX(-1);
}
.month-year-container {
position: absolute;
display: flex;
justify-content: center;
align-items: center;
top: 0;
inset-inline: 3rem;
width: 17.1rem;
height: var(--date-picker-item-height);
z-index: 10;
}
.month-year-container {
margin-block: 0;
}
button.month-year {
font-size: 1.3rem;
border: var(--border);
@ -161,11 +160,37 @@ button.month-year.active::after {
transition: opacity 0.15s;
}
.month-year-view.hidden {
visibility: hidden;
opacity: 0;
}
.month-year-view > .spinner-container {
width: 5.5rem;
margin: 0 0.5rem;
}
.month-year-view .spinner {
transform: scaleY(1);
transform-origin: top;
transition: transform 0.15s;
}
.month-year-view.hidden .spinner {
transform: scaleY(0);
transition: none;
}
.month-year-view .spinner > div {
transform: scaleY(1);
transition: transform 0.15s;
}
.month-year-view.hidden .spinner > div {
transform: scaleY(2.5);
transition: none;
}
.order-month-year > #spinner-month,
.order-year-month > #spinner-year {
order: 1;
@ -178,39 +203,34 @@ button.month-year.active::after {
.calendar-container {
cursor: default;
}
.calendar-container > table:not([hidden]) {
display: flex;
flex-direction: column;
width: var(--calendar-width);
border-spacing: inherit;
}
.week-header > tr,
.days-view > tr {
.week-header {
display: flex;
flex-direction: row;
}
.week-header > tr > th {
.week-header > div {
opacity: .5;
}
.days-view {
min-height: 15rem;
.days-viewport {
height: 15rem;
overflow: hidden;
position: relative;
}
.week-header > tr > th,
.days-view > tr > td {
padding: 0;
font-weight: inherit;
.days-view {
position: absolute;
display: flex;
flex-wrap: wrap;
flex-direction: row;
}
.week-header > tr > th,
.days-view > tr > td {
.week-header > div,
.days-view > div {
align-items: center;
display: flex;
height: var(--date-picker-item-height);
@ -219,11 +239,7 @@ button.month-year.active::after {
width: var(--date-picker-item-width);
}
.days-view > tr > td:focus-visible {
z-index: 1;
}
.days-view > tr > td.outside {
.days-view > .outside {
opacity: .5;
}
@ -231,35 +247,32 @@ button.month-year.active::after {
color: var(--weekend-font-color);
}
.days-view > tr > td.weekend.outside {
.days-view > .weekend.outside {
opacity: .5;
}
.days-view > tr > td.out-of-range,
.days-view > tr > td.off-step {
.days-view > .out-of-range,
.days-view > .off-step {
background: var(--disabled-fill-color);
}
.days-view > tr > td.today {
.days-view > .today {
font-weight: bold;
}
.days-view > tr > td.out-of-range::before,
.days-view > tr > td.off-step::before {
.days-view > .out-of-range::before,
.days-view > .off-step::before {
display: none;
}
.days-view > tr > td:hover::before,
.days-view > tr > td.select::before,
.days-view > tr > td.today::before {
position: absolute;
.days-view > div:hover::before,
.days-view > .select::before,
.days-view > .today::before {
inset: 5%;
z-index: -10;
border-radius: var(--border-radius);
}
#time-picker,
.month-year-view:not([hidden]) {
.month-year-view {
display: flex;
flex-direction: row;
justify-content: center;
@ -319,39 +332,39 @@ button.month-year.active::after {
}
.spinner-container > .spinner > div::before,
.calendar-container .days-view > tr > td::before {
.calendar-container .days-view > div::before {
position: absolute;
inset: 5%;
z-index: -1;
z-index: -10;
border-radius: var(--border-radius);
}
.spinner-container > .spinner > div:hover::before,
.calendar-container .days-view > tr > td:hover::before {
.calendar-container .days-view > div:hover::before {
background: var(--fill-color);
border: var(--border);
content: "";
}
.calendar-container .days-view > tr > td.today::before {
.calendar-container .days-view > div.today::before {
background: var(--today-fill-color);
border: var(--border);
content: "";
}
@media not (prefers-contrast) {
.calendar-container .days-view > tr > td.today::before {
.calendar-container .days-view > div.today::before {
border-width: 0;
}
}
.spinner-container > .spinner:not(.scrolling) > div.selection,
.calendar-container .days-view > tr > td.selection {
.calendar-container .days-view > div.selection {
color: var(--selected-font-color);
}
.spinner-container > .spinner > div.selection::before,
.calendar-container .days-view > tr > td.selection::before {
.calendar-container .days-view > div.selection::before {
background: var(--selected-fill-color);
border: none;
content: "";

Просмотреть файл

@ -310,6 +310,7 @@ STATIC_ATOMS = [
Atom("datetime", "datetime"),
Atom("datetime_local", "datetime-local"),
Atom("datetimeInputBoxWrapper", "datetime-input-box-wrapper"),
Atom("datetimeResetButton", "datetime-reset-button"),
Atom("dd", "dd"),
Atom("decimal", "decimal"),
Atom("decimalFormat", "decimal-format"),