Bug 1838365 add some DecodedStream finished logging r=pehrsons,media-playback-reviewe...
[gecko.git] / toolkit / modules / DateTimePickerPanel.sys.mjs
blob8d67cb0d8e4ee9b458b5d14d37bc1f1c9b4493ec
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 export var DateTimePickerPanel = class {
6   constructor(element) {
7     this.element = element;
9     this.TIME_PICKER_WIDTH = "13em";
10     this.TIME_PICKER_HEIGHT = "22em";
11     this.DATE_PICKER_WIDTH = "24em";
12     this.DATE_PICKER_HEIGHT = "27em";
13   }
15   get dateTimePopupFrame() {
16     let frame = this.element.querySelector("#dateTimePopupFrame");
17     if (!frame) {
18       frame = this.element.ownerDocument.createXULElement("iframe");
19       frame.id = "dateTimePopupFrame";
20       this.element.appendChild(frame);
21     }
22     return frame;
23   }
25   openPicker(type, rect, detail) {
26     if (type == "datetime-local") {
27       type = "date";
28     }
29     this.type = type;
30     this.pickerState = {};
31     // TODO: Resize picker according to content zoom level
32     this.element.style.fontSize = "10px";
33     switch (type) {
34       case "time": {
35         this.detail = detail;
36         this.dateTimePopupFrame.addEventListener("load", this, true);
37         this.dateTimePopupFrame.setAttribute(
38           "src",
39           "chrome://global/content/timepicker.xhtml"
40         );
41         this.dateTimePopupFrame.style.width = this.TIME_PICKER_WIDTH;
42         this.dateTimePopupFrame.style.height = this.TIME_PICKER_HEIGHT;
43         break;
44       }
45       case "date": {
46         this.detail = detail;
47         this.dateTimePopupFrame.addEventListener("load", this, true);
48         this.dateTimePopupFrame.setAttribute(
49           "src",
50           "chrome://global/content/datepicker.xhtml"
51         );
52         this.dateTimePopupFrame.style.width = this.DATE_PICKER_WIDTH;
53         this.dateTimePopupFrame.style.height = this.DATE_PICKER_HEIGHT;
54         break;
55       }
56     }
57     this.element.openPopupAtScreenRect(
58       "after_start",
59       rect.left,
60       rect.top,
61       rect.width,
62       rect.height,
63       false,
64       false
65     );
66   }
68   closePicker(clear) {
69     if (clear) {
70       this.element.dispatchEvent(new CustomEvent("DateTimePickerValueCleared"));
71     } else {
72       this.setInputBoxValue(true);
73     }
74     this.pickerState = {};
75     this.type = undefined;
76     this.dateTimePopupFrame.removeEventListener("load", this, true);
77     this.dateTimePopupFrame.contentDocument.removeEventListener(
78       "message",
79       this
80     );
81     this.dateTimePopupFrame.setAttribute("src", "");
82     this.element.hidePopup();
83   }
85   setPopupValue(data) {
86     switch (this.type) {
87       case "time": {
88         this.postMessageToPicker({
89           name: "PickerSetValue",
90           detail: data.value,
91         });
92         break;
93       }
94       case "date": {
95         const { year, month, day } = data.value;
96         this.postMessageToPicker({
97           name: "PickerSetValue",
98           detail: {
99             year,
100             // Month value from input box starts from 1 instead of 0
101             month: month == undefined ? undefined : month - 1,
102             day,
103           },
104         });
105         break;
106       }
107     }
108   }
110   initPicker(detail) {
111     let locale = new Services.intl.Locale(
112       Services.locale.webExposedLocales[0],
113       {
114         calendar: "gregory",
115       }
116     ).toString();
118     // Workaround for bug 1418061, while we wait for resolution of
119     // http://bugs.icu-project.org/trac/ticket/13592: drop the PT region code,
120     // because it results in "abbreviated" day names that are too long;
121     // the region-less "pt" locale has shorter forms that are better here.
122     locale = locale.replace(/^pt-PT/i, "pt");
124     const dir = Services.locale.isAppLocaleRTL ? "rtl" : "ltr";
126     switch (this.type) {
127       case "time": {
128         const { hour, minute } = detail.value;
129         const format = detail.format || "12";
131         this.postMessageToPicker({
132           name: "PickerInit",
133           detail: {
134             hour,
135             minute,
136             format,
137             locale,
138             min: detail.min,
139             max: detail.max,
140             step: detail.step,
141           },
142         });
143         break;
144       }
145       case "date": {
146         const { year, month, day } = detail.value;
147         const { firstDayOfWeek, weekends } = this.getCalendarInfo(locale);
149         const monthDisplayNames = new Services.intl.DisplayNames(locale, {
150           type: "month",
151           style: "short",
152           calendar: "gregory",
153         });
154         const monthStrings = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].map(
155           month => monthDisplayNames.of(month)
156         );
158         const weekdayDisplayNames = new Services.intl.DisplayNames(locale, {
159           type: "weekday",
160           style: "abbreviated",
161           calendar: "gregory",
162         });
163         const weekdayStrings = [
164           // Weekdays starting Sunday (7) to Saturday (6).
165           7, 1, 2, 3, 4, 5, 6,
166         ].map(weekday => weekdayDisplayNames.of(weekday));
168         this.postMessageToPicker({
169           name: "PickerInit",
170           detail: {
171             year,
172             // Month value from input box starts from 1 instead of 0
173             month: month == undefined ? undefined : month - 1,
174             day,
175             firstDayOfWeek,
176             weekends,
177             monthStrings,
178             weekdayStrings,
179             locale,
180             dir,
181             min: detail.min,
182             max: detail.max,
183             step: detail.step,
184             stepBase: detail.stepBase,
185           },
186         });
187         break;
188       }
189     }
190   }
192   /**
193    * @param {Boolean} passAllValues: Pass spinner values regardless if they've been set/changed or not
194    */
195   setInputBoxValue(passAllValues) {
196     switch (this.type) {
197       case "time": {
198         const { hour, minute, isHourSet, isMinuteSet, isDayPeriodSet } =
199           this.pickerState;
200         const isAnyValueSet = isHourSet || isMinuteSet || isDayPeriodSet;
201         if (passAllValues && isAnyValueSet) {
202           this.sendPickerValueChanged({ hour, minute });
203         } else {
204           this.sendPickerValueChanged({
205             hour: isHourSet || isDayPeriodSet ? hour : undefined,
206             minute: isMinuteSet ? minute : undefined,
207           });
208         }
209         break;
210       }
211       case "date": {
212         this.sendPickerValueChanged(this.pickerState);
213         break;
214       }
215     }
216   }
218   sendPickerValueChanged(value) {
219     switch (this.type) {
220       case "time": {
221         this.element.dispatchEvent(
222           new CustomEvent("DateTimePickerValueChanged", {
223             detail: {
224               hour: value.hour,
225               minute: value.minute,
226             },
227           })
228         );
229         break;
230       }
231       case "date": {
232         this.element.dispatchEvent(
233           new CustomEvent("DateTimePickerValueChanged", {
234             detail: {
235               year: value.year,
236               // Month value from input box starts from 1 instead of 0
237               month: value.month == undefined ? undefined : value.month + 1,
238               day: value.day,
239             },
240           })
241         );
242         break;
243       }
244     }
245   }
247   getCalendarInfo(locale) {
248     const calendarInfo = Services.intl.getCalendarInfo(locale);
250     // Day of week from calendarInfo starts from 1 as Monday to 7 as Sunday,
251     // so they need to be mapped to JavaScript convention with 0 as Sunday
252     // and 6 as Saturday
253     function toDateWeekday(day) {
254       return day === 7 ? 0 : day;
255     }
257     let firstDayOfWeek = toDateWeekday(calendarInfo.firstDayOfWeek),
258       weekend = calendarInfo.weekend;
260     let weekends = weekend.map(toDateWeekday);
262     return {
263       firstDayOfWeek,
264       weekends,
265     };
266   }
268   handleEvent(aEvent) {
269     switch (aEvent.type) {
270       case "load": {
271         this.initPicker(this.detail);
272         this.dateTimePopupFrame.contentWindow.addEventListener("message", this);
273         break;
274       }
275       case "message": {
276         this.handleMessage(aEvent);
277         break;
278       }
279     }
280   }
282   handleMessage(aEvent) {
283     if (
284       !this.dateTimePopupFrame.contentDocument.nodePrincipal.isSystemPrincipal
285     ) {
286       return;
287     }
289     switch (aEvent.data.name) {
290       case "PickerPopupChanged": {
291         this.pickerState = aEvent.data.detail;
292         this.setInputBoxValue();
293         break;
294       }
295       case "ClosePopup": {
296         this.closePicker(aEvent.data.detail);
297         break;
298       }
299     }
300   }
302   postMessageToPicker(data) {
303     if (
304       this.dateTimePopupFrame.contentDocument.nodePrincipal.isSystemPrincipal
305     ) {
306       this.dateTimePopupFrame.contentWindow.postMessage(data, "*");
307     }
308   }