Bug 506471 - FUEL should avoid putting itself in the app-startup category (r=mfinkle)
authorDietrich Ayala <dietrich@mozilla.com>
Sun, 31 Jan 2010 19:12:57 -0800
changeset 37787 1ffccd3864e12f75a10dff304f5ace1b16aa5090
parent 37786 8544060edc73672cd1f5aa73b909a27e67b3f25b
child 37788 7006acb49dfb6560274b11ba82c5cb41c4a353f9
push id11433
push userdietrich@mozilla.com
push dateMon, 01 Feb 2010 03:13:59 +0000
treeherderautoland@1ffccd3864e1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmfinkle
bugs506471
milestone1.9.3a1pre
Bug 506471 - FUEL should avoid putting itself in the app-startup category (r=mfinkle)
browser/fuel/src/fuelApplication.js
toolkit/components/exthelper/extApplication.js
--- a/browser/fuel/src/fuelApplication.js
+++ b/browser/fuel/src/fuelApplication.js
@@ -38,74 +38,64 @@
 const Ci = Components.interfaces;
 const Cc = Components.classes;
 
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 //=================================================
 // Singleton that holds services and utilities
 var Utilities = {
-  _bookmarks : null,
   get bookmarks() {
-    if (!this._bookmarks) {
-      this._bookmarks = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
-                        getService(Ci.nsINavBookmarksService);
-    }
-    return this._bookmarks;
+    let bookmarks = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
+                    getService(Ci.nsINavBookmarksService);
+    this.__defineGetter__("bookmarks", function() bookmarks);
+    return this.bookmarks;
   },
 
-  _livemarks : null,
   get livemarks() {
-    if (!this._livemarks) {
-      this._livemarks = Cc["@mozilla.org/browser/livemark-service;2"].
-                        getService(Ci.nsILivemarkService);
-    }
-    return this._livemarks;
+    let livemarks = Cc["@mozilla.org/browser/livemark-service;2"].
+                    getService(Ci.nsILivemarkService);
+    this.__defineGetter__("livemarks", function() livemarks);
+    return this.livemarks;
   },
 
-  _annotations : null,
   get annotations() {
-    if (!this._annotations) {
-      this._annotations = Cc["@mozilla.org/browser/annotation-service;1"].
-                          getService(Ci.nsIAnnotationService);
-    }
-    return this._annotations;
+    let annotations = Cc["@mozilla.org/browser/annotation-service;1"].
+                      getService(Ci.nsIAnnotationService);
+    this.__defineGetter__("annotations", function() annotations);
+    return this.annotations;
   },
 
-  _history : null,
   get history() {
-    if (!this._history) {
-      this._history = Cc["@mozilla.org/browser/nav-history-service;1"].
-                      getService(Ci.nsINavHistoryService);
-    }
-    return this._history;
+    let history = Cc["@mozilla.org/browser/nav-history-service;1"].
+                  getService(Ci.nsINavHistoryService);
+    this.__defineGetter__("history", function() history);
+    return this.history;
   },
 
-  _windowMediator : null,
   get windowMediator() {
-    if (!this._windowMediator) {
-      this._windowMediator = Cc["@mozilla.org/appshell/window-mediator;1"].
-                             getService(Ci.nsIWindowMediator);
-    }
-    return this._windowMediator;
+    let windowMediator = Cc["@mozilla.org/appshell/window-mediator;1"].
+                         getService(Ci.nsIWindowMediator);
+    this.__defineGetter__("windowMediator", function() windowMediator);
+    return this.windowMediator;
   },
 
   makeURI : function(aSpec) {
     if (!aSpec)
       return null;
     var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
     return ios.newURI(aSpec, null, null);
   },
 
   free : function() {
-    this._bookmarks = null;
-    this._livemarks = null;
-    this._annotations = null;
-    this._history = null;
-    this._windowMediator = null;
+    this.bookmarks = null;
+    this.livemarks = null;
+    this.annotations = null;
+    this.history = null;
+    this.windowMediator = null;
   }
 };
 
 
 //=================================================
 // Window implementation
 function Window(aWindow) {
   this._window = aWindow;
@@ -682,39 +672,41 @@ Application.prototype = {
   classDescription: "Application",
   classID:          Components.ID("fe74cf80-aa2d-11db-abbd-0800200c9a66"),
   contractID:       "@mozilla.org/fuel/application;1",
 
   // redefine the default factory for XPCOMUtils
   _xpcom_factory: ApplicationFactory,
 
   // for nsISupports
-  QueryInterface : XPCOMUtils.generateQI([Ci.fuelIApplication, Ci.extIApplication, Ci.nsIObserver, Ci.nsIClassInfo]),
+  QueryInterface : XPCOMUtils.generateQI([Ci.fuelIApplication, Ci.extIApplication,
+                                          Ci.nsIObserver, Ci.nsIClassInfo]),
 
   getInterfaces : function app_gi(aCount) {
-    var interfaces = [Ci.fuelIApplication, Ci.extIApplication, Ci.nsIObserver, Ci.nsIClassInfo];
+    var interfaces = [Ci.fuelIApplication, Ci.extIApplication, Ci.nsIObserver,
+                      Ci.nsIClassInfo];
     aCount.value = interfaces.length;
     return interfaces;
   },
 
   // for nsIObserver
   observe: function app_observe(aSubject, aTopic, aData) {
     // Call the extApplication version of this function first
     this.__proto__.__proto__.observe.call(this, aSubject, aTopic, aData);
     if (aTopic == "xpcom-shutdown") {
+      this._obs.removeObserver(this, "xpcom-shutdown");
       this._bookmarks = null;
       Utilities.free();
     }
   },
 
   get bookmarks() {
-    if (this._bookmarks == null)
-      this._bookmarks = new BookmarkRoots();
-
-    return this._bookmarks;
+    let bookmarks = new BookmarkRoots();
+    this.__defineGetter__("bookmarks", function() bookmarks);
+    return this.bookmarks;
   },
 
   get windows() {
     var win = [];
     var enum = Utilities.windowMediator.getEnumerator("navigator:browser");
 
     while (enum.hasMoreElements())
       win.push(new Window(enum.getNext()));
--- a/toolkit/components/exthelper/extApplication.js
+++ b/toolkit/components/exthelper/extApplication.js
@@ -30,16 +30,18 @@
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
 //=================================================
 // Shutdown - used to store cleanup functions which will
 //            be called on Application shutdown
 var gShutdown = [];
 
 //=================================================
 // Console constructor
 function Console() {
@@ -98,43 +100,48 @@ EventItem.prototype = {
   },
 
   QueryInterface : XPCOMUtils.generateQI([Ci.extIEventItem])
 };
 
 
 //=================================================
 // Events constructor
-function Events() {
+function Events(notifier) {
   this._listeners = [];
+  this._notifier = notifier;
 }
 
 //=================================================
 // Events implementation
 Events.prototype = {
   addListener : function evts_al(aEvent, aListener) {
+    function hasFilter(element) {
+      return element.event == aEvent && element.listener == aListener;
+    }
+
     if (this._listeners.some(hasFilter))
       return;
 
     this._listeners.push({
       event: aEvent,
       listener: aListener
     });
 
-    function hasFilter(element) {
-      return element.event == aEvent && element.listener == aListener;
+    if (this._notifier) {
+      this._notifier(aEvent, aListener);
     }
   },
 
   removeListener : function evts_rl(aEvent, aListener) {
-    this._listeners = this._listeners.filter(hasFilter);
-
     function hasFilter(element) {
       return (element.event != aEvent) || (element.listener != aListener);
     }
+
+    this._listeners = this._listeners.filter(hasFilter);
   },
 
   dispatch : function evts_dispatch(aEvent, aEventItem) {
     eventItem = new EventItem(aEvent, aEventItem);
 
     this._listeners.forEach(function(key){
       if (key.event == aEvent) {
         key.listener.handleEvent ?
@@ -485,18 +492,19 @@ Extension.prototype = {
 
   QueryInterface : XPCOMUtils.generateQI([Ci.extIExtension])
 };
 
 
 //=================================================
 // Extensions constructor
 function Extensions() {
-  this._extmgr = Components.classes["@mozilla.org/extensions/manager;1"]
-                           .getService(Ci.nsIExtensionManager);
+  XPCOMUtils.defineLazyServiceGetter(this, "_extmgr",
+                                     "@mozilla.org/extensions/manager;1",
+                                     "nsIExtensionManager");
 
   this._cache = {};
 
   var self = this;
   gShutdown.push(function() { self._shutdown(); });
 }
 
 //=================================================
@@ -555,38 +563,32 @@ Extensions.prototype = {
 // extApplication constructor
 function extApplication() {
 }
 
 //=================================================
 // extApplication implementation
 extApplication.prototype = {
   initToolkitHelpers: function extApp_initToolkitHelpers() {
-    this._console = null;
-    this._storage = null;
-    this._prefs = null;
-    this._extensions = null;
-    this._events = null;
+    XPCOMUtils.defineLazyServiceGetter(this, "_info",
+                                       "@mozilla.org/xre/app-info;1",
+                                       "nsIXULAppInfo");
 
-    this._info = Components.classes["@mozilla.org/xre/app-info;1"]
-                           .getService(Ci.nsIXULAppInfo);
-
-    var os = Components.classes["@mozilla.org/observer-service;1"]
-                       .getService(Ci.nsIObserverService);
-
-    os.addObserver(this, "final-ui-startup", false);
-    os.addObserver(this, "quit-application-requested", false);
-    os.addObserver(this, "xpcom-shutdown", false);
+    // While the other event listeners are loaded only if needed,
+    // FUEL *must* listen for shutdown in order to clean up it's
+    // references to various services, and to remove itself as
+    // observer of any other notifications.
+    this._obs = Cc["@mozilla.org/observer-service;1"].
+                getService(Ci.nsIObserverService);
+    this._obs.addObserver(this, "xpcom-shutdown", false);
+    this._registered = {"unload": true};
   },
 
   // get this contractID registered for certain categories via XPCOMUtils
   _xpcom_categories: [
-    // make Application a startup observer
-    { category: "app-startup", service: true },
-
     // add Application as a global property for easy access
     { category: "JavaScript global privileged property" }
   ],
 
   // for nsIClassInfo
   flags : Ci.nsIClassInfo.SINGLETON,
   implementationLanguage : Ci.nsIProgrammingLanguage.JAVASCRIPT,
 
@@ -631,74 +633,74 @@ extApplication.prototype = {
       this.events.dispatch("unload", "application");
 
       // call the cleanup functions and empty the array
       while (gShutdown.length) {
         gShutdown.shift()();
       }
 
       // release our observers
-      var os = Components.classes["@mozilla.org/observer-service;1"]
-                         .getService(Ci.nsIObserverService);
-
-      os.removeObserver(this, "final-ui-startup");
-      os.removeObserver(this, "quit-application-requested");
-      os.removeObserver(this, "xpcom-shutdown");
-
-      this._info = null;
-      this._console = null;
-      this._prefs = null;
-      this._storage = null;
-      this._events = null;
-      this._extensions = null;
+      this._obs.removeObserver(this, "app-startup");
+      this._obs.removeObserver(this, "final-ui-startup");
+      this._obs.removeObserver(this, "quit-application-requested");
+      this._obs.removeObserver(this, "xpcom-shutdown");
     }
   },
 
   get console() {
-    if (this._console == null)
-        this._console = new Console();
-
-    return this._console;
+    let console = new Console();
+    this.__defineGetter__("console", function() console);
+    return this.console;
   },
 
   get storage() {
-    if (this._storage == null)
-        this._storage = new SessionStorage();
-
-    return this._storage;
+    let storage = new SessionStorage();
+    this.__defineGetter__("storage", function() storage);
+    return this.storage;
   },
 
   get prefs() {
-    if (this._prefs == null)
-        this._prefs = new PreferenceBranch("");
-
-    return this._prefs;
+    let prefs = new PreferenceBranch("");
+    this.__defineGetter__("prefs", function() prefs);
+    return this.prefs;
   },
 
   get extensions() {
-    if (this._extensions == null)
-      this._extensions = new Extensions();
-
-    return this._extensions;
+    let extensions = new Extensions();
+    this.__defineGetter__("extensions", function() extensions);
+    return this.extensions;
   },
 
   get events() {
-    if (this._events == null)
-        this._events = new Events();
 
-    return this._events;
+    // This ensures that FUEL only registers for notifications as needed
+    // by callers. Note that the unload (xpcom-shutdown) event is listened
+    // for by default, as it's needed for cleanup purposes.
+    var self = this;
+    function registerCheck(aEvent) {
+      var rmap = { "load": "app-startup",
+                   "ready": "final-ui-startup",
+                   "quit": "quit-application-requested"};
+      if (!(aEvent in rmap) || aEvent in self._registered)
+        return;
+
+      self._obs.addObserver(self, rmap[aEvent]);
+      self._registered[aEvent] = true;
+    }
+
+    let events = new Events(registerCheck);
+    this.__defineGetter__("events", function() events);
+    return this.events;
   },
 
   // helper method for correct quitting/restarting
   _quitWithFlags: function app__quitWithFlags(aFlags) {
-    let os = Components.classes["@mozilla.org/observer-service;1"]
-                       .getService(Components.interfaces.nsIObserverService);
     let cancelQuit = Components.classes["@mozilla.org/supports-PRBool;1"]
                                .createInstance(Components.interfaces.nsISupportsPRBool);
-    os.notifyObservers(cancelQuit, "quit-application-requested", null);
+    this._obs.notifyObservers(cancelQuit, "quit-application-requested", null);
     if (cancelQuit.data)
       return false; // somebody canceled our quit request
 
     let appStartup = Components.classes['@mozilla.org/toolkit/app-startup;1']
                                .getService(Components.interfaces.nsIAppStartup);
     appStartup.quit(aFlags);
     return true;
   },