Bug 1692971 [wpt PR 27638] - WebKit export of https://bugs.webkit.org/show_bug.cgi...
[gecko.git] / toolkit / actors / DateTimePickerChild.jsm
blob7a3a7d7c622505522c082493bfb97f06f6efdf79
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
6 ChromeUtils.defineModuleGetter(
7   this,
8   "LayoutUtils",
9   "resource://gre/modules/LayoutUtils.jsm"
12 var EXPORTED_SYMBOLS = ["DateTimePickerChild"];
14 /**
15  * DateTimePickerChild is the communication channel between the input box
16  * (content) for date/time input types and its picker (chrome).
17  */
18 class DateTimePickerChild extends JSWindowActorChild {
19   /**
20    * On init, just listen for the event to open the picker, once the picker is
21    * opened, we'll listen for update and close events.
22    */
23   constructor() {
24     super();
26     this._inputElement = null;
27   }
29   /**
30    * Cleanup function called when picker is closed.
31    */
32   close() {
33     this.removeListeners(this._inputElement);
34     let dateTimeBoxElement = this._inputElement.dateTimeBoxElement;
35     if (!dateTimeBoxElement) {
36       this._inputElement = null;
37       return;
38     }
40     if (this._inputElement.openOrClosedShadowRoot) {
41       // dateTimeBoxElement is within UA Widget Shadow DOM.
42       // An event dispatch to it can't be accessed by document.
43       let win = this._inputElement.ownerGlobal;
44       dateTimeBoxElement.dispatchEvent(
45         new win.CustomEvent("MozSetDateTimePickerState", { detail: false })
46       );
47     }
49     this._inputElement = null;
50   }
52   /**
53    * Called after picker is opened to start listening for input box update
54    * events.
55    */
56   addListeners(aElement) {
57     aElement.ownerGlobal.addEventListener("pagehide", this);
58   }
60   /**
61    * Stop listeneing for events when picker is closed.
62    */
63   removeListeners(aElement) {
64     aElement.ownerGlobal.removeEventListener("pagehide", this);
65   }
67   /**
68    * Helper function that returns the CSS direction property of the element.
69    */
70   getComputedDirection(aElement) {
71     return aElement.ownerGlobal
72       .getComputedStyle(aElement)
73       .getPropertyValue("direction");
74   }
76   /**
77    * Helper function that returns the rect of the element, which is the position
78    * relative to the left/top of the content area.
79    */
80   getBoundingContentRect(aElement) {
81     return LayoutUtils.getElementBoundingScreenRect(aElement);
82   }
84   getTimePickerPref() {
85     return Services.prefs.getBoolPref("dom.forms.datetime.timepicker");
86   }
88   /**
89    * nsIMessageListener.
90    */
91   receiveMessage(aMessage) {
92     switch (aMessage.name) {
93       case "FormDateTime:PickerClosed": {
94         this.close();
95         break;
96       }
97       case "FormDateTime:PickerValueChanged": {
98         if (!this._inputElement) {
99           return;
100         }
102         let dateTimeBoxElement = this._inputElement.dateTimeBoxElement;
103         if (!dateTimeBoxElement) {
104           return;
105         }
107         let win = this._inputElement.ownerGlobal;
109         if (this._inputElement.openOrClosedShadowRoot) {
110           // dateTimeBoxElement is within UA Widget Shadow DOM.
111           // An event dispatch to it can't be accessed by document.
112           dateTimeBoxElement.dispatchEvent(
113             new win.CustomEvent("MozPickerValueChanged", {
114               detail: Cu.cloneInto(aMessage.data, win),
115             })
116           );
117         }
118         break;
119       }
120       default:
121         break;
122     }
123   }
125   /**
126    * nsIDOMEventListener, for chrome events sent by the input element and other
127    * DOM events.
128    */
129   handleEvent(aEvent) {
130     switch (aEvent.type) {
131       case "MozOpenDateTimePicker": {
132         // Time picker is disabled when preffed off
133         if (
134           !(
135             aEvent.originalTarget instanceof
136             aEvent.originalTarget.ownerGlobal.HTMLInputElement
137           ) ||
138           (aEvent.originalTarget.type == "time" && !this.getTimePickerPref())
139         ) {
140           return;
141         }
143         if (this._inputElement) {
144           // This happens when we're trying to open a picker when another picker
145           // is still open. We ignore this request to let the first picker
146           // close gracefully.
147           return;
148         }
150         this._inputElement = aEvent.originalTarget;
152         let dateTimeBoxElement = this._inputElement.dateTimeBoxElement;
153         if (!dateTimeBoxElement) {
154           throw new Error(
155             "How do we get this event without a UA Widget or XBL binding?"
156           );
157         }
159         if (this._inputElement.openOrClosedShadowRoot) {
160           // dateTimeBoxElement is within UA Widget Shadow DOM.
161           // An event dispatch to it can't be accessed by document, because
162           // the event is not composed.
163           let win = this._inputElement.ownerGlobal;
164           dateTimeBoxElement.dispatchEvent(
165             new win.CustomEvent("MozSetDateTimePickerState", { detail: true })
166           );
167         }
169         this.addListeners(this._inputElement);
171         let value = this._inputElement.getDateTimeInputBoxValue();
172         this.sendAsyncMessage("FormDateTime:OpenPicker", {
173           rect: this.getBoundingContentRect(this._inputElement),
174           dir: this.getComputedDirection(this._inputElement),
175           type: this._inputElement.type,
176           detail: {
177             // Pass partial value if it's available, otherwise pass input
178             // element's value.
179             value: Object.keys(value).length ? value : this._inputElement.value,
180             min: this._inputElement.getMinimum(),
181             max: this._inputElement.getMaximum(),
182             step: this._inputElement.getStep(),
183             stepBase: this._inputElement.getStepBase(),
184           },
185         });
186         break;
187       }
188       case "MozUpdateDateTimePicker": {
189         let value = this._inputElement.getDateTimeInputBoxValue();
190         value.type = this._inputElement.type;
191         this.sendAsyncMessage("FormDateTime:UpdatePicker", { value });
192         break;
193       }
194       case "MozCloseDateTimePicker": {
195         this.sendAsyncMessage("FormDateTime:ClosePicker", {});
196         this.close();
197         break;
198       }
199       case "pagehide": {
200         if (
201           this._inputElement &&
202           this._inputElement.ownerDocument == aEvent.target
203         ) {
204           this.sendAsyncMessage("FormDateTime:ClosePicker", {});
205           this.close();
206         }
207         break;
208       }
209       default:
210         break;
211     }
212   }