Bug 1346808 - Make calUtils.js functions independent of window. r=MakeMyDay
authorPhilipp Kewisch <mozilla@kewis.ch>
Fri, 26 May 2017 19:02:25 +0200
changeset 21599 a4458b9e5377a6cb415bedee8fddf7e677a2b7ce
parent 21598 4259c9a6422319d8d6b55cd27dfe875c5ffffac9
child 21600 ad200b86b833c0250a221d2fada7b23dabe7d394
push id13161
push usermozilla@jorgk.com
push dateSun, 28 May 2017 06:10:02 +0000
treeherdercomm-central@a4458b9e5377 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersMakeMyDay
bugs1346808
Bug 1346808 - Make calUtils.js functions independent of window. r=MakeMyDay
calendar/base/content/agenda-listbox.js
calendar/base/content/calendar-common-sets.js
calendar/base/content/calendar-management.js
calendar/base/content/calendar-task-editing.js
calendar/base/content/calendar-task-tree.xml
calendar/base/content/calendar-unifinder.js
calendar/base/content/calendar-views.js
calendar/base/content/dialogs/calendar-alarm-dialog.js
calendar/base/content/dialogs/calendar-migration-dialog.js
calendar/base/content/dialogs/calendar-print-dialog.js
calendar/base/content/dialogs/chooseCalendarDialog.xul
calendar/base/content/import-export.js
calendar/base/content/widgets/minimonth.xml
calendar/base/src/calUtils.js
calendar/lightning/content/imip-bar.js
--- a/calendar/base/content/agenda-listbox.js
+++ b/calendar/base/content/agenda-listbox.js
@@ -1,16 +1,18 @@
 /* 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/. */
 
 Components.utils.import("resource://gre/modules/Services.jsm");
 Components.utils.import("resource://gre/modules/Preferences.jsm");
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 
+Components.utils.import("resource://calendar/modules/calUtils.jsm");
+
 function Synthetic(aOpen, aDuration, aMultiday) {
     this.open = aOpen;
     this.duration = aDuration;
     this.multiday = aMultiday;
 }
 
 var agendaListbox = {
     agendaListboxControl: null,
@@ -678,17 +680,17 @@ agendaListbox.refreshCalendarQuery = fun
 };
 
 /**
  * Sets up the calendar for the agenda listbox.
  */
 agendaListbox.setupCalendar = function() {
     this.init();
     if (this.calendar == null) {
-        this.calendar = getCompositeCalendar();
+        this.calendar = cal.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();
--- a/calendar/base/content/calendar-common-sets.js
+++ b/calendar/base/content/calendar-common-sets.js
@@ -1,13 +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/. */
 
 Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://calendar/modules/calUtils.jsm");
 
 /* exported injectCalendarCommandController, removeCalendarCommandController,
  *          setupContextItemType, minimonthPick, getSelectedItems,
  *          deleteSelectedItems, calendarUpdateNewItemsCommand
  */
 
 var CalendarDeleteCommandEnabled = false;
 var CalendarNewEventsCommandEnabled = false;
@@ -380,17 +381,17 @@ var calendarController = {
             case "calendar_publish_calendar_command":
                 publishEntireCalendar();
                 break;
             case "calendar_publish_selected_events_command":
                 publishCalendarData();
                 break;
 
             case "calendar_reload_remote_calendars":
-                getCompositeCalendar().refresh();
+                cal.getCompositeCalendar(window).refresh();
                 break;
             case "calendar_show_unifinder_command":
                 toggleUnifinder();
                 break;
             case "calendar_view_next_command":
                 currentView().moveView(1);
                 break;
             case "calendar_view_prev_command":
--- a/calendar/base/content/calendar-management.js
+++ b/calendar/base/content/calendar-management.js
@@ -15,17 +15,17 @@ Components.utils.import("resource://gre/
 Components.utils.import("resource://gre/modules/Preferences.jsm");
 
 /**
  * Get this window's currently selected calendar.
  *
  * @return      The currently selected calendar.
  */
 function getSelectedCalendar() {
-    return getCompositeCalendar().defaultCalendar;
+    return cal.getCompositeCalendar(window).defaultCalendar;
 }
 
 /**
  * Deletes the passed calendar, prompting the user if he really wants to do
  * this. If there is only one calendar left, no calendar is removed and the user
  * is not prompted.
  *
  * @param aCalendar     The calendar to delete.
@@ -82,17 +82,17 @@ function promptDeleteCalendar(aCalendar)
 }
 
 /**
  * Called to initialize the calendar manager for a window.
  */
 function loadCalendarManager() {
     // Set up the composite calendar in the calendar list widget.
     let tree = document.getElementById("calendar-list-tree-widget");
-    let compositeCalendar = getCompositeCalendar();
+    let compositeCalendar = cal.getCompositeCalendar(window);
     tree.compositeCalendar = compositeCalendar;
 
     // Initialize our composite observer
     compositeCalendar.addObserver(compositeObserver);
 
     // Create the home calendar if no calendar exists.
     let calendars = cal.getCalendarManager().getCalendars({});
     if (calendars.length) {
@@ -109,17 +109,17 @@ function loadCalendarManager() {
     }
 }
 
 /**
  * Creates the initial "Home" calendar if no calendar exists.
  */
 function initHomeCalendar() {
     let calMgr = cal.getCalendarManager();
-    let composite = getCompositeCalendar();
+    let composite = cal.getCompositeCalendar(window);
     let url = cal.makeURL("moz-storage-calendar://");
     let homeCalendar = calMgr.createCalendar("storage", url);
     homeCalendar.name = calGetString("calendar", "homeCalendarName");
     calMgr.registerCalendar(homeCalendar);
     Preferences.set("calendar.list.sortOrder", homeCalendar.id);
     composite.addCalendar(homeCalendar);
 
     // Wrapping this in a try/catch block, as if any of the migration code
@@ -134,17 +134,17 @@ function initHomeCalendar() {
 
     return homeCalendar;
 }
 
 /**
  * Called to clean up the calendar manager for a window.
  */
 function unloadCalendarManager() {
-    let compositeCalendar = getCompositeCalendar();
+    let compositeCalendar = cal.getCompositeCalendar(window);
     compositeCalendar.setStatusObserver(null, null);
     compositeCalendar.removeObserver(compositeObserver);
 }
 
 /**
  * Updates the sort order preference based on the given event. The event is a
  * "SortOrderChanged" event, emitted from the calendar-list-tree binding. You
  * can also pass in an object like { sortOrder: "Space separated calendar ids" }
@@ -189,17 +189,17 @@ function calendarListTooltipShowing(even
  * @return              Returns true if the context menu should be shown.
  */
 function calendarListSetupContextMenu(event) {
     let col = {};
     let row = {};
     let calendar;
     let calendars = getCalendarManager().getCalendars({});
     let treeNode = document.getElementById("calendar-list-tree-widget");
-    let composite = getCompositeCalendar();
+    let composite = cal.getCompositeCalendar(window);
 
     if (document.popupNode.localName == "tree") {
         // Using VK_APPS to open the context menu will target the tree
         // itself. In that case we won't have a client point even for
         // opening the context menu. The "target" element should then be the
         // selected calendar.
         row.value = treeNode.tree.currentIndex;
         col.value = treeNode.getColumn("calendarname-treecol");
@@ -301,29 +301,29 @@ function ensureCalendarVisible(aCalendar
 }
 
 /**
  * Hides the specified calendar if it is visible, or shows it if it is hidden.
  *
  * @param aCalendar   The calendar to show or hide
  */
 function toggleCalendarVisible(aCalendar) {
-    let composite = getCompositeCalendar();
+    let composite = cal.getCompositeCalendar(window);
     if (composite.getCalendarById(aCalendar.id)) {
         composite.removeCalendar(aCalendar);
     } else {
         composite.addCalendar(aCalendar);
     }
 }
 
 /**
  * Shows all hidden calendars.
  */
 function showAllCalendars() {
-    let composite = getCompositeCalendar();
+    let composite = cal.getCompositeCalendar(window);
     let cals = cal.getCalendarManager().getCalendars({});
 
     composite.startBatch();
     for (let calendar of cals) {
         if (!composite.getCalendarById(calendar.id)) {
             composite.addCalendar(calendar);
         }
     }
@@ -331,17 +331,17 @@ function showAllCalendars() {
 }
 
 /**
  * Shows only the specified calendar, and hides all others.
  *
  * @param aCalendar   The calendar to show as the only visible calendar
  */
 function showOnlyCalendar(aCalendar) {
-    let composite = getCompositeCalendar();
+    let composite = cal.getCompositeCalendar(window);
     let cals = composite.getCalendars({}) || [];
 
     composite.startBatch();
     for (let calendar of cals) {
         if (calendar.id != aCalendar.id) {
             composite.removeCalendar(calendar);
         }
     }
--- a/calendar/base/content/calendar-task-editing.js
+++ b/calendar/base/content/calendar-task-editing.js
@@ -1,12 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+Components.utils.import("resource://calendar/modules/calUtils.jsm");
 Components.utils.import("resource://calendar/modules/calAlarmUtils.jsm");
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 /**
  * Used by the "quick add" feature for tasks, for example in the task view or
  * the uniinder-todo.
  *
  * NOTE: many of the following methods are called without taskEdit being the
@@ -148,25 +149,25 @@ var taskEdit = {
     onLoad: function(aEvent) {
         window.removeEventListener("load", taskEdit.onLoad, false);
         // TODO use getElementsByClassName
         let taskEditFields = document.getElementsByAttribute("class", "task-edit-field");
         for (let i = 0; i < taskEditFields.length; i++) {
             taskEdit.onBlur({ target: taskEditFields[i] });
         }
 
-        getCompositeCalendar().addObserver(taskEdit.compositeObserver);
+        cal.getCompositeCalendar(window).addObserver(taskEdit.compositeObserver);
         taskEdit.observedCalendar = getSelectedCalendar();
     },
 
     /**
      * Window load function to clean up all quick-add fields.
      */
     onUnload: function() {
-        getCompositeCalendar().removeObserver(taskEdit.compositeObserver);
+        cal.getCompositeCalendar(window).removeObserver(taskEdit.compositeObserver);
         taskEdit.observedCalendar = null;
     },
 
     /**
      * Observer to watch for readonly, disabled and capability changes of the
      * observed calendar.
      *
      * @see calIObserver
--- a/calendar/base/content/calendar-task-tree.xml
+++ b/calendar/base/content/calendar-task-tree.xml
@@ -123,17 +123,17 @@
         this.mFilter = new calFilter();
 
         // set up the custom tree view
         let tree = document.getAnonymousElementByAttribute(this, "anonid", "calendar-task-tree");
         this.mTreeView.tree = tree;
         tree.view = this.mTreeView;
 
         // set up our calendar event observer
-        let composite = getCompositeCalendar();
+        let composite = cal.getCompositeCalendar(window);
         composite.addObserver(this.mTaskTreeObserver);
 
         // set up the preference observer
         let branch = Services.prefs.getBranch("");
         branch.addObserver("calendar.", this, false);
 
 
         // we want to make several attributes on the column
@@ -168,17 +168,17 @@
                 }
             }
         }
       ]]></constructor>
       <destructor><![CDATA[
         Components.utils.import("resource://gre/modules/Services.jsm");
 
         // remove composite calendar observer
-        let composite = getCompositeCalendar();
+        let composite = cal.getCompositeCalendar(window);
         composite.removeObserver(this.mTaskTreeObserver);
 
         // remove the preference observer
         let branch = Services.prefs.getBranch("");
         branch.removeObserver("calendar.", this, false);
 
         let widths = "";
         let ordinals = "";
@@ -974,17 +974,17 @@
           }
         ]]></body>
       </method>
 
       <!-- Called by event observers to update the display -->
       <method name="refresh">
         <parameter name="aFilter"/>
         <body><![CDATA[
-          let cals = getCompositeCalendar().getCalendars({}) || [];
+          let cals = cal.getCompositeCalendar(window).getCalendars({}) || [];
           for (let calendar of cals) {
               if (!calendar.getProperty("disabled")) {
                   this.refreshFromCalendar(calendar, aFilter);
               }
           }
         ]]></body>
       </method>
 
--- a/calendar/base/content/calendar-unifinder.js
+++ b/calendar/base/content/calendar-unifinder.js
@@ -190,17 +190,17 @@ function prepareCalendarUnifinder() {
 
     // Add pref observer
     let branch = Services.prefs.getBranch("");
     branch.addObserver("calendar.", unifinderObserver, false);
 
     // Check if this is not the hidden window, which has no UI elements
     if (unifinderTree) {
         // set up our calendar event observer
-        let ccalendar = getCompositeCalendar();
+        let ccalendar = cal.getCompositeCalendar(window);
         ccalendar.addObserver(unifinderObserver);
 
         kDefaultTimezone = calendarDefaultTimezone();
 
         // Set up the filter
         unifinderTreeView.mFilter = new calFilter();
 
         // Set up the unifinder views.
@@ -239,17 +239,17 @@ function prepareCalendarUnifinder() {
     }
 }
 
 /**
  * Called when the window is unloaded to clean up any observers and listeners
  * added.
  */
 function finishCalendarUnifinder() {
-    let ccalendar = getCompositeCalendar();
+    let ccalendar = cal.getCompositeCalendar(window);
     ccalendar.removeObserver(unifinderObserver);
 
     // Remove pref observer
     let branch = Services.prefs.getBranch("");
     branch.removeObserver("calendar.", unifinderObserver, false);
 
     let viewDeck = getViewDeck();
     if (viewDeck) {
@@ -834,17 +834,17 @@ var unifinderTreeView = {
  * applying the current filter.
  */
 function refreshEventTree() {
     let field = document.getElementById("unifinder-search-field");
     if (field) {
         unifinderTreeView.mFilter.filterText = field.value;
     }
 
-    addItemsFromCalendar(getCompositeCalendar(),
+    addItemsFromCalendar(cal.getCompositeCalendar(window),
                          addItemsFromCompositeCalendarInternal);
 }
 
 /**
  * EXTENSION_POINTS
  * Filters the passed event array according to the currently applied filter.
  * Afterwards, applies the items to the unifinder view.
  *
--- a/calendar/base/content/calendar-views.js
+++ b/calendar/base/content/calendar-views.js
@@ -243,17 +243,17 @@ function switchToView(aViewType) {
     // Anyone wanting to plug in a view needs to follow this naming scheme
     let view = document.getElementById(aViewType + "-view");
     viewDeck.selectedPanel = view;
 
     // Select the corresponding tab
     let viewTabs = document.getElementById("view-tabs");
     viewTabs.selectedIndex = getViewDeck().selectedIndex;
 
-    let compositeCal = getCompositeCalendar();
+    let compositeCal = cal.getCompositeCalendar(window);
     if (view.displayCalendar != compositeCal) {
         view.displayCalendar = compositeCal;
         view.timezone = calendarDefaultTimezone();
         view.controller = calendarViewController;
     }
 
     view.goToDay(selectedDay);
     view.setSelectedItems(currentSelection.length, currentSelection);
@@ -646,17 +646,17 @@ function selectAllEvents() {
         },
         onGetResult: function(aCalendar, aStatus, aItemType, aDetail, aCount, aItems) {
             for (let item of aItems) {
                 items.push(item);
             }
         }
     };
 
-    let composite = getCompositeCalendar();
+    let composite = cal.getCompositeCalendar(window);
     let filter = composite.ITEM_FILTER_CLASS_OCCURRENCES;
 
     if (currentView().tasksInView) {
         filter |= composite.ITEM_FILTER_TYPE_ALL;
     } else {
         filter |= composite.ITEM_FILTER_TYPE_EVENT;
     }
     if (currentView().showCompleted) {
--- a/calendar/base/content/dialogs/calendar-alarm-dialog.js
+++ b/calendar/base/content/dialogs/calendar-alarm-dialog.js
@@ -212,17 +212,17 @@ function aboveSnoozeLimit(aDuration) {
 
     let currentTime = cal.now().getInTimezone(cal.UTC());
     let limitTime = currentTime.clone();
     limitTime.month += LIMIT;
 
     let durationUntilLimit = limitTime.subtractDate(currentTime);
     if (aDuration.compare(durationUntilLimit) > 0) {
         let msg = PluralForm.get(LIMIT, cal.calGetString("calendar", "alarmSnoozeLimitExceeded"));
-        showError(msg.replace("#1", LIMIT));
+        cal.showError(msg.replace("#1", LIMIT), window);
         return true;
     }
     return false;
 }
 
 /**
  * Sets up the window title, counting the number of alarms in the window.
  */
--- a/calendar/base/content/dialogs/calendar-migration-dialog.js
+++ b/calendar/base/content/dialogs/calendar-migration-dialog.js
@@ -276,17 +276,17 @@ var gDataMigrator = {
                     // Remote subscription
                     // XXX check for duplicates
                     var url = makeURL(getRDFAttr(node, "remotePath"));
                     calendar = calManager.createCalendar("ics", url);
                 }
                 calendar.name = getRDFAttr(node, "name");
                 calendar.setProperty("color", getRDFAttr(node, "color"));
                 calManager.registerCalendar(calendar);
-                getCompositeCalendar().addCalendar(calendar);
+                cal.getCompositeCalendar(window).addCalendar(calendar);
             }
             aCallback();
         }
 
         var migrators = [];
 
         // Look in our current profile directory, in case we're upgrading in
         // place
@@ -394,17 +394,17 @@ var gDataMigrator = {
                                 .createInstance(Components.interfaces.nsIConverterOutputStream);
                 convStream.init(stream, 'UTF-8', 0, 0x0000);
                 convStream.writeString(str);
 
                 var calendar = gDataMigrator.importICSToStorage(tempFile);
                 calendar.name = "iCalendar"+i;
                 i++;
                 calManager.registerCalendar(calendar);
-                getCompositeCalendar().addCalendar(calendar);
+                cal.getCompositeCalendar(window).addCalendar(calendar);
             }
             migLOG("icalMig making callback");
             aCallback();
         }
         var profileDir = this.dirService.get("ProfD", Components.interfaces.nsILocalFile);
         var icalSpec = profileDir.path;
         var diverge = icalSpec.indexOf("Thunderbird");
         if (diverge == -1) {
@@ -432,17 +432,17 @@ var gDataMigrator = {
         function evoMigrate(aDataDir, aCallback) {
             var i = 1;
             function evoDataMigrate(dataStore) {
                 migLOG("Migrating evolution data file in " + dataStore.path);
                 if (dataStore.exists()) {
                     var calendar = gDataMigrator.importICSToStorage(dataStore);
                     calendar.name = "Evolution " + (i++);
                     calManager.registerCalendar(calendar);
-                    getCompositeCalendar().addCalendar(calendar);
+                    cal.getCompositeCalendar(window).addCalendar(calendar);
                 }
                 return dataStore.exists();
             }
 
             var calManager = getCalendarManager();
             var dirs = aDataDir.directoryEntries;
             while (dirs.hasMoreElements()) {
                 var dataDir = dirs.getNext().QueryInterface(Components.interfaces.nsIFile);
@@ -484,17 +484,17 @@ var gDataMigrator = {
                     storage.name = name;
 
                     if (color) {
                         storage.setProperty("color", color);
                     }
                     calManager.registerCalendar(storage);
 
                     if (enabled) {
-                        getCompositeCalendar().addCalendar(storage);
+                        cal.getCompositeCalendar(window).addCalendar(storage);
                     }
                 }
             }
             aCallback();
         }
 
         if (!this.dirService.has("LocalAppData")) {
             // We are probably not on windows
@@ -551,20 +551,20 @@ var gDataMigrator = {
         calendar.id = cal.getUUID();
 
         try {
             inputStream.init(icsFile, MODE_RDONLY, parseInt("0444", 8), {});
             items = icsImporter.importFromStream(inputStream, {});
         } catch(ex) {
             switch (ex.result) {
                 case Components.interfaces.calIErrors.INVALID_TIMEZONE:
-                    showError(calGetString("calendar", "timezoneError", [icsFile.path]));
+                    cal.showError(calGetString("calendar", "timezoneError", [icsFile.path]), window);
                     break;
                 default:
-                    showError(calGetString("calendar", "unableToRead") + icsFile.path + "\n"+ ex);
+                    cal.showError(calGetString("calendar", "unableToRead") + icsFile.path + "\n"+ ex, window);
             }
         } finally {
            inputStream.close();
         }
 
         // Defined in import-export.js
         putItemsIntoCal(calendar, items, icsFile.leafName);
 
--- a/calendar/base/content/dialogs/calendar-print-dialog.js
+++ b/calendar/base/content/dialogs/calendar-print-dialog.js
@@ -156,17 +156,17 @@ function getPrintSettings(receiverFunc) 
                         }
                     }
                     settings.eventList = eventWithDueDate;
                 }
             }
         };
         let filter = getFilter(settings);
         if (filter) {
-            window.opener.getCompositeCalendar().getItems(filter, 0, settings.start, settings.end, listener);
+            cal.getCompositeCalendar(window.opener).getItems(filter, 0, settings.start, settings.end, listener);
         } else {
             // No filter means no items, just complete with the empty list set above
             receiverFunc(settings);
         }
     } else {
         receiverFunc(settings);
     }
 }
--- a/calendar/base/content/dialogs/chooseCalendarDialog.xul
+++ b/calendar/base/content/dialogs/chooseCalendarDialog.xul
@@ -17,21 +17,23 @@
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         buttons="accept,cancel"
         onload="setTimeout('loadCalendars()',0);"
         ondialogaccept="return doOK();"
         persist="screenX screenY height width">
 
     <script type="application/javascript" src="chrome://calendar/content/calendar-ui-utils.js"/>
     <script type="application/javascript"><![CDATA[
+        Components.utils.import("resource://calendar/modules/calUtils.jsm");
+
         function loadCalendars() {
             const calendarManager = Components.classes["@mozilla.org/calendar/manager;1"]
                                             .getService(Components.interfaces.calICalendarManager);
             var listbox = document.getElementById("calendar-list");
-            var composite = window.opener.getCompositeCalendar();
+            var composite = cal.getCompositeCalendar(window.opener);
             var selectedIndex = 0;
             var calendars;
 
             if (window.arguments[0].calendars) {
                 calendars = window.arguments[0].calendars;
             } else {
                 calendars = calendarManager.getCalendars({});
             }
--- a/calendar/base/content/import-export.js
+++ b/calendar/base/content/import-export.js
@@ -82,29 +82,29 @@ function loadEventsFromFile(aCalendar) {
 
         try {
             inputStream.init(picker.file, MODE_RDONLY, parseInt("0444", 8), {});
             items = importer.importFromStream(inputStream, {});
         } catch (ex) {
             exception = ex;
             switch (ex.result) {
                 case Components.interfaces.calIErrors.INVALID_TIMEZONE:
-                    showError(cal.calGetString("calendar", "timezoneError", [filePath]));
+                    cal.showError(cal.calGetString("calendar", "timezoneError", [filePath]), window);
                     break;
                 default:
-                    showError(cal.calGetString("calendar", "unableToRead") + filePath + "\n" + ex);
+                    cal.showError(cal.calGetString("calendar", "unableToRead") + filePath + "\n" + ex, window);
             }
         } finally {
             inputStream.close();
         }
 
         if (!items.length && !exception) {
             // the ics did not contain any events, so there's no need to proceed. But we should
             // notify the user about it, if we haven't before.
-            showError(cal.calGetString("calendar", "noItemsInCalendarFile", [filePath]));
+            cal.showError(cal.calGetString("calendar", "noItemsInCalendarFile", [filePath]), window);
             return;
         }
 
         if (aCalendar) {
             putItemsIntoCal(aCalendar, items);
             return;
         }
 
@@ -169,19 +169,19 @@ function putItemsIntoCal(destCal, aItems
                     failedCount++;
                     lastError = aStatus;
                 }
             }
             // See if it is time to end the calendar's batch.
             if (count == aItems.length) {
                 destCal.endBatch();
                 if (!failedCount && duplicateCount) {
-                    showError(calGetString("calendar", "duplicateError", [duplicateCount, aFilePath]));
+                    cal.showError(calGetString("calendar", "duplicateError", [duplicateCount, aFilePath]), window);
                 } else if (failedCount) {
-                    showError(calGetString("calendar", "importItemsFailed", [failedCount, lastError.toString()]));
+                    cal.showError(calGetString("calendar", "importItemsFailed", [failedCount, lastError.toString()]), window);
                 }
             }
         }
     };
 
     for (let item of aItems) {
         // XXX prompt when finding a duplicate.
         try {
@@ -301,17 +301,17 @@ function saveEventsToFile(calendarEventA
             // XXX Do the right thing with unicode and stuff. Or, again, should the
             //     exporter handle that?
             exporter.exportToStream(outputStream,
                                     calendarEventArray.length,
                                     calendarEventArray,
                                     null);
             outputStream.close();
         } catch (ex) {
-            showError(calGetString("calendar", "unableToWrite") + filePath);
+            cal.showError(calGetString("calendar", "unableToWrite") + filePath, window);
         }
     }
 }
 
 /**
  * Exports all the events and tasks in a calendar.  If aCalendar is not specified,
  * the user will be prompted with a list of calendars to choose which one to export.
  *
--- a/calendar/base/content/widgets/minimonth.xml
+++ b/calendar/base/content/widgets/minimonth.xml
@@ -434,16 +434,17 @@
       <field name="mPixelScrollDelta">0</field>
       <field name="mIsReadOnly">false</field>
       <field name="mObservesComposite">false</field>
       <field name="mShowWeekNumber">true</field>
 
       <constructor><![CDATA[
         Components.utils.import("resource://gre/modules/Services.jsm");
         Components.utils.import("resource://gre/modules/Preferences.jsm");
+        Components.utils.import("resource://calendar/modules/calUtils.jsm");
 
         this.mToday = false;
         this.mSelected = false;
         this.mExtra = false;
         this.mValue = new Date(); // Default to "today"
         this.mFocused = null;
         // save references for convenience
         if (this.hasAttribute("readonly")) {
@@ -459,17 +460,17 @@
         let branch = Services.prefs.getBranch("");
         branch.addObserver("calendar.", this, false);
       ]]></constructor>
 
       <destructor><![CDATA[
         Components.utils.import("resource://gre/modules/Services.jsm");
 
         if (this.mObservesComposite == true) {
-            getCompositeCalendar().removeObserver(this);
+            cal.getCompositeCalendar(window).removeObserver(this);
         }
 
         // Remove pref observer
         let branch = Services.prefs.getBranch("");
         branch.removeObserver("calendar.", this, false);
       ]]></destructor>
 
       <!-- calIOperationListener methods -->
@@ -1017,22 +1018,22 @@
         ]]></body>
       </method>
 
       <method name="_setFreeBusy">
         <parameter name="aFreeBusy"/>
         <body><![CDATA[
           if (aFreeBusy == true) {
               if (this.mObservesComposite == false) {
-                  getCompositeCalendar().addObserver(this);
+                  cal.getCompositeCalendar(window).addObserver(this);
                   this.mObservesComposite = true;
                   this.getItems();
               }
           } else if (this.mObservesComposite == true) {
-              getCompositeCalendar().removeObserver(this);
+              cal.getCompositeCalendar(window).removeObserver(this);
               this.mObservesComposite = false;
           }
         ]]></body>
       </method>
 
       <method name="removeAttribute">
         <parameter name="aAttr"/>
         <body><![CDATA[
@@ -1059,17 +1060,17 @@
       </method>
 
       <method name="getItems">
         <parameter name="aCalendar"/>
         <body><![CDATA[
           // The minimonth automatically clears extra styles on a month change.
           // Therefore we only need to fill the minimonth with new info.
 
-          let calendar = aCalendar || getCompositeCalendar();
+          let calendar = aCalendar || cal.getCompositeCalendar(window);
           let filter = calendar.ITEM_FILTER_COMPLETED_ALL |
                        calendar.ITEM_FILTER_CLASS_OCCURRENCES |
                        calendar.ITEM_FILTER_ALL_ITEMS;
 
           // Get new info
           calendar.getItems(filter,
                             0,
                             this.firstDate,
--- a/calendar/base/src/calUtils.js
+++ b/calendar/base/src/calUtils.js
@@ -1019,25 +1019,22 @@ function ASSERT(aCondition, aMessage, aC
                                        aCritical === true ? Components.results.NS_ERROR_UNEXPECTED : aCritical);
     } else {
         Components.utils.reportError(string);
     }
 }
 
 /**
  * Uses the prompt service to display an error message.
- * This function cannot be migrated into a module file, because it relies on an outer window object.
  *
  * @param aMsg The message to be shown
+ * @param aWindow The window to show the message in, or null for any window.
  */
-function showError(aMsg) {
-    let wnd = window || null;
-    if (wnd) {
-        Services.prompt.alert(wnd, calGetString("calendar", "genericErrorTitle"), aMsg);
-    }
+function showError(aMsg, aWindow=null) {
+    Services.prompt.alert(aWindow, cal.calGetString("calendar", "genericErrorTitle"), aMsg);
 }
 
 /**
  * Pick whichever of "black" or "white" will look better when used as a text
  * color against a background of bgColor.
  *
  * @param bgColor   the background color as a "#RRGGBB" string
  */
@@ -1854,39 +1851,32 @@ function binaryInsert(itemArray, item, c
         itemArray.splice(newIndex, 0, item);
     }
     return newIndex;
 }
 
 /**
  * Gets the cached instance of the composite calendar.
  *
- * WARNING: Great care should be taken how this function is called. If it is
- * called as "cal.getCompositeCalendar()" then it is called through calUtils.jsm
- * which means there will be one instance per app. If called as
- * "getCompositeCalendar()" from chrome code, then it will get a window-specific
- * composite calendar, which is often what is wanted
+ * @param aWindow       The window to get the composite calendar for.
  */
-function getCompositeCalendar() {
-    if (getCompositeCalendar.mObject === undefined) {
-        getCompositeCalendar.mObject = Components.classes["@mozilla.org/calendar/calendar;1?type=composite"]
-                                                 .createInstance(Components.interfaces.calICompositeCalendar);
-        getCompositeCalendar.mObject.prefPrefix = "calendar-main";
+function getCompositeCalendar(aWindow) {
+    if (typeof aWindow._compositeCalendar == "undefined") {
+        let comp = aWindow._compositeCalendar = Components.classes["@mozilla.org/calendar/calendar;1?type=composite"]
+                                                          .createInstance(Components.interfaces.calICompositeCalendar);
+        comp.prefPrefix = "calendar-main";
 
-        try {
-            if (gCalendarStatusFeedback) {
-                // If we are in a window that has calendar status feedback, set up
-                // our status observer.
-                let chromeWindow = window.QueryInterface(Components.interfaces.nsIDOMChromeWindow);
-                getCompositeCalendar.mObject.setStatusObserver(gCalendarStatusFeedback, chromeWindow);
-            }
-        } catch (exc) { // catch errors in case we run in contexts without status feedback
+        if (typeof aWindow.gCalendarStatusFeedback != "undefined") {
+            // If we are in a window that has calendar status feedback, set
+            // up our status observer.
+            let chromeWindow = aWindow.QueryInterface(Components.interfaces.nsIDOMChromeWindow);
+            comp.setStatusObserver(gCalendarStatusFeedback, chromeWindow);
         }
     }
-    return getCompositeCalendar.mObject;
+    return aWindow._compositeCalendar;
 }
 
 /**
  * Search for already open item dialog or tab.
  *
  * @param aItem     The item of the dialog or tab to search for.
  */
 function findItemWindow(aItem) {
--- a/calendar/lightning/content/imip-bar.js
+++ b/calendar/lightning/content/imip-bar.js
@@ -334,17 +334,17 @@ var ltnImipBar = {
                                 }
                             });
                         }
                         // For now, we just state the status for the user something very simple
                         let label = cal.itip.getCompleteText(aStatus, aOperationType);
                         imipBar.setAttribute("label", label);
 
                         if (!Components.isSuccessCode(aStatus)) {
-                            showError(label);
+                            cal.showError(label);
                         }
                     },
                     onGetResult: function(aCalendar, aStatus, aItemType, aDetail, aCount, aItems) {
                     }
                 };
 
                 try {
                     aActionFunc(opListener, partStat);