/* 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; } } }