Bug 1820641: Make a test that calls window.restore handle zoomed windows. r=mstange
[gecko.git] / toolkit / actors / DateTimePickerChild.jsm
blob638a03c5a12ef758be8d371da93cf4de2311e82e
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 lazy = {};
6 ChromeUtils.defineESModuleGetters(lazy, {
7   LayoutUtils: "resource://gre/modules/LayoutUtils.sys.mjs",
8 });
10 var EXPORTED_SYMBOLS = ["DateTimePickerChild"];
12 /**
13  * DateTimePickerChild is the communication channel between the input box
14  * (content) for date/time input types and its picker (chrome).
15  */
16 class DateTimePickerChild extends JSWindowActorChild {
17   /**
18    * On init, just listen for the event to open the picker, once the picker is
19    * opened, we'll listen for update and close events.
20    */
21   constructor() {
22     super();
24     this._inputElement = null;
25   }
27   /**
28    * Cleanup function called when picker is closed.
29    */
30   close() {
31     this.removeListeners(this._inputElement);
32     let dateTimeBoxElement = this._inputElement.dateTimeBoxElement;
33     if (!dateTimeBoxElement) {
34       this._inputElement = null;
35       return;
36     }
38     if (this._inputElement.openOrClosedShadowRoot) {
39       // dateTimeBoxElement is within UA Widget Shadow DOM.
40       // An event dispatch to it can't be accessed by document.
41       let win = this._inputElement.ownerGlobal;
42       dateTimeBoxElement.dispatchEvent(
43         new win.CustomEvent("MozSetDateTimePickerState", { detail: false })
44       );
45     }
47     this._inputElement = null;
48   }
50   /**
51    * Called after picker is opened to start listening for input box update
52    * events.
53    */
54   addListeners(aElement) {
55     aElement.ownerGlobal.addEventListener("pagehide", this);
56   }
58   /**
59    * Stop listeneing for events when picker is closed.
60    */
61   removeListeners(aElement) {
62     aElement.ownerGlobal.removeEventListener("pagehide", this);
63   }
65   /**
66    * Helper function that returns the CSS direction property of the element.
67    */
68   getComputedDirection(aElement) {
69     return aElement.ownerGlobal
70       .getComputedStyle(aElement)
71       .getPropertyValue("direction");
72   }
74   /**
75    * Helper function that returns the rect of the element, which is the position
76    * relative to the left/top of the content area.
77    */
78   getBoundingContentRect(aElement) {
79     return lazy.LayoutUtils.getElementBoundingScreenRect(aElement);
80   }
82   getTimePickerPref() {
83     return Services.prefs.getBoolPref("dom.forms.datetime.timepicker");
84   }
86   /**
87    * nsIMessageListener.
88    */
89   receiveMessage(aMessage) {
90     switch (aMessage.name) {
91       case "FormDateTime:PickerClosed": {
92         if (!this._inputElement) {
93           return;
94         }
96         this.close();
97         break;
98       }
99       case "FormDateTime:PickerValueChanged": {
100         if (!this._inputElement) {
101           return;
102         }
104         let dateTimeBoxElement = this._inputElement.dateTimeBoxElement;
105         if (!dateTimeBoxElement) {
106           return;
107         }
109         let win = this._inputElement.ownerGlobal;
111         // dateTimeBoxElement is within UA Widget Shadow DOM.
112         // An event dispatch to it can't be accessed by document.
113         dateTimeBoxElement.dispatchEvent(
114           new win.CustomEvent("MozPickerValueChanged", {
115             detail: Cu.cloneInto(aMessage.data, win),
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           !aEvent.originalTarget.ownerGlobal.HTMLInputElement.isInstance(
135             aEvent.originalTarget
136           ) ||
137           (aEvent.originalTarget.type == "time" && !this.getTimePickerPref())
138         ) {
139           return;
140         }
142         if (this._inputElement) {
143           // This happens when we're trying to open a picker when another picker
144           // is still open. We ignore this request to let the first picker
145           // close gracefully.
146           return;
147         }
149         this._inputElement = aEvent.originalTarget;
151         let dateTimeBoxElement = this._inputElement.dateTimeBoxElement;
152         if (!dateTimeBoxElement) {
153           throw new Error(
154             "How do we get this event without a UA Widget or XBL binding?"
155           );
156         }
158         if (this._inputElement.openOrClosedShadowRoot) {
159           // dateTimeBoxElement is within UA Widget Shadow DOM.
160           // An event dispatch to it can't be accessed by document, because
161           // the event is not composed.
162           let win = this._inputElement.ownerGlobal;
163           dateTimeBoxElement.dispatchEvent(
164             new win.CustomEvent("MozSetDateTimePickerState", { detail: true })
165           );
166         }
168         this.addListeners(this._inputElement);
170         let value = this._inputElement.getDateTimeInputBoxValue();
171         this.sendAsyncMessage("FormDateTime:OpenPicker", {
172           rect: this.getBoundingContentRect(this._inputElement),
173           dir: this.getComputedDirection(this._inputElement),
174           type: this._inputElement.type,
175           detail: {
176             // Pass partial value if it's available, otherwise pass input
177             // element's value.
178             value: Object.keys(value).length ? value : this._inputElement.value,
179             min: this._inputElement.getMinimum(),
180             max: this._inputElement.getMaximum(),
181             step: this._inputElement.getStep(),
182             stepBase: this._inputElement.getStepBase(),
183           },
184         });
185         break;
186       }
187       case "MozUpdateDateTimePicker": {
188         let value = this._inputElement.getDateTimeInputBoxValue();
189         value.type = this._inputElement.type;
190         this.sendAsyncMessage("FormDateTime:UpdatePicker", { value });
191         break;
192       }
193       case "MozCloseDateTimePicker": {
194         this.sendAsyncMessage("FormDateTime:ClosePicker", {});
195         this.close();
196         break;
197       }
198       case "pagehide": {
199         if (
200           this._inputElement &&
201           this._inputElement.ownerDocument == aEvent.target
202         ) {
203           this.sendAsyncMessage("FormDateTime:ClosePicker", {});
204           this.close();
205         }
206         break;
207       }
208       default:
209         break;
210     }
211   }