Bug 722254 - Add an XPCOMUtils method to generate a singleton factory.
authorMarco Bonardo <mbonardo@mozilla.com>
Tue, 07 Feb 2012 10:17:42 +0100
changeset 86438 df86f29bd1a334a50d0933ada1cbc2390261d07e
parent 86437 58aa97264acd1cf1671985fe37a9ea4b1eccf3c6
child 86439 b6bf0037c91f379544f64fe87080c3ff064badd7
push id94
push userbturner@mozilla.com
push dateWed, 08 Feb 2012 05:39:15 +0000
bugs722254
milestone13.0a1
Bug 722254 - Add an XPCOMUtils method to generate a singleton factory. Use the new factory in Places js services, to ensure they can't be instanced multiple times. r=bsmedberg
js/xpconnect/loader/XPCOMUtils.jsm
js/xpconnect/tests/unit/test_xpcomutils.js
toolkit/components/places/PlacesCategoriesStarter.js
toolkit/components/places/PlacesUtils.jsm
toolkit/components/places/nsLivemarkService.js
toolkit/components/places/nsPlacesAutoComplete.js
toolkit/components/places/nsPlacesExpiration.js
toolkit/components/places/nsTaggingService.js
--- a/js/xpconnect/loader/XPCOMUtils.jsm
+++ b/js/xpconnect/loader/XPCOMUtils.jsm
@@ -312,16 +312,42 @@ var XPCOMUtils = {
   importRelative: function XPCOMUtils__importRelative(that, path) {
     if (!("__URI__" in that))
       throw Error("importRelative may only be used from a JSM, and its first argument "+
                   "must be that JSM's global object (hint: use this)");
     let uri = that.__URI__;
     let i = uri.lastIndexOf("/");
     Components.utils.import(uri.substring(0, i+1) + path, that);
   },
+
+  /**
+   * generates a singleton nsIFactory implementation that can be used as
+   * the _xpcom_factory of the component.
+   * @param aServiceConstructor
+   *        Constructor function of the component.
+   */
+  generateSingletonFactory:
+  function XPCOMUtils_generateSingletonFactory(aServiceConstructor) {
+    return {
+      _instance: null,
+      createInstance: function XPCU_SF_createInstance(aOuter, aIID) {
+        if (aOuter !== null) {
+          throw Cr.NS_ERROR_NO_AGGREGATION;
+        }
+        if (this._instance === null) {
+          this._instance = new aServiceConstructor();
+        }
+        return this._instance.QueryInterface(aIID);
+      },
+      lockFactory: function XPCU_SF_lockFactory(aDoLock) {
+        throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+      },
+      QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory])
+    };
+  },
 };
 
 /**
  * Helper for XPCOMUtils.generateQI to avoid leaks - see bug 381651#c1
  */
 function makeQI(interfaceNames) {
   return function XPCOMUtils_QueryInterface(iid) {
     if (iid.equals(Ci.nsISupports))
@@ -331,9 +357,8 @@ function makeQI(interfaceNames) {
     for each(let interfaceName in interfaceNames) {
       if (Ci[interfaceName].equals(iid))
         return this;
     }
 
     throw Cr.NS_ERROR_NO_INTERFACE;
   };
 }
-
--- a/js/xpconnect/tests/unit/test_xpcomutils.js
+++ b/js/xpconnect/tests/unit/test_xpcomutils.js
@@ -45,17 +45,17 @@ Components.utils.import("resource://gre/
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 
 
 ////////////////////////////////////////////////////////////////////////////////
 //// Tests
 
-function test_generateQI_string_names()
+add_test(function test_generateQI_string_names()
 {
     var x = {
         QueryInterface: XPCOMUtils.generateQI([
             Components.interfaces.nsIClassInfo,
             "nsIDOMNode"
         ])
     };
 
@@ -68,20 +68,21 @@ function test_generateQI_string_names()
         x.QueryInterface(Components.interfaces.nsIDOMNode);
     } catch(e) {
         do_throw("Should QI to nsIDOMNode");
     }
     try {
         x.QueryInterface(Components.interfaces.nsIDOMDocument);
         do_throw("QI should not have succeeded!");
     } catch(e) {}
-}
+    run_next_test();
+});
 
 
-function test_generateCI()
+add_test(function test_generateCI()
 {
     const classID = Components.ID("562dae2e-7cff-432b-995b-3d4c03fa2b89");
     const classDescription = "generateCI test component";
     const flags = Components.interfaces.nsIClassInfo.DOM_OBJECT;
     var x = {
         QueryInterface: XPCOMUtils.generateQI([]),
         classInfo: XPCOMUtils.generateCI({classID: classID,
                                           interfaces: [],
@@ -94,19 +95,20 @@ function test_generateCI()
         ci = ci.QueryInterface(Components.interfaces.nsISupports);
         ci = ci.QueryInterface(Components.interfaces.nsIClassInfo);
         do_check_eq(ci.classID, classID);
         do_check_eq(ci.flags, flags);
         do_check_eq(ci.classDescription, classDescription);
     } catch(e) {
         do_throw("Classinfo for x should not be missing or broken");
     }
-}
+    run_next_test();
+});
 
-function test_defineLazyGetter()
+add_test(function test_defineLazyGetter()
 {
     let accessCount = 0;
     let obj = {
       inScope: false
     };
     const TEST_VALUE = "test value";
     XPCOMUtils.defineLazyGetter(obj, "foo", function() {
         accessCount++;
@@ -119,38 +121,40 @@ function test_defineLazyGetter()
     do_check_eq(obj.foo, TEST_VALUE);
     do_check_eq(accessCount, 1);
     do_check_true(obj.inScope);
 
     // Get the property once more, making sure the access count has not
     // increased.
     do_check_eq(obj.foo, TEST_VALUE);
     do_check_eq(accessCount, 1);
-}
+    run_next_test();
+});
 
 
-function test_defineLazyServiceGetter()
+add_test(function test_defineLazyServiceGetter()
 {
     let obj = { };
     XPCOMUtils.defineLazyServiceGetter(obj, "service",
                                        "@mozilla.org/consoleservice;1",
                                        "nsIConsoleService");
     let service = Cc["@mozilla.org/consoleservice;1"].
                   getService(Ci.nsIConsoleService);
 
     // Check that the lazy service getter and the actual service have the same
     // properties.
     for (let prop in obj.service)
         do_check_true(prop in service);
     for (let prop in service)
         do_check_true(prop in obj.service);
-}
+    run_next_test();
+});
 
 
-function test_categoryRegistration()
+add_test(function test_categoryRegistration()
 {
   const CATEGORY_NAME = "test-cat";
   const XULAPPINFO_CONTRACTID = "@mozilla.org/xre/app-info;1";
   const XULAPPINFO_CID = Components.ID("{fc937916-656b-4fb3-a395-8c63569e27a8}");
 
   // Create a fake app entry for our category registration apps filter.
   let XULAppInfo = {
     vendor: "Mozilla",
@@ -198,29 +202,50 @@ function test_categoryRegistration()
   while (entries.hasMoreElements()) {
     foundEntriesCount++;
     let entry = entries.getNext().QueryInterface(Ci.nsISupportsCString).data;
     print("Check the found category entry (" + entry + ") is expected.");  
     do_check_true(EXPECTED_ENTRIES.indexOf(entry) != -1);
   }
   print("Check there are no more or less than expected entries.");
   do_check_eq(foundEntriesCount, EXPECTED_ENTRIES.length);
-}
+  run_next_test();
+});
+
+add_test(function test_generateSingletonFactory()
+{
+  const XPCCOMPONENT_CONTRACTID = "@mozilla.org/singletonComponentTest;1";
+  const XPCCOMPONENT_CID = Components.ID("{31031c36-5e29-4dd9-9045-333a5d719a3e}");
 
+  function XPCComponent() {}
+  XPCComponent.prototype = {
+    classID: XPCCOMPONENT_CID,
+    _xpcom_factory: XPCOMUtils.generateSingletonFactory(XPCComponent),
+    QueryInterface: XPCOMUtils.generateQI([])
+  };
+  let NSGetFactory = XPCOMUtils.generateNSGetFactory([XPCComponent]);
+  let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+  registrar.registerFactory(
+    XPCCOMPONENT_CID,
+    "XPCComponent",
+    XPCCOMPONENT_CONTRACTID,
+    NSGetFactory(XPCCOMPONENT_CID)
+  );
+
+  // First, try to instance the component.
+  let instance = Cc[XPCCOMPONENT_CONTRACTID].createInstance(Ci.nsISupports);
+  // Try again, check that it returns the same instance as before.
+  do_check_eq(instance,
+              Cc[XPCCOMPONENT_CONTRACTID].createInstance(Ci.nsISupports));
+  // Now, for sanity, check that getService is also returning the same instance.
+  do_check_eq(instance,
+              Cc[XPCCOMPONENT_CONTRACTID].getService(Ci.nsISupports));
+
+  run_next_test();
+});
 
 ////////////////////////////////////////////////////////////////////////////////
 //// Test Runner
 
-let tests = [
-    test_generateQI_string_names,
-    test_generateCI,
-    test_defineLazyGetter,
-    test_defineLazyServiceGetter,
-    test_categoryRegistration,
-];
-
 function run_test()
 {
-    tests.forEach(function(test) {
-        print("Running next test: " + test.name);
-        test();
-    });
+  run_next_test();
 }
--- a/toolkit/components/places/PlacesCategoriesStarter.js
+++ b/toolkit/components/places/PlacesCategoriesStarter.js
@@ -124,16 +124,18 @@ PlacesCategoriesStarter.prototype = {
     }
   },
 
   //////////////////////////////////////////////////////////////////////////////
   //// nsISupports
 
   classID: Components.ID("803938d5-e26d-4453-bf46-ad4b26e41114"),
 
+  _xpcom_factory: XPCOMUtils.generateSingletonFactory(PlacesCategoriesStarter),
+
   QueryInterface: XPCOMUtils.generateQI([
     Ci.nsIObserver
   , Ci.nsINavBookmarkObserver
   ])
 };
 
 ////////////////////////////////////////////////////////////////////////////////
 //// Module Registration
--- a/toolkit/components/places/PlacesUtils.jsm
+++ b/toolkit/components/places/PlacesUtils.jsm
@@ -1,9 +1,9 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* ***** BEGIN LICENSE BLOCK *****
  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  *
  * The contents of this file are subject to the Mozilla Public License Version
  * 1.1 (the "License"); you may not use this file except in compliance with
  * the License. You may obtain a copy of the License at
  * http://www.mozilla.org/MPL/
  *
--- a/toolkit/components/places/nsLivemarkService.js
+++ b/toolkit/components/places/nsLivemarkService.js
@@ -354,19 +354,23 @@ LivemarkService.prototype = {
     if (!this.isLivemark(aItemId)) {
       return;
     }
 
     this._livemarks[aItemId].terminate();
     delete this._livemarks[aItemId];
   },
 
-  // nsISupports
+  //////////////////////////////////////////////////////////////////////////////
+  //// nsISupports
+
   classID: Components.ID("{dca61eb5-c7cd-4df1-b0fb-d0722baba251}"),
 
+  _xpcom_factory: XPCOMUtils.generateSingletonFactory(LivemarkService),
+
   QueryInterface: XPCOMUtils.generateQI([
     Ci.nsILivemarkService
   , Ci.nsINavBookmarkObserver
   , Ci.nsIObserver
   ])
 };
 
 /**
--- a/toolkit/components/places/nsPlacesAutoComplete.js
+++ b/toolkit/components/places/nsPlacesAutoComplete.js
@@ -1257,16 +1257,18 @@ nsPlacesAutoComplete.prototype = {
     return this._pendingQuery == aHandle;
   },
 
   //////////////////////////////////////////////////////////////////////////////
   //// nsISupports
 
   classID: Components.ID("d0272978-beab-4adc-a3d4-04b76acfa4e7"),
 
+  _xpcom_factory: XPCOMUtils.generateSingletonFactory(nsPlacesAutoComplete),
+
   QueryInterface: XPCOMUtils.generateQI([
     Ci.nsIAutoCompleteSearch,
     Ci.nsIAutoCompleteSimpleResultListener,
     Ci.mozIPlacesAutoComplete,
     Ci.mozIStorageStatementCallback,
     Ci.nsIObserver,
     Ci.nsISupportsWeakReference,
   ])
@@ -1573,16 +1575,18 @@ urlInlineComplete.prototype = {
     return this._pendingQuery == aHandle;
   },
 
   //////////////////////////////////////////////////////////////////////////////
   //// nsISupports
 
   classID: Components.ID("c88fae2d-25cf-4338-a1f4-64a320ea7440"),
 
+  _xpcom_factory: XPCOMUtils.generateSingletonFactory(urlInlineComplete),
+
   QueryInterface: XPCOMUtils.generateQI([
     Ci.nsIAutoCompleteSearch,
     Ci.mozIStorageStatementCallback,
     Ci.nsIObserver,
     Ci.nsISupportsWeakReference,
   ])
 };
 
--- a/toolkit/components/places/nsPlacesExpiration.js
+++ b/toolkit/components/places/nsPlacesExpiration.js
@@ -56,33 +56,16 @@
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
 ////////////////////////////////////////////////////////////////////////////////
-//// nsIFactory
-
-const nsPlacesExpirationFactory = {
-  _instance: null,
-  createInstance: function(aOuter, aIID) {
-    if (aOuter != null)
-      throw Components.results.NS_ERROR_NO_AGGREGATION;
-    return this._instance === null ? this._instance = new nsPlacesExpiration() :
-                                     this._instance;
-  },
-  lockFactory: function (aDoLock) {},
-  QueryInterface: XPCOMUtils.generateQI([
-    Ci.nsIFactory
-  ]),
-};
-
-////////////////////////////////////////////////////////////////////////////////
 //// Constants
 
 // Last expiration step should run before the final sync.
 const TOPIC_SHUTDOWN = "places-will-close-connection";
 const TOPIC_PREF_CHANGED = "nsPref:changed";
 const TOPIC_DEBUG_START_EXPIRATION = "places-debug-start-expiration";
 const TOPIC_EXPIRATION_FINISHED = "places-expiration-finished";
 const TOPIC_IDLE_BEGIN = "idle";
@@ -1026,23 +1009,23 @@ nsPlacesExpiration.prototype = {
     return this._timer = timer;
   },
 
   //////////////////////////////////////////////////////////////////////////////
   //// nsISupports
 
   classID: Components.ID("705a423f-2f69-42f3-b9fe-1517e0dee56f"),
 
-  _xpcom_factory: nsPlacesExpirationFactory,
+  _xpcom_factory: XPCOMUtils.generateSingletonFactory(nsPlacesExpiration),
 
   QueryInterface: XPCOMUtils.generateQI([
-    Ci.nsIObserver,
-    Ci.nsINavHistoryObserver,
-    Ci.nsITimerCallback,
-    Ci.mozIStorageStatementCallback,
+    Ci.nsIObserver
+  , Ci.nsINavHistoryObserver
+  , Ci.nsITimerCallback
+  , Ci.mozIStorageStatementCallback
   ])
 };
 
 ////////////////////////////////////////////////////////////////////////////////
 //// Module Registration
 
 let components = [nsPlacesExpiration];
 var NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
--- a/toolkit/components/places/nsTaggingService.js
+++ b/toolkit/components/places/nsTaggingService.js
@@ -467,19 +467,23 @@ TaggingService.prototype = {
       delete this._tagFolders[aItemId];
   },
 
   onItemVisited: function () {},
   onBeforeItemRemoved: function () {},
   onBeginUpdateBatch: function () {},
   onEndUpdateBatch: function () {},
 
-  // nsISupports
+  //////////////////////////////////////////////////////////////////////////////
+  //// nsISupports
+
   classID: Components.ID("{bbc23860-2553-479d-8b78-94d9038334f7}"),
-  
+
+  _xpcom_factory: XPCOMUtils.generateSingletonFactory(TaggingService),
+
   QueryInterface: XPCOMUtils.generateQI([
     Ci.nsITaggingService
   , Ci.nsINavBookmarkObserver
   , Ci.nsIObserver
   ])
 };
 
 
@@ -692,18 +696,22 @@ TagAutoCompleteSearch.prototype = {
 
   /**
    * Stop an asynchronous search that is in progress
    */
   stopSearch: function PTACS_stopSearch() {
     this._stopped = true;
   },
 
-  // nsISupports
+  //////////////////////////////////////////////////////////////////////////////
+  //// nsISupports
+
+  classID: Components.ID("{1dcc23b0-d4cb-11dc-9ad6-479d56d89593}"),
+
+  _xpcom_factory: XPCOMUtils.generateSingletonFactory(TagAutoCompleteSearch),
+
   QueryInterface: XPCOMUtils.generateQI([
     Ci.nsIAutoCompleteSearch
-  ]),
-
-  classID: Components.ID("{1dcc23b0-d4cb-11dc-9ad6-479d56d89593}")
+  ])
 };
 
 let component = [TaggingService, TagAutoCompleteSearch];
 var NSGetFactory = XPCOMUtils.generateNSGetFactory(component);