Bug 1534774 - [de-xbl] Convert minimonth bindings to custom elements. r=philipp
authorPaul Morris <paul@paulwmorris.com>
Tue, 02 Jul 2019 00:04:04 +0200
changeset 35147 099f40c736f06cbc1975c5be79a62f53f9356418
parent 35146 c1649b46c7a37900587f35ef9cc352bafa71b824
child 35148 9d7a0c93a3b1aaf72df2acabe83215fcf82fe0ab
push id2454
push userclokep@gmail.com
push dateTue, 09 Jul 2019 01:59:18 +0000
treeherdercomm-beta@741524ea4c59 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersphilipp
bugs1534774
Bug 1534774 - [de-xbl] Convert minimonth bindings to custom elements. r=philipp Thanks to aleca for help with getting mozmill tests working on macos.
.eslintignore
calendar/base/content/dialogs/calendar-event-dialog-attendees.xul
calendar/base/content/dialogs/calendar-event-dialog-recurrence.js
calendar/base/content/dialogs/calendar-event-dialog-recurrence.xul
calendar/base/content/dialogs/calendar-event-dialog-reminder.xul
calendar/base/content/dialogs/calendar-event-dialog-timezone.xul
calendar/base/content/dialogs/calendar-print-dialog.xul
calendar/base/content/today-pane.js
calendar/base/content/today-pane.xul
calendar/base/content/widgets/calendar-minimonth.js
calendar/base/content/widgets/calendar-widget-bindings.css
calendar/base/content/widgets/minimonth.xml
calendar/base/jar.mn
calendar/base/themes/common/widgets/minimonth.css
calendar/lightning/content/lightning-item-iframe.xul
calendar/lightning/content/messenger-overlay-sidebar.xul
calendar/resources/content/datetimepickers/datetimepickers.js
calendar/test/mozmill/cal-recurrence/testAnnualRecurrence.js
calendar/test/mozmill/cal-recurrence/testBiweeklyRecurrence.js
calendar/test/mozmill/cal-recurrence/testDailyRecurrence.js
calendar/test/mozmill/cal-recurrence/testLastDayOfMonthRecurrence.js
calendar/test/mozmill/cal-recurrence/testWeeklyUntilRecurrence.js
calendar/test/mozmill/cal-recurrence/testWeeklyWithExceptionRecurrence.js
calendar/test/mozmill/recurrenceRotated/testAnnualRecurrence.js
calendar/test/mozmill/recurrenceRotated/testBiweeklyRecurrence.js
calendar/test/mozmill/recurrenceRotated/testDailyRecurrence.js
calendar/test/mozmill/recurrenceRotated/testLastDayOfMonthRecurrence.js
calendar/test/mozmill/recurrenceRotated/testWeeklyUntilRecurrence.js
calendar/test/mozmill/recurrenceRotated/testWeeklyWithExceptionRecurrence.js
calendar/test/mozmill/shared-modules/test-calendar-utils.js
calendar/test/mozmill/testBasicFunctionality.js
calendar/test/mozmill/views/testMonthView.js
calendar/test/mozmill/views/testMultiweekView.js
--- a/.eslintignore
+++ b/.eslintignore
@@ -11,20 +11,21 @@ obj*/**
 mozilla/**
 
 # These directories don't contain any js and are not meant to
 config/**
 db/**
 other-licenses/**
 testing/**
 
-# Temporarily disabled until the XUL parser is fixed.
+# Temporarily disabled until the XUL parser is fixed (see bug 1542548).
 calendar/lightning/content/messenger-overlay-sidebar.xul
 calendar/providers/gdata/content/gdata-calendar-creation.xul
 common/src/viewSource.xul
+calendar/base/content/today-pane.xul
 
 # We ignore all these directories by default, until we get them enabled.
 # If you are enabling a directory, please add directory specific exclusions
 # below.
 build/**
 suite/**
 
 # chat exclusions
--- a/calendar/base/content/dialogs/calendar-event-dialog-attendees.xul
+++ b/calendar/base/content/dialogs/calendar-event-dialog-attendees.xul
@@ -28,16 +28,17 @@
 
   <!-- Javascript includes -->
   <script src="chrome://calendar/content/calendar-event-dialog-attendees-custom-elements.js"/>
   <script src="chrome://calendar/content/calendar-event-dialog-attendees.js"/>
   <script src="chrome://calendar/content/calendar-dialog-utils.js"/>
   <script src="chrome://calendar/content/calendar-statusbar.js"/>
   <script src="chrome://calendar/content/calendar-ui-utils.js"/>
   <script src="chrome://messenger/content/customElements.js"/>
+  <script src="chrome://calendar/content/widgets/calendar-minimonth.js"/>
   <script src="chrome://calendar/content/datetimepickers/datetimepickers.js"/>
 
   <hbox align="center" pack="end">
     <spacer flex="1"/>
     <label value="&event.freebusy.suggest.slot;"/>
     <button label="&event.freebusy.button.previous.slot;"
             dir="normal"
             class="left-icon"
--- a/calendar/base/content/dialogs/calendar-event-dialog-recurrence.js
+++ b/calendar/base/content/dialogs/calendar-event-dialog-recurrence.js
@@ -52,17 +52,17 @@ const RecurrencePreview = {
             this.mDateTime = cal.dtz.now();
         }
         return this.mDateTime;
     },
     /**
      * Updates #recurrence-preview node layout on window resize.
      */
     onResize() {
-        let minimonth = this.node.querySelector("minimonth");
+        let minimonth = this.node.querySelector("calendar-minimonth");
 
         let row = this.node.querySelector("row");
         let rows = row.parentNode;
 
         let minimonthRect = minimonth.getBoundingClientRect();
         let nodeRect = this.node.getBoundingClientRect();
         let contentWidth = minimonthRect.width;
         let containerWidth = nodeRect.width;
@@ -142,17 +142,17 @@ const RecurrencePreview = {
             }
             row = row.nextSibling;
         }
     },
     /**
      * Updates preview of #recurrence-preview node.
      */
     updatePreview(recurrenceInfo) {
-        let minimonth = this.node.querySelector("minimonth");
+        let minimonth = this.node.querySelector("calendar-minimonth");
         this.node.style.minHeight = minimonth.getBoundingClientRect().height + "px";
 
         this.mRecurrenceInfo = recurrenceInfo;
         let start = this.dateTime.clone();
         start.day = 1;
         start.hour = 0;
         start.minute = 0;
         start.second = 0;
--- a/calendar/base/content/dialogs/calendar-event-dialog-recurrence.xul
+++ b/calendar/base/content/dialogs/calendar-event-dialog-recurrence.xul
@@ -27,16 +27,17 @@
 
   <!-- Javascript includes -->
   <script src="chrome://calendar/content/calendar-event-dialog-recurrence.js"/>
   <script src="chrome://calendar/content/calendar-dialog-utils.js"/>
   <script src="chrome://calendar/content/calendar-ui-utils.js"/>
   <script src="chrome://calendar/content/calendar-statusbar.js"/>
 
   <script src="chrome://messenger/content/customElements.js"/>
+  <script src="chrome://calendar/content/widgets/calendar-minimonth.js"/>
   <script src="chrome://calendar/content/datetimepickers/datetimepickers.js"/>
   <script src="chrome://calendar/content/calendar-daypicker.js"/>
 
   <!-- recurrence pattern -->
   <groupbox id="recurrence-pattern-groupbox">
     <hbox class="groupbox-title">
       <label id="recurrence-pattern-caption" class="header">&event.recurrence.pattern.label;</label>
     </hbox>
@@ -554,18 +555,18 @@
     <box id="recurrence-preview" flex="1">
       <grid flex="1">
         <columns>
           <column class="first-column"/>
           <column flex="1"/>
         </columns>
         <rows>
           <row>
-            <minimonth readonly="true"/>
-            <minimonth readonly="true"/>
-            <minimonth readonly="true"/>
+            <calendar-minimonth readonly="true"/>
+            <calendar-minimonth readonly="true"/>
+            <calendar-minimonth readonly="true"/>
             <spacer/>
           </row>
         </rows>
       </grid>
     </box>
   </groupbox>
 </dialog>
--- a/calendar/base/content/dialogs/calendar-event-dialog-reminder.xul
+++ b/calendar/base/content/dialogs/calendar-event-dialog-reminder.xul
@@ -21,16 +21,17 @@
         onload="onLoad()"
         persist="screenX screenY width height"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml">
 
   <!-- Javascript includes -->
   <script src="chrome://calendar/content/calendar-event-dialog-reminder.js"/>
   <script src="chrome://calendar/content/calendar-ui-utils.js"/>
   <script src="chrome://messenger/content/customElements.js"/>
+  <script src="chrome://calendar/content/widgets/calendar-minimonth.js"/>
   <script src="chrome://calendar/content/datetimepickers/datetimepickers.js"/>
 
   <hbox id="reminder-notifications" class="notification-inline" flex="1">
     <!-- notificationbox will be added here lazily. -->
   </hbox>
 
   <!-- Listbox with custom reminders -->
   <vbox flex="1">
--- a/calendar/base/content/dialogs/calendar-event-dialog-timezone.xul
+++ b/calendar/base/content/dialogs/calendar-event-dialog-timezone.xul
@@ -20,16 +20,17 @@
 <dialog id="calendar-event-dialog-timezone"
         title="&timezone.title.label;"
         windowtype="Calendar:EventDialog:Timezone"
         onload="onLoad()"
         persist="screenX screenY width height"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml">
 
   <!-- Javascript includes -->
+  <script src="chrome://calendar/content/widgets/calendar-minimonth.js"/>
   <script src="chrome://calendar/content/calendar-event-dialog-timezone.js"/>
   <script src="chrome://calendar/content/calendar-dialog-utils.js"/>
   <script src="chrome://calendar/content/calendar-ui-utils.js"/>
   <script src="chrome://messenger/content/customElements.js"/>
   <script src="chrome://calendar/content/datetimepickers/datetimepickers.js"/>
 
   <hbox align="center">
     <spacer flex="1"/>
--- a/calendar/base/content/dialogs/calendar-print-dialog.xul
+++ b/calendar/base/content/dialogs/calendar-print-dialog.xul
@@ -24,16 +24,17 @@
         buttonaccesskeyaccept="&calendar.print.button.accesskey;"
         defaultButton="accept"
         persist="screenX screenY width height"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml">
 
   <script src="chrome://calendar/content/calendar-print-dialog.js"/>
   <script src="chrome://calendar/content/calendar-ui-utils.js"/>
   <script src="chrome://global/content/printUtils.js"/>
+  <script src="chrome://calendar/content/widgets/calendar-minimonth.js"/>
   <script src="chrome://calendar/content/datetimepickers/datetimepickers.js"/>
 
   <hbox id="firstHbox" flex="1">
     <vbox id="groupboxVbox">
       <groupbox id="settingsGroup">
         <hbox class="groupbox-title">
           <label class="header">&calendar.print.settingsGroup.label;</label>
         </hbox>
--- a/calendar/base/content/today-pane.js
+++ b/calendar/base/content/today-pane.js
@@ -341,17 +341,17 @@ var TodayPane = {
 
         let monthnamelabel = document.getElementById("monthNameContainer");
         monthnamelabel.value = cal.getDateFormatter().shortMonthName(this.start.month) + " " + this.start.year;
 
         let currentweeklabel = document.getElementById("currentWeek-label");
         currentweeklabel.value = cal.l10n.getCalString("shortcalendarweek") + " " + cal.getWeekInfoService().getWeekTitle(this.start);
 
         if (!aDontUpdateMinimonth) {
-            document.getElementById("today-Minimonth").value = cal.dtz.dateTimeToJsDate(this.start);
+            document.getElementById("today-minimonth").value = cal.dtz.dateTimeToJsDate(this.start);
         }
         this.updatePeriod();
         this.setDay.alreadySettingDay = false;
     },
 
     /**
      * Advance by a given number of days in the today pane.
      *
@@ -385,17 +385,17 @@ var TodayPane = {
      * Display a certain section in the minday/minimonth part of the todaypane.
      *
      * @param aSection      The section to display
      */
     displayMiniSection: function(aSection) {
         document.getElementById("today-minimonth-box").setVisible(aSection == "minimonth");
         document.getElementById("mini-day-box").setVisible(aSection == "miniday");
         document.getElementById("today-none-box").setVisible(aSection == "none");
-        setBooleanAttribute(document.getElementById("today-Minimonth"), "freebusy", aSection == "minimonth");
+        setBooleanAttribute(document.getElementById("today-minimonth"), "freebusy", aSection == "minimonth");
     },
 
     /**
      * Handler function for the DOMAttrModified event used to observe the
      * todaypane-splitter.
      *
      * @param aEvent        The DOM event occurring on attribute modification.
      */
--- a/calendar/base/content/today-pane.xul
+++ b/calendar/base/content/today-pane.xul
@@ -63,17 +63,19 @@
         <modebox id="today-minimonth-box"
                   pack="center"
                   class="today-subpane"
                   mode="mail,calendar,task"
                   broadcaster="modeBroadcaster"
                   collapsedinmodes="mail,calendar,task"
                   refcontrol="ltnTodayPaneDisplayMinimonth"
                   persist="collapsedinmodes">
-          <minimonth id="today-Minimonth" freebusy="true" onchange="TodayPane.setDaywithjsDate(this.value);"/>
+          <calendar-minimonth id="today-minimonth"
+                              freebusy="true"
+                              onchange="TodayPane.setDaywithjsDate(this.value);"/>
         </modebox>
         <modebox id="mini-day-box"
                  mode="mail,calendar,task"
                  class="today-subpane"
                  refcontrol="ltnTodayPaneDisplayMiniday"
                  broadcaster="modeBroadcaster"
                  collapsedinmodes=""
                  persist="collapsedinmodes"
@@ -130,20 +132,20 @@
                   <spacer flex="1"/>
                 </hbox>
               </vbox>
               <toolbarbutton id="miniday-dropdown-button"
                              tooltiptext="&showselectedday.tooltip;"
                              type="menu">
                 <panel id="miniday-month-panel" position="after_end"
                        onpopupshown="this.firstChild.focusCalendar();">
-                  <minimonth id="todayMinimonth"
-                             flex="1"
-                             onchange="TodayPane.setDaywithjsDate(this.value);
-                                       document.getElementById('miniday-month-panel').hidePopup();"/>
+                  <calendar-minimonth id="miniday-dropdown-minimonth"
+                                      flex="1"
+                                      onchange="TodayPane.setDaywithjsDate(this.value);
+                                                document.getElementById('miniday-month-panel').hidePopup();"/>
                 </panel>
               </toolbarbutton>
             </hbox>
           </stack>
         </modebox>
         <vbox flex="1">
           <hbox id="agenda-toolbar" iconsize="small">
             <toolbarbutton id="todaypane-new-event-button"
rename from calendar/base/content/widgets/minimonth.xml
rename to calendar/base/content/widgets/calendar-minimonth.js
--- a/calendar/base/content/widgets/minimonth.xml
+++ b/calendar/base/content/widgets/calendar-minimonth.js
@@ -1,358 +1,362 @@
-<?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/. -->
+/* 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/. */
+
+/* globals cal MozXULElement setBooleanAttribute */
 
-<!--
-   MiniMonth Calendar: day-of-month grid XBL component.
-   Displays month name and year above grid of days of month by week rows.
-   Arrows move forward or back a month.
-   Selecting a month name from month menu moves to that month in same year.
-   Selecting a year from year menu moves to same month in selected year.
-   Clicking on a day cell calls onchange attribute.
-   Changing month via arrows or menus calls onmonthchange attribute.
+"use strict";
 
-   At site, can provide id, and code to run when value changed by picker.
-     <calendar id="my-date-picker" onchange="myDatePick( this );"/>
-
-   May get/set value in javascript with
-     document.getElementById("my-date-picker").value = new Date();
+// Wrap in a block to prevent leaking to window scope.
+{
+    const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 
-   Use attributes onpopuplisthidden and onmonthchange for working around
-   bugs that occur when minimonth is displayed in a popup (as in datepicker):
-     Currently (2005.3)
-       whenever a child popup is hidden, the parent popup needs to be reshown.
-         Use onpopuplisthidden to reshow parent popop (hidePopup, openPopup).
-       When title month or year changes, parent popup may need to be reshown.
-         Use onmonthchange to reshow parent popop (hidePopup, openPopup).
--->
-
-<!-- import-globals-from ../calendar-ui-utils.js -->
+    /**
+     * MiniMonth Calendar: day-of-month grid component.
+     * Displays month name and year above grid of days of month by week rows.
+     * Arrows move forward or back a month or a year.
+     * Clicking on a day cell selects that day.
+     * At site, can provide id, and code to run when value changed by picker.
+     *   <calendar-minimonth id="my-date-picker" onchange="myDatePick( this );"/>
+     *
+     * May get/set value in javascript with
+     *   document.querySelector("#my-date-picker").value = new Date();
+     *
+     * @implements {calIOperationListener}
+     * @implements {calIObserver}
+     * @implements {calICompositeObserver}
+     * @implements {nsIObserver}
+     */
+    class CalendarMinimonth extends MozXULElement {
+        constructor() {
+            super();
+            // Set up custom interfaces.
+            // calIObserver is not used like the others are, so it is not set up here.
+            this.calICompositeObserver = this.getCustomInterfaceCallback(Ci.calICompositeObserver);
+            this.calIOperationListener = this.getCustomInterfaceCallback(Ci.calIOperationListener);
+            this.nsIObserver = this.getCustomInterfaceCallback(Ci.nsIObserver);
+        }
 
-<!DOCTYPE bindings
-[
-    <!ENTITY % dtd1 SYSTEM "chrome://calendar/locale/global.dtd" > %dtd1;
-    <!ENTITY % dtd2 SYSTEM "chrome://global/locale/global.dtd" > %dtd2;
-]>
+        static get inheritedAttributes() {
+            return {
+                ".minimonth-header": "readonly,month,year",
+                ".minimonth-month-name": "selectedIndex=month",
+                ".minimonth-year-name": "value=year",
+            };
+        }
 
-<bindings id="xulMiniMonth"
-          xmlns="http://www.mozilla.org/xbl"
-          xmlns:html="http://www.w3.org/1999/xhtml"
-          xmlns:xbl="http://www.mozilla.org/xbl"
-          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+        connectedCallback() {
+            if (this.delayConnectedCallback() || this.hasChildNodes()) {
+                return;
+            }
 
-  <binding id="minimonth-header">
-    <content class="minimonth-month-box" align="center">
-      <xul:toolbarbutton anonid="months-back-button" class="minimonth-nav-btns" dir="-1"
-                         oncommand="this.kMinimonth.advanceMonth(parseInt(this.getAttribute('dir'), 10))"
-                         tooltiptext="&onemonthbackward.tooltip;"/>
-      <xul:deck anonid="monthheader" xbl:inherits="selectedIndex=month" class="minimonth-month-name" tabindex="-1">
-        <xul:text value="&month.1.name;"/>
-        <xul:text value="&month.2.name;"/>
-        <xul:text value="&month.3.name;"/>
-        <xul:text value="&month.4.name;"/>
-        <xul:text value="&month.5.name;"/>
-        <xul:text value="&month.6.name;"/>
-        <xul:text value="&month.7.name;"/>
-        <xul:text value="&month.8.name;"/>
-        <xul:text value="&month.9.name;"/>
-        <xul:text value="&month.10.name;"/>
-        <xul:text value="&month.11.name;"/>
-        <xul:text value="&month.12.name;"/>
-      </xul:deck>
-      <xul:toolbarbutton anonid="months-forward-button" class="minimonth-nav-btns" dir="1"
-                         oncommand="this.kMinimonth.advanceMonth(parseInt(this.getAttribute('dir'), 10))"
-                         tooltiptext="&onemonthforward.tooltip;"/>
-      <xul:toolbarbutton anonid="years-back-button" class="minimonth-nav-btns" dir="-1"
-                         oncommand="this.kMinimonth.advanceYear(parseInt(this.getAttribute('dir'), 10))"
-                         tooltiptext="&oneyearbackward.tooltip;"/>
-      <xul:text anonid="yearcell" class="minimonth-year-name" xbl:inherits="value=year" tabindex="-1"/>
-      <xul:toolbarbutton anonid="years-forward-button" class="minimonth-nav-btns" dir="1"
-                         oncommand="this.kMinimonth.advanceYear(parseInt(this.getAttribute('dir'), 10))"
-                         tooltiptext="&oneyearforward.tooltip;"/>
-      <xul:spacer flex="1"/>
-      <xul:toolbarbutton anonid="today-button" class="minimonth-nav-btns" dir="0"
-                         oncommand="this.kMinimonth.value = new Date();"
-                         tooltiptext="&showToday.tooltip;"/>
-    </content>
-    <implementation>
-      <field name="kMinimonth">null</field>
-      <constructor><![CDATA[
-          const { cal } = ChromeUtils.import("resource://calendar/modules/calUtils.jsm");
+            const minimonthHeader = `
+                <hbox class="minimonth-header minimonth-month-box">
+                  <toolbarbutton class="months-back-button minimonth-nav-btns"
+                                 dir="-1"
+                                 oncommand="this.kMinimonth.advanceMonth(parseInt(this.getAttribute('dir'), 10))"
+                                 tooltiptext="&onemonthbackward.tooltip;"></toolbarbutton>
+                  <deck class="monthheader minimonth-month-name"
+                        tabindex="-1">
+                    <text value="&month.1.name;"></text>
+                    <text value="&month.2.name;"></text>
+                    <text value="&month.3.name;"></text>
+                    <text value="&month.4.name;"></text>
+                    <text value="&month.5.name;"></text>
+                    <text value="&month.6.name;"></text>
+                    <text value="&month.7.name;"></text>
+                    <text value="&month.8.name;"></text>
+                    <text value="&month.9.name;"></text>
+                    <text value="&month.10.name;"></text>
+                    <text value="&month.11.name;"></text>
+                    <text value="&month.12.name;"></text>
+                  </deck>
+                  <toolbarbutton class="months-forward-button minimonth-nav-btns"
+                                 dir="1"
+                                 oncommand="this.kMinimonth.advanceMonth(parseInt(this.getAttribute('dir'), 10))"
+                                 tooltiptext="&onemonthforward.tooltip;"></toolbarbutton>
+                  <toolbarbutton class="years-back-button minimonth-nav-btns"
+                                 dir="-1"
+                                 oncommand="this.kMinimonth.advanceYear(parseInt(this.getAttribute('dir'), 10))"
+                                 tooltiptext="&oneyearbackward.tooltip;"></toolbarbutton>
+                  <text class="yearcell minimonth-year-name"
+                        tabindex="-1"></text>
+                  <toolbarbutton class="years-forward-button minimonth-nav-btns"
+                                 dir="1"
+                                 oncommand="this.kMinimonth.advanceYear(parseInt(this.getAttribute('dir'), 10))"
+                                 tooltiptext="&oneyearforward.tooltip;"></toolbarbutton>
+                  <spacer flex="1"></spacer>
+                  <toolbarbutton class="today-button minimonth-nav-btns"
+                                 dir="0"
+                                 oncommand="this.kMinimonth.value = new Date();"
+                                 tooltiptext="&showToday.tooltip;"></toolbarbutton>
+                </hbox>
+            `;
 
-          this.kMinimonth = cal.view.getParentNodeOrThis(this, "minimonth");
-          document.getAnonymousElementByAttribute(this, "anonid", "months-back-button").kMinimonth = this.kMinimonth;
-          document.getAnonymousElementByAttribute(this, "anonid", "months-forward-button").kMinimonth = this.kMinimonth;
-          document.getAnonymousElementByAttribute(this, "anonid", "years-back-button").kMinimonth = this.kMinimonth;
-          document.getAnonymousElementByAttribute(this, "anonid", "years-forward-button").kMinimonth = this.kMinimonth;
-          document.getAnonymousElementByAttribute(this, "anonid", "today-button").kMinimonth = this.kMinimonth;
-      ]]></constructor>
-    </implementation>
-  </binding>
+            const minimonthWeekRow = `
+                <html:tr class="minimonth-row-body">
+                  <html:th class="minimonth-week" scope="row"></html:th>
+                  <html:td class="minimonth-day" tabindex="-1"></html:td>
+                  <html:td class="minimonth-day" tabindex="-1"></html:td>
+                  <html:td class="minimonth-day" tabindex="-1"></html:td>
+                  <html:td class="minimonth-day" tabindex="-1"></html:td>
+                  <html:td class="minimonth-day" tabindex="-1"></html:td>
+                  <html:td class="minimonth-day" tabindex="-1"></html:td>
+                  <html:td class="minimonth-day" tabindex="-1"></html:td>
+                </html:tr>
+            `;
+
+            this.appendChild(MozXULElement.parseXULToFragment(`
+                ${minimonthHeader}
+                <html:div class="minimonth-readonly-header minimonth-month-box"></html:div>
+                <html:table class="minimonth-calendar minimonth-cal-box">
+                  <html:tr class="minimonth-row-head">
+                    <html:th class="minimonth-row-header-week" scope="col"></html:th>
+                    <html:th class="minimonth-row-header" scope="col"></html:th>
+                    <html:th class="minimonth-row-header" scope="col"></html:th>
+                    <html:th class="minimonth-row-header" scope="col"></html:th>
+                    <html:th class="minimonth-row-header" scope="col"></html:th>
+                    <html:th class="minimonth-row-header" scope="col"></html:th>
+                    <html:th class="minimonth-row-header" scope="col"></html:th>
+                    <html:th class="minimonth-row-header" scope="col"></html:th>
+                  </html:tr>
+                  ${minimonthWeekRow}
+                  ${minimonthWeekRow}
+                  ${minimonthWeekRow}
+                  ${minimonthWeekRow}
+                  ${minimonthWeekRow}
+                  ${minimonthWeekRow}
+                </html:table>
+            `,
+                [
+                    "chrome://calendar/locale/global.dtd",
+                    "chrome://global/locale/global.dtd"
+                ]
+            ));
+            this.initializeAttributeInheritance();
+            this.setAttribute("orient", "vertical");
 
-  <binding id="minimonth">
-    <content orient="vertical" xbl:inherits="onchange,onmonthchange,onpopuplisthidden,readonly" role="group">
-      <xul:minimonth-header anonid="minimonth-header" xbl:inherits="readonly,month,year"/>
-      <xul:hbox anonid="minimonth-readonly-header" class="minimonth-month-box" align="center" pack="center">
-        <xul:text anonid="minimonth-readonly-header-text"/>
-      </xul:hbox>
-      <html:table anonid="minimonth-calendar" class="minimonth-cal-box">
-        <html:tr class="minimonth-row-head" anonid="minimonth-row-header">
-          <html:th class="minimonth-row-header-week" scope="col"/>
-          <html:th class="minimonth-row-header" scope="col"/>
-          <html:th class="minimonth-row-header" scope="col"/>
-          <html:th class="minimonth-row-header" scope="col"/>
-          <html:th class="minimonth-row-header" scope="col"/>
-          <html:th class="minimonth-row-header" scope="col"/>
-          <html:th class="minimonth-row-header" scope="col"/>
-          <html:th class="minimonth-row-header" scope="col"/>
-        </html:tr>
-        <html:tr class="minimonth-row-body">
-          <html:th class="minimonth-week" scope="row"/>
-          <html:td class="minimonth-day" tabindex="-1"/>
-          <html:td class="minimonth-day" tabindex="-1"/>
-          <html:td class="minimonth-day" tabindex="-1"/>
-          <html:td class="minimonth-day" tabindex="-1"/>
-          <html:td class="minimonth-day" tabindex="-1"/>
-          <html:td class="minimonth-day" tabindex="-1"/>
-          <html:td class="minimonth-day" tabindex="-1"/>
-        </html:tr>
-        <html:tr class="minimonth-row-body">
-          <html:th class="minimonth-week" scope="row"/>
-          <html:td class="minimonth-day" tabindex="-1"/>
-          <html:td class="minimonth-day" tabindex="-1"/>
-          <html:td class="minimonth-day" tabindex="-1"/>
-          <html:td class="minimonth-day" tabindex="-1"/>
-          <html:td class="minimonth-day" tabindex="-1"/>
-          <html:td class="minimonth-day" tabindex="-1"/>
-          <html:td class="minimonth-day" tabindex="-1"/>
-        </html:tr>
-        <html:tr class="minimonth-row-body">
-          <html:th class="minimonth-week" scope="row"/>
-          <html:td class="minimonth-day" tabindex="-1"/>
-          <html:td class="minimonth-day" tabindex="-1"/>
-          <html:td class="minimonth-day" tabindex="-1"/>
-          <html:td class="minimonth-day" tabindex="-1"/>
-          <html:td class="minimonth-day" tabindex="-1"/>
-          <html:td class="minimonth-day" tabindex="-1"/>
-          <html:td class="minimonth-day" tabindex="-1"/>
-        </html:tr>
-        <html:tr class="minimonth-row-body">
-          <html:th class="minimonth-week" scope="row"/>
-          <html:td class="minimonth-day" tabindex="-1"/>
-          <html:td class="minimonth-day" tabindex="-1"/>
-          <html:td class="minimonth-day" tabindex="-1"/>
-          <html:td class="minimonth-day" tabindex="-1"/>
-          <html:td class="minimonth-day" tabindex="-1"/>
-          <html:td class="minimonth-day" tabindex="-1"/>
-          <html:td class="minimonth-day" tabindex="-1"/>
-        </html:tr>
-        <html:tr class="minimonth-row-body">
-          <html:th class="minimonth-week" scope="row"/>
-          <html:td class="minimonth-day" tabindex="-1"/>
-          <html:td class="minimonth-day" tabindex="-1"/>
-          <html:td class="minimonth-day" tabindex="-1"/>
-          <html:td class="minimonth-day" tabindex="-1"/>
-          <html:td class="minimonth-day" tabindex="-1"/>
-          <html:td class="minimonth-day" tabindex="-1"/>
-          <html:td class="minimonth-day" tabindex="-1"/>
-        </html:tr>
-        <html:tr class="minimonth-row-body">
-          <html:th class="minimonth-week" scope="row"/>
-          <html:td class="minimonth-day" tabindex="-1"/>
-          <html:td class="minimonth-day" tabindex="-1"/>
-          <html:td class="minimonth-day" tabindex="-1"/>
-          <html:td class="minimonth-day" tabindex="-1"/>
-          <html:td class="minimonth-day" tabindex="-1"/>
-          <html:td class="minimonth-day" tabindex="-1"/>
-          <html:td class="minimonth-day" tabindex="-1"/>
-        </html:tr>
-      </html:table>
-    </content>
+            // Set up header buttons.
+            const kMinimonth = this.closest("calendar-minimonth");
+            this.querySelector(".months-back-button").kMinimonth = kMinimonth;
+            this.querySelector(".months-forward-button").kMinimonth = kMinimonth;
+            this.querySelector(".years-back-button").kMinimonth = kMinimonth;
+            this.querySelector(".years-forward-button").kMinimonth = kMinimonth;
+            this.querySelector(".today-button").kMinimonth = kMinimonth;
+
+            this.mDaymap = null;
+            this.mValue = null;
+            this.mEditorDate = null;
+            this.mExtraDate = null;
+            this.mPixelScrollDelta = 0;
+            this.mObservesComposite = false;
+            this.mShowWeekNumber = true;
+            this.mToday = false;
+            this.mSelected = false;
+            this.mExtra = false;
+            this.mValue = new Date(); // Default to "today".
+            this.mFocused = null;
+
+            this.refreshDisplay();
+            if (this.hasAttribute("freebusy")) {
+                this._setFreeBusy(this.getAttribute("freebusy") == "true");
+            }
+            this.mShowWeekNumber = Services.prefs.getBoolPref("calendar.view-minimonth.showWeekNumber", true);
+
+            // Add pref observer.
+            Services.prefs.getBranch("").addObserver("calendar.", this.nsIObserver);
+
+            // Add event listeners.
+            this.addEventListener("click", (event) => {
+                if (event.button == 0 &&
+                    event.originalTarget.classList.contains("minimonth-day")) {
+                    this.onDayActivate(event);
+                    event.stopPropagation();
+                    event.preventDefault();
+                }
+            });
 
-    <implementation implements="calICompositeObserver calIOperationListener nsIObserver" >
-      <property name="value"
-                onget="return this.mValue"
-                onset="this.update(val)"/>
+            this.addEventListener("keypress", (event) => {
+                if (event.originalTarget.classList.contains("minimonth-day")) {
+                    switch (event.keyCode) {
+                        case KeyEvent.DOM_VK_LEFT:
+                            this.moveDateByOffset(0, 0, -1);
+                            break;
+                        case KeyEvent.DOM_VK_RIGHT:
+                            this.moveDateByOffset(0, 0, 1);
+                            break;
+                        case KeyEvent.DOM_VK_UP:
+                            this.moveDateByOffset(0, 0, -7);
+                            break;
+                        case KeyEvent.DOM_VK_DOWN:
+                            this.moveDateByOffset(0, 0, 7);
+                            break;
+                        case KeyEvent.DOM_VK_PAGE_UP:
+                            this.moveDateByOffset(0, -1, 0);
+                            break;
+                        case KeyEvent.DOM_VK_PAGE_DOWN:
+                            this.moveDateByOffset(0, 1, 0);
+                            break;
+                        case KeyEvent.DOM_VK_ESCAPE:
+                            this.focusDate(this.mValue || this.mExtraDate);
+                            break;
+                        case KeyEvent.DOM_VK_HOME: {
+                            const today = new Date();
+                            this.update(today);
+                            this.focusDate(today);
+                            break;
+                        }
+                        case KeyEvent.DOM_VK_RETURN:
+                            this.onDayActivate(event);
+                            break;
+                    }
+                    event.stopPropagation();
+                    event.preventDefault();
+                }
+            });
 
-      <property name="extra"
-                onget="return this.mExtraDate"
-                onset="this.mExtraDate = val"/>
+            this.addEventListener("wheel", (event) => {
+                const pixelThreshold = 150;
+                let deltaView = 0;
+                if (this.getAttribute("readonly") == "true") {
+                    // No scrolling on readonly months.
+                    return;
+                }
+                if (event.deltaMode == event.DOM_DELTA_LINE ||
+                    event.deltaMode == event.DOM_DELTA_PAGE) {
+                    if (event.deltaY != 0) {
+                        deltaView = event.deltaY > 0 ? 1 : -1;
+                    }
+                } else if (event.deltaMode == event.DOM_DELTA_PIXEL) {
+                    this.mPixelScrollDelta += event.deltaY;
+                    if (this.mPixelScrollDelta > pixelThreshold) {
+                        deltaView = 1;
+                        this.mPixelScrollDelta = 0;
+                    } else if (this.mPixelScrollDelta < -pixelThreshold) {
+                        deltaView = -1;
+                        this.mPixelScrollDelta = 0;
+                    }
+                }
+
+                if (deltaView != 0) {
+                    const classList = event.originalTarget.classList;
 
-      <!--returns the first (inclusive) date of the minimonth as a calIDateTime object-->
-      <property name="firstDate" readonly="true">
-        <getter><![CDATA[
+                    if (classList.contains("years-forward-button") ||
+                        classList.contains("yearcell") ||
+                        classList.contains("years-back-button")) {
+                        this.advanceYear(deltaView);
+                    } else if (!classList.contains("today-button")) {
+                        this.advanceMonth(deltaView);
+                    }
+                }
+
+                event.stopPropagation();
+                event.preventDefault();
+            });
+        }
+
+        set value(val) {
+            this.update(val);
+        }
+
+        get value() {
+            return this.mValue;
+        }
+
+        set extra(val) {
+            this.mExtraDate = val;
+        }
+
+        get extra() {
+            return this.mExtraDate;
+        }
+
+        /**
+         * Returns the first (inclusive) date of the minimonth as a calIDateTime object.
+         */
+        get firstDate() {
             let date = this._getCalBoxNode(1, 1).date;
             return cal.dtz.jsDateToDateTime(date);
-        ]]></getter>
-      </property>
+        }
 
-      <!--returns the last (exclusive) date of the minimonth as a calIDateTime object-->
-      <property name="lastDate" readonly="true">
-        <getter><![CDATA[
+        /**
+         * Returns the last (exclusive) date of the minimonth as a calIDateTime object.
+         */
+        get lastDate() {
             let date = this._getCalBoxNode(6, 7).date;
             let lastDateTime = cal.dtz.jsDateToDateTime(date);
             lastDateTime.day = lastDateTime.day + 1;
             return lastDateTime;
-        ]]></getter>
-      </property>
-
-      <property name="mReadOnlyHeaderText">
-        <getter><![CDATA[
-            return document.getAnonymousElementByAttribute(this, "anonid", "minimonth-readonly-header-text");
-        ]]></getter>
-      </property>
-
-      <field name="mDaymap">null</field>
-      <field name="mValue">null</field>
-      <field name="mEditorDate">null</field>
-      <field name="mExtraDate">null</field>
-      <field name="mPixelScrollDelta">0</field>
-      <field name="mIsReadOnly">false</field>
-      <field name="mObservesComposite">false</field>
-      <field name="mShowWeekNumber">true</field>
-
-      <constructor><![CDATA[
-          const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+        }
 
-          this.mToday = false;
-          this.mSelected = false;
-          this.mExtra = false;
-          this.mValue = new Date(); // Default to "today"
-          this.mFocused = null;
-          // save references for convenience
-          if (this.hasAttribute("readonly")) {
-              this.mIsReadOnly = this.getAttribute("readonly") == "true";
-          }
-          this.refreshDisplay();
-          if (this.hasAttribute("freebusy")) {
-              this._setFreeBusy(this.getAttribute("freebusy") == "true");
-          }
-          this.mShowWeekNumber = Services.prefs.getBoolPref("calendar.view-minimonth.showWeekNumber", true);
+        get mReadOnlyHeader() {
+            return this.querySelector(".minimonth-readonly-header");
+        }
 
-          // Add pref observer
-          let branch = Services.prefs.getBranch("");
-          branch.addObserver("calendar.", this);
-      ]]></constructor>
+        // calIOperationListener methods.
 
-      <destructor><![CDATA[
-          const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
-
-          if (this.mObservesComposite) {
-              cal.view.getCompositeCalendar(window).removeObserver(this);
-          }
+        onOperationComplete(aCalendar, aStatus, aOperationType, aId, aDetail) {}
 
-          // Remove pref observer
-          let branch = Services.prefs.getBranch("");
-          branch.removeObserver("calendar.", this);
-      ]]></destructor>
-
-      <!-- calIOperationListener methods -->
-      <method name="onOperationComplete">
-        <parameter name="aCalendar"/>
-        <parameter name="aStatus"/>
-        <parameter name="aOperationType"/>
-        <parameter name="aId"/>
-        <parameter name="aDetail"/>
-        <body><![CDATA[
-        ]]></body>
-      </method>
+        onGetResult(aCalendar, aStatus, aItemType, aDetail, aCount, aItems) {
+            if (Components.isSuccessCode(aStatus)) {
+                aItems.forEach(item => this.setBusyDaysForOccurrence(item, true));
+            }
+        }
 
-      <method name="onGetResult">
-        <parameter name="aCalendar"/>
-        <parameter name="aStatus"/>
-        <parameter name="aItemType"/>
-        <parameter name="aDetail"/>
-        <parameter name="aCount"/>
-        <parameter name="aItems"/>
-        <body><![CDATA[
-            if (!Components.isSuccessCode(aStatus)) {
-                return;
-            }
-            for (let item of aItems) {
-                this.setBusyDaysForOccurrence(item, true);
-            }
-        ]]></body>
-      </method>
+        setBusyDaysForItem(aItem, aState) {
+            let items = aItem.recurrenceInfo
+                ? aItem.getOccurrencesBetween(this.firstDate, this.lastDate, {})
+                : [aItem];
+            items.forEach(item => this.setBusyDaysForOccurrence(item, aState));
+        }
 
-      <method name="setBusyDaysForItem">
-        <parameter name="aItem"/>
-        <parameter name="aState"/>
-        <body><![CDATA[
-            let items = [aItem];
-            if (aItem.recurrenceInfo) {
-                let startDate = this.firstDate;
-                let endDate = this.lastDate;
-                items = aItem.getOccurrencesBetween(startDate, endDate, {});
-            }
-            for (let item of items) {
-                this.setBusyDaysForOccurrence(item, aState);
-            }
-        ]]></body>
-      </method>
-
-      <method name="parseBoxBusy">
-        <parameter name="aBox"/>
-        <body><![CDATA[
+        parseBoxBusy(aBox) {
             let boxBusy = {};
 
             let busyStr = aBox.getAttribute("busy");
             if (busyStr && busyStr.length > 0) {
                 let calChunks = busyStr.split("\u001A");
                 for (let chunk of calChunks) {
                     let expr = chunk.split("=");
                     boxBusy[expr[0]] = parseInt(expr[1], 10);
                 }
             }
 
             return boxBusy;
-        ]]></body>
-      </method>
+        }
 
-      <method name="updateBoxBusy">
-        <parameter name="aBox"/>
-        <parameter name="aBoxBusy"/>
-        <body><![CDATA[
+        updateBoxBusy(aBox, aBoxBusy) {
             let calChunks = [];
 
             for (let calId in aBoxBusy) {
                 if (aBoxBusy[calId]) {
                     calChunks.push(calId + "=" + aBoxBusy[calId]);
                 }
             }
 
             if (calChunks.length > 0) {
                 let busyStr = calChunks.join("\u001A");
                 aBox.setAttribute("busy", busyStr);
             } else {
                 aBox.removeAttribute("busy");
             }
-        ]]></body>
-      </method>
+        }
 
-      <method name="removeCalendarFromBoxBusy">
-        <parameter name="aBox"/>
-        <parameter name="aCalendar"/>
-        <body><![CDATA[
+        removeCalendarFromBoxBusy(aBox, aCalendar) {
             let boxBusy = this.parseBoxBusy(aBox);
             if (boxBusy[aCalendar.id]) {
                 delete boxBusy[aCalendar.id];
             }
             this.updateBoxBusy(aBox, boxBusy);
-        ]]></body>
-      </method>
+        }
 
-      <method name="setBusyDaysForOccurrence">
-        <parameter name="aOccurrence"/>
-        <parameter name="aState"/>
-        <body><![CDATA[
+        setBusyDaysForOccurrence(aOccurrence, aState) {
             if (aOccurrence.getProperty("TRANSP") == "TRANSPARENT") {
-                // Skip transparent events
+                // Skip transparent events.
                 return;
             }
             let start = aOccurrence[cal.dtz.startDateProp(aOccurrence)] || aOccurrence.dueDate;
             let end = aOccurrence[cal.dtz.endDateProp(aOccurrence)] || start;
             if (!start) {
                 return;
             }
 
@@ -383,205 +387,150 @@
                     if (!busyCalendars[aOccurrence.calendar.id]) {
                         busyCalendars[aOccurrence.calendar.id] = 0;
                     }
                     busyCalendars[aOccurrence.calendar.id] += (aState ? 1 : -1);
                     this.updateBoxBusy(box, busyCalendars);
                 }
                 current.day++;
             }
-        ]]></body>
-      </method>
+        }
 
-      <!--calIObserver methods -->
-      <method name="onStartBatch">
-        <parameter name="aCalendar"/>
-        <body><![CDATA[
-        ]]></body>
-      </method>
+        // End of calIOperationListener methods.
+        // calIObserver methods.
 
-      <method name="onEndBatch">
-        <parameter name="aCalendar"/>
-        <body><![CDATA[
-        ]]></body>
-      </method>
+        onStartBatch(aCalendar) {}
+
+        onEndBatch(aCalendar) {}
 
-      <method name="onLoad">
-        <parameter name="aCalendar"/>
-        <body><![CDATA[
-        ]]></body>
-      </method>
+        onLoad(aCalendar) {}
 
-      <method name="onAddItem">
-        <parameter name="aItem"/>
-        <body><![CDATA[
+        onAddItem(aItem) {
             this.setBusyDaysForItem(aItem, true);
-        ]]></body>
-      </method>
+        }
 
-      <method name="onDeleteItem">
-        <parameter name="aItem"/>
-        <body><![CDATA[
+        onDeleteItem(aItem) {
             this.setBusyDaysForItem(aItem, false);
-        ]]></body>
-      </method>
+        }
 
-      <method name="onModifyItem">
-        <parameter name="aNewItem"/>
-        <parameter name="aOldItem"/>
-        <body><![CDATA[
+        onModifyItem(aNewItem, aOldItem) {
             this.setBusyDaysForItem(aOldItem, false);
             this.setBusyDaysForItem(aNewItem, true);
-        ]]></body>
-      </method>
+        }
 
-      <method name="onError">
-        <parameter name="aCalendar"/>
-        <parameter name="aErrNo"/>
-        <parameter name="aMessage"/>
-        <body><![CDATA[
-        ]]></body>
-      </method>
+        onError(aCalendar, aErrNo, aMessage) {}
 
-      <method name="onPropertyChanged">
-        <parameter name="aCalendar"/>
-        <parameter name="aName"/>
-        <parameter name="aValue"/>
-        <parameter name="aOldValue"/>
-        <body><![CDATA[
+        onPropertyChanged(aCalendar, aName, aValue, aOldValue) {
             switch (aName) {
                 case "disabled":
                     this.resetAttributesForDate();
                     this.getItems();
                     break;
             }
-        ]]></body>
-      </method>
+        }
 
-      <method name="onPropertyDeleting">
-        <parameter name="aCalendar"/>
-        <parameter name="aName"/>
-        <body><![CDATA[
+        onPropertyDeleting(aCalendar, aName) {
             this.onPropertyChanged(aCalendar, aName, null, null);
-        ]]></body>
-      </method>
+        }
 
-      <!-- calICompositeObserver methods -->
-      <method name="onCalendarAdded">
-        <parameter name="aCalendar"/>
-        <body><![CDATA[
+        // End of calIObserver methods.
+        // calICompositeObserver methods.
+
+        onCalendarAdded(aCalendar) {
             this.getItems(aCalendar);
-        ]]></body>
-      </method>
+        }
 
-      <method name="onCalendarRemoved">
-        <parameter name="aCalendar"/>
-        <body><![CDATA[
+        onCalendarRemoved(aCalendar) {
             for (let day in this.mDayMap) {
                 this.removeCalendarFromBoxBusy(this.mDayMap[day], aCalendar);
             }
-        ]]></body>
-      </method>
+        }
+
+        onDefaultCalendarChanged(aCalendar) {}
 
-      <method name="onDefaultCalendarChanged">
-        <parameter name="aCalendar"/>
-        <body><![CDATA[
-        ]]></body>
-      </method>
+        // End calICompositeObserver methods.
+        // nsIObserver methods.
 
-      <!-- nsIObserver methods -->
-      <method name="observe">
-        <parameter name="aSubject"/>
-        <parameter name="aTopic"/>
-        <parameter name="aData"/>
-        <body><![CDATA[
+        observe(aSubject, aTopic, aData) {
             switch (aData) {
                 case "calendar.week.start":
                 case "calendar.view-minimonth.showWeekNumber":
                     this.refreshDisplay();
                     break;
             }
-        ]]></body>
-      </method>
+        }
 
-      <method name="refreshDisplay">
-        <body><![CDATA[
-            const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+        // End nsIObserver methods.
 
-            // Find out which should be the first day of the week
+        refreshDisplay() {
+            // Find out which should be the first day of the week.
             this.weekStart = Services.prefs.getIntPref("calendar.week.start", 0);
             this.mShowWeekNumber = Services.prefs.getBoolPref("calendar.view-minimonth.showWeekNumber", true);
             if (!this.mValue) {
                 this.mValue = new Date();
             }
             this.setHeader();
             this.showMonth(this.mValue);
             this.updateAccessibleLabel();
-        ]]></body>
-      </method>
+        }
 
-      <method name="_getCalBoxNode">
-        <parameter name="aRow"/>
-        <parameter name="aCol"/>
-        <body><![CDATA[
+        _getCalBoxNode(aRow, aCol) {
             if (!this.mCalBox) {
-                this.mCalBox = document.getAnonymousElementByAttribute(this, "anonid", "minimonth-calendar");
+                this.mCalBox = this.querySelector(".minimonth-calendar");
             }
             return this.mCalBox.children[aRow].children[aCol];
-        ]]></body>
-      </method>
+        }
 
-      <method name="setHeader">
-        <body><![CDATA[
-            // Reset the headers
+        setHeader() {
+            // Reset the headers.
             let dayList = new Array(7);
             let longDayList = new Array(7);
             let tempDate = new Date();
             let i, j;
             let useOSFormat;
             tempDate.setDate(tempDate.getDate() - (tempDate.getDay() - this.weekStart));
             for (i = 0; i < 7; i++) {
-                // if available, use UILocale days, else operating system format
+                // If available, use UILocale days, else operating system format.
                 try {
                     dayList[i] = cal.l10n.getDateFmtString(`day.${tempDate.getDay() + 1}.short`);
                 } catch (e) {
                     dayList[i] = tempDate.toLocaleDateString(undefined, { weekday: "short" });
                     useOSFormat = true;
                 }
                 longDayList[i] = tempDate.toLocaleDateString(undefined, { weekday: "long" });
                 tempDate.setDate(tempDate.getDate() + 1);
             }
 
             if (useOSFormat) {
                 // To keep datepicker popup compact, shrink localized weekday
                 // abbreviations down to 1 or 2 chars so each column of week can
                 // be as narrow as 2 digits.
                 //
                 // 1. Compute the minLength of the day name abbreviations.
-                let minLength = dayList[0].length;
-                for (i = 1; i < dayList.length; i++) {
-                    minLength = Math.min(minLength, dayList[i].length);
-                }
+                let minLength = dayList
+                    .map(name => name.length)
+                    .reduce((min, len) => Math.min(min, len));
+
                 // 2. If some day name abbrev. is longer than 2 chars (not Catalan),
                 //    and ALL localized day names share same prefix (as in Chinese),
                 //    then trim shared "day-" prefix.
                 if (dayList.some(dayAbbr => dayAbbr.length > 2)) {
                     for (let endPrefix = 0; endPrefix < minLength; endPrefix++) {
                         let suffix = dayList[0][endPrefix];
                         if (dayList.some(dayAbbr => dayAbbr[endPrefix] != suffix)) {
                             if (endPrefix > 0) {
                                 for (i = 0; i < dayList.length; i++) { // trim prefix chars.
                                     dayList[i] = dayList[i].substring(endPrefix);
                                 }
                             }
                             break;
                         }
                     }
                 }
-                // 3. trim each day abbreviation to 1 char if unique, else 2 chars.
+                // 3. Trim each day abbreviation to 1 char if unique, else 2 chars.
                 for (i = 0; i < dayList.length; i++) {
                     let foundMatch = 1;
                     for (j = 0; j < dayList.length; j++) {
                         if (i != j) {
                             if (dayList[i].substring(0, 1) == dayList[j].substring(0, 1)) {
                                 foundMatch = 2;
                                 break;
                             }
@@ -592,136 +541,125 @@
             }
 
             setBooleanAttribute(this._getCalBoxNode(0, 0), "hidden", !this.mShowWeekNumber);
             for (let column = 1; column < 8; column++) {
                 let node = this._getCalBoxNode(0, column);
                 node.textContent = dayList[column - 1];
                 node.setAttribute("aria-label", longDayList[column - 1]);
             }
-        ]]></body>
-      </method>
+        }
 
-      <method name="showMonth">
-        <parameter name="aDate"/>
-        <body><![CDATA[
-            const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
-
+        showMonth(aDate) {
             // Use mExtraDate if aDate is null.
             aDate = new Date(aDate || this.mExtraDate);
 
             aDate.setDate(1);
             // We set the hour and minute to something highly unlikely to be the
             // exact change point of DST, so timezones like America/Sao Paulo
             // don't display some days twice.
             aDate.setHours(12);
             aDate.setMinutes(34);
             aDate.setSeconds(0);
             aDate.setMilliseconds(0);
             // Don't fire onmonthchange event upon initialization
             let monthChanged = this.mEditorDate && (this.mEditorDate.valueOf() != aDate.valueOf());
-            this.mEditorDate = aDate; // only place mEditorDate is set.
+            this.mEditorDate = aDate; // Only place mEditorDate is set.
 
             if (this.mToday) {
                 this.mToday.removeAttribute("today");
                 this.mToday = null;
             }
 
             if (this.mSelected) {
                 this.mSelected.removeAttribute("selected");
                 this.mSelected = null;
             }
 
             if (this.mExtra) {
                 this.mExtra.removeAttribute("extra");
                 this.mExtra = null;
             }
 
-            // Update the month and year title
+            // Update the month and year title.
             this.setAttribute("month", aDate.getMonth());
             this.setAttribute("year", aDate.getFullYear());
-            this.mReadOnlyHeaderText.setAttribute("value",
-                cal.l10n.getDateFmtString(`month.${aDate.getMonth() + 1}.name`) +
-                " " + aDate.getFullYear()
-            );
 
-            // Update the calendar
-            let calbox = document.getAnonymousElementByAttribute(this, "anonid", "minimonth-calendar");
+            let dateString = cal.l10n.getDateFmtString(`month.${aDate.getMonth() + 1}.name`);
+            this.mReadOnlyHeader.textContent = dateString + " " + aDate.getFullYear();
+
+            // Update the calendar.
+            let calbox = this.querySelector(".minimonth-calendar");
             let date = this._getStartDate(aDate);
 
-            // get today's date
+            // Get today's date.
             let today = new Date();
 
             if (aDate.getFullYear() == (this.mValue || this.mExtraDate).getFullYear()) {
-                calbox.setAttribute("aria-label", cal.l10n.getDateFmtString(`month.${aDate.getMonth() + 1}.name`));
+                calbox.setAttribute("aria-label", dateString);
             } else {
-                let monthName = cal.l10n.formatMonth(aDate.getMonth() + 1,
-                                                     "calendar", "monthInYear");
-                let label = cal.l10n.getCalString(
-                    "monthInYear",
-                    [monthName, aDate.getFullYear()]
-                );
+                let monthName = cal.l10n.formatMonth(aDate.getMonth() + 1, "calendar", "monthInYear");
+                let label = cal.l10n.getCalString("monthInYear", [monthName, aDate.getFullYear()]);
                 calbox.setAttribute("aria-label", label);
             }
 
             this.mDayMap = {};
             let defaultTz = cal.dtz.defaultTimezone;
-            let dateFormatter =
-                new Services.intl.DateTimeFormat(undefined, { dateStyle: "long" });
+            let dateFormatter = new Services.intl.DateTimeFormat(undefined, { dateStyle: "long" });
             for (let k = 1; k < 7; k++) {
                 // Set the week number.
                 let firstElement = this._getCalBoxNode(k, 0);
                 setBooleanAttribute(firstElement, "hidden", !this.mShowWeekNumber);
                 if (this.mShowWeekNumber) {
-                    let weekNumber = cal.getWeekInfoService()
-                                        .getWeekTitle(cal.dtz.jsDateToDateTime(date, defaultTz));
+                    let weekNumber = cal.getWeekInfoService().getWeekTitle(
+                        cal.dtz.jsDateToDateTime(date, defaultTz)
+                    );
                     let weekTitle = cal.l10n.getCalString("WeekTitle", [weekNumber]);
                     firstElement.textContent = weekNumber;
                     firstElement.setAttribute("aria-label", weekTitle);
                 }
 
                 for (let i = 1; i < 8; i++) {
                     let day = this._getCalBoxNode(k, i);
-                    let ymd = date.getFullYear() + "-" +
-                              date.getMonth() + "-" +
-                              date.getDate();
+                    let ymd = date.getFullYear() + "-" + date.getMonth() + "-" + date.getDate();
                     this.mDayMap[ymd] = day;
 
-                    if (!this.mIsReadOnly) {
+                    if (this.getAttribute("readonly") != "true") {
                         day.setAttribute("interactive", "true");
                     }
 
                     if (aDate.getMonth() == date.getMonth()) {
                         day.removeAttribute("othermonth");
                     } else {
                         day.setAttribute("othermonth", "true");
                     }
 
-                    // highlight today
+                    // Highlight today.
                     if (this._sameDay(today, date)) {
                         this.mToday = day;
                         day.setAttribute("today", "true");
                     }
 
-                    // highlight the current date
+                    // Highlight the current date.
                     let val = this.value;
                     if (this._sameDay(val, date)) {
                         this.mSelected = day;
                         day.setAttribute("selected", "true");
                     }
 
-                    // highlight the extra date
+                    // Highlight the extra date.
                     if (this._sameDay(this.mExtraDate, date)) {
                         this.mExtra = day;
                         day.setAttribute("extra", "true");
                     }
 
                     if (aDate.getMonth() == date.getMonth() &&
                         aDate.getFullYear() == date.getFullYear()) {
-                        day.setAttribute("aria-label", date.toLocaleDateString(undefined, { day: "numeric" }));
+                        day.setAttribute("aria-label", date.toLocaleDateString(undefined, { day: "numeric" })
+                        );
                     } else {
                         day.setAttribute("aria-label", dateFormatter.format(date));
                     }
 
                     day.removeAttribute("busy");
 
                     day.date = new Date(date);
                     day.textContent = date.getDate();
@@ -739,39 +677,32 @@
 
             if (monthChanged) {
                 this.fireEvent("monthchange");
             }
 
             if (this.getAttribute("freebusy") == "true") {
                 this.getItems();
             }
-        ]]></body>
-      </method>
+        }
 
-      <!--Attention - duplicate!!!!-->
-      <method name="fireEvent">
-        <parameter name="aEventName"/>
-        <body><![CDATA[
+        /**
+         * Attention - duplicate!!!!
+         */
+        fireEvent(aEventName) {
             this.dispatchEvent(new CustomEvent(aEventName, { bubbles: true }));
-        ]]></body>
-      </method>
+        }
 
-      <method name="getBoxForDate">
-        <parameter name="aDate"/>
-        <body><![CDATA[
-            // aDate is a calIDateTime
+        getBoxForDate(aDate) {
+            // aDate is a calIDateTime.
             let ymd = [aDate.year, aDate.month, aDate.day].join("-");
             return (ymd in this.mDayMap ? this.mDayMap[ymd] : null);
-        ]]></body>
-      </method>
+        }
 
-      <method name="resetAttributesForDate">
-        <parameter name="aDate"/>
-        <body><![CDATA[
+        resetAttributesForDate(aDate) {
             function removeForBox(aBox) {
                 let allowedAttributes = 0;
                 while (aBox.attributes.length > allowedAttributes) {
                     switch (aBox.attributes[allowedAttributes].nodeName) {
                         case "selected":
                         case "othermonth":
                         case "today":
                         case "extra":
@@ -796,381 +727,233 @@
                 }
             } else {
                 for (let k = 1; k < 7; k++) {
                     for (let i = 1; i < 8; i++) {
                         removeForBox(this._getCalBoxNode(k, i));
                     }
                 }
             }
-        ]]></body>
-      </method>
+        }
 
-      <method name="_setFreeBusy">
-        <parameter name="aFreeBusy"/>
-        <body><![CDATA[
+        _setFreeBusy(aFreeBusy) {
             if (aFreeBusy) {
                 if (!this.mObservesComposite) {
-                    cal.view.getCompositeCalendar(window).addObserver(this);
+                    cal.view.getCompositeCalendar(window).addObserver(this.calICompositeObserver);
                     this.mObservesComposite = true;
                     this.getItems();
                 }
             } else if (this.mObservesComposite) {
-                cal.view.getCompositeCalendar(window).removeObserver(this);
+                cal.view.getCompositeCalendar(window).removeObserver(this.calICompositeObserver);
                 this.mObservesComposite = false;
             }
-        ]]></body>
-      </method>
+        }
 
-      <method name="removeAttribute">
-        <parameter name="aAttr"/>
-        <body><![CDATA[
+        removeAttribute(aAttr) {
             if (aAttr == "freebusy") {
                 this._setFreeBusy(false);
             }
-            // this should be done using lookupMethod(), see bug 286629
-            let ret = XULElement.prototype.removeAttribute.call(this, aAttr);
-            return ret;
-        ]]></body>
-      </method>
+            return super.removeAttribute(aAttr);
+        }
 
-      <method name="setAttribute">
-        <parameter name="aAttr"/>
-        <parameter name="aVal"/>
-        <body><![CDATA[
+        setAttribute(aAttr, aVal) {
             if (aAttr == "freebusy") {
                 this._setFreeBusy(aVal == "true");
             }
-            // this should be done using lookupMethod(), see bug 286629
-            let ret = XULElement.prototype.setAttribute.call(this, aAttr, aVal);
-            return ret;
-        ]]></body>
-      </method>
+            return super.setAttribute(aAttr, aVal);
+        }
 
-      <method name="getItems">
-        <parameter name="aCalendar"/>
-        <body><![CDATA[
+        getItems(aCalendar) {
             // The minimonth automatically clears extra styles on a month change.
             // Therefore we only need to fill the minimonth with new info.
 
             let calendar = aCalendar || cal.view.getCompositeCalendar(window);
             let filter = calendar.ITEM_FILTER_COMPLETED_ALL |
-                         calendar.ITEM_FILTER_CLASS_OCCURRENCES |
-                         calendar.ITEM_FILTER_ALL_ITEMS;
+                calendar.ITEM_FILTER_CLASS_OCCURRENCES |
+                calendar.ITEM_FILTER_ALL_ITEMS;
 
-            // Get new info
-            calendar.getItems(filter,
-                              0,
-                              this.firstDate,
-                              this.lastDate,
-                              this);
-        ]]></body>
-      </method>
+            // Get new info.
+            calendar.getItems(filter, 0, this.firstDate, this.lastDate, this.calIOperationListener);
+        }
 
-      <method name="updateAccessibleLabel">
-        <body><![CDATA[
-            const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
-
+        updateAccessibleLabel() {
             let label;
             if (this.mValue) {
-                let dateFormatter =
-                    new Services.intl.DateTimeFormat(undefined, { dateStyle: "long" });
+                let dateFormatter = new Services.intl.DateTimeFormat(
+                    undefined,
+                    { dateStyle: "long" }
+                );
                 label = dateFormatter.format(this.mValue);
             } else {
                 label = cal.l10n.getCalString("minimonthNoSelectedDate");
             }
             this.setAttribute("aria-label", label);
-        ]]></body>
-      </method>
+        }
 
-      <method name="update">
-        <parameter name="aValue"/>
-        <body><![CDATA[
+        update(aValue) {
             let changed = this.mValue && aValue && (
                 this.mValue.getFullYear() != aValue.getFullYear() ||
                 this.mValue.getMonth() != aValue.getMonth() ||
-                this.mValue.getDate() != aValue.getDate()
-            );
+                this.mValue.getDate() != aValue.getDate());
+
             this.mValue = aValue;
             if (changed) {
                 this.fireEvent("change");
             }
             this.showMonth(aValue);
             if (aValue) {
                 this.setFocusedDate(aValue);
             }
             this.updateAccessibleLabel();
-        ]]></body>
-      </method>
+        }
 
-      <method name="setFocusedDate">
-        <parameter name="aDate"/>
-        <parameter name="aForceFocus"/>
-        <body><![CDATA[
-            let newFocused = this.getBoxForDate(cal.dtz.jsDateToDateTime(aDate, cal.dtz.defaultTimezone));
+        setFocusedDate(aDate, aForceFocus) {
+            let newFocused = this.getBoxForDate(
+                cal.dtz.jsDateToDateTime(aDate, cal.dtz.defaultTimezone)
+            );
             if (!newFocused) {
                 return;
             }
             if (this.mFocused) {
                 this.mFocused.setAttribute("tabindex", "-1");
             }
             this.mFocused = newFocused;
             this.mFocused.setAttribute("tabindex", "0");
-            // only actually move the focus if it is already in the calendar box
+            // Only actually move the focus if it is already in the calendar box.
             if (!aForceFocus) {
-                let calbox = document.getAnonymousElementByAttribute(this, "anonid", "minimonth-calendar");
+                let calbox = this.querySelector(".minimonth-calendar");
                 aForceFocus = calbox.contains(document.commandDispatcher.focusedElement);
             }
             if (aForceFocus) {
                 this.mFocused.focus();
             }
-        ]]></body>
-      </method>
+        }
 
-      <method name="focusDate">
-        <parameter name="aDate"/>
-        <body><![CDATA[
+        focusDate(aDate) {
             this.showMonth(aDate);
             this.setFocusedDate(aDate);
-        ]]></body>
-      </method>
+        }
 
-      <method name="switchMonth">
-        <parameter name="aMonth"/>
-        <body><![CDATA[
+        switchMonth(aMonth) {
             let newMonth = new Date(this.mEditorDate);
             newMonth.setMonth(aMonth);
             this.showMonth(newMonth);
-        ]]></body>
-      </method>
+        }
 
-      <method name="switchYear">
-        <parameter name="aYear"/>
-        <body><![CDATA[
+        switchYear(aYear) {
             let newMonth = new Date(this.mEditorDate);
             newMonth.setFullYear(aYear);
             this.showMonth(newMonth);
-        ]]></body>
-      </method>
+        }
 
-      <method name="selectDate">
-        <parameter name="aDate"/>
-        <parameter name="aMainDate"/>
-        <body><![CDATA[
-            if (!aMainDate || aDate < this._getStartDate(aMainDate) || aDate > this._getEndDate(aMainDate)) {
+        selectDate(aDate, aMainDate) {
+            if (!aMainDate ||
+                aDate < this._getStartDate(aMainDate) ||
+                aDate > this._getEndDate(aMainDate)) {
                 aMainDate = new Date(aDate);
                 aMainDate.setDate(1);
             }
-            // note, that aMainDate and this.mEditorDate refer to the first day
-            // of the corresponding month
+            // Note that aMainDate and this.mEditorDate refer to the first day
+            // of the corresponding month.
             let sameMonth = this._sameDay(aMainDate, this.mEditorDate);
             let sameDate = this._sameDay(aDate, this.mValue);
             if (!sameMonth && !sameDate) {
-                // change month and select day
+                // Change month and select day.
                 this.mValue = aDate;
                 this.showMonth(aMainDate);
             } else if (!sameMonth) {
-                // change month only
+                // Change month only.
                 this.showMonth(aMainDate);
             } else if (!sameDate) {
-                // select day only
-                let day = this.getBoxForDate(cal.dtz.jsDateToDateTime(aDate, cal.dtz.defaultTimezone));
+                // Select day only.
+                let day = this.getBoxForDate(
+                    cal.dtz.jsDateToDateTime(aDate, cal.dtz.defaultTimezone)
+                );
                 if (this.mSelected) {
                     this.mSelected.removeAttribute("selected");
                 }
                 this.mSelected = day;
                 day.setAttribute("selected", "true");
                 this.mValue = aDate;
                 this.setFocusedDate(aDate);
             }
-        ]]></body>
-      </method>
+        }
 
-      <method name="_getStartDate">
-        <parameter name="aMainDate"/>
-        <body><![CDATA[
+        _getStartDate(aMainDate) {
             let date = new Date(aMainDate);
             let firstWeekday = (7 + aMainDate.getDay() - this.weekStart) % 7;
             date.setDate(date.getDate() - firstWeekday);
             return date;
-        ]]></body>
-      </method>
+        }
 
-      <method name="_getEndDate">
-        <parameter name="aMainDate"/>
-        <body><![CDATA[
+        _getEndDate(aMainDate) {
             let date = this._getStartDate(aMainDate);
-            let calbox = document.getAnonymousElementByAttribute(this, "anonid", "minimonth-calendar");
+            let calbox = this.querySelector(".minimonth-calendar");
             let days = (calbox.childNodes.length - 1) * 7;
             date.setDate(date.getDate() + days - 1);
             return date;
-        ]]></body>
-      </method>
+        }
 
-      <method name="_sameDay">
-        <parameter name="aDate1"/>
-        <parameter name="aDate2"/>
-        <body><![CDATA[
+        _sameDay(aDate1, aDate2) {
             if (aDate1 && aDate2 &&
-               (aDate1.getDate() == aDate2.getDate()) &&
-               (aDate1.getMonth() == aDate2.getMonth()) &&
-               (aDate1.getFullYear() == aDate2.getFullYear())) {
+                (aDate1.getDate() == aDate2.getDate()) &&
+                (aDate1.getMonth() == aDate2.getMonth()) &&
+                (aDate1.getFullYear() == aDate2.getFullYear())) {
                 return true;
             }
             return false;
-        ]]></body>
-      </method>
+        }
 
-      <method name="advanceMonth">
-        <parameter name="aDir"/>
-        <body><![CDATA[
-            let advEditorDate = new Date(this.mEditorDate); // at 1st of month
+        advanceMonth(aDir) {
+            let advEditorDate = new Date(this.mEditorDate); // At 1st of month.
             let advMonth = this.mEditorDate.getMonth() + aDir;
             advEditorDate.setMonth(advMonth);
             this.showMonth(advEditorDate);
-        ]]></body>
-      </method>
+        }
 
-      <method name="advanceYear">
-        <parameter name="aDir"/>
-        <body><![CDATA[
-            let advEditorDate = new Date(this.mEditorDate); // at 1st of month
+        advanceYear(aDir) {
+            let advEditorDate = new Date(this.mEditorDate); // At 1st of month.
             let advYear = this.mEditorDate.getFullYear() + aDir;
             advEditorDate.setFullYear(advYear);
             this.showMonth(advEditorDate);
-        ]]></body>
-      </method>
-
-      <method name="moveByOffset">
-        <parameter name="aYears"/>
-        <parameter name="aMonths"/>
-        <parameter name="aDays"/>
-        <body><![CDATA[
-            let day = new Date(this.mFocused.date.getFullYear() + aYears,
-                               this.mFocused.date.getMonth() + aMonths,
-                               this.mFocused.date.getDate() + aDays);
-            this.focusDate(day);
-        ]]></body>
-      </method>
-
-      <method name="focusCalendar">
-        <body><![CDATA[
-            this.mFocused.focus();
-        ]]></body>
-      </method>
+        }
 
-      <method name="onDayActivate">
-        <parameter name="aEvent"/>
-        <body><![CDATA[
-            if (aEvent.originalTarget.className == "minimonth-day") {
-                // the associated date might change when setting this.value if month changes
-                let date = aEvent.originalTarget.date;
-                if (!this.mIsReadOnly) {
-                    this.value = date;
-                    this.fireEvent("select");
-                }
-                this.setFocusedDate(date, true);
+        moveDateByOffset(aYears, aMonths, aDays) {
+            const date = new Date(
+                this.mFocused.date.getFullYear() + aYears,
+                this.mFocused.date.getMonth() + aMonths,
+                this.mFocused.date.getDate() + aDays
+            );
+            this.focusDate(date);
+        }
 
-                aEvent.stopPropagation();
-                aEvent.preventDefault();
-            }
-        ]]></body>
-      </method>
-
-      <method name="onDayMovement">
-        <parameter name="aEvent"/>
-        <parameter name="aYears"/>
-        <parameter name="aMonths"/>
-        <parameter name="aDays"/>
-        <body><![CDATA[
-            if (aEvent.originalTarget.className == "minimonth-day") {
-                this.moveByOffset(aYears, aMonths, aDays);
-                aEvent.stopPropagation();
-                aEvent.preventDefault();
-            }
-        ]]></body>
-      </method>
-    </implementation>
+        focusCalendar() {
+            this.mFocused.focus();
+        }
 
-    <handlers>
-      <handler event="wheel"><![CDATA[
-          const pixelThreshold = 150;
-          let deltaView = 0;
-          if (this.mIsReadOnly) {
-              // No scrolling on readonly months
-              return;
-          }
-          if (event.deltaMode == event.DOM_DELTA_LINE ||
-              event.deltaMode == event.DOM_DELTA_PAGE) {
-              if (event.deltaY != 0) {
-                  deltaView = event.deltaY > 0 ? 1 : -1;
-              }
-          } else if (event.deltaMode == event.DOM_DELTA_PIXEL) {
-              this.mPixelScrollDelta += event.deltaY;
-              if (this.mPixelScrollDelta > pixelThreshold) {
-                  deltaView = 1;
-                  this.mPixelScrollDelta = 0;
-              } else if (this.mPixelScrollDelta < -pixelThreshold) {
-                  deltaView = -1;
-                  this.mPixelScrollDelta = 0;
-              }
-          }
-
-          if (deltaView != 0) {
-              switch (event.originalTarget.getAttribute("anonid")) {
-                  case "today-button":
-                      break;
-                  case "years-forward-button":
-                  case "yearcell":
-                  case "years-back-button":
-                      this.advanceYear(deltaView);
-                      break;
-                  default:
-                      this.advanceMonth(deltaView);
-                      break;
-              }
-          }
+        onDayActivate(aEvent) {
+            // The associated date might change when setting this.value if month changes.
+            const date = aEvent.originalTarget.date;
+            if (this.getAttribute("readonly") != "true") {
+                this.value = date;
+                this.fireEvent("select");
+            }
+            this.setFocusedDate(date, true);
+        }
 
-          event.stopPropagation();
-          event.preventDefault();
-      ]]></handler>
-      <!-- day handlers -->
-      <handler event="keypress" keycode="VK_LEFT" modifiers="control shift any"
-               action="this.onDayMovement(event, 0, 0, -1);" phase="target"/>
-      <handler event="keypress" keycode="VK_RIGHT" modifiers="control shift any"
-               action="this.onDayMovement(event, 0, 0, 1);" phase="target"/>
-      <handler event="keypress" keycode="VK_UP" modifiers="control shift any"
-               action="this.onDayMovement(event, 0, 0, -7);" phase="target"/>
-      <handler event="keypress" keycode="VK_DOWN" modifiers="control shift any"
-               action="this.onDayMovement(event, 0, 0, 7);" phase="target"/>
-      <handler event="keypress" keycode="VK_PAGE_UP"
-               action="this.onDayMovement(event, 0, -1, 0);" phase="target"/>
-      <handler event="keypress" keycode="VK_PAGE_DOWN"
-               action="this.onDayMovement(event, 0, 1, 0);" phase="target"/>
-      <handler event="keypress" keycode="VK_PAGE_UP" modifiers="shift"
-               action="this.onDayMovement(event, -1, 0, 0);" phase="target"/>
-      <handler event="keypress" keycode="VK_PAGE_DOWN" modifiers="shift"
-               action="this.onDayMovement(event, 1, 0, 0);" phase="target"/>
-      <handler event="keypress" keycode="VK_ESCAPE"
-               phase="target"><![CDATA[
-          if (event.originalTarget.className == "minimonth-day") {
-              this.focusDate(this.mValue || this.mExtraDate);
-              event.stopPropagation();
-              event.preventDefault();
-          }
-      ]]></handler>
-      <handler event="keypress" keycode="VK_HOME"
-               phase="target"><![CDATA[
-          if (event.originalTarget.className == "minimonth-day") {
-              let today = new Date();
-              this.update(today);
-              this.focusDate(today);
+        disconnectedCallback() {
+            if (this.mObservesComposite) {
+                cal.view.getCompositeCalendar(window).removeObserver(this.calICompositeObserver);
+            }
 
-              event.stopPropagation();
-              event.preventDefault();
-          }
-      ]]></handler>
-      <handler event="keypress" keycode="VK_RETURN"
-               action="onDayActivate(event);" phase="target"/>
-      <handler event="click" button="0" action="onDayActivate(event);"/>
-    </handlers>
-  </binding>
-</bindings>
+            // Remove pref observer.
+            Services.prefs.getBranch("").removeObserver("calendar.", this.nsIObserver);
+        }
+    }
+
+    MozXULElement.implementCustomInterface(CalendarMinimonth, [
+        // Ci.calIObserver is omitted since it is not used like the others are.
+        Ci.calICompositeObserver,
+        Ci.calIOperationListener,
+        Ci.nsIObserver
+    ]);
+    customElements.define("calendar-minimonth", CalendarMinimonth);
+}
--- a/calendar/base/content/widgets/calendar-widget-bindings.css
+++ b/calendar/base/content/widgets/calendar-widget-bindings.css
@@ -8,19 +8,11 @@ modevbox {
   -moz-binding: url(chrome://calendar/content/widgets/calendar-widgets.xml#modebox);
   -moz-user-focus: normal;
 }
 
 modevbox {
   -moz-box-orient: vertical;
 }
 
-minimonth {
-  -moz-binding: url("chrome://calendar/content/widgets/minimonth.xml#minimonth");
-}
-
-minimonth-header {
-  -moz-binding: url("chrome://calendar/content/widgets/minimonth.xml#minimonth-header");
-}
-
 dragndropContainer {
    -moz-binding: url(chrome://calendar/content/widgets/calendar-widgets.xml#dragndropContainer);
 }
--- a/calendar/base/jar.mn
+++ b/calendar/base/jar.mn
@@ -87,18 +87,18 @@ calendar.jar:
     content/calendar/chooseCalendarDialog.js               (content/dialogs/chooseCalendarDialog.js)
     content/calendar/chooseCalendarDialog.xul              (content/dialogs/chooseCalendarDialog.xul)
     content/calendar/preferences/alarms.js                 (content/preferences/alarms.js)
     content/calendar/preferences/categories.js             (content/preferences/categories.js)
     content/calendar/preferences/editCategory.xul          (content/preferences/editCategory.xul)
     content/calendar/preferences/editCategory.js           (content/preferences/editCategory.js)
     content/calendar/preferences/general.js                (content/preferences/general.js)
     content/calendar/preferences/views.js                  (content/preferences/views.js)
-    content/calendar/widgets/minimonth.xml                 (content/widgets/minimonth.xml)
     content/calendar/widgets/calendar-alarm-widget.js      (content/widgets/calendar-alarm-widget.js)
+    content/calendar/widgets/calendar-minimonth.js         (content/widgets/calendar-minimonth.js)
     content/calendar/widgets/calendar-widgets.xml          (content/widgets/calendar-widgets.xml)
     content/calendar/widgets/calendar-list-tree.js         (content/widgets/calendar-list-tree.js)
     content/calendar/calendar-subscriptions-list.js        (content/widgets/calendar-subscriptions-list.js)
     content/calendar/widgets/calendar-widget-bindings.css  (content/widgets/calendar-widget-bindings.css)
     content/calendar/calApplicationUtils.js                (src/calApplicationUtils.js)
     content/calendar/calFilter.js                          (src/calFilter.js)
     content/calendar/WindowsNTToZoneInfoTZId.properties    (src/WindowsNTToZoneInfoTZId.properties)
 % skin calendar classic/1.0 chrome/skin/linux/calendar/
--- a/calendar/base/themes/common/widgets/minimonth.css
+++ b/calendar/base/themes/common/widgets/minimonth.css
@@ -1,13 +1,13 @@
 /* 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/. */
 
-minimonth {
+calendar-minimonth {
   --mmMainColor: WindowText;
   --mmMainBackground: #fff;
   --mmHighlightColor: HighlightText;
   --mmHighlightBackground: Highlight;
   --mmHighlightBorderColor: Highlight;
   --mmBoxBackground: #f5f5f6;
   --mmBoxBorderColor: #c0c0c0;
   --mmDayColor: #2e4e73;
@@ -19,17 +19,17 @@ minimonth {
   --mmDayTodayBorderColor: #67acd8;
   --mmDaySelectedColor: #2e4e73;
   --mmDaySelectedBackground: #fffabc;
   --mmDaySelectedBorderColor: #d9c585;
   --mmDaySelectedTodayBackground: #f2edb2;
   --mmDaySelectedTodayBorderColor: #67acd8;
 }
 
-:root[systemcolors] minimonth {
+:root[systemcolors] calendar-minimonth {
   --mmMainBackground: -moz-field;
   --mmBoxBackground: -moz-Dialog;
   --mmBoxBorderColor: ThreeDShadow;
   --mmDayColor: WindowText;
   --mmDayBorderColor: Window;
   --mmDayOtherColor: GrayText;
   --mmDayOtherBackground: ButtonFace;
   --mmDayOtherBorderColor: Transparent;
@@ -37,29 +37,35 @@ minimonth {
   --mmDayTodayBorderColor: Highlight;
   --mmDaySelectedColor: HighlightText;
   --mmDaySelectedBackground: Highlight;
   --mmDaySelectedBorderColor: ButtonFace;
   --mmDaySelectedTodayBackground: Highlight;
   --mmDaySelectedTodayBorderColor: ButtonFace;
 }
 
-minimonth {
+calendar-minimonth {
   background-color: var(--mmMainBackground);
   border-width: 0;
   color: var(--mmMainColor);
   padding: 4px;
   min-width: 175px;
 }
 
-minimonth:not([readonly="true"]) [anonid="minimonth-readonly-header"],
-minimonth[readonly="true"] [anonid="minimonth-header"] {
+calendar-minimonth:not([readonly="true"]) .minimonth-readonly-header,
+calendar-minimonth[readonly="true"] .minimonth-header {
   display: none;
 }
 
+calendar-minimonth[readonly="true"] .minimonth-readonly-header {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+
 .minimonth-month-box {
   background-color: var(--mmBoxBackground);
   border: 1px dotted var(--mmBoxBorderColor);
   font-weight: bold;
   text-align: center;
   height: 22px;
 }
 
--- a/calendar/lightning/content/lightning-item-iframe.xul
+++ b/calendar/lightning/content/lightning-item-iframe.xul
@@ -43,16 +43,17 @@
   <script src="chrome://calendar/content/calendar-dialog-utils.js"/>
   <script src="chrome://calendar/content/calendar-ui-utils.js"/>
   <script src="chrome://calendar/content/calApplicationUtils.js"/>
   <script src="chrome://global/content/globalOverlay.js"/>
   <script src="chrome://global/content/editMenuOverlay.js"/>
   <script src="chrome://global/content/printUtils.js"/>
   <script src="chrome://calendar/content/calendar-statusbar.js"/>
   <script src="chrome://messenger/content/customElements.js"/>
+  <script src="chrome://calendar/content/widgets/calendar-minimonth.js"/>
   <script src="chrome://calendar/content/datetimepickers/datetimepickers.js"/>
 
   <commandset id="">
     <command id="cmd_recurrence"
              oncommand="editRepeat();"/>
     <command id="cmd_attendees"
              oncommand="editAttendees();"/>
     <command id="cmd_email"
--- a/calendar/lightning/content/messenger-overlay-sidebar.xul
+++ b/calendar/lightning/content/messenger-overlay-sidebar.xul
@@ -55,16 +55,17 @@
   <script src="chrome://calendar/content/calendar-month-base-view.js"/>
   <script src="chrome://calendar/content/calendar-event-column.js"/>
   <script src="chrome://calendar/content/calendar-multiday-base-view.js"/>
   <script src="chrome://calendar/content/calendar-views.js"/>
 
   <script src="chrome://calendar/content/calendar-creation.js"/>
   <script src="chrome://calendar/content/calendar-dnd-listener.js"/>
   <script src="chrome://calendar/content/calendar-statusbar.js"/>
+  <script src="chrome://calendar/content/widgets/calendar-minimonth.js"/>
   <script src="chrome://global/content/nsDragAndDrop.js"/>
   <script src="chrome://calendar/content/widgets/calendar-list-tree.js"/>
 
   <!-- NEEDED FOR TASK VIEW/LIST SUPPORT -->
   <script src="chrome://calendar/content/calendar-task-editing.js"/>
 
   <script src="chrome://calendar/content/calendar-extract.js"/>
 
@@ -199,17 +200,17 @@
       </modevbox>
       <hbox id="calendarContent" flex="1">
         <vbox id="ltnSidebar"
               width="200"
               persist="collapsed width">
           <modevbox id="minimonth-pane" mode="calendar,task" broadcaster="modeBroadcaster" refcontrol="calendar_toggle_minimonthpane_command">
             <vbox align="center">
               <hbox id="calMinimonthBox" pack="center">
-                <minimonth id="calMinimonth" onchange="minimonthPick(this.value);" freebusy="true"/>
+                <calendar-minimonth id="calMinimonth" onchange="minimonthPick(this.value);" freebusy="true"/>
               </hbox>
             </vbox>
           </modevbox>
           <separator id="minimonth-splitter" minwidth="100"/>
           <vbox id="calendar-panel" flex="1">
             <modevbox id="task-filter-pane" mode="task" broadcaster="modeBroadcaster" refcontrol="calendar_toggle_filter_command">
               <checkbox id="task-tree-filter-header"
                                checked="true"
--- a/calendar/resources/content/datetimepickers/datetimepickers.js
+++ b/calendar/resources/content/datetimepickers/datetimepickers.js
@@ -742,17 +742,17 @@
             if (this.delayConnectedCallback()) {
                 return;
             }
 
             this.prepend(CalendarDatePicker.fragment.cloneNode(true));
             this._menulist = this.querySelector(".datepicker-menulist");
             this._inputField = this._menulist._inputField;
             this._popup = this._menulist.menupopup;
-            this._minimonth = this.querySelector("minimonth");
+            this._minimonth = this.querySelector("calendar-minimonth");
 
             if (this.getAttribute("type") == "forever") {
                 this._valueIsForever = false;
                 this._foreverString =
                     cal.l10n.getString("calendar-event-dialog", "eventRecurrenceForeverLabel");
 
                 this._foreverItem = document.createXULElement("button");
                 this._foreverItem.setAttribute("label", this._foreverString);
@@ -794,19 +794,17 @@
                 }
                 this._inputBoxValue = this._minimonthValue = value;
                 this._valueIsForever = false;
 
                 this.dispatchEvent(new CustomEvent("change", { bubbles: true }));
             });
             this._popup.addEventListener("popupshown", () => {
                 this._minimonth.focusDate(this._minimonthValue);
-                let calendar = document.getAnonymousElementByAttribute(
-                    this._minimonth, "anonid", "minimonth-calendar"
-                );
+                const calendar = this._minimonth.querySelector(".minimonth-calendar");
                 calendar.querySelector("td[selected]").focus();
             });
             this._minimonth.addEventListener("change", (event) => {
                 event.stopPropagation();
             });
             this._minimonth.addEventListener("select", () => {
                 this._inputBoxValue = this._minimonthValue;
                 this._valueIsForever = false;
@@ -833,17 +831,17 @@
 
         static get fragment() {
             // Accessibility information of these nodes will be
             // presented on XULComboboxAccessible generated from <menulist>;
             // hide these nodes from the accessibility tree.
             let frag = document.importNode(MozXULElement.parseXULToFragment(`
                 <menulist is="menulist-editable" class="datepicker-menulist" editable="true" sizetopopup="false">
                     <menupopup ignorekeys="true" popupanchor="bottomright" popupalign="topright">
-                        <minimonth tabindex="0"/>
+                        <calendar-minimonth tabindex="0"/>
                     </menupopup>
                 </menulist>
             `), true);
 
             Object.defineProperty(this, "fragment", { value: frag });
             return frag;
         }
 
--- a/calendar/test/mozmill/cal-recurrence/testAnnualRecurrence.js
+++ b/calendar/test/mozmill/cal-recurrence/testAnnualRecurrence.js
@@ -66,20 +66,21 @@ function testAnnualRecurrence() {
         controller.waitForElement(lookupEventBox("multiweek", ALLDAY, 1, column, null, EVENTPATH));
 
         // month view
         switchToView(controller, "month");
         controller.waitForElement(lookupEventBox("month", ALLDAY, 1, column, null, EVENTPATH));
     }
 
     // Delete event.
+    switchToView(controller, "day");
     goToDate(controller, checkYears[0], 1, 1);
-    switchToView(controller, "day");
-    let box = getEventBoxPath("day", ALLDAY, null, 1, null) + EVENTPATH;
-    controller.click(lookup(box));
-    handleOccurrencePrompt(controller, eid("day-view"), "delete", true);
-    controller.waitForElementNotPresent(lookup(box));
+    const boxPath = getEventBoxPath("day", ALLDAY, null, 1, null) + EVENTPATH;
+    const box = lookup(boxPath);
+    controller.click(box);
+    handleOccurrencePrompt(controller, box, "delete", true);
+    controller.waitForElementNotPresent(box);
 }
 
 function teardownTest(module) {
     deleteCalendars(controller, CALENDARNAME);
     closeAllEventDialogs();
 }
--- a/calendar/test/mozmill/cal-recurrence/testBiweeklyRecurrence.js
+++ b/calendar/test/mozmill/cal-recurrence/testBiweeklyRecurrence.js
@@ -93,16 +93,16 @@ function testBiweeklyRecurrence() {
 
     // March
     controller.waitForElement(lookupEventBox("month", EVENT_BOX, 2, 7, null, EVENTPATH));
     controller.assertNode(lookupEventBox("month", EVENT_BOX, 4, 7, null, EVENTPATH));
 
     // Delete event.
     let box = lookupEventBox("month", EVENT_BOX, 4, 7, null, EVENTPATH);
     controller.click(box);
-    handleOccurrencePrompt(controller, eid("month-view"), "delete", true);
+    handleOccurrencePrompt(controller, box, "delete", true);
     controller.waitForElementNotPresent(box);
 }
 
 function teardownTest(module) {
     deleteCalendars(controller, CALENDARNAME);
     closeAllEventDialogs();
 }
--- a/calendar/test/mozmill/cal-recurrence/testDailyRecurrence.js
+++ b/calendar/test/mozmill/cal-recurrence/testDailyRecurrence.js
@@ -104,17 +104,17 @@ function testDailyRecurrence() {
         for (let day = 1; day <= 7; day++) {
             controller.assertNode(lookupEventBox("month", EVENT_BOX, week, day, null, EVENTPATH));
         }
     }
 
     // Delete 3rd January occurrence.
     let saturday = lookupEventBox("month", EVENT_BOX, 1, 7, null, EVENTPATH);
     controller.click(saturday);
-    handleOccurrencePrompt(controller, eid("month-view"), "delete", false);
+    handleOccurrencePrompt(controller, saturday, "delete", false);
 
     // Verify in all views.
     controller.waitForElementNotPresent(saturday);
 
     switchToView(controller, "multiweek");
     controller.assertNodeNotExist(lookupEventBox("multiweek", EVENT_BOX, 1, 7, null, EVENTPATH));
 
     switchToView(controller, "week");
@@ -181,16 +181,16 @@ function testDailyRecurrence() {
             lookupEventBox("month", EVENT_BOX, i, 1, null, EVENTPATH)
         );
         controller.assertNodeNotExist(lookupEventBox("month", EVENT_BOX, i, 7, null, EVENTPATH));
     }
 
     // Delete event.
     day = lookupEventBox("month", EVENT_BOX, 1, 5, null, EVENTPATH);
     controller.click(day);
-    handleOccurrencePrompt(controller, eid("month-view"), "delete", true);
+    handleOccurrencePrompt(controller, day, "delete", true);
     controller.waitForElementNotPresent(day);
 }
 
 function teardownTest(module) {
     deleteCalendars(controller, CALENDARNAME);
     closeAllEventDialogs();
 }
--- a/calendar/test/mozmill/cal-recurrence/testLastDayOfMonthRecurrence.js
+++ b/calendar/test/mozmill/cal-recurrence/testLastDayOfMonthRecurrence.js
@@ -105,17 +105,17 @@ function testLastDayOfMonthRecurrence() 
         );
     }
 
     // Delete event.
     goToDate(controller, checkingData[0][0], checkingData[0][1], checkingData[0][2]);
     switchToView(controller, "day");
     let box = lookupEventBox("day", EVENT_BOX, null, 1, null, EVENTPATH);
     controller.waitThenClick(box);
-    handleOccurrencePrompt(controller, eid("day-view"), "delete", true);
+    handleOccurrencePrompt(controller, box, "delete", true);
     controller.waitForElementNotPresent(box);
 }
 
 function setRecurrence(recurrence) {
     let {
         sleep: recsleep,
         lookup: reclookup,
         eid: recid
--- a/calendar/test/mozmill/cal-recurrence/testWeeklyUntilRecurrence.js
+++ b/calendar/test/mozmill/cal-recurrence/testWeeklyUntilRecurrence.js
@@ -123,17 +123,17 @@ function testWeeklyUntilRecurrence() {
     // Check month view.
     switchToView(controller, "month");
     goToDate(controller, 2009, 1, 5);
     checkMultiWeekView("month");
 
     // Delete event.
     box = lookupEventBox("month", EVENT_BOX, 2, 2, null, EVENTPATH);
     controller.click(box);
-    handleOccurrencePrompt(controller, eid("month-view"), "delete", true);
+    handleOccurrencePrompt(controller, box, "delete", true);
     controller.waitForElementNotPresent(box);
 }
 
 function setRecurrence(recurrence) {
     let { sleep: recsleep, lookup: reclookup, eid: recid } = helpersForController(recurrence);
 
     // weekly
     recurrence.waitForElement(recid("period-list"));
--- a/calendar/test/mozmill/cal-recurrence/testWeeklyWithExceptionRecurrence.js
+++ b/calendar/test/mozmill/cal-recurrence/testWeeklyWithExceptionRecurrence.js
@@ -188,17 +188,17 @@ function testWeeklyWithExceptionRecurren
     switchToView(controller, "month");
     checkMultiWeekView("month");
 
     // delete event
     switchToView(controller, "day");
     goToDate(controller, 2009, 1, 12);
     path = lookupEventBox("day", EVENT_BOX, null, 1, null, EVENTPATH);
     controller.click(path);
-    handleOccurrencePrompt(controller, eid("day-view"), "delete", true);
+    handleOccurrencePrompt(controller, path, "delete", true);
     controller.waitForElementNotPresent(path);
 }
 
 function setRecurrence(recurrence) {
     let { lookup: reclookup, eid: recid } = helpersForController(recurrence);
 
     // weekly
     menulistSelect(recid("period-list"), "1", recurrence);
--- a/calendar/test/mozmill/recurrenceRotated/testAnnualRecurrence.js
+++ b/calendar/test/mozmill/recurrenceRotated/testAnnualRecurrence.js
@@ -71,20 +71,21 @@ function testAnnualRecurrence() {
         // month view
         switchToView(controller, "month");
         controller.waitForElement(lookupEventBox("month", ALLDAY, 1, column, null, EVENTPATH));
     }
 
     // Delete event.
     goToDate(controller, checkYears[0], 1, 1);
     switchToView(controller, "day");
-    let box = getEventBoxPath("day", ALLDAY, null, 1, null) + EVENTPATH;
-    controller.click(lookup(box));
-    handleOccurrencePrompt(controller, eid("day-view"), "delete", true);
-    controller.waitForElementNotPresent(lookup(box));
+    const boxPath = getEventBoxPath("day", ALLDAY, null, 1, null) + EVENTPATH;
+    const box = lookup(boxPath);
+    controller.click(box);
+    handleOccurrencePrompt(controller, box, "delete", true);
+    controller.waitForElementNotPresent(box);
 }
 
 function teardownTest(module) {
     deleteCalendars(controller, CALENDARNAME);
     // Reset view.
     if (eid("day-view").getNode().orient == "horizontal") {
         controller.mainMenu.click("#ltnViewRotated");
     }
--- a/calendar/test/mozmill/recurrenceRotated/testBiweeklyRecurrence.js
+++ b/calendar/test/mozmill/recurrenceRotated/testBiweeklyRecurrence.js
@@ -47,16 +47,17 @@ function testBiweeklyRecurrence() {
     invokeEventDialog(controller, eventBox, (event, iframe) => {
         let { eid: eventid } = helpersForController(event);
 
         menulistSelect(eventid("item-repeat"), "bi.weekly", event);
         event.click(eventid("button-saveandclose"));
     });
 
     // Check day view.
+    switchToView(controller, "day");
     for (let i = 0; i < 4; i++) {
         controller.waitForElement(lookupEventBox("day", EVENT_BOX, null, 1, null, EVENTPATH));
         viewForward(controller, 14);
     }
 
     // Check week view.
     switchToView(controller, "week");
     goToDate(controller, 2009, 1, 31);
@@ -96,17 +97,17 @@ function testBiweeklyRecurrence() {
 
     // March
     controller.waitForElement(lookupEventBox("month", EVENT_BOX, 2, 7, null, EVENTPATH));
     controller.assertNode(lookupEventBox("month", EVENT_BOX, 4, 7, null, EVENTPATH));
 
     // Delete event.
     let box = lookupEventBox("month", EVENT_BOX, 4, 7, null, EVENTPATH);
     controller.click(box);
-    handleOccurrencePrompt(controller, eid("month-view"), "delete", true);
+    handleOccurrencePrompt(controller, box, "delete", true);
     controller.waitForElementNotPresent(box);
 }
 
 function teardownTest(module) {
     deleteCalendars(controller, CALENDARNAME);
     // Reset view.
     switchToView(controller, "day");
     if (eid("day-view").getNode().orient == "horizontal") {
--- a/calendar/test/mozmill/recurrenceRotated/testDailyRecurrence.js
+++ b/calendar/test/mozmill/recurrenceRotated/testDailyRecurrence.js
@@ -108,17 +108,17 @@ function testDailyRecurrence() {
         for (let day = 1; day <= 7; day++) {
             controller.assertNode(lookupEventBox("month", EVENT_BOX, week, day, null, EVENTPATH));
         }
     }
 
     // Delete 3rd January occurrence.
     let saturday = lookupEventBox("month", EVENT_BOX, 1, 7, null, EVENTPATH);
     controller.click(saturday);
-    handleOccurrencePrompt(controller, eid("month-view"), "delete", false);
+    handleOccurrencePrompt(controller, saturday, "delete", false);
 
     // Verify in all views.
     controller.waitForElementNotPresent(saturday);
 
     switchToView(controller, "multiweek");
     controller.assertNodeNotExist(lookupEventBox("multiweek", EVENT_BOX, 1, 7, null, EVENTPATH));
 
     switchToView(controller, "week");
@@ -185,17 +185,17 @@ function testDailyRecurrence() {
             lookupEventBox("month", EVENT_BOX, i, 1, null, EVENTPATH)
         );
         controller.assertNodeNotExist(lookupEventBox("month", EVENT_BOX, i, 7, null, EVENTPATH));
     }
 
     // Delete event.
     day = lookupEventBox("month", EVENT_BOX, 1, 5, null, EVENTPATH);
     controller.click(day);
-    handleOccurrencePrompt(controller, eid("month-view"), "delete", true);
+    handleOccurrencePrompt(controller, day, "delete", true);
     controller.waitForElementNotPresent(day);
 }
 
 function teardownTest(module) {
     deleteCalendars(controller, CALENDARNAME);
     // Reset view.
     switchToView(controller, "day");
     if (eid("day-view").getNode().orient == "horizontal") {
--- a/calendar/test/mozmill/recurrenceRotated/testLastDayOfMonthRecurrence.js
+++ b/calendar/test/mozmill/recurrenceRotated/testLastDayOfMonthRecurrence.js
@@ -39,16 +39,17 @@ function setupModule(module) {
     collector.getModule("item-editing-helpers").setupModule(module);
 
     ({ plan_for_modal_dialog, wait_for_modal_dialog } =
         collector.getModule("window-helpers")
     );
 
     createCalendar(controller, CALENDARNAME);
     // Rotate view.
+    switchToView(controller, "day");
     controller.mainMenu.click("#ltnViewRotated");
     controller.waitFor(() => eid("day-view").getNode().orient == "horizontal");
 }
 
 function testLastDayOfMonthRecurrence() {
     goToDate(controller, 2008, 1, 31); // Start with a leap year.
 
     // Create monthly recurring event.
@@ -108,17 +109,17 @@ function testLastDayOfMonthRecurrence() 
         );
     }
 
     // Delete event.
     goToDate(controller, checkingData[0][0], checkingData[0][1], checkingData[0][2]);
     switchToView(controller, "day");
     let box = lookupEventBox("day", EVENT_BOX, null, 1, null, EVENTPATH);
     controller.waitThenClick(box);
-    handleOccurrencePrompt(controller, eid("day-view"), "delete", true);
+    handleOccurrencePrompt(controller, box, "delete", true);
     controller.waitForElementNotPresent(box);
 }
 
 function setRecurrence(recurrence) {
     let {
         sleep: recsleep,
         lookup: reclookup,
         eid: recid
--- a/calendar/test/mozmill/recurrenceRotated/testWeeklyUntilRecurrence.js
+++ b/calendar/test/mozmill/recurrenceRotated/testWeeklyUntilRecurrence.js
@@ -126,17 +126,17 @@ function testWeeklyUntilRecurrence() {
     // Check month view.
     switchToView(controller, "month");
     goToDate(controller, 2009, 1, 5);
     checkMultiWeekView("month");
 
     // Delete event.
     box = lookupEventBox("month", EVENT_BOX, 2, 2, null, EVENTPATH);
     controller.click(box);
-    handleOccurrencePrompt(controller, eid("month-view"), "delete", true);
+    handleOccurrencePrompt(controller, box, "delete", true);
     controller.waitForElementNotPresent(box);
 }
 
 function setRecurrence(recurrence) {
     let { sleep: recsleep, lookup: reclookup, eid: recid } = helpersForController(recurrence);
 
     // weekly
     recurrence.waitForElement(recid("period-list"));
--- a/calendar/test/mozmill/recurrenceRotated/testWeeklyWithExceptionRecurrence.js
+++ b/calendar/test/mozmill/recurrenceRotated/testWeeklyWithExceptionRecurrence.js
@@ -191,17 +191,17 @@ function testWeeklyWithExceptionRecurren
     switchToView(controller, "month");
     checkMultiWeekView("month");
 
     // Delete event.
     switchToView(controller, "day");
     goToDate(controller, 2009, 1, 12);
     path = lookupEventBox("day", EVENT_BOX, null, 1, null, EVENTPATH);
     controller.click(path);
-    handleOccurrencePrompt(controller, eid("day-view"), "delete", true);
+    handleOccurrencePrompt(controller, path, "delete", true);
     controller.waitForElementNotPresent(path);
 }
 
 function setRecurrence(recurrence) {
     let { lookup: reclookup, eid: recid } = helpersForController(recurrence);
 
     // weekly
     menulistSelect(recid("period-list"), "1", recurrence);
--- a/calendar/test/mozmill/shared-modules/test-calendar-utils.js
+++ b/calendar/test/mozmill/shared-modules/test-calendar-utils.js
@@ -42,17 +42,18 @@ var MULTIWEEK_VIEW = `${VIEWDECK}/id("mu
 var MONTH_VIEW = `${VIEWDECK}/id("month-view")`;
 var TASK_VIEW = `${CALENDAR_PANEL}/id("calendarDisplayDeck")/id("calendar-task-box")/`;
 
 var MINIMONTH = `
     ${CALENDAR_PANEL}/id("ltnSidebar")/id("minimonth-pane")/{"align":"center"}/
     id("calMinimonthBox")/id("calMinimonth")
 `;
 var TODAY_BUTTON = `
-    ${MINIMONTH}/anon({"anonid":"minimonth-header"})/anon({"anonid":"today-button"})
+    ${MINIMONTH}/{"class":"minimonth-header minimonth-month-box"}/
+    {"class":"today-button minimonth-nav-btns"}
 `;
 var CALENDARLIST = `
     ${CALENDAR_PANEL}/id("ltnSidebar")/id("calendar-panel")/id("calendar-list-pane")/
     id("calendar-listtree-pane")/id("calendar-list-tree-widget")/{"class":"treechildren"}
 `;
 var TODAY_PANE = `
     /id("messengerWindow")/id("tabmail-container")/id("today-pane-panel")
 `;
@@ -221,68 +222,76 @@ function switchToView(controller, view) 
  * @param year          Four-digit year
  * @param month         1-based index of a month
  * @param day           1-based index of a day
  */
 function goToDate(controller, year, month, day) {
     let { lookup } = helpersForController(controller);
 
     let activeYear = lookup(`
-        ${MINIMONTH}/anon({"anonid":"minimonth-header"})/anon({"anonid":"yearcell"})
+        ${MINIMONTH}/{"class":"minimonth-header minimonth-month-box"}/
+        {"class":"yearcell minimonth-year-name"}
     `).getNode().getAttribute("value");
 
     let activeMonth = lookup(`
-        ${MINIMONTH}/anon({"anonid":"minimonth-header"})/anon({"anonid":"monthheader"})
+        ${MINIMONTH}/{"class":"minimonth-header minimonth-month-box"}/
+        {"class":"monthheader minimonth-month-name"}
     `).getNode().getAttribute("selectedIndex");
 
     let yearDifference = activeYear - year;
     let monthDifference = activeMonth - (month - 1);
 
     if (yearDifference != 0) {
-        let scrollArrow = (yearDifference > 0) ? "years-back-button" : "years-forward-button";
+        let scrollArrow = (yearDifference > 0)
+            ? "years-back-button minimonth-nav-btns"
+            : "years-forward-button minimonth-nav-btns";
         scrollArrow = lookup(
-            `${MINIMONTH}/anon({"anonid":"minimonth-header"})/anon({"anonid":"${scrollArrow}"})`
+            `${MINIMONTH}/{"class":"minimonth-header minimonth-month-box"}/
+            {"class":"${scrollArrow}"}`
         );
 
         controller.waitForElement(scrollArrow);
         scrollArrow = scrollArrow.getNode();
 
         for (let i = 0; i < Math.abs(yearDifference); i++) {
             scrollArrow.doCommand();
             controller.sleep(10);
         }
     }
 
     if (monthDifference != 0) {
-        let scrollArrow = (monthDifference > 0) ? "months-back-button" : "months-forward-button";
+        let scrollArrow = (monthDifference > 0)
+            ? "months-back-button minimonth-nav-btns"
+            : "months-forward-button minimonth-nav-btns";
         scrollArrow = lookup(
-            `${MINIMONTH}/anon({"anonid":"minimonth-header"})/anon({"anonid":"${scrollArrow}"})`
+            `${MINIMONTH}/{"class":"minimonth-header minimonth-month-box"}/
+            {"class":"${scrollArrow}"}`
         );
 
         controller.waitForElement(scrollArrow);
         scrollArrow = scrollArrow.getNode();
 
         for (let i = 0; i < Math.abs(monthDifference); i++) {
             scrollArrow.doCommand();
             controller.sleep(25);
         }
     }
 
     let lastDayInFirstRow = lookup(`
-        ${MINIMONTH}/anon({"anonid":"minimonth-calendar"})/[3]/[15]
+        ${MINIMONTH}/{"class":"minimonth-calendar minimonth-cal-box"}/[1]/[7]
     `).getNode().innerHTML;
 
     let positionOfFirst = 7 - lastDayInFirstRow;
     let dateColumn = (positionOfFirst + day - 1) % 7;
     let dateRow = Math.floor((positionOfFirst + day - 1) / 7);
 
     // Pick day.
     controller.click(lookup(`
-        ${MINIMONTH}/anon({"anonid":"minimonth-calendar"})/[${(dateRow + 1) * 2 + 1}]/
-        [${(dateColumn + 1) * 2 + 1}]
+        ${MINIMONTH}/{"class":"minimonth-calendar minimonth-cal-box"}/
+        [${dateRow + 1}]/[${dateColumn + 1}]
     `));
     ensureViewLoaded(controller);
 }
 
 /**
  * Opens the event dialog by clicking on the (optional) box and executing the
  * body. The event dialog must be closed in the body function.
  *
--- a/calendar/test/mozmill/testBasicFunctionality.js
+++ b/calendar/test/mozmill/testBasicFunctionality.js
@@ -35,17 +35,17 @@ function setupModule(module) {
 
 function testSmokeTest() {
     let dateFormatter = cal.getDateFormatter();
 
     // Check for minimonth.
     controller.waitForElement(eid("calMinimonth"));
     // Every month has a first.
     controller.assertNode(lookup(`
-        ${MINIMONTH}/anon({"anonid":"minimonth-calendar"})/[3]/{"aria-label":"1"}
+        ${MINIMONTH}/{"class":"minimonth-calendar minimonth-cal-box"}/[1]/{"aria-label":"1"}
     `));
 
     // Check for calendar list.
     controller.assertNode(eid("calendar-list-pane"));
     controller.assertNode(lookup(CALENDARLIST));
 
     // Check for event search.
     controller.assertNode(eid("bottom-events-box"));
--- a/calendar/test/mozmill/views/testMonthView.js
+++ b/calendar/test/mozmill/views/testMonthView.js
@@ -106,16 +106,16 @@ function testMonthView() {
         `${EVENTPATH}/${getEventDetails("month")}/anon({"flex":"1"})/anon({"anonid":"event-name"})`
     );
 
     controller.waitForElement(eventName);
     controller.assertValue(eventName, TITLE2);
 
     // Delete event.
     controller.click(eventBox);
-    controller.keypress(eid("month-view"), "VK_DELETE", {});
+    controller.keypress(eventBox, "VK_DELETE", {});
     controller.waitForElementNotPresent(eventBox);
 }
 
 function teardownTest(module) {
     deleteCalendars(controller, CALENDARNAME);
     closeAllEventDialogs();
 }
--- a/calendar/test/mozmill/views/testMultiweekView.js
+++ b/calendar/test/mozmill/views/testMultiweekView.js
@@ -107,16 +107,16 @@ function testMultiWeekView() {
         anon({"anonid":"event-name"})`
     );
 
     controller.waitForElement(eventName);
     controller.assertValue(eventName, TITLE2);
 
     // Delete event.
     controller.click(eventBox);
-    controller.keypress(eid("multiweek-view"), "VK_DELETE", {});
+    controller.keypress(eventBox, "VK_DELETE", {});
     controller.waitForElementNotPresent(eventBox);
 }
 
 function teardownTest(module) {
     deleteCalendars(controller, CALENDARNAME);
     closeAllEventDialogs();
 }