Bug 1512489 - Convert datetime-popup binding to a JSM r=Felipe
authorTimothy Guan-tin Chien <timdream@gmail.com>
Fri, 21 Dec 2018 21:51:57 +0000
changeset 451767 c41d930784a8821cf918fb971770a07b0997aa5c
parent 451766 c559079456f465e97f01fbb592e4f7d7abf1ced8
child 451768 e5e8ced1e070d6bad84fcb19198fa002fc04ed28
push id35253
push userccoroiu@mozilla.com
push dateSat, 22 Dec 2018 04:12:04 +0000
treeherdermozilla-central@d30b4fd63e17 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersFelipe
bugs1512489
milestone66.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1512489 - Convert datetime-popup binding to a JSM r=Felipe Differential Revision: https://phabricator.services.mozilla.com/D13944
browser/base/content/browser.css
toolkit/content/jar.mn
toolkit/content/tests/browser/head.js
toolkit/content/widgets/datetimepopup.xml
toolkit/modules/DateTimePickerPanel.jsm
toolkit/modules/DateTimePickerParent.jsm
toolkit/modules/moz.build
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -734,20 +734,16 @@ html|input.urlbar-input {
 #PopupAutoCompleteRichResult > richlistbox {
   transition: height 100ms;
 }
 
 #PopupAutoCompleteRichResult.showSearchSuggestionsNotification > richlistbox {
   transition: none;
 }
 
-#DateTimePickerPanel[active="true"] {
-  -moz-binding: url("chrome://global/content/bindings/datetimepopup.xml#datetime-popup");
-}
-
 #urlbar[pageproxystate=invalid] > #page-action-buttons > .urlbar-page-action,
 #identity-box.chromeUI ~ #page-action-buttons > .urlbar-page-action:not(#star-button-box),
 .urlbar-history-dropmarker[usertyping],
 .urlbar-go-button:not([usertyping]),
 .urlbar-go-button:not([parentfocused="true"]),
 #urlbar[pageproxystate="invalid"] > #identity-box > #blocked-permissions-container,
 #urlbar[pageproxystate="invalid"] > #identity-box > #notification-popup-box,
 #urlbar[pageproxystate="invalid"] > #identity-box > #identity-icon-labels {
--- a/toolkit/content/jar.mn
+++ b/toolkit/content/jar.mn
@@ -63,17 +63,16 @@ toolkit.jar:
    content/global/widgets.css
    content/global/bindings/autocomplete.xml    (widgets/autocomplete.xml)
    content/global/bindings/browser.xml         (widgets/browser.xml)
    content/global/bindings/button.xml          (widgets/button.xml)
    content/global/bindings/calendar.js         (widgets/calendar.js)
    content/global/bindings/checkbox.xml        (widgets/checkbox.xml)
    content/global/bindings/datekeeper.js       (widgets/datekeeper.js)
    content/global/bindings/datepicker.js       (widgets/datepicker.js)
-   content/global/bindings/datetimepopup.xml   (widgets/datetimepopup.xml)
    content/global/bindings/datetimebox.xml     (widgets/datetimebox.xml)
    content/global/bindings/datetimebox.css     (widgets/datetimebox.css)
 *  content/global/bindings/dialog.xml          (widgets/dialog.xml)
    content/global/bindings/general.xml         (widgets/general.xml)
    content/global/bindings/menu.xml            (widgets/menu.xml)
    content/global/bindings/menulist.xml        (widgets/menulist.xml)
    content/global/bindings/notification.xml    (widgets/notification.xml)
    content/global/bindings/numberbox.xml       (widgets/numberbox.xml)
--- a/toolkit/content/tests/browser/head.js
+++ b/toolkit/content/tests/browser/head.js
@@ -137,24 +137,17 @@ class DateTimeTestHelper {
    * Opens a new tab with the URL of the test page, and make sure the picker is
    * ready for testing.
    *
    * @param  {String} pageUrl
    */
   async openPicker(pageUrl) {
     this.tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
     await BrowserTestUtils.synthesizeMouseAtCenter("input", {}, gBrowser.selectedBrowser);
-    // If dateTimePopupFrame doesn't exist yet, wait for the binding to be
-    // attached.
-    // FIXME: This has a race condition and we may miss the following events.
-    //        (bug 1423498)
-    if (!this.panel.dateTimePopupFrame) {
-      await BrowserTestUtils.waitForEvent(this.panel, "DateTimePickerBindingReady");
-    }
-    this.frame = this.panel.dateTimePopupFrame;
+    this.frame = this.panel.querySelector("#dateTimePopupFrame");
     await this.waitForPickerReady();
   }
 
   async waitForPickerReady() {
     let readyPromise;
     let loadPromise = new Promise(resolve => {
       this.frame.addEventListener("load", () => {
        // Add the PickerReady event listener directly inside the load event
@@ -202,17 +195,16 @@ class DateTimeTestHelper {
    * Close the panel and the tab
    */
   async tearDown() {
     if (!this.panel.hidden) {
       let pickerClosePromise = new Promise(resolve => {
         this.panel.addEventListener("popuphidden", resolve, {once: true});
       });
       this.panel.hidePopup();
-      this.panel.closePicker();
       await pickerClosePromise;
     }
     BrowserTestUtils.removeTab(this.tab);
     this.tab = null;
   }
 
   /**
    * Clean up after tests. Remove the frame to prevent leak.
rename from toolkit/content/widgets/datetimepopup.xml
rename to toolkit/modules/DateTimePickerPanel.jsm
--- a/toolkit/content/widgets/datetimepopup.xml
+++ b/toolkit/modules/DateTimePickerPanel.jsm
@@ -1,337 +1,317 @@
-<?xml version="1.0"?>
+/* 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/. */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = [
+  "DateTimePickerPanel",
+];
+
+ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+var DateTimePickerPanel = class {
+  constructor(element) {
+    this.element = element;
+
+    this.TIME_PICKER_WIDTH = "12em";
+    this.TIME_PICKER_HEIGHT = "21em";
+    this.DATE_PICKER_WIDTH = "23.1em";
+    this.DATE_PICKER_HEIGHT = "20.7em";
+  }
+
+  get dateTimePopupFrame() {
+    let frame = this.element.querySelector("#dateTimePopupFrame");
+    if (!frame) {
+      frame = this.element.ownerDocument.createXULElement("iframe");
+      frame.id = "dateTimePopupFrame";
+      this.element.appendChild(frame);
+    }
+    return frame;
+  }
 
-<!-- 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/. -->
+  openPicker(type, anchor, detail) {
+    this.type = type;
+    this.pickerState = {};
+    // TODO: Resize picker according to content zoom level
+    this.element.style.fontSize = "10px";
+    switch (type) {
+      case "time":
+        {
+          this.detail = detail;
+          this.dateTimePopupFrame.addEventListener("load", this, true);
+          this.dateTimePopupFrame.setAttribute("src", "chrome://global/content/timepicker.xhtml");
+          this.dateTimePopupFrame.style.width = this.TIME_PICKER_WIDTH;
+          this.dateTimePopupFrame.style.height = this.TIME_PICKER_HEIGHT;
+          break;
+        }
+      case "date":
+        {
+          this.detail = detail;
+          this.dateTimePopupFrame.addEventListener("load", this, true);
+          this.dateTimePopupFrame.setAttribute("src", "chrome://global/content/datepicker.xhtml");
+          this.dateTimePopupFrame.style.width = this.DATE_PICKER_WIDTH;
+          this.dateTimePopupFrame.style.height = this.DATE_PICKER_HEIGHT;
+          break;
+        }
+    }
+    this.element.hidden = false;
+    this.element.openPopup(anchor, "after_start", 0, 0);
+  }
+
+  closePicker() {
+    this.setInputBoxValue(true);
+    this.pickerState = {};
+    this.type = undefined;
+    this.dateTimePopupFrame.removeEventListener("load", this, true);
+    this.dateTimePopupFrame.contentDocument.removeEventListener("message", this);
+    this.dateTimePopupFrame.setAttribute("src", "");
+    this.element.hidden = true;
+  }
 
-<bindings id="dateTimePopupBindings"
-   xmlns="http://www.mozilla.org/xbl"
-   xmlns:html="http://www.w3.org/1999/xhtml"
-   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-   xmlns:xbl="http://www.mozilla.org/xbl">
-  <binding id="datetime-popup"
-           extends="chrome://global/content/bindings/popup.xml#arrowpanel">
-    <implementation>
-      <property name="dateTimePopupFrame">
-        <getter>
-          let frame = this.querySelector("#dateTimePopupFrame");
-          if (!frame) {
-            frame = this.ownerDocument.createXULElement("iframe");
-            frame.id = "dateTimePopupFrame";
-            this.appendChild(frame);
-          }
-          return frame;
-        </getter>
-      </property>
-      <field name="TIME_PICKER_WIDTH" readonly="true">"12em"</field>
-      <field name="TIME_PICKER_HEIGHT" readonly="true">"21em"</field>
-      <field name="DATE_PICKER_WIDTH" readonly="true">"23.1em"</field>
-      <field name="DATE_PICKER_HEIGHT" readonly="true">"20.7em"</field>
-      <constructor><![CDATA[
-        this.mozIntl = Cc["@mozilla.org/mozintl;1"]
-                         .getService(Ci.mozIMozIntl);
-        // Notify DateTimePickerParent.jsm that binding is ready.
-        this.dispatchEvent(new CustomEvent("DateTimePickerBindingReady"));
-      ]]></constructor>
-      <method name="openPicker">
-        <parameter name="type"/>
-        <parameter name="anchor"/>
-        <parameter name="detail"/>
-        <body><![CDATA[
-          this.type = type;
-          this.pickerState = {};
-          // TODO: Resize picker according to content zoom level
-          this.style.fontSize = "10px";
-          switch (type) {
-            case "time": {
-              this.detail = detail;
-              this.dateTimePopupFrame.addEventListener("load", this, true);
-              this.dateTimePopupFrame.setAttribute("src", "chrome://global/content/timepicker.xhtml");
-              this.dateTimePopupFrame.style.width = this.TIME_PICKER_WIDTH;
-              this.dateTimePopupFrame.style.height = this.TIME_PICKER_HEIGHT;
-              break;
-            }
-            case "date": {
-              this.detail = detail;
-              this.dateTimePopupFrame.addEventListener("load", this, true);
-              this.dateTimePopupFrame.setAttribute("src", "chrome://global/content/datepicker.xhtml");
-              this.dateTimePopupFrame.style.width = this.DATE_PICKER_WIDTH;
-              this.dateTimePopupFrame.style.height = this.DATE_PICKER_HEIGHT;
-              break;
-            }
-          }
-          this.hidden = false;
-          this.openPopup(anchor, "after_start", 0, 0);
-        ]]></body>
-      </method>
-      <method name="closePicker">
-        <body><![CDATA[
-          this.setInputBoxValue(true);
-          this.pickerState = {};
-          this.type = undefined;
-          this.dateTimePopupFrame.removeEventListener("load", this, true);
-          this.dateTimePopupFrame.contentDocument.removeEventListener("message", this);
-          this.dateTimePopupFrame.setAttribute("src", "");
-          this.hidden = true;
-        ]]></body>
-      </method>
-      <method name="setPopupValue">
-        <parameter name="data"/>
-        <body><![CDATA[
-          switch (this.type) {
-            case "time": {
-              this.postMessageToPicker({
-                name: "PickerSetValue",
-                detail: data.value,
-              });
-              break;
-            }
-            case "date": {
-              const { year, month, day } = data.value;
-              this.postMessageToPicker({
-                name: "PickerSetValue",
-                detail: {
-                  year,
-                  // Month value from input box starts from 1 instead of 0
-                  month: month == undefined ? undefined : month - 1,
-                  day,
-                },
-              });
-              break;
-            }
-          }
-        ]]></body>
-      </method>
-      <method name="initPicker">
-        <parameter name="detail"/>
-        <body><![CDATA[
-          // TODO: When bug 1376616 lands, replace this.setGregorian with
-          //       mozIntl.Locale for setting calendar to Gregorian
-          const locale = this.setGregorian(Services.locale.appLocaleAsBCP47);
-          const dir = this.mozIntl.getLocaleInfo(locale).direction;
+  setPopupValue(data) {
+    switch (this.type) {
+      case "time":
+        {
+          this.postMessageToPicker({
+            name: "PickerSetValue",
+            detail: data.value,
+          });
+          break;
+        }
+      case "date":
+        {
+          const { year, month, day } = data.value;
+          this.postMessageToPicker({
+            name: "PickerSetValue",
+            detail: {
+              year,
+              // Month value from input box starts from 1 instead of 0
+              month: month == undefined ? undefined : month - 1,
+              day,
+            },
+          });
+          break;
+        }
+    }
+  }
+
+  initPicker(detail) {
+    // TODO: When bug 1376616 lands, replace this.setGregorian with
+    //       mozIntl.Locale for setting calendar to Gregorian
+    const locale = this.setGregorian(Services.locale.appLocaleAsBCP47);
+    const dir = Services.intl.getLocaleInfo(locale).direction;
+
+    switch (this.type) {
+      case "time":
+        {
+          const { hour, minute } = detail.value;
+          const format = detail.format || "12";
 
-          switch (this.type) {
-            case "time": {
-              const { hour, minute } = detail.value;
-              const format = detail.format || "12";
-
-              this.postMessageToPicker({
-                name: "PickerInit",
-                detail: {
-                  hour,
-                  minute,
-                  format,
-                  locale,
-                  min: detail.min,
-                  max: detail.max,
-                  step: detail.step,
-                },
-              });
-              break;
-            }
-            case "date": {
-              const { year, month, day } = detail.value;
-              const { firstDayOfWeek, weekends } =
-                this.getCalendarInfo(locale);
-              const monthStrings = this.getDisplayNames(
-                locale, [
-                  "dates/gregorian/months/january",
-                  "dates/gregorian/months/february",
-                  "dates/gregorian/months/march",
-                  "dates/gregorian/months/april",
-                  "dates/gregorian/months/may",
-                  "dates/gregorian/months/june",
-                  "dates/gregorian/months/july",
-                  "dates/gregorian/months/august",
-                  "dates/gregorian/months/september",
-                  "dates/gregorian/months/october",
-                  "dates/gregorian/months/november",
-                  "dates/gregorian/months/december",
-                ], "short");
-              const weekdayStrings = this.getDisplayNames(
-                locale, [
-                  "dates/gregorian/weekdays/sunday",
-                  "dates/gregorian/weekdays/monday",
-                  "dates/gregorian/weekdays/tuesday",
-                  "dates/gregorian/weekdays/wednesday",
-                  "dates/gregorian/weekdays/thursday",
-                  "dates/gregorian/weekdays/friday",
-                  "dates/gregorian/weekdays/saturday",
-                ], "short");
+          this.postMessageToPicker({
+            name: "PickerInit",
+            detail: {
+              hour,
+              minute,
+              format,
+              locale,
+              min: detail.min,
+              max: detail.max,
+              step: detail.step,
+            },
+          });
+          break;
+        }
+      case "date":
+        {
+          const { year, month, day } = detail.value;
+          const { firstDayOfWeek, weekends } =
+          this.getCalendarInfo(locale);
+          const monthStrings = this.getDisplayNames(
+            locale, [
+              "dates/gregorian/months/january",
+              "dates/gregorian/months/february",
+              "dates/gregorian/months/march",
+              "dates/gregorian/months/april",
+              "dates/gregorian/months/may",
+              "dates/gregorian/months/june",
+              "dates/gregorian/months/july",
+              "dates/gregorian/months/august",
+              "dates/gregorian/months/september",
+              "dates/gregorian/months/october",
+              "dates/gregorian/months/november",
+              "dates/gregorian/months/december",
+            ], "short");
+          const weekdayStrings = this.getDisplayNames(
+            locale, [
+              "dates/gregorian/weekdays/sunday",
+              "dates/gregorian/weekdays/monday",
+              "dates/gregorian/weekdays/tuesday",
+              "dates/gregorian/weekdays/wednesday",
+              "dates/gregorian/weekdays/thursday",
+              "dates/gregorian/weekdays/friday",
+              "dates/gregorian/weekdays/saturday",
+            ], "short");
 
-              this.postMessageToPicker({
-                name: "PickerInit",
-                detail: {
-                  year,
-                  // Month value from input box starts from 1 instead of 0
-                  month: month == undefined ? undefined : month - 1,
-                  day,
-                  firstDayOfWeek,
-                  weekends,
-                  monthStrings,
-                  weekdayStrings,
-                  locale,
-                  dir,
-                  min: detail.min,
-                  max: detail.max,
-                  step: detail.step,
-                  stepBase: detail.stepBase,
-                },
-              });
-              break;
-            }
+          this.postMessageToPicker({
+            name: "PickerInit",
+            detail: {
+              year,
+              // Month value from input box starts from 1 instead of 0
+              month: month == undefined ? undefined : month - 1,
+              day,
+              firstDayOfWeek,
+              weekends,
+              monthStrings,
+              weekdayStrings,
+              locale,
+              dir,
+              min: detail.min,
+              max: detail.max,
+              step: detail.step,
+              stepBase: detail.stepBase,
+            },
+          });
+          break;
+        }
+    }
+  }
+
+  /**
+   * @param {Boolean} passAllValues: Pass spinner values regardless if they've been set/changed or not
+   */
+  setInputBoxValue(passAllValues) {
+    switch (this.type) {
+      case "time":
+        {
+          const { hour, minute, isHourSet, isMinuteSet, isDayPeriodSet } = this.pickerState;
+          const isAnyValueSet = isHourSet || isMinuteSet || isDayPeriodSet;
+          if (passAllValues && isAnyValueSet) {
+            this.sendPickerValueChanged({ hour, minute });
+          } else {
+            this.sendPickerValueChanged({
+              hour: isHourSet || isDayPeriodSet ? hour : undefined,
+              minute: isMinuteSet ? minute : undefined,
+            });
           }
-        ]]></body>
-      </method>
-      <method name="setInputBoxValue">
-        <parameter name="passAllValues"/>
-        <body><![CDATA[
-          /**
-           * @param {Boolean} passAllValues: Pass spinner values regardless if they've been set/changed or not
-           */
-          switch (this.type) {
-            case "time": {
-              const { hour, minute, isHourSet, isMinuteSet, isDayPeriodSet } = this.pickerState;
-              const isAnyValueSet = isHourSet || isMinuteSet || isDayPeriodSet;
-              if (passAllValues && isAnyValueSet) {
-                this.sendPickerValueChanged({ hour, minute });
-              } else {
-                this.sendPickerValueChanged({
-                  hour: isHourSet || isDayPeriodSet ? hour : undefined,
-                  minute: isMinuteSet ? minute : undefined,
-                });
-              }
-              break;
-            }
-            case "date": {
-              this.sendPickerValueChanged(this.pickerState);
-              break;
-            }
-          }
-        ]]></body>
-      </method>
-      <method name="sendPickerValueChanged">
-        <parameter name="value"/>
-        <body><![CDATA[
-          switch (this.type) {
-            case "time": {
-              this.dispatchEvent(new CustomEvent("DateTimePickerValueChanged", {
-                detail: {
-                  hour: value.hour,
-                  minute: value.minute,
-                },
-              }));
-              break;
-            }
-            case "date": {
-              this.dispatchEvent(new CustomEvent("DateTimePickerValueChanged", {
-                detail: {
-                  year: value.year,
-                  // Month value from input box starts from 1 instead of 0
-                  month: value.month == undefined ? undefined : value.month + 1,
-                  day: value.day,
-                },
-              }));
-              break;
-            }
-          }
-        ]]></body>
-      </method>
-      <method name="getCalendarInfo">
-        <parameter name="locale"/>
-        <body><![CDATA[
-          const calendarInfo = this.mozIntl.getCalendarInfo(locale);
+          break;
+        }
+      case "date":
+        {
+          this.sendPickerValueChanged(this.pickerState);
+          break;
+        }
+    }
+  }
+
+  sendPickerValueChanged(value) {
+    switch (this.type) {
+      case "time":
+        {
+          this.element.dispatchEvent(new CustomEvent("DateTimePickerValueChanged", {
+            detail: {
+              hour: value.hour,
+              minute: value.minute,
+            },
+          }));
+          break;
+        }
+      case "date":
+        {
+          this.element.dispatchEvent(new CustomEvent("DateTimePickerValueChanged", {
+            detail: {
+              year: value.year,
+              // Month value from input box starts from 1 instead of 0
+              month: value.month == undefined ? undefined : value.month + 1,
+              day: value.day,
+            },
+          }));
+          break;
+        }
+    }
+  }
+
+  getCalendarInfo(locale) {
+    const calendarInfo = Services.intl.getCalendarInfo(locale);
 
-          // Day of week from calendarInfo starts from 1 as Sunday to 7 as Saturday,
-          // so they need to be mapped to JavaScript convention with 0 as Sunday
-          // and 6 as Saturday
-          let firstDayOfWeek = calendarInfo.firstDayOfWeek - 1,
-              weekendStart = calendarInfo.weekendStart - 1,
-              weekendEnd = calendarInfo.weekendEnd - 1;
+    // Day of week from calendarInfo starts from 1 as Sunday to 7 as Saturday,
+    // so they need to be mapped to JavaScript convention with 0 as Sunday
+    // and 6 as Saturday
+    let firstDayOfWeek = calendarInfo.firstDayOfWeek - 1,
+      weekendStart = calendarInfo.weekendStart - 1,
+      weekendEnd = calendarInfo.weekendEnd - 1;
 
-          let weekends = [];
+    let weekends = [];
+
+    // Make sure weekendEnd is greater than weekendStart
+    if (weekendEnd < weekendStart) {
+      weekendEnd += 7;
+    }
 
-          // Make sure weekendEnd is greater than weekendStart
-          if (weekendEnd < weekendStart) {
-            weekendEnd += 7;
-          }
+    // We get the weekends by incrementing weekendStart up to weekendEnd.
+    // If the start and end is the same day, then weekends only has one day.
+    for (let day = weekendStart; day <= weekendEnd; day++) {
+      weekends.push(day % 7);
+    }
+
+    return {
+      firstDayOfWeek,
+      weekends,
+    };
+  }
 
-          // We get the weekends by incrementing weekendStart up to weekendEnd.
-          // If the start and end is the same day, then weekends only has one day.
-          for (let day = weekendStart; day <= weekendEnd; day++) {
-            weekends.push(day % 7);
-          }
+  getDisplayNames(locale, keys, style) {
+    const displayNames = Services.intl.getDisplayNames(locale, { keys, style });
+    return keys.map(key => displayNames.values[key]);
+  }
+
+  setGregorian(locale) {
+    if (locale.match(/u-ca-/)) {
+      return locale.replace(/u-ca-[^-]+/, "u-ca-gregory");
+    }
+    return locale + "-u-ca-gregory";
+  }
 
-          return {
-            firstDayOfWeek,
-            weekends,
-          };
-        ]]></body>
-      </method>
-      <method name="getDisplayNames">
-        <parameter name="locale"/>
-        <parameter name="keys"/>
-        <parameter name="style"/>
-        <body><![CDATA[
-          const displayNames = this.mozIntl.getDisplayNames(locale, {keys, style});
-          return keys.map(key => displayNames.values[key]);
-        ]]></body>
-      </method>
-      <method name="setGregorian">
-        <parameter name="locale"/>
-        <body><![CDATA[
-          if (locale.match(/u-ca-/)) {
-            return locale.replace(/u-ca-[^-]+/, "u-ca-gregory");
-          }
-          return locale + "-u-ca-gregory";
-        ]]></body>
-      </method>
-      <method name="handleEvent">
-        <parameter name="aEvent"/>
-        <body><![CDATA[
-          switch (aEvent.type) {
-            case "load": {
-              this.initPicker(this.detail);
-              this.dateTimePopupFrame.contentWindow.addEventListener("message", this);
-              break;
-            }
-            case "message": {
-              this.handleMessage(aEvent);
-              break;
-            }
-          }
-        ]]></body>
-      </method>
-      <method name="handleMessage">
-        <parameter name="aEvent"/>
-        <body><![CDATA[
-          if (!this.dateTimePopupFrame.contentDocument.nodePrincipal.isSystemPrincipal) {
-            return;
-          }
+  handleEvent(aEvent) {
+    switch (aEvent.type) {
+      case "load":
+        {
+          this.initPicker(this.detail);
+          this.dateTimePopupFrame.contentWindow.addEventListener("message", this);
+          break;
+        }
+      case "message":
+        {
+          this.handleMessage(aEvent);
+          break;
+        }
+    }
+  }
+
+  handleMessage(aEvent) {
+    if (!this.dateTimePopupFrame.contentDocument.nodePrincipal.isSystemPrincipal) {
+      return;
+    }
 
-          switch (aEvent.data.name) {
-            case "PickerPopupChanged": {
-              this.pickerState = aEvent.data.detail;
-              this.setInputBoxValue();
-              break;
-            }
-            case "ClosePopup": {
-              this.hidePopup();
-              this.closePicker();
-              break;
-            }
-          }
-        ]]></body>
-      </method>
-      <method name="postMessageToPicker">
-        <parameter name="data"/>
-        <body><![CDATA[
-          if (this.dateTimePopupFrame.contentDocument.nodePrincipal.isSystemPrincipal) {
-            this.dateTimePopupFrame.contentWindow.postMessage(data, "*");
-          }
-        ]]></body>
-      </method>
+    switch (aEvent.data.name) {
+      case "PickerPopupChanged":
+        {
+          this.pickerState = aEvent.data.detail;
+          this.setInputBoxValue();
+          break;
+        }
+      case "ClosePopup":
+        {
+          this.element.hidePopup();
+          this.closePicker();
+          break;
+        }
+    }
+  }
 
-    </implementation>
-  </binding>
-</bindings>
+  postMessageToPicker(data) {
+    if (this.dateTimePopupFrame.contentDocument.nodePrincipal.isSystemPrincipal) {
+      this.dateTimePopupFrame.contentWindow.postMessage(data, "*");
+    }
+  }
+};
--- a/toolkit/modules/DateTimePickerParent.jsm
+++ b/toolkit/modules/DateTimePickerParent.jsm
@@ -11,16 +11,17 @@ function debug(aStr) {
   }
 }
 
 var EXPORTED_SYMBOLS = [
   "DateTimePickerParent",
 ];
 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
+ChromeUtils.defineModuleGetter(this, "DateTimePickerPanel", "resource://gre/modules/DateTimePickerPanel.jsm");
 
 /*
  * DateTimePickerParent receives message from content side (input box) and
  * is reposible for opening, closing and updating the picker. Similarly,
  * DateTimePickerParent listens for picker's events and notifies the content
  * side (input box) about them.
  */
 var DateTimePickerParent = {
@@ -100,17 +101,17 @@ var DateTimePickerParent = {
     let browser = this.weakBrowser ? this.weakBrowser.get() : null;
     if (browser) {
       browser.messageManager.sendAsyncMessage(
         "FormDateTime:PickerValueChanged", aEvent.detail);
     }
   },
 
   // Get picker from browser and show it anchored to the input box.
-  async showPicker(aBrowser, aData) {
+  showPicker(aBrowser, aData) {
     let rect = aData.rect;
     let type = aData.type;
     let detail = aData.detail;
 
     this._anchor = aBrowser.ownerGlobal.gBrowser.popupAnchor;
     this._anchor.left = rect.left;
     this._anchor.top = rect.top;
     this._anchor.width = rect.width;
@@ -124,32 +125,21 @@ var DateTimePickerParent = {
     if (Services.focus.activeWindow != window ||
         tabbrowser.selectedBrowser != aBrowser) {
       // We were sent a message from a window or tab that went into the
       // background, so we'll ignore it for now.
       return;
     }
 
     this.weakBrowser = Cu.getWeakReference(aBrowser);
-    this.picker = aBrowser.dateTimePicker;
-    if (!this.picker) {
+    if (!aBrowser.dateTimePicker) {
       debug("aBrowser.dateTimePicker not found, exiting now.");
       return;
     }
-    // The datetimepopup binding is only attached when it is needed.
-    // Check if openPicker method is present to determine if binding has
-    // been attached. If not, attach the binding first before calling it.
-    if (!this.picker.openPicker) {
-      let bindingPromise = new Promise(resolve => {
-        this.picker.addEventListener("DateTimePickerBindingReady",
-                                     resolve, {once: true});
-      });
-      this.picker.setAttribute("active", true);
-      await bindingPromise;
-    }
+    this.picker = new DateTimePickerPanel(aBrowser.dateTimePicker);
     // The arrow panel needs an anchor to work. The popupAnchor (this._anchor)
     // is a transparent div that the arrow can point to.
     this.picker.openPicker(type, this._anchor, detail);
 
     this.addPickerListeners();
   },
 
   // Picker is closed, do some cleanup.
@@ -160,21 +150,21 @@ var DateTimePickerParent = {
     this._anchor.hidden = true;
   },
 
   // Listen to picker's event.
   addPickerListeners() {
     if (!this.picker) {
       return;
     }
-    this.picker.addEventListener("popuphidden", this);
-    this.picker.addEventListener("DateTimePickerValueChanged", this);
+    this.picker.element.addEventListener("popuphidden", this);
+    this.picker.element.addEventListener("DateTimePickerValueChanged", this);
   },
 
   // Stop listening to picker's event.
   removePickerListeners() {
     if (!this.picker) {
       return;
     }
-    this.picker.removeEventListener("popuphidden", this);
-    this.picker.removeEventListener("DateTimePickerValueChanged", this);
+    this.picker.element.removeEventListener("popuphidden", this);
+    this.picker.element.removeEventListener("DateTimePickerValueChanged", this);
   },
 };
--- a/toolkit/modules/moz.build
+++ b/toolkit/modules/moz.build
@@ -189,16 +189,17 @@ EXTRA_JS_MODULES += [
     'CanonicalJSON.jsm',
     'CertUtils.jsm',
     'CharsetMenu.jsm',
     'ClientID.jsm',
     'Color.jsm',
     'Console.jsm',
     'CreditCard.jsm',
     'css-selector.js',
+    'DateTimePickerPanel.jsm',
     'DateTimePickerParent.jsm',
     'DeferredTask.jsm',
     'Deprecated.jsm',
     'E10SUtils.jsm',
     'EventEmitter.jsm',
     'FileUtils.jsm',
     'FindBarContent.jsm',
     'Finder.jsm',