Bug 1472883 - Fixes to make WebExt Lightning run better; r=philipp a=jorgk
authorGeoff Lankow <geoff@darktrojan.net>
Tue, 17 Jul 2018 23:30:23 +1200
changeset 31499 93917f786dd20f71742e2898ed27506aded0433a
parent 31498 8d96840851f322e476d94a6db4d6999a30cfef5d
child 31500 38233158f3e05f98f2bb8f13beab64ad0388613d
push id2279
push usermozilla@jorgk.com
push dateTue, 17 Jul 2018 19:46:38 +0000
treeherdercomm-beta@96ecd38678b5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersphilipp, jorgk
bugs1472883
Bug 1472883 - Fixes to make WebExt Lightning run better; r=philipp a=jorgk
calendar/base/content/agenda-listbox.js
calendar/base/content/agenda-listbox.xml
calendar/base/content/calendar-chrome-startup.js
calendar/base/content/calendar-common-sets.js
calendar/base/content/calendar-common-sets.xul
calendar/base/content/calendar-multiday-view.xml
calendar/base/content/calendar-task-tree.xml
calendar/base/content/calendar-unifinder-todo.js
calendar/base/content/calendar-unifinder.js
calendar/base/content/calendar-views.js
calendar/base/content/today-pane.js
calendar/base/content/today-pane.xul
calendar/base/content/widgets/calendar-list-tree.xml
calendar/base/content/widgets/calendar-widgets.xml
calendar/base/jar.mn
calendar/lightning/content/lightning-item-iframe.xul
calendar/lightning/content/messenger-overlay-sidebar.js
calendar/test/mozmill/shared-modules/test-calendar-utils.js
calendar/test/mozmill/testBasicFunctionality.js
mail/base/content/tabmail.xml
--- a/calendar/base/content/agenda-listbox.js
+++ b/calendar/base/content/agenda-listbox.js
@@ -3,47 +3,60 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/Preferences.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 ChromeUtils.import("resource://calendar/modules/calUtils.jsm");
 
-function Synthetic(aOpen, aDuration, aMultiday) {
-    this.open = aOpen;
+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 = function() {
+agendaListbox.init = async function() {
     this.agendaListboxControl = document.getElementById("agenda-listbox");
     this.agendaListboxControl.removeAttribute("suppressonselect");
-    let showTodayHeader = (document.getElementById("today-header-hidden").getAttribute("checked") == "true");
-    let showTomorrowHeader = (document.getElementById("tomorrow-header-hidden").getAttribute("checked") == "true");
-    let showSoonHeader = (document.getElementById("nextweek-header-hidden").getAttribute("checked") == "true");
+    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);
+    }
+
     let prefObserver = {
         observe: function(aSubject, aTopic, aPrefName) {
             switch (aPrefName) {
                 case "calendar.agendaListbox.soondays":
                     agendaListbox.soonDays = getSoondaysPreference();
                     agendaListbox.updateSoonSection();
                     break;
             }
@@ -77,55 +90,56 @@ agendaListbox.uninit = function() {
 };
 
 /**
  * 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,
- *                        without the "-hidden" suffix.
+ * @param aItemId       The id of an <agenda-checkbox-richlist-item> to add to.
  */
 agendaListbox.addPeriodListItem = function(aPeriod, aItemId) {
-    aPeriod.listItem = document.getElementById(aItemId + "-hidden").cloneNode(true);
-    agendaListbox.agendaListboxControl.appendChild(aPeriod.listItem);
-    aPeriod.listItem.id = aItemId;
+    aPeriod.listItem = document.getElementById(aItemId);
+    aPeriod.listItem.hidden = false;
     aPeriod.listItem.getCheckbox().checked = aPeriod.open;
-    aPeriod.listItem.getCheckbox().addEventListener("CheckboxStateChange", this.onCheckboxChange, true);
 };
 
 /**
  * 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.remove();
+            aPeriod.listItem.hidden = true;
             aPeriod.listItem = null;
         }
     }
 };
 
 /**
  * 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;
+    }
+
     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 + "-hidden").setAttribute("checked",
+    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);
@@ -677,18 +691,19 @@ agendaListbox.refreshCalendarQuery = fun
     };
 
     refreshJob.execute();
 };
 
 /**
  * Sets up the calendar for the agenda listbox.
  */
-agendaListbox.setupCalendar = function() {
-    this.init();
+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);
--- a/calendar/base/content/agenda-listbox.xml
+++ b/calendar/base/content/agenda-listbox.xml
@@ -42,16 +42,18 @@
     <content>
       <xul:treenode-checkbox class="agenda-checkbox" anonid="agenda-checkbox-widget"
                                                    flex="1"
                                                    xbl:inherits="selected,label,hidden,disabled"/>
     </content>
     <implementation>
       <field name="kCheckbox">null</field>;
       <constructor><![CDATA[
+          this.dispatchEvent(new CustomEvent("bindingattached", { bubbles: false }));
+
           ChromeUtils.import("resource://calendar/modules/calUtils.jsm");
           this.kCheckbox = document.getAnonymousElementByAttribute(this, "anonid", "agenda-checkbox-widget");
       ]]></constructor>
 
       <method name="getItem">
         <body><![CDATA[
             return this.mItem;
         ]]></body>
--- a/calendar/base/content/calendar-chrome-startup.js
+++ b/calendar/base/content/calendar-chrome-startup.js
@@ -14,19 +14,16 @@ ChromeUtils.import("resource://gre/modul
  */
 function commonInitCalendar() {
     // Move around toolbarbuttons and whatever is needed in the UI.
     migrateCalendarUI();
 
     // Load the Calendar Manager
     loadCalendarManager();
 
-    // Restore the last shown calendar view
-    switchCalendarView(getLastCalendarView(), false);
-
     // set up the unifinder
     prepareCalendarToDoUnifinder();
 
     // Make sure we update ourselves if the program stays open over midnight
     scheduleMidnightUpdate(refreshUIBits);
 
     // Set up the command controller from calendar-common-sets.js
     injectCalendarCommandController();
--- a/calendar/base/content/calendar-common-sets.js
+++ b/calendar/base/content/calendar-common-sets.js
@@ -896,20 +896,16 @@ function deleteSelectedItems() {
     if (calendarController.todo_tasktree_focused) {
         deleteToDoCommand();
     } else if (calendarController.isInMode("calendar")) {
         deleteSelectedEvents();
     }
 }
 
 function calendarUpdateNewItemsCommand() {
-    // keep current current status
-    let oldEventValue = CalendarNewEventsCommandEnabled;
-    let oldTaskValue = CalendarNewTasksCommandEnabled;
-
     // define command set to update
     let eventCommands = [
         "calendar_new_event_command",
         "calendar_new_event_context_command"
     ];
     let taskCommands = [
         "calendar_new_todo_command",
         "calendar_new_todo_context_command",
@@ -922,23 +918,18 @@ function calendarUpdateNewItemsCommand()
     let calendars = cal.getCalendarManager().getCalendars({}).filter(cal.acl.isCalendarWritable).filter(cal.acl.userCanAddItemsToCalendar);
     if (calendars.some(cal.item.isEventCalendar)) {
         CalendarNewEventsCommandEnabled = true;
     }
     if (calendars.some(cal.item.isTaskCalendar)) {
         CalendarNewTasksCommandEnabled = true;
     }
 
-    // update command status if required
-    if (CalendarNewEventsCommandEnabled != oldEventValue) {
-        eventCommands.forEach(goUpdateCommand);
-    }
-    if (CalendarNewTasksCommandEnabled != oldTaskValue) {
-        taskCommands.forEach(goUpdateCommand);
-    }
+    eventCommands.forEach(goUpdateCommand);
+    taskCommands.forEach(goUpdateCommand);
 }
 
 function calendarUpdateDeleteCommand(selectedItems) {
     let oldValue = CalendarDeleteCommandEnabled;
     CalendarDeleteCommandEnabled = (selectedItems.length > 0);
 
     /* we must disable "delete" when at least one item cannot be deleted */
     for (let item of selectedItems) {
--- a/calendar/base/content/calendar-common-sets.xul
+++ b/calendar/base/content/calendar-common-sets.xul
@@ -6,19 +6,16 @@
 <!DOCTYPE overlay [
   <!ENTITY % calendarDTD SYSTEM "chrome://calendar/locale/calendar.dtd" > %calendarDTD;
   <!ENTITY % eventDialogDTD SYSTEM "chrome://calendar/locale/calendar-event-dialog.dtd"> %eventDialogDTD;
   <!ENTITY % menuOverlayDTD SYSTEM "chrome://calendar/locale/menuOverlay.dtd" > %menuOverlayDTD;
 ]>
 
 <overlay id="calendar-common-sets-overlay"
          xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
-  <stringbundleset id="calendar_stringbundles">
-    <stringbundle id="bundle_branding" src="chrome://branding/locale/brand.properties"/>
-  </stringbundleset>
   <script type="application/javascript" src="chrome://calendar/content/calendar-common-sets.js"/>
 
   <broadcasterset id="calendar_broadcasters">
     <broadcaster id="modeBroadcaster" mode="calendar"/>
     <broadcaster id="calendarviewBroadcaster"/>
     <broadcaster id="unifinder-todo-filter-broadcaster"
                  persist="value"
                  value="throughcurrent"/>
--- a/calendar/base/content/calendar-multiday-view.xml
+++ b/calendar/base/content/calendar-multiday-view.xml
@@ -25,19 +25,20 @@
 
     <implementation>
       <field name="mPixPerMin">0.6</field>
       <field name="mStartMin">0</field>
       <field name="mEndMin">24 * 60</field>
       <field name="mDayStartHour">0</field>
       <field name="mDayEndHour">24</field>
 
-      <constructor>
+      <constructor><![CDATA[
           this.relayout();
-      </constructor>
+          this.dispatchEvent(new CustomEvent("bindingattached", { bubbles: false }));
+      ]]></constructor>
 
       <method name="setDayStartEndHours">
         <parameter name="aDayStartHour"/>
         <parameter name="aDayEndHour"/>
         <body><![CDATA[
             if (aDayStartHour * 60 < this.mStartMin ||
                 aDayStartHour > aDayEndHour ||
                 aDayEndHour * 60 > this.mEndMin) {
@@ -2641,17 +2642,21 @@
             } else if (aUpdateTheTimer === false) {
                 // Set the time for every positioning after the first
                 document.getElementById("day-view").mTimeIndicatorMinutes = nowMinutes;
                 document.getElementById("week-view").mTimeIndicatorMinutes = nowMinutes;
             }
             // Update the position of the indicator.
             let position = Math.round(this.mPixPerMin * this.mTimeIndicatorMinutes) - 1;
             let posAttr = (this.orient == "vertical" ? "top: " : "left: ");
-            this.timeBarTimeIndicator.setAttribute("style", posAttr + position + "px;");
+
+            if (this.timeBarTimeIndicator) {
+                this.timeBarTimeIndicator.setAttribute("style", posAttr + position + "px;");
+            }
+
             let todayColumn = this.findColumnForDate(this.today());
             if (todayColumn) {
                 todayColumn.column.timeIndicatorBox.setAttribute("style", "margin-" + posAttr + position + "px;");
             }
         ]]></body>
       </method>
 
       <method name="handlePreference">
@@ -3177,20 +3182,25 @@
             return this.getAttribute("orient") || "vertical";
         ]]></getter>
         <setter><![CDATA[
             this.setAttribute("orient", val);
             return val;
         ]]></setter>
       </property>
 
+      <property name="timebar" readonly="true">
+        <getter><![CDATA[
+            return document.getAnonymousElementByAttribute(this, "anonid", "timebar");
+        ]]></getter>
+      </property>
+
       <property name="timeBarTimeIndicator" readonly="true">
         <getter><![CDATA[
-            let timebar = document.getAnonymousElementByAttribute(this, "anonid", "timebar");
-            return document.getAnonymousElementByAttribute(timebar, "anonid", "timeIndicatorBoxTimeBar");
+            return document.getAnonymousElementByAttribute(this.timebar, "anonid", "timeIndicatorBoxTimeBar");
         ]]></getter>
       </property>
 
       <method name="setAttribute">
         <parameter name="aAttr"/>
         <parameter name="aVal"/>
         <body><![CDATA[
             let needsreorient = false;
@@ -3777,29 +3787,34 @@
             this.mFirstVisibleMinute = aMinute;
         ]]></body>
       </method>
 
       <method name="setDayStartEndMinutes">
         <parameter name="aDayStartMin"/>
         <parameter name="aDayEndMin"/>
         <body><![CDATA[
+            if (!("setDayStartEndHours" in this.timebar)) {
+                this.timebar.addEventListener(
+                  "bindingattached", () => this.setDayStartEndMinutes(aDayStartMin, aDayEndMin), { once: true }
+                );
+                return;
+            }
             if (aDayStartMin < this.mStartMin || aDayStartMin > aDayEndMin ||
                 aDayEndMin > this.mEndMin) {
                 throw Components.results.NS_ERROR_INVALID_ARG;
             }
             if (this.mDayStartMin != aDayStartMin ||
                 this.mDayEndMin != aDayEndMin) {
                 this.mDayStartMin = aDayStartMin;
                 this.mDayEndMin = aDayEndMin;
 
                 // Also update on the time-bar
-                document.getAnonymousElementByAttribute(this, "anonid", "timebar")
-                        .setDayStartEndHours(this.mDayStartMin / 60,
-                                             this.mDayEndMin / 60);
+                this.timebar.setDayStartEndHours(this.mDayStartMin / 60,
+                                                 this.mDayEndMin / 60);
             }
 
         ]]></body>
       </method>
 
       <method name="setVisibleMinutes">
         <parameter name="aVisibleMinutes"/>
         <body><![CDATA[
--- a/calendar/base/content/calendar-task-tree.xml
+++ b/calendar/base/content/calendar-task-tree.xml
@@ -162,16 +162,18 @@
               }
               if (sorted && sorted.length > 0) {
                   if (sorted == content) {
                       this.mTreeView.sortDirection = sortDirection;
                       this.mTreeView.selectedColumn = treecols[i];
                   }
               }
           }
+
+          this.dispatchEvent(new CustomEvent("bindingattached", { bubbles: false }));
       ]]></constructor>
       <destructor><![CDATA[
           ChromeUtils.import("resource://gre/modules/Services.jsm");
 
           // remove composite calendar observer
           let composite = cal.view.getCompositeCalendar(window);
           composite.removeObserver(this.mTaskTreeObserver);
 
--- a/calendar/base/content/calendar-unifinder-todo.js
+++ b/calendar/base/content/calendar-unifinder-todo.js
@@ -14,20 +14,24 @@ function prepareCalendarToDoUnifinder() 
     updateCalendarToDoUnifinder();
 }
 
 /**
  * Updates the applied filter and show completed view of the unifinder todo.
  *
  * @param aFilter        The filter name to set.
  */
-function updateCalendarToDoUnifinder(aFilter) {
+async function updateCalendarToDoUnifinder(aFilter) {
+    let tree = document.getElementById("unifinder-todo-tree");
+    if (!("updateFilter" in tree)) {
+        await new Promise(resolve => tree.addEventListener("bindingattached", resolve, { once: true }));
+    }
+
     // Set up hiding completed tasks for the unifinder-todo tree
     let showCompleted = document.getElementById("show-completed-checkbox").checked;
-    let tree = document.getElementById("unifinder-todo-tree");
     let oldFilter = document.getElementById("unifinder-todo-filter-broadcaster").getAttribute("value");
     let filter = oldFilter;
 
     // This function acts as an event listener, in which case we get the Event as the
     // parameter instead of a filter.
     if (aFilter && !(aFilter instanceof Event)) {
         filter = aFilter;
     }
--- a/calendar/base/content/calendar-unifinder.js
+++ b/calendar/base/content/calendar-unifinder.js
@@ -231,16 +231,18 @@ function prepareCalendarUnifinder() {
             }
         }
         // Display something upon first load. onLoad doesn't work properly for
         // observers
         if (!isUnifinderHidden()) {
             gUnifinderNeedsRefresh = false;
             refreshEventTree();
         }
+
+        unifinderTreeView.ready = true;
     }
 }
 
 /**
  * Called when the window is unloaded to clean up any observers and listeners
  * added.
  */
 function finishCalendarUnifinder() {
@@ -393,16 +395,17 @@ var unifinderTreeView = {
     // cludgy if (this.tree) { this.tree.rowCountChanged(...); } constructs.
     tree: {
         rowCountChanged: function() {},
         beginUpdateBatch: function() {},
         endUpdateBatch: function() {},
         invalidate: function() {}
     },
 
+    ready: false,
     treeElement: null,
     doingSelection: false,
     mFilter: null,
     mSelectedColumn: null,
     sortDirection: null,
 
     /**
      * Returns the currently selected column in the unifinder (used for sorting).
@@ -594,17 +597,18 @@ var unifinderTreeView = {
     },
 
     /**
      * Change the selection in the unifinder.
      *
      * @param aItemArray        An array of items to select.
      */
     setSelectedItems: function(aItemArray) {
-        if (this.doingSelection || !this.tree || !this.tree.view) {
+        if (this.doingSelection || !this.tree || !this.tree.view ||
+            !("getSelectedItems" in currentView())) {
             return;
         }
 
         this.doingSelection = true;
 
         // If no items were passed, get the selected items from the view.
         aItemArray = aItemArray || currentView().getSelectedItems({});
 
@@ -821,16 +825,20 @@ var unifinderTreeView = {
     outParameter: {} // used to obtain dates during sort
 };
 
 /**
  * Refresh the unifinder tree by getting items from the composite calendar and
  * applying the current filter.
  */
 function refreshEventTree() {
+    if (!unifinderTreeView.ready) {
+        return;
+    }
+
     let field = document.getElementById("unifinder-search-field");
     if (field) {
         unifinderTreeView.mFilter.filterText = field.value;
     }
 
     addItemsFromCalendar(cal.view.getCompositeCalendar(window),
                          addItemsFromCompositeCalendarInternal);
 }
--- a/calendar/base/content/calendar-views.js
+++ b/calendar/base/content/calendar-views.js
@@ -383,19 +383,26 @@ function updateStyleSheetForViews(aCalen
         updateStyleSheetForViews.ruleCache = {};
     }
     let ruleCache = updateStyleSheetForViews.ruleCache;
 
     if (!(aCalendar.id in ruleCache)) {
         // We haven't create a rule for this calendar yet, do so now.
         let sheet = getViewStyleSheet();
         let ruleString = '.calendar-color-box[calendar-id="' + aCalendar.id + '"] {} ';
-        let ruleIndex = sheet.insertRule(ruleString, sheet.cssRules.length);
 
-        ruleCache[aCalendar.id] = sheet.cssRules[ruleIndex];
+        try {
+            let ruleIndex = sheet.insertRule(ruleString, sheet.cssRules.length);
+            ruleCache[aCalendar.id] = sheet.cssRules[ruleIndex];
+        } catch (ex) {
+            sheet.ownerNode.addEventListener("load",
+                                             () => updateStyleSheetForViews(aCalendar),
+                                             { once: true });
+            return;
+        }
     }
 
     let color = aCalendar.getProperty("color") || "#A8C2E1";
     ruleCache[aCalendar.id].style.backgroundColor = color;
     ruleCache[aCalendar.id].style.color = cal.view.getContrastingTextColor(color);
 }
 
 /**
@@ -603,17 +610,17 @@ function goToDate(aDate) {
 /**
  * Returns the calendar view that was selected before restart, or the current
  * calendar view if it has already been set in this session
  *
  * @return          The last calendar view.
  */
 function getLastCalendarView() {
     let deck = getViewDeck();
-    if (deck.selectedIndex > -1) {
+    if (deck.hasAttribute("selectedIndex")) {
         let viewNode = deck.childNodes[deck.selectedIndex];
         return viewNode.id.replace(/-view/, "");
     }
 
     // No deck item was selected beforehand, default to week view.
     return "week";
 }
 
--- a/calendar/base/content/today-pane.js
+++ b/calendar/base/content/today-pane.js
@@ -19,23 +19,28 @@ var TodayPane = {
         startY: 0,
         distance: 0,
         session: false
     },
 
     /**
      * Load Handler, sets up the today pane controls.
      */
-    onLoad: function() {
+    onLoad: async function() {
+        let panel = document.getElementById("agenda-panel");
+        if (!("isVisible" in panel)) {
+            await new Promise(resolve => panel.addEventListener("bindingattached", resolve, { once: true }));
+        }
+
         TodayPane.paneViews = [
             cal.l10n.getCalString("eventsandtasks"),
             cal.l10n.getCalString("tasksonly"),
             cal.l10n.getCalString("eventsonly")
         ];
-        agendaListbox.setupCalendar();
+        await agendaListbox.setupCalendar();
         TodayPane.initializeMiniday();
         TodayPane.setShortWeekdays();
 
         document.getElementById("modeBroadcaster").addEventListener("DOMAttrModified", TodayPane.onModeModified);
         TodayPane.setTodayHeader();
 
         document.getElementById("today-splitter").addEventListener("command", onCalendarViewResize);
         TodayPane.updateSplitterState();
--- a/calendar/base/content/today-pane.xul
+++ b/calendar/base/content/today-pane.xul
@@ -163,31 +163,17 @@
                                mode="mail"
                                iconsize="small"
                                orient="horizontal"
                                label="&calendar.newevent.button.label;"
                                tooltiptext="&calendar.newevent.button.tooltip;"
                                oncommand="agendaListbox.createNewEvent(event)">
                   <observes element="calendar_new_event_command" attribute="disabled"/>
                 </toolbarbutton>
-             </hbox>
-             <vbox id="richlistitem-container" hidden="true">
-                <agenda-checkbox-richlist-item id="today-header-hidden"
-                                               title="&calendar.today.button.label;"
-                                               checked="true"
-                                               persist="checked"/>
-                <agenda-checkbox-richlist-item id="tomorrow-header-hidden"
-                                               title="&calendar.tomorrow.button.label;"
-                                               checked="false"
-                                               persist="checked"/>
-                <agenda-checkbox-richlist-item id="nextweek-header-hidden"
-                                               title="&calendar.upcoming.button.label;"
-                                               checked="false"
-                                               persist="checked"/>
-              </vbox>
+              </hbox>
               <richlistbox id="agenda-listbox" flex="1" context="_child"
                            onblur="agendaListbox.onBlur();"
                            onfocus="agendaListbox.onFocus();"
                            onkeypress="agendaListbox.onKeyPress(event);"
                            ondblclick="agendaListbox.createNewEvent(event);"
                            ondragstart="nsDragAndDrop.startDrag(event, calendarCalendarButtonDNDObserver);"
                            ondragover="nsDragAndDrop.dragOver(event, calendarCalendarButtonDNDObserver);"
                            ondrop="nsDragAndDrop.drop(event, calendarCalendarButtonDNDObserver);">
@@ -373,16 +359,28 @@
                       <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"/>
               </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/content/widgets/calendar-list-tree.xml
+++ b/calendar/base/content/widgets/calendar-list-tree.xml
@@ -597,19 +597,26 @@
                 let ruleString = "calendar-list-tree > tree > treechildren" +
                                  "::-moz-tree-cell(color-treecol, id-" +
                                  aCalendar.id + ") {}";
 
                 let disabledRuleString = "calendar-list-tree > tree > treechildren" +
                                          "::-moz-tree-cell(color-treecol, id-" +
                                          aCalendar.id + ", disabled) {}";
 
-                let ruleIndex = sheet.insertRule(ruleString, sheet.cssRules.length);
-                let disabledIndex = sheet.insertRule(disabledRuleString, sheet.cssRules.length);
-                this.ruleCache[aCalendar.id] = [sheet.cssRules[ruleIndex], sheet.cssRules[disabledIndex]];
+                try {
+                    let ruleIndex = sheet.insertRule(ruleString, sheet.cssRules.length);
+                    let disabledIndex = sheet.insertRule(disabledRuleString, sheet.cssRules.length);
+                    this.ruleCache[aCalendar.id] = [sheet.cssRules[ruleIndex], sheet.cssRules[disabledIndex]];
+                } catch (ex) {
+                    sheet.ownerNode.addEventListener("load",
+                                                     () => this.updateCalendarColor(aCalendar),
+                                                     { once: true });
+                    return;
+                }
             }
 
             let [enabledRule, disabledRule] = this.ruleCache[aCalendar.id];
             enabledRule.style.backgroundColor = color;
 
             let colorMatch = color.match(/#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})?/);
             if (colorMatch && this.disabledState == "disabled") {
                 let gray = (
--- a/calendar/base/content/widgets/calendar-widgets.xml
+++ b/calendar/base/content/widgets/calendar-widgets.xml
@@ -329,16 +329,18 @@
                       binding: this,
                       handleEvent: function(aEvent, aHandled) {
                           return this.binding.onCheckboxStateChange(aEvent, this.binding);
                       }
                   };
                   this.mRefControl.addEventListener("CheckboxStateChange", this.mControlHandler, true);
               }
           }
+
+          this.dispatchEvent(new CustomEvent("bindingattached", { bubbles: false }));
       ]]></constructor>
 
       <destructor><![CDATA[
           if (this.mBroadcaster) {
               this.mBroadcaster.removeEventListener("DOMAttrModified", this.mModHandler, true);
           }
           if (this.mRefControl) {
               this.mRefControl.removeEventListener("CheckboxStateChange", this.mControlHandler, true);
--- a/calendar/base/jar.mn
+++ b/calendar/base/jar.mn
@@ -103,17 +103,16 @@ calendar.jar:
     content/calendar/WindowsNTToZoneInfoTZId.properties    (src/WindowsNTToZoneInfoTZId.properties)
 % skin calendar classic/1.0 chrome/skin/linux/calendar/
 % skin calendar classic/1.0 chrome/skin/osx/calendar/ os=Darwin
 % skin calendar classic/1.0 chrome/skin/windows/calendar/ os=WINNT
 % skin calendar-common classic/1.0 chrome/skin/common/
 % style chrome://messenger/content/customizeToolbar.xul chrome://calendar/skin/calendar-task-view.css
 % style chrome://messenger/content/customizeToolbar.xul chrome://calendar-common/skin/dialogs/calendar-event-dialog.css
 % style chrome://calendar/content/calendar-event-dialog.xul chrome://calendar-common/skin/dialogs/calendar-event-dialog.css
-% style chrome://lightning/content/lightning-item-iframe.xul chrome://calendar-common/skin/dialogs/calendar-event-dialog.css
 % style chrome://calendar/content/calendar-event-dialog-attendees.xul chrome://calendar-common/skin/dialogs/calendar-event-dialog.css
     ../icons/default/calendar-alarm-dialog.ico        (themes/common/icons/calendar-alarm-dialog.ico)
     ../icons/default/calendar-alarm-dialog.png        (themes/common/icons/calendar-alarm-dialog.png)
     ../icons/default/calendar-event-dialog.ico        (themes/common/icons/calendar-event-dialog.ico)
     ../icons/default/calendar-event-dialog.png        (themes/common/icons/calendar-event-dialog.png)
     ../icons/default/calendar-event-summary-dialog.ico (themes/common/icons/calendar-event-summary-dialog.ico)
     ../icons/default/calendar-event-summary-dialog.png (themes/common/icons/calendar-event-summary-dialog.png)
     ../icons/default/calendar-task-dialog.ico         (themes/common/icons/calendar-task-dialog.ico)
--- a/calendar/lightning/content/lightning-item-iframe.xul
+++ b/calendar/lightning/content/lightning-item-iframe.xul
@@ -10,16 +10,17 @@
 <?xml-stylesheet type="text/css" href="chrome://calendar-common/skin/calendar-alarms.css"?>
 <?xml-stylesheet type="text/css" href="chrome://calendar-common/skin/calendar-attendees.css"?>
 <?xml-stylesheet type="text/css" href="chrome://calendar/content/widgets/calendar-widget-bindings.css"?>
 <?xml-stylesheet type="text/css" href="chrome://calendar/skin/calendar-event-dialog.css"?>
 <?xml-stylesheet type="text/css" href="chrome://calendar/content/calendar-event-dialog.css"?>
 <?xml-stylesheet type="text/css" href="chrome://calendar/content/datetimepickers/datetimepickers.css"?>
 <?xml-stylesheet type="text/css" href="chrome://messenger/skin/primaryToolbar.css"?>
 <?xml-stylesheet type="text/css" href="chrome://messenger/skin/messenger.css"?>
+<?xml-stylesheet type="text/css" href="chrome://calendar-common/skin/dialogs/calendar-event-dialog.css"?>
 
 
 <!DOCTYPE window [
     <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
     <!ENTITY % globalDTD SYSTEM "chrome://calendar/locale/global.dtd">
     <!ENTITY % calendarDTD SYSTEM "chrome://calendar/locale/calendar.dtd">
     <!ENTITY % eventDialogDTD SYSTEM "chrome://calendar/locale/calendar-event-dialog.dtd">
     %brandDTD;
--- a/calendar/lightning/content/messenger-overlay-sidebar.js
+++ b/calendar/lightning/content/messenger-overlay-sidebar.js
@@ -54,16 +54,18 @@ var calendarTabMonitor = {
 var calendarTabType = {
     name: "calendar",
     panelId: "calendarTabPanel",
     modes: {
         calendar: {
             type: "calendar",
             maxTabs: 1,
             openTab: function(aTab, aArgs) {
+                gLastShownCalendarView = getLastCalendarView();
+
                 aTab.title = aArgs.title;
                 if (!("background" in aArgs) || !aArgs.background) {
                     // Only do calendar mode switching if the tab is opened in
                     // foreground.
                     ltnSwitch2Calendar();
                 }
             },
 
--- a/calendar/test/mozmill/shared-modules/test-calendar-utils.js
+++ b/calendar/test/mozmill/shared-modules/test-calendar-utils.js
@@ -312,17 +312,17 @@ function goToDate(controller, year, mont
 function invokeEventDialog(controller, clickBox, body) {
     if (clickBox) {
         controller.waitForElement(clickBox);
         controller.doubleClick(clickBox, 1, 1);
     }
 
     controller.waitFor(() => {
         return mozmill.utils.getWindows("Calendar:EventDialog").length > 0;
-    }, MID_SLEEP);
+    }, "event-dialog did not load in time", MID_SLEEP);
 
     let eventWindow = mozmill.utils.getWindows("Calendar:EventDialog")[0];
     let eventController = new mozmill.controller.MozMillController(eventWindow);
     let iframe = eventController.window.document.getElementById("lightning-item-panel-iframe");
 
     eventController.waitFor(() => {
         return iframe.contentWindow.onLoad &&
                iframe.contentWindow.onLoad.hasLoaded;
--- a/calendar/test/mozmill/testBasicFunctionality.js
+++ b/calendar/test/mozmill/testBasicFunctionality.js
@@ -54,16 +54,18 @@ function testSmokeTest() {
         anon({"anonid":"tree"})/anon({"anonid":"treechildren"})
     `));
 
     // check for event search
     controller.assertNode(eid("bottom-events-box"));
     // there should be search field
     controller.assertNode(eid("unifinder-search-field"));
 
+    controller.click(eid("calendar-day-view-button"));
+
     // default view is day view which should have 09:00 label and box
     let someTime = cal.createDateTime();
     someTime.resetTo(someTime.year, someTime.month, someTime.day, 9, 0, 0, someTime.timezone);
     let label = dateFormatter.formatTime(someTime);
     controller.assertNode(lookup(`
         ${path}/id("calendarDisplayDeck")/id("calendar-view-box")/
         id("view-deck")/id("day-view")/anon({"anonid":"mainbox"})/
         anon({"anonid":"scrollbox"})/anon({"anonid":"timebar"})/
--- a/mail/base/content/tabmail.xml
+++ b/mail/base/content/tabmail.xml
@@ -317,16 +317,19 @@
         new Array();
       </field>
       <field name="mLastTabOpener">
         null
       </field>
       <field name="mTabsProgressListeners">
         new Set();
       </field>
+      <field name="unrestoredTabs" readonly="true">
+        new Array();
+      </field>
       <method name="createTooltip">
         <parameter name="event"/>
         <body><![CDATA[
           event.stopPropagation();
           var tab = document.tooltipNode;
           if (tab.localName != "tab" || this.tabContainer.draggedTab) {
             event.preventDefault();
             return;
@@ -354,16 +357,30 @@
               this.defaultTabMode = modeDetails;
           }
           if (aTabType.panelId)
             aTabType.panel = document.getElementById(aTabType.panelId);
           else if (!aTabType.perTabPanel) {
             throw("Trying to register a tab type with neither panelId " +
                   "nor perTabPanel attributes.")
           }
+
+          setTimeout(() => {
+            for (let modeName of Object.keys(aTabType.modes)) {
+              for (let i = 0; i < this.unrestoredTabs.length;) {
+                let state = this.unrestoredTabs[i];
+                if (state.mode == modeName) {
+                  this.restoreTab(state);
+                  this.unrestoredTabs.splice(i, 1);
+                } else {
+                  i++;
+                }
+              }
+            }
+          }, 0);
         ]]></body>
       </method>
       <method name="unregisterTabType">
         <parameter name="aTabType"/>
         <body><![CDATA[
           // we can skip if the tab type was never registered...
           if (!(aTabType.name in this.tabTypes))
              return;
@@ -1026,18 +1043,20 @@
         ]]></body>
       </method>
       <method name="restoreTab">
         <parameter name="aState"/>
         <body><![CDATA[
 
           // if we no longer know about the mode, we can't restore the tab
           let mode = this.tabModes[aState.mode];
-          if (!mode)
+          if (!mode) {
+            this.unrestoredTabs.push(aState);
             return false;
+          }
 
           let restoreFunc = mode.restoreTab || mode.tabType.restoreTab;
 
           if (!restoreFunc)
             return false;
 
           // normalize the state to have an ext attribute if it does not.
           if (!("ext" in aState))