Bug 1531305 - [de-xbl] convert agenda-list-bindings to custom elements. r=philipp
authorKhushil Mistry <khushil324@gmail.com>
Sat, 13 Apr 2019 12:14:00 +0200
changeset 26334 f57c1c1b7400d272bfd2cd6015cd1942e193e9bf
parent 26333 c4c1ac428ecd8666e445c2ad22bb5080b2a868f0
child 26335 bddc7eeb6720ddcaf31bdafc85d47166d0414a5a
push id15790
push usermozilla@jorgk.com
push dateSat, 13 Apr 2019 22:04:23 +0000
treeherdercomm-central@bddc7eeb6720 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersphilipp
bugs1531305
Bug 1531305 - [de-xbl] convert agenda-list-bindings to custom elements. r=philipp
calendar/.eslintrc.js
calendar/base/content/agenda-listbox-utils.js
calendar/base/content/agenda-listbox.js
calendar/base/content/agenda-listbox.xml
calendar/base/content/calendar-common-sets.js
calendar/base/content/calendar-task-tree.xml
calendar/base/content/today-pane.js
calendar/base/content/today-pane.xul
calendar/base/jar.mn
calendar/base/themes/common/today-pane.css
calendar/test/mozmill/testTodayPane.js
--- a/calendar/.eslintrc.js
+++ b/calendar/.eslintrc.js
@@ -457,17 +457,17 @@ module.exports = {
         "id-length": [2, {
             min: 3,
             exceptions: [
                 /* sorting */ "a", "b",
                 /* exceptions */ "e", "ex",
                 /* loop indices */ "i", "j", "k", "n",
                 /* coordinates */ "x", "y",
                 /* regexes */ "re",
-                /* known words */ "rc", "rv", "id", "OS", "os", "db",
+                /* known words */ "rc", "rv", "id", "OS", "os", "db", "is",
                 /* mail/calendar words */ "to", "cc",
                 /* Components */ "Ci", "Cc", "Cu", "Cr",
             ]
         }],
 
         // Disallow lexical declarations in case/default clauses
         "no-case-declarations": 2,
 
copy from calendar/base/content/agenda-listbox.js
copy to calendar/base/content/agenda-listbox-utils.js
--- a/calendar/base/content/agenda-listbox.js
+++ b/calendar/base/content/agenda-listbox-utils.js
@@ -90,21 +90,21 @@ agendaListbox.uninit = function() {
                                        this.onCheckboxChange,
                                        true);
         }
     }
 };
 
 /**
  * Adds a period item to the listbox. This is a section of the today pane like
- * "Today", "Tomorrow", and is usually a <agenda-checkbox-richlist-item> tag. A
+ * "Today", "Tomorrow", and is usually a <agenda-header-richlist-item> tag. A
  * copy of the template node is made and added to the agenda listbox.
  *
  * @param aPeriod       The period item to add.
- * @param aItemId       The id of an <agenda-checkbox-richlist-item> to add to.
+ * @param aItemId       The id of an <agenda-header-richlist-item> to add to.
  */
 agendaListbox.addPeriodListItem = function(aPeriod, aItemId) {
     aPeriod.listItem = document.getElementById(aItemId);
     aPeriod.listItem.hidden = false;
     aPeriod.listItem.getCheckbox().checked = aPeriod.open;
 };
 
 /**
@@ -124,17 +124,17 @@ agendaListbox.removePeriodListItem = fun
 /**
  * Handler function called when changing the checkbox state on period items.
  *
  * @param event     The DOM event that triggered the checkbox state change.
  */
 agendaListbox.onCheckboxChange = function(event) {
     let periodCheckbox = event.target;
     let lopen = (periodCheckbox.getAttribute("checked") == "true");
-    let listItem = cal.view.getParentNodeOrThis(periodCheckbox, "agenda-checkbox-richlist-item");
+    let listItem = cal.view.getParentNodeOrThis(periodCheckbox, "richlistitem");
     let period = listItem.getItem();
     if (!period) {
         return;
     }
 
     period.open = lopen;
     // as the agenda-checkboxes are only transient we have to set the "checked"
     // attribute at their hidden origins to make that attribute persistent.
@@ -282,19 +282,19 @@ agendaListbox.getEnd = function() {
  * @param aAgendaItem   The existing item to insert before.
  * @param aPeriod       The period to add the item to.
  * @param visible       If true, the item should be visible.
  * @return              The newly created XUL element.
  */
 agendaListbox.addItemBefore = function(aNewItem, aAgendaItem, aPeriod, visible) {
     let newelement = null;
     if (aNewItem.startDate.isDate) {
-        newelement = document.createXULElement("agenda-allday-richlist-item");
+        newelement = document.createElement("richlistitem", { is: "agenda-allday-richlist-item" });
     } else {
-        newelement = document.createXULElement("agenda-richlist-item");
+        newelement = document.createElement("richlistitem", { is: "agenda-richlist-item" });
     }
     // set the item at the richlistItem. When the duration of the period
     // is bigger than 1 (day) the starttime of the item has to include
     // information about the day of the item
     if (aAgendaItem == null) {
         this.agendaListboxControl.appendChild(newelement);
     } else {
         this.agendaListboxControl.insertBefore(newelement, aAgendaItem);
@@ -817,17 +817,17 @@ agendaListbox.getSelectedItems = functio
  * period item).
  *
  * @param aListItem     The node to check for.
  * @return              True, if the node is not a period item.
  */
 agendaListbox.isEventListItem = function(aListItem) {
     let isListItem = (aListItem != null);
     if (isListItem) {
-        let localName = aListItem.localName;
+        let localName = aListItem.getAttribute("is");
         isListItem = (localName == "agenda-richlist-item" ||
                       localName == "agenda-allday-richlist-item");
     }
     return isListItem;
 };
 
 /**
  * Removes all Event items, keeping the period items intact.
--- a/calendar/base/content/agenda-listbox.js
+++ b/calendar/base/content/agenda-listbox.js
@@ -1,1147 +1,331 @@
 /* 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/. */
 
-/* import-globals-from calendar-common-sets.js */
-/* import-globals-from calendar-item-editing.js */
-/* import-globals-from calendar-management.js */
-/* import-globals-from calendar-ui-utils.js */
-/* import-globals-from calendar-views.js */
-
-var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
-
-var { cal } = ChromeUtils.import("resource://calendar/modules/calUtils.jsm");
-
-function Synthetic(aHeader, aDuration, aMultiday) {
-    this.open = aHeader.getAttribute("checked") == "true";
-    this.duration = aDuration;
-    this.multiday = aMultiday;
-}
-
-var agendaListbox = {
-    agendaListboxControl: null,
-    mPendingRefreshJobs: null,
-    kDefaultTimezone: null,
-    showsToday: false,
-    soonDays: 5
-};
-
-/**
- * Initialize the agenda listbox, used on window load.
- */
-agendaListbox.init = async function() {
-    this.agendaListboxControl = document.getElementById("agenda-listbox");
-    this.agendaListboxControl.removeAttribute("suppressonselect");
-    let showTodayHeader = document.getElementById("today-header");
-    let showTomorrowHeader = document.getElementById("tomorrow-header");
-    let showSoonHeader = document.getElementById("nextweek-header");
-    if (!("getCheckbox" in showTodayHeader)) {
-        await new Promise(resolve => showTodayHeader.addEventListener("bindingattached", resolve, { once: true }));
-    }
-    if (!("getCheckbox" in showTomorrowHeader)) {
-        await new Promise(resolve => showTomorrowHeader.addEventListener("bindingattached", resolve, { once: true }));
-    }
-    if (!("getCheckbox" in showSoonHeader)) {
-        await new Promise(resolve => showSoonHeader.addEventListener("bindingattached", resolve, { once: true }));
-    }
-    this.today = new Synthetic(showTodayHeader, 1, false);
-    this.addPeriodListItem(this.today, "today-header");
-    this.tomorrow = new Synthetic(showTomorrowHeader, 1, false);
-    this.soonDays = getSoondaysPreference();
-    this.soon = new Synthetic(showSoonHeader, this.soonDays, true);
-    this.periods = [this.today, this.tomorrow, this.soon];
-    this.mPendingRefreshJobs = new Map();
-
-    for (let header of [showTodayHeader, showTomorrowHeader, showSoonHeader]) {
-        header.getCheckbox().addEventListener("CheckboxStateChange", this.onCheckboxChange, true);
-    }
+"use strict";
 
-    let prefObserver = {
-        observe: function(aSubject, aTopic, aPrefName) {
-            switch (aPrefName) {
-                case "calendar.agendaListbox.soondays":
-                    agendaListbox.soonDays = getSoondaysPreference();
-                    agendaListbox.updateSoonSection();
-                    break;
-            }
+/* global MozXULElement, MozElements, unitPluralForm, cal, agendaListbox,
+   invokeEventDragSession, setBooleanAttribute, hideElement, onMouseOverItem */
+{
+    /**
+     * The MozAgendaHeaderRichlistItem widget is typically used to display the
+     * Today, Tomorrow, and Upcoming headers of the Today Pane listing.
+     *
+     * @extends {MozElements.MozRichlistitem}
+     */
+    class MozAgendaHeaderRichlistItem extends MozElements.MozRichlistitem {
+        static get inheritedAttributes() {
+            return { ".agenda-checkbox": "selected,label,hidden,disabled" };
         }
-    };
-    Services.prefs.addObserver("calendar.agendaListbox", prefObserver);
-
-    // Make sure the agenda listbox is unloaded
-    window.addEventListener("unload", () => {
-        Services.prefs.removeObserver("calendar.agendaListbox", prefObserver);
-        this.uninit();
-    });
-};
-
-/**
- * Clean up the agenda listbox, used on window unload.
- */
-agendaListbox.uninit = function() {
-    if (this.calendar) {
-        this.calendar.removeObserver(this.calendarObserver);
-    }
 
-    for (let period of this.periods) {
-        if (period.listItem) {
-            period.listItem.getCheckbox()
-                  .removeEventListener("CheckboxStateChange",
-                                       this.onCheckboxChange,
-                                       true);
-        }
-    }
-};
-
-/**
- * Adds a period item to the listbox. This is a section of the today pane like
- * "Today", "Tomorrow", and is usually a <agenda-checkbox-richlist-item> tag. A
- * copy of the template node is made and added to the agenda listbox.
- *
- * @param aPeriod       The period item to add.
- * @param aItemId       The id of an <agenda-checkbox-richlist-item> to add to.
- */
-agendaListbox.addPeriodListItem = function(aPeriod, aItemId) {
-    aPeriod.listItem = document.getElementById(aItemId);
-    aPeriod.listItem.hidden = false;
-    aPeriod.listItem.getCheckbox().checked = aPeriod.open;
-};
-
-/**
- * Remove a period item from the agenda listbox.
- * @see agendaListbox::addPeriodListItem
- */
-agendaListbox.removePeriodListItem = function(aPeriod) {
-    if (aPeriod.listItem) {
-        aPeriod.listItem.getCheckbox().removeEventListener("CheckboxStateChange", this.onCheckboxChange, true);
-        if (aPeriod.listItem) {
-            aPeriod.listItem.hidden = true;
-            aPeriod.listItem = null;
-        }
-    }
-};
+        connectedCallback() {
+            if (this.delayConnectedCallback() || this.hasChildNodes()) {
+                return;
+            }
+            this.kCheckbox = document.createElement("checkbox");
+            this.kCheckbox.classList.add("agenda-checkbox", "treenode-checkbox");
+            this.appendChild(this.kCheckbox);
 
-/**
- * Handler function called when changing the checkbox state on period items.
- *
- * @param event     The DOM event that triggered the checkbox state change.
- */
-agendaListbox.onCheckboxChange = function(event) {
-    let periodCheckbox = event.target;
-    let lopen = (periodCheckbox.getAttribute("checked") == "true");
-    let listItem = cal.view.getParentNodeOrThis(periodCheckbox, "agenda-checkbox-richlist-item");
-    let period = listItem.getItem();
-    if (!period) {
-        return;
-    }
+            this.dispatchEvent(new CustomEvent("bindingattached", { bubbles: false }));
+            this.initializeAttributeInheritance();
+        }
 
-    period.open = lopen;
-    // as the agenda-checkboxes are only transient we have to set the "checked"
-    // attribute at their hidden origins to make that attribute persistent.
-    document.getElementById(listItem.id).setAttribute("checked",
-                            periodCheckbox.getAttribute("checked"));
-    if (lopen) {
-        agendaListbox.refreshCalendarQuery(period.start, period.end);
-    } else {
-        listItem = listItem.nextSibling;
-        let leaveloop;
-        do {
-            leaveloop = (listItem == null);
-            if (!leaveloop) {
-                let nextItemSibling = listItem.nextSibling;
-                leaveloop = !agendaListbox.isEventListItem(listItem);
-                if (!leaveloop) {
-                    listItem.remove();
-                    listItem = nextItemSibling;
-                }
-            }
-        } while (!leaveloop);
-    }
-    calendarController.onSelectionChanged({ detail: [] });
-};
-
-/**
- * Handler function called when an agenda listbox item is selected
- *
- * @param aListItem     The agenda-base-richlist-item that was selected.
- */
-agendaListbox.onSelect = function(aListItem) {
-    let listbox = document.getElementById("agenda-listbox");
-    let item = aListItem || listbox.selectedItem;
-    if (aListItem) {
-        listbox.selectedItem = item;
-    }
-    calendarController.onSelectionChanged({ detail: agendaListbox.getSelectedItems() });
-};
-
-/**
- * Handler function called when the agenda listbox becomes focused
- */
-agendaListbox.onFocus = function() {
-    calendarController.onSelectionChanged({ detail: agendaListbox.getSelectedItems() });
-};
+        getItem() {
+            return this.mItem;
+        }
 
-/**
- * Handler function called when the agenda listbox loses focus.
- */
-agendaListbox.onBlur = function() {
-    calendarController.onSelectionChanged({ detail: [] });
-};
-
-
-/**
- * Handler function called when a key was pressed on the agenda listbox
- */
-agendaListbox.onKeyPress = function(aEvent) {
-    let listItem = aEvent.target;
-    if (listItem.localName == "richlistbox") {
-        listItem = listItem.selectedItem;
-    }
-    switch (aEvent.key) {
-        case "Enter":
-            document.getElementById("agenda_edit_event_command").doCommand();
-            break;
-        case "Delete":
-            document.getElementById("agenda_delete_event_command").doCommand();
-            aEvent.stopPropagation();
-            aEvent.preventDefault();
-            break;
-        case "ArrowLeft":
-            if (!this.isEventListItem(listItem)) {
-                listItem.getCheckbox().checked = false;
-            }
-            break;
-        case "ArrowRight":
-            if (!this.isEventListItem(listItem)) {
-                listItem.getCheckbox().checked = true;
-            }
-            break;
-    }
-};
-
-/**
- * Calls the event dialog to edit the currently selected item
- */
-agendaListbox.editSelectedItem = function() {
-    let listItem = document.getElementById("agenda-listbox").selectedItem;
-    if (listItem) {
-        modifyEventWithDialog(listItem.occurrence, null, true);
-    }
-};
-
-/**
- * Finds the appropriate period for the given item, i.e finds "Tomorrow" if the
- * item occurs tomorrow.
- *
- * @param aItem     The item to find the period for.
- */
-agendaListbox.findPeriodsForItem = function(aItem) {
-    let retPeriods = [];
-    for (let i = 0; i < this.periods.length; i++) {
-        if (this.periods[i].open) {
-            if (cal.item.checkIfInRange(aItem, this.periods[i].start, this.periods[i].end)) {
-                retPeriods.push(this.periods[i]);
+        setItem(synthetic, showsToday) {
+            this.mItem = synthetic;
+            let duration = synthetic.duration;
+            if (showsToday) {
+                this.kCheckbox.label = this.getAttribute("title");
+                if (this.id == "nextweek-header") {
+                    if (duration > 7) {
+                        this.kCheckbox.label += " (" + unitPluralForm(duration / 7, "weeks") + ")";
+                    } else {
+                        this.kCheckbox.label += " (" + unitPluralForm(duration, "days") + ")";
+                    }
+                }
+            } else if (synthetic.duration == 1) {
+                this.kCheckbox.label = cal.getDateFormatter().formatDate(synthetic.start);
+            } else {
+                this.kCheckbox.label = cal.getDateFormatter().formatInterval(synthetic.start, synthetic.end);
             }
         }
-    }
-    return retPeriods;
-};
-
-/**
- * Gets the start of the earliest period shown in the agenda listbox
- */
-agendaListbox.getStart = function() {
-    let retStart = null;
-    for (let i = 0; i < this.periods.length; i++) {
-        if (this.periods[i].open) {
-            retStart = this.periods[i].start;
-            break;
-        }
-    }
-    return retStart;
-};
-
-/**
- * Gets the end of the latest period shown in the agenda listbox
- */
-agendaListbox.getEnd = function() {
-    let retEnd = null;
-    for (let i = this.periods.length - 1; i >= 0; i--) {
-        if (this.periods[i].open) {
-            retEnd = this.periods[i].end;
-            break;
-        }
-    }
-    return retEnd;
-};
-
-/**
- * Adds an item to an agenda period before another existing item.
- *
- * @param aNewItem      The calIItemBase to add.
- * @param aAgendaItem   The existing item to insert before.
- * @param aPeriod       The period to add the item to.
- * @param visible       If true, the item should be visible.
- * @return              The newly created XUL element.
- */
-agendaListbox.addItemBefore = function(aNewItem, aAgendaItem, aPeriod, visible) {
-    let newelement = null;
-    if (aNewItem.startDate.isDate) {
-        newelement = document.createXULElement("agenda-allday-richlist-item");
-    } else {
-        newelement = document.createXULElement("agenda-richlist-item");
-    }
-    // set the item at the richlistItem. When the duration of the period
-    // is bigger than 1 (day) the starttime of the item has to include
-    // information about the day of the item
-    if (aAgendaItem == null) {
-        this.agendaListboxControl.appendChild(newelement);
-    } else {
-        this.agendaListboxControl.insertBefore(newelement, aAgendaItem);
-    }
-    newelement.setOccurrence(aNewItem, aPeriod);
-    newelement.removeAttribute("selected");
-    return newelement;
-};
-
-/**
- * Adds an item to the agenda listbox. This function finds the correct period
- * for the item and inserts it correctly so the period stays sorted.
- *
- * @param aItem         The calIItemBase to add.
- * @return              The newly created XUL element.
- */
-agendaListbox.addItem = function(aItem) {
-    if (!cal.item.isEvent(aItem)) {
-        return null;
-    }
-    aItem.QueryInterface(Ci.calIEvent);
-    let periods = this.findPeriodsForItem(aItem);
-    if (periods.length == 0) {
-        return null;
-    }
-    let newlistItem = null;
-    for (let i = 0; i < periods.length; i++) {
-        let period = periods[i];
-        let complistItem = period.listItem;
-        let visible = complistItem.getCheckbox().checked;
-        if (aItem.startDate.isDate && period.duration == 1 && aItem.duration.days == 1) {
-            if (this.getListItems(aItem, period).length == 0) {
-                this.addItemBefore(aItem, period.listItem.nextSibling, period, visible);
-            }
-        } else {
-            do {
-                complistItem = complistItem.nextSibling;
-                if (this.isEventListItem(complistItem)) {
-                    let compitem = complistItem.occurrence;
-                    if (this.isSameEvent(aItem, compitem)) {
-                        // The same event occurs on several calendars but we only
-                        // display the first one.
-                        // TODO: find a way to display this special circumstance
-                        break;
-                    } else if (this.isBefore(aItem, compitem, period)) {
-                        if (this.isSameEvent(aItem, compitem)) {
-                            newlistItem = this.addItemBefore(aItem, complistItem, period, visible);
-                            break;
-                        } else {
-                            newlistItem = this.addItemBefore(aItem, complistItem, period, visible);
-                            break;
-                        }
-                    }
-                } else {
-                    newlistItem = this.addItemBefore(aItem, complistItem, period, visible);
-                    break;
-                }
-            } while (complistItem);
-        }
-    }
-    return newlistItem;
-};
-
-/**
- * Checks if the given item happens before the comparison item.
- *
- * @param aItem         The item to compare.
- * @param aCompItem     The item to compare with.
- * @param aPeriod       The period where the items are inserted.
- * @return              True, if the aItem happens before aCompItem.
- */
-agendaListbox.isBefore = function(aItem, aCompItem, aPeriod) {
-    let itemDate = this.comparisonDate(aItem, aPeriod);
-    let compItemDate = this.comparisonDate(aCompItem, aPeriod);
-    let itemDateEndDate = itemDate.clone();
-    itemDateEndDate.day++;
-
-    if (compItemDate.day == itemDate.day) {
-        // In the same day the order is:
-        // - all-day events (single day);
-        // - all-day events spanning multiple days: start, end, intermediate;
-        // - events and events spanning multiple days: start, end, (sorted by
-        //   time) and intermediate.
-        if (itemDate.isDate && aItem.duration.days == 1) {
-            // all-day events with duration one day
-            return true;
-        } else if (itemDate.isDate) {
-            if (aItem.startDate.compare(itemDate) == 0) {
-                // starting day of an all-day events spannig multiple days
-                return !compItemDate.isDate || aCompItem.duration.days != 1;
-            } else if (aItem.endDate.compare(itemDateEndDate) == 0) {
-                // ending day of an all-day events spannig multiple days
-                return !compItemDate.isDate ||
-                       (aCompItem.duration.days != 1 &&
-                        aCompItem.startDate.compare(compItemDate) != 0);
-            } else {
-                // intermediate day of an all-day events spannig multiple days
-                return !compItemDate.isDate;
-            }
-        } else if (aCompItem.startDate.isDate) {
-            return false;
-        }
-    }
-    // Non all-day event sorted by date-time. When equal, sorted by start
-    // date-time then by end date-time.
-    let comp = itemDate.compare(compItemDate);
-    if (comp == 0) {
-        comp = aItem.startDate.compare(aCompItem.startDate);
-        if (comp == 0) {
-            comp = aItem.endDate.compare(aCompItem.endDate);
-        }
-    }
-    return (comp <= 0);
-};
-
-/**
- * Returns the start or end date of an item according to which of them
- * must be displayed in a given period of the agenda
- *
- * @param aItem         The item to compare.
- * @param aPeriod       The period where the item is inserted.
- * @return              The start or end date of the item showed in the agenda.
- */
-agendaListbox.comparisonDate = function(aItem, aPeriod) {
-    let periodStartDate = aPeriod.start.clone();
-    periodStartDate.isDate = true;
-    let periodEndDate = aPeriod.end.clone();
-    periodEndDate.day--;
-    let startDate = aItem.startDate.clone();
-    startDate.isDate = true;
-    let endDate = aItem.endDate.clone();
-
-    let endDateToReturn = aItem.endDate.clone();
-    if (aItem.startDate.isDate && aPeriod.duration == 1) {
-        endDateToReturn = periodEndDate.clone();
-    } else if (endDate.isDate) {
-        endDateToReturn.day--;
-    } else if (endDate.hour == 0 && endDate.minute == 0) {
-        // End at midnight -> end date in the day where midnight occurs
-        endDateToReturn.day--;
-        endDateToReturn.hour = 23;
-        endDateToReturn.minute = 59;
-        endDateToReturn.second = 59;
-    }
-    endDate.isDate = true;
-    if (startDate.compare(endDate) != 0 &&
-         startDate.compare(periodStartDate) < 0) {
-        // returns a end date when the item is a multiday event AND
-        // it starts before the given period
-        return endDateToReturn;
-    }
-    return aItem.startDate.clone();
-};
-
-/**
- * Gets the listitems for a given item, possibly in a given period.
- *
- * @param aItem         The item to get the list items for.
- * @param aPeriod       (optional) the period to search in.
- * @return              An array of list items for the given item.
- */
-agendaListbox.getListItems = function(aItem, aPeriod) {
-    let retlistItems = [];
-    let periods = [aPeriod];
-    if (!aPeriod) {
-        periods = this.findPeriodsForItem(aItem);
-    }
-    if (periods.length > 0) {
-        for (let i = 0; i < periods.length; i++) {
-            let period = periods[i];
-            let complistItem = period.listItem;
-            let leaveloop;
-            do {
-                complistItem = complistItem.nextSibling;
-                leaveloop = !this.isEventListItem(complistItem);
-                if (!leaveloop) {
-                    if (this.isSameEvent(aItem, complistItem.occurrence)) {
-                        retlistItems.push(complistItem);
-                        break;
-                    }
-                }
-            } while (!leaveloop);
-        }
-    }
-    return retlistItems;
-};
-
-/**
- * Removes the given item from the agenda listbox
- *
- * @param aItem             The item to remove.
- * @param aMoveSelection    If true, the selection will be moved to the next
- *                            sibling that is not an period item.
- * @return                  Returns true if the removed item was selected.
- */
-agendaListbox.deleteItem = function(aItem, aMoveSelection) {
-    let isSelected = false;
-    let listItems = this.getListItems(aItem);
-    if (listItems.length > 0) {
-        for (let i = listItems.length - 1; i >= 0; i--) {
-            let listItem = listItems[i];
-            let isSelected2 = listItem.selected;
-            if (isSelected2 && !isSelected) {
-                isSelected = true;
-                if (aMoveSelection) {
-                    this.moveSelection();
-                }
-            }
-            listItem.remove();
-        }
-    }
-    return isSelected;
-};
-
-/**
- * Remove all items belonging to the specified calendar.
- *
- * @param aCalendar         The item to compare.
- */
-agendaListbox.deleteItemsFromCalendar = function(aCalendar) {
-    let childNodes = Array.from(this.agendaListboxControl.childNodes);
-    for (let childNode of childNodes) {
-        if (childNode && childNode.occurrence &&
-            childNode.occurrence.calendar.id == aCalendar.id) {
-            childNode.remove();
+        getCheckbox() {
+            return this.kCheckbox;
         }
     }
-};
-
-/**
- * Compares two items to see if they have the same id and their start date
- * matches
- *
- * @param aItem         The item to compare.
- * @param aCompItem     The item to compare with.
- * @return              True, if the items match with the above noted criteria.
- */
-agendaListbox.isSameEvent = function(aItem, aCompItem) {
-    return aItem.id == aCompItem.id &&
-           aItem[cal.dtz.startDateProp(aItem)].compare(aCompItem[cal.dtz.startDateProp(aCompItem)]) == 0;
-};
-
-/**
- * Checks if the currently selected node in the listbox is an Event item (not a
- * period item).
- *
- * @return              True, if the node is not a period item.
- */
-agendaListbox.isEventSelected = function() {
-    let listItem = this.agendaListboxControl.selectedItem;
-    if (listItem) {
-        return this.isEventListItem(listItem);
-    }
-    return false;
-};
 
-/**
- * Delete the selected item from its calendar (if it is an event item)
- *
- * @param aDoNotConfirm     If true, the user will not be prompted.
- */
-agendaListbox.deleteSelectedItem = function(aDoNotConfirm) {
-    let listItem = this.agendaListboxControl.selectedItem;
-    if (this.isEventListItem(listItem)) {
-        let selectedItems = [listItem.occurrence];
-        calendarViewController.deleteOccurrences(selectedItems.length,
-                                                 selectedItems,
-                                                 false,
-                                                 aDoNotConfirm);
-    }
-};
+    MozXULElement.implementCustomInterface(MozAgendaHeaderRichlistItem, [Ci.nsIDOMXULSelectControlItemElement]);
 
-/**
- * If a Period item is targeted by the passed DOM event, opens the event dialog
- * with the period's start date prefilled.
- *
- * @param aEvent            The DOM event that targets the period.
- */
-agendaListbox.createNewEvent = function(aEvent) {
-    if (!this.isEventListItem(aEvent.target)) {
-        // Create new event for the date currently displayed in the agenda. Setting
-        // isDate = true automatically makes the start time be the next full hour.
-        let eventStart = agendaListbox.today.start.clone();
-        eventStart.isDate = true;
-        if (calendarController.isCommandEnabled("calendar_new_event_command")) {
-            createEventWithDialog(getSelectedCalendar(), eventStart);
-        }
-    }
-};
-
-/**
- * Sets up the context menu for the agenda listbox
- *
- * @param popup         The <menupopup> element to set up.
- */
-agendaListbox.setupContextMenu = function(popup) {
-    let listItem = this.agendaListboxControl.selectedItem;
-    let enabled = this.isEventListItem(listItem);
-    let menuitems = popup.childNodes;
-    for (let i = 0; i < menuitems.length; i++) {
-        setBooleanAttribute(menuitems[i], "disabled", !enabled);
-    }
-
-    let menu = document.getElementById("calendar-today-pane-menu-attendance-menu");
-    setupAttendanceMenu(menu, agendaListbox.getSelectedItems({}));
-};
+    customElements.define("agenda-header-richlist-item", MozAgendaHeaderRichlistItem, { "extends": "richlistitem" });
 
 
-/**
- * Refreshes the agenda listbox. If aStart or aEnd is not passed, the agenda
- * listbox's limiting dates will be used.
- *
- * @param aStart        (optional) The start date for the item query.
- * @param aEnd          (optional) The end date for the item query.
- * @param aCalendar     (optional) If specified, the single calendar from
- *                                   which the refresh will occur.
- */
-agendaListbox.refreshCalendarQuery = function(aStart, aEnd, aCalendar) {
-    let refreshJob = {
-        QueryInterface: ChromeUtils.generateQI([Ci.calIOperationListener]),
-        agendaListbox: this,
-        calendar: null,
-        calId: null,
-        operation: null,
-        cancelled: false,
-
-        onOperationComplete: function(aOpCalendar, aStatus, aOperationType, aId, aDateTime) {
-            if (this.agendaListbox.mPendingRefreshJobs.has(this.calId)) {
-                this.agendaListbox.mPendingRefreshJobs.delete(this.calId);
-            }
-
-            if (!this.cancelled) {
-                setCurrentEvent();
-            }
-        },
-
-        onGetResult: function(aOpCalendar, aStatus, aItemType, aDetail, aCount, aItems) {
-            if (this.cancelled || !Components.isSuccessCode(aStatus)) {
-                return;
-            }
-            for (let item of aItems) {
-                this.agendaListbox.addItem(item);
-            }
-        },
-
-        cancel: function() {
-            this.cancelled = true;
-            let operation = cal.wrapInstance(this.operation, Ci.calIOperation);
-            if (operation && operation.isPending) {
-                operation.cancel();
-                this.operation = null;
-            }
-        },
-
-        execute: function() {
-            if (!(aStart || aEnd || aCalendar)) {
-                this.agendaListbox.removeListItems();
-            }
-
-            if (!aCalendar) {
-                aCalendar = this.agendaListbox.calendar;
-            }
-            if (!aStart) {
-                aStart = this.agendaListbox.getStart();
-            }
-            if (!aEnd) {
-                aEnd = this.agendaListbox.getEnd();
-            }
-            if (!(aStart || aEnd || aCalendar)) {
+    /**
+     * The MozAgendaAlldayRichlistItem widget displays the information about
+     * all day event: i.e. event start time, icon and calendar-month-day-box-item.
+     * It is typically shown under the the agenda-header-richlist-item dropdown.
+     *
+     * @extends {MozElements.MozRichlistitem}
+     */
+    class MozAgendaAlldayRichlistItem extends MozElements.MozRichlistitem {
+        static get inheritedAttributes() {
+            return {
+                ".agenda-allday-container-box": "selected,disabled",
+                ".agenda-event-start": "selected"
+            };
+        }
+        connectedCallback() {
+            if (this.delayConnectedCallback() || this.hasChildNodes()) {
                 return;
             }
-
-            if (aCalendar.type == "composite") {
-                // we're refreshing from the composite calendar, so we can cancel
-                // all other pending refresh jobs.
-                this.calId = "composite";
-                for (let job of this.agendaListbox.mPendingRefreshJobs.values()) {
-                    job.cancel();
+            this.setAttribute("is", "agenda-allday-richlist-item");
+            this.addEventListener("click", (event) => {
+                if (event.detail == 1) {
+                    agendaListbox.onSelect(this);
+                } else if (event.button == 0) {
+                    // We only care about button 0 doubleclick events
+                    document.getElementById("agenda_edit_event_command").doCommand();
+                    event.stopPropagation();
+                    event.preventDefault();
                 }
-                this.agendaListbox.mPendingRefreshJobs.clear();
-            } else {
-                this.calId = aCalendar.id;
-                if (this.agendaListbox.mPendingRefreshJobs.has(this.calId)) {
-                    this.agendaListbox.mPendingRefreshJobs.get(this.calId).cancel();
-                    this.agendaListbox.mPendingRefreshJobs.delete(this.calId);
-                }
-            }
-            this.calendar = aCalendar;
+            });
 
-            let filter = this.calendar.ITEM_FILTER_CLASS_OCCURRENCES |
-                         this.calendar.ITEM_FILTER_TYPE_EVENT;
-            let operation = this.calendar.getItems(filter, 0, aStart, aEnd, this);
-            operation = cal.wrapInstance(operation, Ci.calIOperation);
-            if (operation && operation.isPending) {
-                this.operation = operation;
-                this.agendaListbox.mPendingRefreshJobs.set(this.calId, this);
-            }
-        }
-    };
-
-    refreshJob.execute();
-};
-
-/**
- * Sets up the calendar for the agenda listbox.
- */
-agendaListbox.setupCalendar = async function() {
-    await this.init();
-
-    if (this.calendar == null) {
-        this.calendar = cal.view.getCompositeCalendar(window);
-    }
-    if (this.calendar) {
-        // XXX This always gets called, does that happen on purpose?
-        this.calendar.removeObserver(this.calendarObserver);
-    }
-    this.calendar.addObserver(this.calendarObserver);
-    if (this.mListener) {
-        this.mListener.updatePeriod();
-    }
-};
+            this.addEventListener("mouseover", (event) => {
+                event.stopPropagation();
+                onMouseOverItem(event);
+            });
 
-/**
- * Refreshes the period dates, especially when a period is showing "today".
- * Usually called at midnight to update the agenda pane. Also retrieves the
- * items from the calendar.
- *
- * @see #refreshCalendarQuery
- * @param newDate       The first date to show if the agenda pane doesn't show
- *                        today.
- */
-agendaListbox.refreshPeriodDates = function(newDate) {
-    this.kDefaultTimezone = cal.dtz.defaultTimezone;
-    // Today: now until midnight of tonight
-    let oldshowstoday = this.showstoday;
-    this.showstoday = this.showsToday(newDate);
-    if ((this.showstoday) && (!oldshowstoday)) {
-        this.addPeriodListItem(this.tomorrow, "tomorrow-header");
-        this.addPeriodListItem(this.soon, "nextweek-header");
-    } else if (!this.showstoday) {
-        this.removePeriodListItem(this.tomorrow);
-        this.removePeriodListItem(this.soon);
-    }
-    newDate.isDate = true;
-    for (let i = 0; i < this.periods.length; i++) {
-        let curPeriod = this.periods[i];
-        newDate.hour = newDate.minute = newDate.second = 0;
-        if (i == 0 && this.showstoday) {
-            curPeriod.start = cal.dtz.now();
-        } else {
-            curPeriod.start = newDate.clone();
-        }
-        newDate.day += curPeriod.duration;
-        curPeriod.end = newDate.clone();
-        curPeriod.listItem.setItem(curPeriod, this.showstoday);
-    }
-    this.refreshCalendarQuery();
-};
+            this.addEventListener("dragstart", (event) => {
+                invokeEventDragSession(this.mAllDayItem.occurrence.clone(), this);
+                event.stopPropagation();
+                event.preventDefault();
+            }, true);
+
+
+            // Prevent double clicks from opening a dialog to create a new event.
+            this.addEventListener("dblclick", (event) => {
+                event.stopPropagation();
+            });
 
-/**
- * Adds a listener to this agenda listbox.
- *
- * @param aListener     The listener to add.
- */
-agendaListbox.addListener = function(aListener) {
-    this.mListener = aListener;
-};
-
-/**
- * Checks if the agenda listbox is showing "today". Without arguments, this
- * function assumes the today attribute of the agenda listbox.
- *
- * @param aStartDate    (optional) The day to check if its "today".
- * @return              Returns true if today is shown.
- */
-agendaListbox.showsToday = function(aStartDate) {
-    let lstart = aStartDate;
-    if (!lstart) {
-        lstart = this.today.start;
-    }
-    let lshowsToday = cal.dtz.sameDay(cal.dtz.now(), lstart);
-    if (lshowsToday) {
-        this.periods = [this.today, this.tomorrow, this.soon];
-    } else {
-        this.periods = [this.today];
-    }
-    return lshowsToday;
-};
-
-/**
- * Moves the selection. Moves down unless the next item is a period item, in
- * which case the selection moves up.
- */
-agendaListbox.moveSelection = function() {
-    if (this.isEventListItem(this.agendaListboxControl.selectedItem.nextSibling)) {
-        this.agendaListboxControl.moveByOffset(-1);
-    } else {
-        this.agendaListboxControl.moveByOffset(1);
-    }
-};
+            this.appendChild(MozXULElement.parseXULToFragment(`
+                <hbox class="agenda-allday-container-box" flex="1">
+                    <vbox pack="center" flex="1">
+                        <label class="agenda-event-start" crop="end" hidden="true"></label>
+                        <hbox flex="1" align="start">
+                            <image class="agenda-multiDayEvent-image"></image>
+                            <calendar-month-day-box-item flex="1" flat="true"></calendar-month-day-box-item>
+                        </hbox>
+                    </vbox>
+                </hbox>
+            `));
+            this.mAllDayItem = null;
+            this.mOccurrence = null;
+            this.initializeAttributeInheritance();
+        }
 
-/**
- * Gets an array of selected items. If a period node is selected, it is not
- * included.
- *
- * @return      An array with all selected items.
- */
-agendaListbox.getSelectedItems = function() {
-    let items = [];
-    if (this.isEventListItem(this.agendaListboxControl.selectedItem)) {
-        // If at some point we support selecting multiple items, this array can
-        // be expanded.
-        items = [this.agendaListboxControl.selectedItem.occurrence];
-    }
-    return items;
-};
-
-/**
- * Checks if the passed node in the listbox is an Event item (not a
- * period item).
- *
- * @param aListItem     The node to check for.
- * @return              True, if the node is not a period item.
- */
-agendaListbox.isEventListItem = function(aListItem) {
-    let isListItem = (aListItem != null);
-    if (isListItem) {
-        let localName = aListItem.localName;
-        isListItem = (localName == "agenda-richlist-item" ||
-                      localName == "agenda-allday-richlist-item");
-    }
-    return isListItem;
-};
-
-/**
- * Removes all Event items, keeping the period items intact.
- */
-agendaListbox.removeListItems = function() {
-    let listItem = this.agendaListboxControl.lastChild;
-    if (listItem) {
-        let leaveloop = false;
-        do {
-            let newlistItem = null;
-            if (listItem) {
-                newlistItem = listItem.previousSibling;
+        setOccurrence(aOccurrence, aPeriod) {
+            this.mOccurrence = aOccurrence;
+            this.mAllDayItem = this.querySelector("calendar-month-day-box-item");
+            this.mAllDayItem.occurrence = aOccurrence;
+            let dateFormatter = cal.getDateFormatter();
+            let periodStartDate = aPeriod.start.clone();
+            periodStartDate.isDate = true;
+            let periodEndDate = aPeriod.end;
+            let startDate = this.mOccurrence[cal.dtz.startDateProp(this.mOccurrence)].getInTimezone(cal.dtz.defaultTimezone);
+            let endDate = this.mOccurrence[cal.dtz.endDateProp(this.mOccurrence)].getInTimezone(cal.dtz.defaultTimezone);
+            let endPreviousDay = endDate.clone();
+            endPreviousDay.day--;
+            // Show items's date for long periods but also for "Upcoming"
+            // period with one day duration.
+            let showDate = aPeriod.multiday || aPeriod.duration > 1;
+            let date = "";
+            let iconType = "";
+            let allDayDateLabel = this.querySelector(".agenda-event-start");
+            setBooleanAttribute(allDayDateLabel, "hidden", !showDate);
+            if (startDate.compare(endPreviousDay) == 0) {
+                // All day event one day duration.
+                date = dateFormatter.formatDate(startDate);
+            } else if (startDate.compare(periodStartDate) >= 0 &&
+                startDate.compare(periodEndDate) <= 0) {
+                // All day event spanning multiple days.
+                iconType = "start";
+                date = dateFormatter.formatDate(startDate);
+            } else if (endDate.compare(periodStartDate) >= 0 &&
+                endDate.compare(periodEndDate) <= 0) {
+                iconType = "end";
+                date = dateFormatter.formatDate(endPreviousDay);
             } else {
-                leaveloop = true;
-            }
-            if (this.isEventListItem(listItem)) {
-                if (listItem == this.agendaListboxControl.firstChild) {
-                    leaveloop = true;
-                } else {
-                    listItem.remove();
-                }
-            }
-            listItem = newlistItem;
-        } while (!leaveloop);
-    }
-};
-
-/**
- * Gets the list item node by its associated event's hashId.
- *
- * @return The XUL node if successful, otherwise null.
- */
-agendaListbox.getListItemByHashId = function(ahashId) {
-    let listItem = this.agendaListboxControl.firstChild;
-    let leaveloop = false;
-    do {
-        if (this.isEventListItem(listItem)) {
-            if (listItem.occurrence.hashId == ahashId) {
-                return listItem;
+                iconType = "continue";
+                hideElement(allDayDateLabel);
             }
+            let multiDayImage = this.querySelector(".agenda-multiDayEvent-image");
+            multiDayImage.setAttribute("type", iconType);
+            // class wrap causes allday items to wrap its text in today-pane
+            let addWrap = document.getAnonymousElementByAttribute(this.mAllDayItem, "anonid", "eventbox");
+            addWrap.classList.add("wrap");
+            addWrap = document.getAnonymousElementByAttribute(this.mAllDayItem, "anonid", "event-detail-box");
+            addWrap.classList.add("wrap");
+            allDayDateLabel.value = date;
         }
-        listItem = listItem.nextSibling;
-        leaveloop = (listItem == null);
-    } while (!leaveloop);
-    return null;
-};
-
-/**
- * The operation listener used for calendar queries.
- * Implements calIOperationListener.
- */
-agendaListbox.calendarOpListener = { agendaListbox: agendaListbox };
-
-/**
- * Calendar and composite observer, used to keep agenda listbox up to date.
- * @see calIObserver
- * @see calICompositeObserver
- */
-agendaListbox.calendarObserver = { agendaListbox: agendaListbox };
-
-agendaListbox.calendarObserver.QueryInterface = cal.generateQI([
-    Ci.calIObserver,
-    Ci.calICompositeObserver
-]);
-
-// calIObserver:
-agendaListbox.calendarObserver.onStartBatch = function() {
-};
-
-agendaListbox.calendarObserver.onEndBatch = function() {
-};
-
-agendaListbox.calendarObserver.onLoad = function() {
-    this.agendaListbox.refreshCalendarQuery();
-};
-
-agendaListbox.calendarObserver.onAddItem = function(item) {
-    if (!cal.item.isEvent(item)) {
-        return;
-    }
-    // get all sub items if it is a recurring item
-    let occs = this.getOccurrencesBetween(item);
-    occs.forEach(this.agendaListbox.addItem, this.agendaListbox);
-    setCurrentEvent();
-};
-
-agendaListbox.calendarObserver.getOccurrencesBetween = function(aItem) {
-    let occs = [];
-    let start = this.agendaListbox.getStart();
-    let end = this.agendaListbox.getEnd();
-    if (start && end) {
-        occs = aItem.getOccurrencesBetween(start, end, {});
-    }
-    return occs;
-};
-
-agendaListbox.calendarObserver.onDeleteItem = function(item, rebuildFlag) {
-    this.onLocalDeleteItem(item, true);
-};
-
-agendaListbox.calendarObserver.onLocalDeleteItem = function(item, moveSelection) {
-    if (!cal.item.isEvent(item)) {
-        return false;
-    }
-    let selectedItemHashId = -1;
-    // get all sub items if it is a recurring item
-    let occs = this.getOccurrencesBetween(item);
-    for (let i = 0; i < occs.length; i++) {
-        let isSelected = this.agendaListbox.deleteItem(occs[i], moveSelection);
-        if (isSelected) {
-            selectedItemHashId = occs[i].hashId;
+        get occurrence() {
+            return this.mOccurrence;
         }
     }
-    return selectedItemHashId;
-};
+
+    MozXULElement.implementCustomInterface(
+        MozAgendaAlldayRichlistItem, [Ci.nsIDOMXULSelectControlItemElement]
+    );
+
+    customElements.define("agenda-allday-richlist-item", MozAgendaAlldayRichlistItem,
+        { "extends": "richlistitem" });
+
+    /**
+     * The MozAgendaRichlistItem widget displays the information about
+     * event: i.e. event start time, icon and name. It is shown under
+     * agenda-header-richlist-item dropdown as a richlistitem.
+     *
+     * @extends {MozElements.MozRichlistitem}
+     */
+    class MozAgendaRichlistItem extends MozElements.MozRichlistitem {
+        static get inheritedAttributes() {
+            return {
+                ".agenda-container-box": "selected,disabled,current",
+                ".agenda-event-start": "selected",
+                ".agenda-event-title": "selected"
+            };
+        }
 
-agendaListbox.calendarObserver.onModifyItem = function(newItem, oldItem) {
-    let selectedItemHashId = this.onLocalDeleteItem(oldItem, false);
-    if (!cal.item.isEvent(newItem)) {
-        return;
-    }
-    this.onAddItem(newItem);
-    if (selectedItemHashId != -1) {
-        let listItem = agendaListbox.getListItemByHashId(selectedItemHashId);
-        if (listItem) {
-            agendaListbox.agendaListboxControl.clearSelection();
-            agendaListbox.agendaListboxControl.ensureElementIsVisible(listItem);
-            agendaListbox.agendaListboxControl.selectedItem = listItem;
-        }
-    }
-    setCurrentEvent();
-};
+        connectedCallback() {
+            if (this.delayConnectedCallback() || this.hasChildNodes()) {
+                return;
+            }
+            this.setAttribute("is", "agenda-richlist-item");
+            this.addEventListener("click", (event) => {
+                if (event.detail == 1) {
+                    agendaListbox.onSelect(this);
+                } else if (event.button == 0) {
+                    // We only care about button 0 doubleclick events
+                    document.getElementById("agenda_edit_event_command").doCommand();
+                    event.stopPropagation();
+                    event.preventDefault();
+                }
+            }, true);
 
-agendaListbox.calendarObserver.onError = function(_cal, errno, msg) {};
+            this.addEventListener("mouseover", (event) => {
+                event.stopPropagation();
+                onMouseOverItem(event);
+            });
+
+            this.addEventListener("dragstart", (event) => {
+                invokeEventDragSession(this.mOccurrence.clone(), this);
+            });
+            // Prevent double clicks from opening a dialog to create a new event.
+            this.addEventListener("dblclick", (event) => {
+                event.stopPropagation();
+            });
 
-agendaListbox.calendarObserver.onPropertyChanged = function(aCalendar, aName, aValue, aOldValue) {
-    switch (aName) {
-        case "disabled":
-            this.agendaListbox.refreshCalendarQuery();
-            break;
-        case "color":
-            for (let node = agendaListbox.agendaListboxControl.firstChild;
-                 node;
-                 node = node.nextSibling) {
-                // Change color on all nodes that don't do so themselves, which
-                // is currently only he agenda-richlist-item
-                if (node.localName != "agenda-richlist-item") {
-                    continue;
+            this.appendChild(MozXULElement.parseXULToFragment(`
+                <hbox class="agenda-container-box" flex="1">
+                    <hbox>
+                        <vbox>
+                            <image class="agenda-calendar-image"></image>
+                            <spacer flex="1"></spacer>
+                        </vbox>
+                    </hbox>
+                    <vbox flex="1" class="agenda-description">
+                        <hbox align="start">
+                            <image class="agenda-multiDayEvent-image"></image>
+                            <label class="agenda-event-start" crop="end" flex="1"></label>
+                        </hbox>
+                        <label class="agenda-event-title" crop="end"></label>
+                    </vbox>
+                </hbox>
+            `));
+            this.mOccurrence = null;
+            this.initializeAttributeInheritance();
+        }
+        setOccurrence(aItem, aPeriod) {
+            this.mOccurrence = aItem;
+            this.setAttribute("status", aItem.status);
+            let dateFormatter = Cc["@mozilla.org/calendar/datetime-formatter;1"].getService(Ci.calIDateTimeFormatter);
+            let periodStartDate = aPeriod.start.clone();
+            periodStartDate.isDate = true;
+            let periodEndDate = aPeriod.end.clone();
+            periodEndDate.day--;
+            let start = this.mOccurrence[cal.dtz.startDateProp(this.mOccurrence)].getInTimezone(cal.dtz.defaultTimezone);
+            let end = this.mOccurrence[cal.dtz.endDateProp(this.mOccurrence)].getInTimezone(cal.dtz.defaultTimezone);
+            let startDate = start.clone();
+            startDate.isDate = true;
+            let endDate = end.clone();
+            endDate.isDate = true;
+            let endAtMidnight = (end.hour == 0 && end.minute == 0);
+            if (endAtMidnight) {
+                endDate.day--;
+            }
+            // Show items's date for long periods but also for "Upcoming"
+            // period with one day duration.
+            let longFormat = aPeriod.multiday || aPeriod.duration > 1;
+            let duration = "";
+            let iconType = "";
+            if (startDate.compare(endDate) == 0) {
+                // event that starts and ends in the same day, midnight included
+                duration = longFormat ? dateFormatter.formatDateTime(start)
+                    : dateFormatter.formatTime(start);
+            } else if (startDate.compare(periodStartDate) >= 0 &&
+                startDate.compare(periodEndDate) <= 0) {
+                // event spanning multiple days, start date within period
+                iconType = "start";
+                duration = longFormat ? dateFormatter.formatDateTime(start)
+                    : dateFormatter.formatTime(start);
+            } else if (endDate.compare(periodStartDate) >= 0 &&
+                endDate.compare(periodEndDate) <= 0) {
+                // event spanning multiple days, end date within period
+                iconType = "end";
+                if (endAtMidnight) {
+                    duration = dateFormatter.formatDate(endDate) + " ";
+                    duration = longFormat ? duration + cal.l10n.getDateFmtString("midnight")
+                        : cal.l10n.getDateFmtString("midnight");
+                } else {
+                    duration = longFormat ? dateFormatter.formatDateTime(end)
+                        : dateFormatter.formatTime(end);
                 }
-                node.refreshColor();
+            } else {
+                iconType = "continue";
             }
-            break;
-    }
-};
-
-agendaListbox.calendarObserver.onPropertyDeleting = function(aCalendar, aName) {
-    this.onPropertyChanged(aCalendar, aName, null, null);
-};
+            let multiDayImage = this.querySelector(".agenda-multiDayEvent-image");
+            multiDayImage.setAttribute("type", iconType);
+            let durationbox = this.querySelector(".agenda-event-start");
+            durationbox.textContent = duration;
 
 
-agendaListbox.calendarObserver.onCalendarRemoved = function(aCalendar) {
-    if (!aCalendar.getProperty("disabled")) {
-        this.agendaListbox.deleteItemsFromCalendar(aCalendar);
-    }
-};
-
-agendaListbox.calendarObserver.onCalendarAdded = function(aCalendar) {
-    if (!aCalendar.getProperty("disabled")) {
-        this.agendaListbox.refreshCalendarQuery(null, null, aCalendar);
-    }
-};
-
-agendaListbox.calendarObserver.onDefaultCalendarChanged = function(aCalendar) {
-};
+            // show items with time only (today & tomorrow) as one line.
+            if (longFormat) {
+                let titlebox = this.querySelector(".agenda-event-title");
+                titlebox.textContent = aItem.title;
+            } else {
+                durationbox.textContent += " " + aItem.title;
+            }
+            this.refreshColor();
+        }
 
-/**
- * Updates the "Upcoming" section of today pane when preference soondays changes
- **/
-agendaListbox.updateSoonSection = function() {
-    this.soon.duration = this.soonDays;
-    this.soon.open = true;
-    let soonHeader = document.getElementById("nextweek-header");
-    if (soonHeader) {
-        soonHeader.setItem(this.soon, true);
-        agendaListbox.refreshPeriodDates(cal.dtz.now());
-    }
-};
+        refreshColor() {
+            let calcolor = (this.mOccurrence &&
+                this.mOccurrence.calendar.getProperty("color")) ||
+                "#a8c2e1";
 
-/**
- * Updates the event considered "current". This goes through all "today" items
- * and sets the "current" attribute on all list items that are currently
- * occurring.
- *
- * @see scheduleNextCurrentEventUpdate
- */
-function setCurrentEvent() {
-    if (!agendaListbox.showsToday() || !agendaListbox.today.open) {
-        return;
+            let imagebox = this.querySelector(".agenda-calendar-image");
+            imagebox.setAttribute("style", "background-color: " + calcolor + ";");
+        }
+
+        get occurrence() {
+            return this.mOccurrence;
+        }
     }
 
-    let msScheduleTime = -1;
-    let complistItem = agendaListbox.tomorrow.listItem.previousSibling;
-    let removelist = [];
-    let anow = cal.dtz.now();
-    let msuntillend = 0;
-    let msuntillstart = 0;
-    let leaveloop;
-    do {
-        leaveloop = !agendaListbox.isEventListItem(complistItem);
-        if (!leaveloop) {
-            msuntillstart = complistItem.occurrence.startDate
-                                        .getInTimezone(agendaListbox.kDefaultTimezone)
-                                        .subtractDate(anow).inSeconds;
-            if (msuntillstart <= 0) {
-                msuntillend = complistItem.occurrence.endDate
-                                          .getInTimezone(agendaListbox.kDefaultTimezone)
-                                          .subtractDate(anow).inSeconds;
-                if (msuntillend > 0) {
-                    complistItem.setAttribute("current", "true");
-                    if (msuntillend < msScheduleTime || msScheduleTime == -1) {
-                        msScheduleTime = msuntillend;
-                    }
-                } else {
-                    removelist.push(complistItem);
-                }
-            } else {
-                complistItem.removeAttribute("current");
-            }
-            if (msScheduleTime == -1 || msuntillstart < msScheduleTime) {
-                if (msuntillstart > 0) {
-                    msScheduleTime = msuntillstart;
-                }
-            }
-        }
-        if (!leaveloop) {
-            complistItem = complistItem.previousSibling;
-        }
-    } while (!leaveloop);
 
-    if (msScheduleTime > -1) {
-        scheduleNextCurrentEventUpdate(setCurrentEvent, msScheduleTime * 1000);
-    }
-
-    if (removelist) {
-        if (removelist.length > 0) {
-            for (let i = 0; i < removelist.length; i++) {
-                removelist[i].remove();
-            }
-        }
-    }
-}
-
-var gEventTimer;
+    MozXULElement.implementCustomInterface(MozAgendaRichlistItem, [Ci.nsIDOMXULSelectControlItemElement]);
 
-/**
- * Creates a timer that will fire after the next event is current.
- *  Pass in a function as aRefreshCallback that should be called at that time.
- *
- * @param aRefreshCallback      The function to call when the next event is
- *                                current.
- * @param aMsUntil              The number of milliseconds until the next event
- *                                is current.
- */
-function scheduleNextCurrentEventUpdate(aRefreshCallback, aMsUntil) {
-    // Is an nsITimer/callback extreme overkill here? Yes, but it's necessary to
-    // workaround bug 291386.  If we don't, we stand a decent chance of getting
-    // stuck in an infinite loop.
-    let udCallback = {
-        notify: function(timer) {
-            aRefreshCallback();
-        }
-    };
-
-    if (gEventTimer) {
-        gEventTimer.cancel();
-    } else {
-        // Observer for wake after sleep/hibernate/standby to create new timers and refresh UI
-        let wakeObserver = {
-            observe: function(aSubject, aTopic, aData) {
-                if (aTopic == "wake_notification") {
-                    aRefreshCallback();
-                }
-            }
-        };
-        // Add observer
-        Services.obs.addObserver(wakeObserver, "wake_notification");
-
-        // Remove observer on unload
-        window.addEventListener("unload", () => {
-            Services.obs.removeObserver(wakeObserver, "wake_notification");
-        });
-
-        gEventTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
-    }
-    gEventTimer.initWithCallback(udCallback, aMsUntil, gEventTimer.TYPE_ONE_SHOT);
+    customElements.define("agenda-richlist-item", MozAgendaRichlistItem, { "extends": "richlistitem" });
 }
-
-/**
- * Gets a right value for calendar.agendaListbox.soondays preference, avoid
- * erroneous values edited in the lightning.js preference file
- **/
-function getSoondaysPreference() {
-    let prefName = "calendar.agendaListbox.soondays";
-    let soonpref = Services.prefs.getIntPref(prefName, 5);
-
-    if (soonpref > 0 && soonpref <= 28) {
-        if (soonpref % 7 != 0) {
-            let intSoonpref = Math.floor(soonpref / 7) * 7;
-            soonpref = (intSoonpref == 0 ? soonpref : intSoonpref);
-            Services.prefs.setIntPref(prefName, soonpref);
-        }
-    } else {
-        soonpref = soonpref > 28 ? 28 : 1;
-        Services.prefs.setIntPref(prefName, soonpref);
-    }
-    return soonpref;
-}
deleted file mode 100644
--- a/calendar/base/content/agenda-listbox.xml
+++ /dev/null
@@ -1,292 +0,0 @@
-<?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/. -->
-
-<!-- import-globals-from ../../resources/content/mouseoverPreviews.js -->
-<!-- import-globals-from agenda-listbox.js -->
-<!-- import-globals-from calendar-dnd-listener.js -->
-
-<bindings id="agenda-list-bindings"
-          xmlns="http://www.mozilla.org/xbl"
-          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-          xmlns:xbl="http://www.mozilla.org/xbl">
-
-  <binding id="agenda-base-richlist-item"
-           extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
-    <implementation>
-      <field name="mOccurrence">null</field>;
-      <property name="occurrence" readonly="true">
-        <getter><![CDATA[
-            return this.mOccurrence;
-        ]]></getter>
-      </property>
-    </implementation>
-
-    <handlers>
-      <handler event="click" phase="capturing"><![CDATA[
-          if (event.detail == 1) {
-              agendaListbox.onSelect(this);
-          } else if (event.button == 0) {
-              // We only care about button 0 doubleclick events
-              document.getElementById("agenda_edit_event_command").doCommand();
-              event.stopPropagation();
-              event.preventDefault();
-          }
-      ]]></handler>
-      <handler event="mouseover"><![CDATA[
-          event.stopPropagation();
-          onMouseOverItem(event);
-      ]]></handler>
-    </handlers>
-  </binding>
-
-  <binding id="agenda-checkbox-richlist-item"
-           extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
-    <content>
-      <xul:checkbox class="agenda-checkbox treenode-checkbox" anonid="agenda-checkbox-widget"
-                                                   flex="1"
-                                                   xbl:inherits="selected,label,hidden,disabled"/>
-    </content>
-    <implementation>
-      <field name="kCheckbox">null</field>;
-      <constructor><![CDATA[
-          this.kCheckbox = document.getAnonymousElementByAttribute(this, "anonid", "agenda-checkbox-widget");
-
-          this.dispatchEvent(new CustomEvent("bindingattached", { bubbles: false }));
-      ]]></constructor>
-
-      <method name="getItem">
-        <body><![CDATA[
-            return this.mItem;
-        ]]></body>
-      </method>
-
-      <method name="setItem">
-        <parameter name="synthetic"/>
-        <parameter name="showsToday"/>
-        <body><![CDATA[
-            this.mItem = synthetic;
-            let duration = synthetic.duration;
-            if (showsToday) {
-                this.kCheckbox.label = this.getAttribute("title");
-                if (this.id == "nextweek-header") {
-                    if (duration > 7) {
-                        this.kCheckbox.label += " (" + unitPluralForm(duration / 7, "weeks") + ")";
-                    } else {
-                        this.kCheckbox.label += " (" + unitPluralForm(duration, "days") + ")";
-                    }
-                }
-            } else if (synthetic.duration == 1) {
-                this.kCheckbox.label = cal.getDateFormatter().formatDate(synthetic.start);
-            } else {
-                this.kCheckbox.label = cal.getDateFormatter().formatInterval(synthetic.start, synthetic.end);
-            }
-        ]]></body>
-      </method>
-
-      <method name="getCheckbox">
-        <body><![CDATA[
-            return this.kCheckbox;
-        ]]></body>
-      </method>
-    </implementation>
-  </binding>
-
-  <binding id="agenda-allday-richlist-item"
-           extends="chrome://calendar/content/agenda-listbox.xml#agenda-base-richlist-item">
-    <content tooltip="itemTooltip">
-      <xul:hbox anonid="agenda-container-box"
-                class="agenda-allday-container-box"
-                xbl:inherits="selected,disabled"
-                flex="1">
-        <xul:vbox pack="center" flex="1">
-          <xul:label anonid="agenda-allDayEvent-date" class="agenda-event-start"
-                     crop="end" xbl:inherits="selected" hidden="true"/>
-          <xul:hbox flex="1" align="start">
-            <xul:image anonid="agenda-multiDayEvent-image" class="agenda-multiDayEvent-image"/>
-            <xul:calendar-month-day-box-item anonid="allday-item"
-                                             flex="1"
-                                             flat="true"/>
-          </xul:hbox>
-        </xul:vbox>
-      </xul:hbox>
-    </content>
-    <implementation>
-      <field name="mAllDayItem">null</field>;
-
-      <method name="setOccurrence">
-        <parameter name="aOccurrence"/>
-        <parameter name="aPeriod"/>
-        <body><![CDATA[
-            this.mOccurrence = aOccurrence;
-            this.mAllDayItem = document.getAnonymousElementByAttribute(this, "anonid", "allday-item");
-            this.mAllDayItem.occurrence = aOccurrence;
-            let dateFormatter = cal.getDateFormatter();
-            let periodStartDate = aPeriod.start.clone();
-            periodStartDate.isDate = true;
-            let periodEndDate = aPeriod.end;
-            let startDate = this.mOccurrence[cal.dtz.startDateProp(this.mOccurrence)]
-                                .getInTimezone(cal.dtz.defaultTimezone);
-            let endDate = this.mOccurrence[cal.dtz.endDateProp(this.mOccurrence)]
-                              .getInTimezone(cal.dtz.defaultTimezone);
-            let endPreviousDay = endDate.clone();
-            endPreviousDay.day--;
-            // Show items's date for long periods but also for "Upcoming"
-            // period with one day duration.
-            let showDate = aPeriod.multiday || aPeriod.duration > 1;
-
-            let date = "";
-            let iconType = "";
-            let allDayDateLabel = document.getAnonymousElementByAttribute(this, "anonid", "agenda-allDayEvent-date");
-            setBooleanAttribute(allDayDateLabel, "hidden", !showDate);
-            if (startDate.compare(endPreviousDay) == 0) {
-                // All day event one day duration.
-                date = dateFormatter.formatDate(startDate);
-            } else if (startDate.compare(periodStartDate) >= 0 &&
-                 startDate.compare(periodEndDate) <= 0) {
-                // All day event spanning multiple days.
-                iconType = "start";
-                date = dateFormatter.formatDate(startDate);
-            } else if (endDate.compare(periodStartDate) >= 0 &&
-                       endDate.compare(periodEndDate) <= 0) {
-                iconType = "end";
-                date = dateFormatter.formatDate(endPreviousDay);
-            } else {
-                iconType = "continue";
-                hideElement(allDayDateLabel);
-            }
-
-            let multiDayImage = document.getAnonymousElementByAttribute(this, "anonid", "agenda-multiDayEvent-image");
-            multiDayImage.setAttribute("type", iconType);
-            // class wrap causes allday items to wrap its text in today-pane
-            let addWrap = document.getAnonymousElementByAttribute(this.mAllDayItem, "anonid", "eventbox");
-            addWrap.classList.add("wrap");
-            addWrap = document.getAnonymousElementByAttribute(this.mAllDayItem, "anonid", "event-detail-box");
-            addWrap.classList.add("wrap");
-            allDayDateLabel.value = date;
-        ]]></body>
-      </method>
-    </implementation>
-
-    <handlers>
-      <handler event="dragstart" phase="capturing"><![CDATA[
-          invokeEventDragSession(this.mAllDayItem.occurrence.clone(), this);
-          event.stopPropagation();
-          event.preventDefault();
-      ]]></handler>
-    </handlers>
-  </binding>
-
-  <binding id="agenda-richlist-item"
-           extends="chrome://calendar/content/agenda-listbox.xml#agenda-base-richlist-item">
-    <content tooltip="itemTooltip">
-      <xul:hbox anonid="agenda-container-box" class="agenda-container-box" xbl:inherits="selected,disabled,current" flex="1">
-        <xul:hbox>
-          <xul:vbox>
-            <xul:image anonid="agenda-calendar-image" class="agenda-calendar-image"/>
-            <xul:spacer flex="1"/>
-          </xul:vbox>
-        </xul:hbox>
-        <xul:vbox anonid="agenda-description" flex="1">
-          <xul:hbox align="start">
-            <xul:image anonid="agenda-multiDayEvent-image" class="agenda-multiDayEvent-image"/>
-            <xul:label anonid="agenda-event-start" class="agenda-event-start" crop="end" flex="1" xbl:inherits="selected"/>
-          </xul:hbox>
-          <xul:label anonid="agenda-event-title" class="agenda-event-title" crop="end" xbl:inherits="selected"/>
-        </xul:vbox>
-      </xul:hbox>
-    </content>
-
-    <implementation>
-      <method name="setOccurrence">
-        <parameter name="aItem"/>
-        <parameter name="aPeriod"/>
-        <body><![CDATA[
-            this.mOccurrence = aItem;
-            this.setAttribute("status", aItem.status);
-            let dateFormatter = Cc["@mozilla.org/calendar/datetime-formatter;1"]
-                                  .getService(Ci.calIDateTimeFormatter);
-
-            let periodStartDate = aPeriod.start.clone();
-            periodStartDate.isDate = true;
-            let periodEndDate = aPeriod.end.clone();
-            periodEndDate.day--;
-            let start = this.mOccurrence[cal.dtz.startDateProp(this.mOccurrence)]
-                            .getInTimezone(cal.dtz.defaultTimezone);
-            let end = this.mOccurrence[cal.dtz.endDateProp(this.mOccurrence)]
-                          .getInTimezone(cal.dtz.defaultTimezone);
-            let startDate = start.clone();
-            startDate.isDate = true;
-            let endDate = end.clone();
-            endDate.isDate = true;
-            let endAtMidnight = (end.hour == 0 && end.minute == 0);
-            if (endAtMidnight) {
-                endDate.day--;
-            }
-            // Show items's date for long periods but also for "Upcoming"
-            // period with one day duration.
-            let longFormat = aPeriod.multiday || aPeriod.duration > 1;
-
-            let duration = "";
-            let iconType = "";
-            if (startDate.compare(endDate) == 0) {
-                // event that starts and ends in the same day, midnight included
-                duration = longFormat ? dateFormatter.formatDateTime(start)
-                                      : dateFormatter.formatTime(start);
-            } else if (startDate.compare(periodStartDate) >= 0 &&
-                       startDate.compare(periodEndDate) <= 0) {
-                // event spanning multiple days, start date within period
-                iconType = "start";
-                duration = longFormat ? dateFormatter.formatDateTime(start)
-                                      : dateFormatter.formatTime(start);
-            } else if (endDate.compare(periodStartDate) >= 0 &&
-                       endDate.compare(periodEndDate) <= 0) {
-                // event spanning multiple days, end date within period
-                iconType = "end";
-                if (endAtMidnight) {
-                    duration = dateFormatter.formatDate(endDate) + " ";
-                    duration = longFormat ? duration + cal.l10n.getDateFmtString("midnight")
-                                          : cal.l10n.getDateFmtString("midnight");
-                } else {
-                    duration = longFormat ? dateFormatter.formatDateTime(end)
-                                          : dateFormatter.formatTime(end);
-                }
-            } else {
-                iconType = "continue";
-            }
-            let multiDayImage = document.getAnonymousElementByAttribute(this, "anonid", "agenda-multiDayEvent-image");
-            multiDayImage.setAttribute("type", iconType);
-            let durationbox = document.getAnonymousElementByAttribute(this, "anonid", "agenda-event-start");
-            durationbox.textContent = duration;
-
-            // show items with time only (today & tomorrow) as one line.
-            if (longFormat) {
-                let titlebox = document.getAnonymousElementByAttribute(this, "anonid", "agenda-event-title");
-                titlebox.textContent = aItem.title;
-            } else {
-                durationbox.textContent += " " + aItem.title;
-            }
-            this.refreshColor();
-        ]]></body>
-      </method>
-
-      <method name="refreshColor">
-        <body><![CDATA[
-            let calcolor = (this.mOccurrence &&
-                             this.mOccurrence.calendar.getProperty("color")) ||
-                            "#a8c2e1";
-
-            let imagebox = document.getAnonymousElementByAttribute(this, "anonid", "agenda-calendar-image");
-            imagebox.setAttribute("style", "background-color: " + calcolor + ";");
-        ]]></body>
-      </method>
-    </implementation>
-
-    <handlers>
-      <handler event="dragstart"><![CDATA[
-          invokeEventDragSession(this.mOccurrence.clone(), this);
-      ]]></handler>
-    </handlers>
-  </binding>
-</bindings>
--- a/calendar/base/content/calendar-common-sets.js
+++ b/calendar/base/content/calendar-common-sets.js
@@ -1,16 +1,16 @@
 /* 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/. */
 
 /* import-globals-from ../../../../toolkit/content/globalOverlay.js */
 /* import-globals-from ../../lightning/content/messenger-overlay-sidebar.js */
 /* import-globals-from ../../resources/content/publish.js */
-/* import-globals-from agenda-listbox.js */
+/* import-globals-from agenda-listbox-utils.js */
 /* import-globals-from calendar-clipboard.js */
 /* import-globals-from calendar-management.js */
 /* import-globals-from calendar-task-tree.js */
 /* import-globals-from calendar-ui-utils.js */
 /* import-globals-from calendar-unifinder.js */
 /* import-globals-from calendar-views.js */
 /* import-globals-from import-export.js */
 
--- a/calendar/base/content/calendar-task-tree.xml
+++ b/calendar/base/content/calendar-task-tree.xml
@@ -1,14 +1,14 @@
 <?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/. -->
 
-<!-- import-globals-from agenda-listbox.js -->
+<!-- import-globals-from agenda-listbox-utils.js -->
 <!-- import-globals-from calendar-common-sets.js -->
 <!-- import-globals-from calendar-dnd-listener.js -->
 <!-- import-globals-from calendar-task-tree.js -->
 
 <!DOCTYPE dialog [
   <!ENTITY % dtd1 SYSTEM "chrome://calendar/locale/global.dtd" > %dtd1;
   <!ENTITY % dtd2 SYSTEM "chrome://calendar/locale/calendar.dtd" > %dtd2;
 ]>
--- a/calendar/base/content/today-pane.js
+++ b/calendar/base/content/today-pane.js
@@ -1,14 +1,14 @@
 /* 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/. */
 
 /* import-globals-from ../../lightning/content/messenger-overlay-sidebar.js */
-/* import-globals-from agenda-listbox.js */
+/* import-globals-from agenda-listbox-utils.js */
 /* import-globals-from calendar-chrome-startup.js */
 /* import-globals-from calendar-unifinder-todo.js */
 
 var { cal } = ChromeUtils.import("resource://calendar/modules/calUtils.jsm");
 
 /**
  * Namespace object to hold functions related to the today pane.
  */
--- a/calendar/base/content/today-pane.xul
+++ b/calendar/base/content/today-pane.xul
@@ -12,18 +12,19 @@
   <!ENTITY % dtd6 SYSTEM "chrome://calendar/locale/calendar-event-dialog.dtd"> %dtd6;
 ]>
 
 <?xml-stylesheet href="chrome://calendar/skin/today-pane.css" type="text/css"?>
 <?xml-stylesheet href="chrome://calendar/content/widgets/calendar-widget-bindings.css" type="text/css"?>
 
 <overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml">
 
+  <script type="application/javascript" src="chrome://calendar/content/agenda-listbox.js"/>
   <script type="application/javascript" src="chrome://calendar/content/today-pane.js"/>
-  <script type="application/javascript" src="chrome://calendar/content/agenda-listbox.js"/>
+  <script type="application/javascript" src="chrome://calendar/content/agenda-listbox-utils.js"/>
   <script type="application/javascript" src="chrome://calendar/content/calendar-management.js"/>
   <script type="application/javascript" src="chrome://calendar/content/calendar-dnd-listener.js"/>
   <script type="application/javascript" src="chrome://calendar/content/calendar-item-editing.js"/>
 
   <modevbox id="today-pane-panel"
             mode="mail,calendar,task" modewidths="200,200,200" modesplitterstates="open,open,open"
             refcontrol="calendar_toggle_todaypane_command"
             broadcaster="modeBroadcaster" persist="modewidths">
@@ -345,28 +346,31 @@
                   <menuitem id="agenda-context-menu-attendance-needsaction-all-menu"
                             label="&calendar.context.attendance.all.needsaction.label;"
                             name="agenda-context-attendance-needaction-all"
                             scope="all-occurrences"
                             value="NEEDS-ACTION"/>
                 </menupopup>
               </menu>
             </menupopup>
-            <agenda-checkbox-richlist-item id="today-header"
-                                           title="&calendar.today.button.label;"
-                                           checked="true"
-                                           persist="checked"/>
-            <agenda-checkbox-richlist-item id="tomorrow-header"
-                                           title="&calendar.tomorrow.button.label;"
-                                           checked="false"
-                                           persist="checked"/>
-            <agenda-checkbox-richlist-item id="nextweek-header"
-                                           title="&calendar.upcoming.button.label;"
-                                           checked="false"
-                                           persist="checked"/>
+            <richlistitem is="agenda-header-richlist-item" id="today-header"
+                          class="agenda-header-richlist-item"
+                          title="&calendar.today.button.label;"
+                          checked="true"
+                          persist="checked"/>
+            <richlistitem is="agenda-header-richlist-item" id="tomorrow-header"
+                          class="agenda-header-richlist-item"
+                          title="&calendar.tomorrow.button.label;"
+                          checked="false"
+                          persist="checked"/>
+            <richlistitem is="agenda-header-richlist-item" id="nextweek-header"
+                          class="agenda-header-richlist-item"
+                          title="&calendar.upcoming.button.label;"
+                          checked="false"
+                          persist="checked"/>
           </richlistbox>
         </vbox>
       </modevbox>
       <splitter id="today-pane-splitter" persist="hidden"/>
       <modevbox id="todo-tab-panel" flex="1" mode="mail,calendar"
                 collapsedinmodes="mail,task"
                 broadcaster="modeBroadcaster"
                 persist="height collapsedinmodes"
--- a/calendar/base/jar.mn
+++ b/calendar/base/jar.mn
@@ -1,18 +1,18 @@
 #filter substitution
 # 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/.
 
 calendar.jar:
 % resource calendar .
 % content calendar %content/calendar/
+    content/calendar/agenda-listbox-utils.js               (content/agenda-listbox-utils.js)
     content/calendar/agenda-listbox.js                     (content/agenda-listbox.js)
-    content/calendar/agenda-listbox.xml                    (content/agenda-listbox.xml)
     content/calendar/calendar-bindings.css                 (content/calendar-bindings.css)
     content/calendar/calendar-calendars-list.xul           (content/calendar-calendars-list.xul)
     content/calendar/calendar-chrome-startup.js            (content/calendar-chrome-startup.js)
     content/calendar/calendar-clipboard.js                 (content/calendar-clipboard.js)
     content/calendar/calendar-common-sets.xul              (content/calendar-common-sets.xul)
     content/calendar/calendar-common-sets.js               (content/calendar-common-sets.js)
     content/calendar/calendar-daypicker.xml                (content/calendar-daypicker.xml)
     content/calendar/calendar-event-gripbar.js             (content/calendar-event-gripbar.js)
--- a/calendar/base/themes/common/today-pane.css
+++ b/calendar/base/themes/common/today-pane.css
@@ -259,30 +259,28 @@
 }
 
 :root[lwt-tree] #agenda-listbox {
   border-top-color: var(--sidebar-border-color);
   background-color: var(--sidebar-background-color);
   color: var(--sidebar-text-color);
 }
 
-agenda-checkbox-richlist-item {
-  -moz-binding: url("chrome://calendar/content/agenda-listbox.xml#agenda-checkbox-richlist-item");
+agenda-header-richlist-item {
   -moz-user-focus: normal;
 }
 
 agenda-richlist-item {
-  -moz-binding: url("chrome://calendar/content/agenda-listbox.xml#agenda-richlist-item");
   -moz-user-focus: normal;
 }
 
 agenda-allday-richlist-item {
-  -moz-binding: url("chrome://calendar/content/agenda-listbox.xml#agenda-allday-richlist-item");
   -moz-user-focus: normal;
 }
+
 .wrap {
   overflow: visible;
 }
 
 .agenda-container-box {
   border-bottom: 1px dotted #C0C0C0;
   margin-inline-start: 4px;
   margin-inline-end: 4px;
--- a/calendar/test/mozmill/testTodayPane.js
+++ b/calendar/test/mozmill/testTodayPane.js
@@ -87,35 +87,35 @@ function testTodayPane() {
 
     // Verify today pane open.
     controller.assertNotDOMProperty(lookup(TODAY_PANE), "collapsed");
 
     // Verify today pane's date.
     controller.assertValue(eid("datevalue-label"), (new Date()).getDate());
 
     let expandArrow = `
-        anon({"anonid":"agenda-checkbox-widget"})/anon({"class":"checkbox-check"})
+        anon({"class":"agenda-checkbox treenode-checkbox"})/anon({"class":"checkbox-check"})
     `;
     // Tomorrow and soon are collapsed by default.
     controller.click(lookup(`${AGENDA_LISTBOX}/id("tomorrow-header")/${expandArrow}`));
     controller.click(lookup(`${AGENDA_LISTBOX}/id("nextweek-header")/${expandArrow}`));
     sleep();
 
     // Verify events shown in today pane.
     let now = new Date();
     now.setHours(startHour);
     now.setMinutes(0);
     let dtz = cal.dtz.defaultTimezone;
     let probeDate = cal.dtz.jsDateToDateTime(now, dtz);
     let dateFormatter = cal.getDateFormatter();
     let startTime = dateFormatter.formatTime(probeDate);
 
     let eventStart = `
-        anon({"anonid":"agenda-container-box"})/anon({"anonid":"agenda-description"})/[0]/
-        anon({"anonid":"agenda-event-start"})
+        anon({"class":"agenda-container-box"})/anon({"class":"agenda-description"})/[0]/
+        anon({"class":"agenda-event-start"})
     `;
     controller.assertText(lookup(
         `${AGENDA_LISTBOX}/[2]/${eventStart}`), startTime + " Today's Event"
     );
 
     let tomorrow = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1, 9, 0);
     probeDate = cal.dtz.jsDateToDateTime(tomorrow, dtz);
     startTime = dateFormatter.formatTime(probeDate);
@@ -127,18 +127,18 @@ function testTodayPane() {
     probeDate = cal.dtz.jsDateToDateTime(future, dtz);
     startTime = dateFormatter.formatDateTime(probeDate);
 
     // Future event's start time.
     controller.assertText(lookup(`${AGENDA_LISTBOX}/[6]/${eventStart}`), startTime);
 
     // Future event's title.
     controller.assertText(lookup(`
-        ${AGENDA_LISTBOX}/[6]/anon({"anonid":"agenda-container-box"})/
-        anon({"anonid":"agenda-description"})/anon({"anonid":"agenda-event-title"})
+        ${AGENDA_LISTBOX}/[6]/anon({"class":"agenda-container-box"})/
+        anon({"class":"agenda-description"})/anon({"class":"agenda-event-title"})
     `), "Future Event");
 
     // Delete events.
     controller.click(lookup(`${AGENDA_LISTBOX}/[2]`));
 
     controller.keypress(eid("agenda-listbox"), "VK_DELETE", {});
     controller.waitForElementNotPresent(lookup(`${AGENDA_LISTBOX}/[6]`));