forked from mirrors/gecko-dev
On macos re-showing it without using `hidden` can cause, apparently, extra popuphidden events. Let's do the right thing and close the popup properly using hidePopup(). This code wasn't being hit before bug 1339380 because we relied on the parent side to hide it, which did correctly fire the events. Differential Revision: https://phabricator.services.mozilla.com/D106431
338 lines
9.3 KiB
JavaScript
338 lines
9.3 KiB
JavaScript
/* 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";
|
|
|
|
var EXPORTED_SYMBOLS = ["DateTimePickerPanel"];
|
|
|
|
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
|
|
|
var DateTimePickerPanel = class {
|
|
constructor(element) {
|
|
this.element = element;
|
|
|
|
this.TIME_PICKER_WIDTH = "12em";
|
|
this.TIME_PICKER_HEIGHT = "21em";
|
|
this.DATE_PICKER_WIDTH = "23.1em";
|
|
this.DATE_PICKER_HEIGHT = "20.7em";
|
|
}
|
|
|
|
get dateTimePopupFrame() {
|
|
let frame = this.element.querySelector("#dateTimePopupFrame");
|
|
if (!frame) {
|
|
frame = this.element.ownerDocument.createXULElement("iframe");
|
|
frame.id = "dateTimePopupFrame";
|
|
this.element.appendChild(frame);
|
|
}
|
|
return frame;
|
|
}
|
|
|
|
openPicker(type, rect, detail) {
|
|
this.type = type;
|
|
this.pickerState = {};
|
|
// TODO: Resize picker according to content zoom level
|
|
this.element.style.fontSize = "10px";
|
|
switch (type) {
|
|
case "time": {
|
|
this.detail = detail;
|
|
this.dateTimePopupFrame.addEventListener("load", this, true);
|
|
this.dateTimePopupFrame.setAttribute(
|
|
"src",
|
|
"chrome://global/content/timepicker.xhtml"
|
|
);
|
|
this.dateTimePopupFrame.style.width = this.TIME_PICKER_WIDTH;
|
|
this.dateTimePopupFrame.style.height = this.TIME_PICKER_HEIGHT;
|
|
break;
|
|
}
|
|
case "date": {
|
|
this.detail = detail;
|
|
this.dateTimePopupFrame.addEventListener("load", this, true);
|
|
this.dateTimePopupFrame.setAttribute(
|
|
"src",
|
|
"chrome://global/content/datepicker.xhtml"
|
|
);
|
|
this.dateTimePopupFrame.style.width = this.DATE_PICKER_WIDTH;
|
|
this.dateTimePopupFrame.style.height = this.DATE_PICKER_HEIGHT;
|
|
break;
|
|
}
|
|
}
|
|
this.element.openPopupAtScreenRect(
|
|
"after_start",
|
|
rect.left,
|
|
rect.top,
|
|
rect.width,
|
|
rect.height,
|
|
false,
|
|
false
|
|
);
|
|
}
|
|
|
|
closePicker() {
|
|
this.setInputBoxValue(true);
|
|
this.pickerState = {};
|
|
this.type = undefined;
|
|
this.dateTimePopupFrame.removeEventListener("load", this, true);
|
|
this.dateTimePopupFrame.contentDocument.removeEventListener(
|
|
"message",
|
|
this
|
|
);
|
|
this.dateTimePopupFrame.setAttribute("src", "");
|
|
this.element.hidePopup();
|
|
}
|
|
|
|
setPopupValue(data) {
|
|
switch (this.type) {
|
|
case "time": {
|
|
this.postMessageToPicker({
|
|
name: "PickerSetValue",
|
|
detail: data.value,
|
|
});
|
|
break;
|
|
}
|
|
case "date": {
|
|
const { year, month, day } = data.value;
|
|
this.postMessageToPicker({
|
|
name: "PickerSetValue",
|
|
detail: {
|
|
year,
|
|
// Month value from input box starts from 1 instead of 0
|
|
month: month == undefined ? undefined : month - 1,
|
|
day,
|
|
},
|
|
});
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
initPicker(detail) {
|
|
let locale = new Services.intl.Locale(
|
|
Services.locale.webExposedLocales[0],
|
|
{
|
|
calendar: "gregory",
|
|
}
|
|
).toString();
|
|
|
|
// Workaround for bug 1418061, while we wait for resolution of
|
|
// http://bugs.icu-project.org/trac/ticket/13592: drop the PT region code,
|
|
// because it results in "abbreviated" day names that are too long;
|
|
// the region-less "pt" locale has shorter forms that are better here.
|
|
locale = locale.replace(/^pt-PT/i, "pt");
|
|
|
|
const dir = Services.locale.isAppLocaleRTL ? "rtl" : "ltr";
|
|
|
|
switch (this.type) {
|
|
case "time": {
|
|
const { hour, minute } = detail.value;
|
|
const format = detail.format || "12";
|
|
|
|
this.postMessageToPicker({
|
|
name: "PickerInit",
|
|
detail: {
|
|
hour,
|
|
minute,
|
|
format,
|
|
locale,
|
|
min: detail.min,
|
|
max: detail.max,
|
|
step: detail.step,
|
|
},
|
|
});
|
|
break;
|
|
}
|
|
case "date": {
|
|
const { year, month, day } = detail.value;
|
|
const { firstDayOfWeek, weekends } = this.getCalendarInfo(locale);
|
|
const monthStrings = this.getDisplayNames(
|
|
locale,
|
|
[
|
|
"dates/gregorian/months/january",
|
|
"dates/gregorian/months/february",
|
|
"dates/gregorian/months/march",
|
|
"dates/gregorian/months/april",
|
|
"dates/gregorian/months/may",
|
|
"dates/gregorian/months/june",
|
|
"dates/gregorian/months/july",
|
|
"dates/gregorian/months/august",
|
|
"dates/gregorian/months/september",
|
|
"dates/gregorian/months/october",
|
|
"dates/gregorian/months/november",
|
|
"dates/gregorian/months/december",
|
|
],
|
|
"short"
|
|
);
|
|
const weekdayStrings = this.getDisplayNames(
|
|
locale,
|
|
[
|
|
"dates/gregorian/weekdays/sunday",
|
|
"dates/gregorian/weekdays/monday",
|
|
"dates/gregorian/weekdays/tuesday",
|
|
"dates/gregorian/weekdays/wednesday",
|
|
"dates/gregorian/weekdays/thursday",
|
|
"dates/gregorian/weekdays/friday",
|
|
"dates/gregorian/weekdays/saturday",
|
|
],
|
|
"short"
|
|
);
|
|
|
|
this.postMessageToPicker({
|
|
name: "PickerInit",
|
|
detail: {
|
|
year,
|
|
// Month value from input box starts from 1 instead of 0
|
|
month: month == undefined ? undefined : month - 1,
|
|
day,
|
|
firstDayOfWeek,
|
|
weekends,
|
|
monthStrings,
|
|
weekdayStrings,
|
|
locale,
|
|
dir,
|
|
min: detail.min,
|
|
max: detail.max,
|
|
step: detail.step,
|
|
stepBase: detail.stepBase,
|
|
},
|
|
});
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {Boolean} passAllValues: Pass spinner values regardless if they've been set/changed or not
|
|
*/
|
|
setInputBoxValue(passAllValues) {
|
|
switch (this.type) {
|
|
case "time": {
|
|
const {
|
|
hour,
|
|
minute,
|
|
isHourSet,
|
|
isMinuteSet,
|
|
isDayPeriodSet,
|
|
} = this.pickerState;
|
|
const isAnyValueSet = isHourSet || isMinuteSet || isDayPeriodSet;
|
|
if (passAllValues && isAnyValueSet) {
|
|
this.sendPickerValueChanged({ hour, minute });
|
|
} else {
|
|
this.sendPickerValueChanged({
|
|
hour: isHourSet || isDayPeriodSet ? hour : undefined,
|
|
minute: isMinuteSet ? minute : undefined,
|
|
});
|
|
}
|
|
break;
|
|
}
|
|
case "date": {
|
|
this.sendPickerValueChanged(this.pickerState);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
sendPickerValueChanged(value) {
|
|
switch (this.type) {
|
|
case "time": {
|
|
this.element.dispatchEvent(
|
|
new CustomEvent("DateTimePickerValueChanged", {
|
|
detail: {
|
|
hour: value.hour,
|
|
minute: value.minute,
|
|
},
|
|
})
|
|
);
|
|
break;
|
|
}
|
|
case "date": {
|
|
this.element.dispatchEvent(
|
|
new CustomEvent("DateTimePickerValueChanged", {
|
|
detail: {
|
|
year: value.year,
|
|
// Month value from input box starts from 1 instead of 0
|
|
month: value.month == undefined ? undefined : value.month + 1,
|
|
day: value.day,
|
|
},
|
|
})
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
getCalendarInfo(locale) {
|
|
const calendarInfo = Services.intl.getCalendarInfo(locale);
|
|
|
|
// Day of week from calendarInfo starts from 1 as Sunday to 7 as Saturday,
|
|
// so they need to be mapped to JavaScript convention with 0 as Sunday
|
|
// and 6 as Saturday
|
|
let firstDayOfWeek = calendarInfo.firstDayOfWeek - 1,
|
|
weekendStart = calendarInfo.weekendStart - 1,
|
|
weekendEnd = calendarInfo.weekendEnd - 1;
|
|
|
|
let weekends = [];
|
|
|
|
// Make sure weekendEnd is greater than weekendStart
|
|
if (weekendEnd < weekendStart) {
|
|
weekendEnd += 7;
|
|
}
|
|
|
|
// We get the weekends by incrementing weekendStart up to weekendEnd.
|
|
// If the start and end is the same day, then weekends only has one day.
|
|
for (let day = weekendStart; day <= weekendEnd; day++) {
|
|
weekends.push(day % 7);
|
|
}
|
|
|
|
return {
|
|
firstDayOfWeek,
|
|
weekends,
|
|
};
|
|
}
|
|
|
|
getDisplayNames(locale, keys, style) {
|
|
const displayNames = Services.intl.getDisplayNames(locale, { keys, style });
|
|
return keys.map(key => displayNames.values[key]);
|
|
}
|
|
|
|
handleEvent(aEvent) {
|
|
switch (aEvent.type) {
|
|
case "load": {
|
|
this.initPicker(this.detail);
|
|
this.dateTimePopupFrame.contentWindow.addEventListener("message", this);
|
|
break;
|
|
}
|
|
case "message": {
|
|
this.handleMessage(aEvent);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
handleMessage(aEvent) {
|
|
if (
|
|
!this.dateTimePopupFrame.contentDocument.nodePrincipal.isSystemPrincipal
|
|
) {
|
|
return;
|
|
}
|
|
|
|
switch (aEvent.data.name) {
|
|
case "PickerPopupChanged": {
|
|
this.pickerState = aEvent.data.detail;
|
|
this.setInputBoxValue();
|
|
break;
|
|
}
|
|
case "ClosePopup": {
|
|
this.closePicker();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
postMessageToPicker(data) {
|
|
if (
|
|
this.dateTimePopupFrame.contentDocument.nodePrincipal.isSystemPrincipal
|
|
) {
|
|
this.dateTimePopupFrame.contentWindow.postMessage(data, "*");
|
|
}
|
|
}
|
|
};
|