summaryrefslogtreecommitdiffstats
path: root/toolkit/actors/DateTimePickerChild.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/actors/DateTimePickerChild.sys.mjs')
-rw-r--r--toolkit/actors/DateTimePickerChild.sys.mjs204
1 files changed, 204 insertions, 0 deletions
diff --git a/toolkit/actors/DateTimePickerChild.sys.mjs b/toolkit/actors/DateTimePickerChild.sys.mjs
new file mode 100644
index 0000000000..9ef55af435
--- /dev/null
+++ b/toolkit/actors/DateTimePickerChild.sys.mjs
@@ -0,0 +1,204 @@
+/* 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/. */
+
+const lazy = {};
+ChromeUtils.defineESModuleGetters(lazy, {
+ LayoutUtils: "resource://gre/modules/LayoutUtils.sys.mjs",
+});
+
+/**
+ * DateTimePickerChild is the communication channel between the input box
+ * (content) for date/time input types and its picker (chrome).
+ */
+export class DateTimePickerChild extends JSWindowActorChild {
+ /**
+ * On init, just listen for the event to open the picker, once the picker is
+ * opened, we'll listen for update and close events.
+ */
+ constructor() {
+ super();
+
+ this._inputElement = null;
+ }
+
+ /**
+ * Cleanup function called when picker is closed.
+ */
+ close() {
+ this.removeListeners(this._inputElement);
+ let dateTimeBoxElement = this._inputElement.dateTimeBoxElement;
+ if (!dateTimeBoxElement) {
+ this._inputElement = null;
+ return;
+ }
+
+ // dateTimeBoxElement is within UA Widget Shadow DOM.
+ // An event dispatch to it can't be accessed by document.
+ let win = this._inputElement.ownerGlobal;
+ dateTimeBoxElement.dispatchEvent(
+ new win.CustomEvent("MozSetDateTimePickerState", { detail: false })
+ );
+
+ this._inputElement = null;
+ }
+
+ /**
+ * Called after picker is opened to start listening for input box update
+ * events.
+ */
+ addListeners(aElement) {
+ aElement.ownerGlobal.addEventListener("pagehide", this);
+ }
+
+ /**
+ * Stop listeneing for events when picker is closed.
+ */
+ removeListeners(aElement) {
+ aElement.ownerGlobal.removeEventListener("pagehide", this);
+ }
+
+ /**
+ * Helper function that returns the CSS direction property of the element.
+ */
+ getComputedDirection(aElement) {
+ return aElement.ownerGlobal
+ .getComputedStyle(aElement)
+ .getPropertyValue("direction");
+ }
+
+ /**
+ * Helper function that returns the rect of the element, which is the position
+ * relative to the left/top of the content area.
+ */
+ getBoundingContentRect(aElement) {
+ return lazy.LayoutUtils.getElementBoundingScreenRect(aElement);
+ }
+
+ getTimePickerPref() {
+ return Services.prefs.getBoolPref("dom.forms.datetime.timepicker");
+ }
+
+ /**
+ * nsIMessageListener.
+ */
+ receiveMessage(aMessage) {
+ switch (aMessage.name) {
+ case "FormDateTime:PickerClosed": {
+ if (!this._inputElement) {
+ return;
+ }
+
+ this.close();
+ break;
+ }
+ case "FormDateTime:PickerValueChanged": {
+ if (!this._inputElement) {
+ return;
+ }
+
+ let dateTimeBoxElement = this._inputElement.dateTimeBoxElement;
+ if (!dateTimeBoxElement) {
+ return;
+ }
+
+ let win = this._inputElement.ownerGlobal;
+
+ // dateTimeBoxElement is within UA Widget Shadow DOM.
+ // An event dispatch to it can't be accessed by document.
+ dateTimeBoxElement.dispatchEvent(
+ new win.CustomEvent("MozPickerValueChanged", {
+ detail: Cu.cloneInto(aMessage.data, win),
+ })
+ );
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ /**
+ * nsIDOMEventListener, for chrome events sent by the input element and other
+ * DOM events.
+ */
+ handleEvent(aEvent) {
+ switch (aEvent.type) {
+ case "MozOpenDateTimePicker": {
+ // Time picker is disabled when preffed off
+ if (
+ !aEvent.originalTarget.ownerGlobal.HTMLInputElement.isInstance(
+ aEvent.originalTarget
+ ) ||
+ (aEvent.originalTarget.type == "time" && !this.getTimePickerPref())
+ ) {
+ return;
+ }
+
+ if (this._inputElement) {
+ // This happens when we're trying to open a picker when another picker
+ // is still open. We ignore this request to let the first picker
+ // close gracefully.
+ return;
+ }
+
+ this._inputElement = aEvent.originalTarget;
+
+ let dateTimeBoxElement = this._inputElement.dateTimeBoxElement;
+ if (!dateTimeBoxElement) {
+ throw new Error("How do we get this event without a UA Widget?");
+ }
+
+ // dateTimeBoxElement is within UA Widget Shadow DOM.
+ // An event dispatch to it can't be accessed by document, because
+ // the event is not composed.
+ let win = this._inputElement.ownerGlobal;
+ dateTimeBoxElement.dispatchEvent(
+ new win.CustomEvent("MozSetDateTimePickerState", { detail: true })
+ );
+
+ this.addListeners(this._inputElement);
+
+ let value = this._inputElement.getDateTimeInputBoxValue();
+ this.sendAsyncMessage("FormDateTime:OpenPicker", {
+ rect: this.getBoundingContentRect(this._inputElement),
+ dir: this.getComputedDirection(this._inputElement),
+ type: this._inputElement.type,
+ detail: {
+ // Pass partial value if it's available, otherwise pass input
+ // element's value.
+ value: Object.keys(value).length ? value : this._inputElement.value,
+ min: this._inputElement.getMinimum(),
+ max: this._inputElement.getMaximum(),
+ step: this._inputElement.getStep(),
+ stepBase: this._inputElement.getStepBase(),
+ },
+ });
+ break;
+ }
+ case "MozUpdateDateTimePicker": {
+ let value = this._inputElement.getDateTimeInputBoxValue();
+ value.type = this._inputElement.type;
+ this.sendAsyncMessage("FormDateTime:UpdatePicker", { value });
+ break;
+ }
+ case "MozCloseDateTimePicker": {
+ this.sendAsyncMessage("FormDateTime:ClosePicker", {});
+ this.close();
+ break;
+ }
+ case "pagehide": {
+ if (
+ this._inputElement &&
+ this._inputElement.ownerDocument == aEvent.target
+ ) {
+ this.sendAsyncMessage("FormDateTime:ClosePicker", {});
+ this.close();
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+}