Bug 1089332 - Add a getObservers API for accessing the history observers list. r=Mano
authorMarco Bonardo <mbonardo@mozilla.com>
Wed, 19 Nov 2014 16:10:53 +0100
changeset 216866 dc582009548d11d05a5735ff1cc045d7e5ba48e0
parent 216865 bfc68d26c7da2051486321ec7517b1f6710e17d3
child 216867 44ebd54f1b9cece005c9d9e8034c1940d727e58b
push id10095
push usermak77@bonardo.net
push dateFri, 21 Nov 2014 21:35:24 +0000
treeherderfx-team@44ebd54f1b9c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersMano
bugs1089332
milestone36.0a1
Bug 1089332 - Add a getObservers API for accessing the history observers list. r=Mano
toolkit/components/places/History.jsm
toolkit/components/places/nsINavHistoryService.idl
toolkit/components/places/nsNavHistory.cpp
toolkit/components/places/tests/unit/nsDummyObserver.js
toolkit/components/places/tests/unit/test_history_catobs.js
toolkit/components/places/tests/unit/test_null_interfaces.js
--- a/toolkit/components/places/History.jsm
+++ b/toolkit/components/places/History.jsm
@@ -136,16 +136,34 @@ XPCOMUtils.defineLazyGetter(this, "DBCon
  */
 let gIsClosed = false;
 function ensureModuleIsOpen() {
   if (gIsClosed) {
     throw new Error("History.jsm has been shutdown");
   }
 }
 
+/**
+ * Sends a bookmarks notification through the given observers.
+ *
+ * @param observers
+ *        array of nsINavBookmarkObserver objects.
+ * @param notification
+ *        the notification name.
+ * @param args
+ *        array of arguments to pass to the notification.
+ */
+function notify(observers, notification, args) {
+  for (let observer of observers) {
+    try {
+      observer[notification](...args);
+    } catch (ex) {}
+  }
+}
+
 this.History = Object.freeze({
   /**
    * Fetch the available information for one page.
    *
    * @param guidOrURI: (URL or nsIURI)
    *      The full URI of the page.
    *            or (string)
    *      Either the full URI of the page or the GUID of the page.
--- a/toolkit/components/places/nsINavHistoryService.idl
+++ b/toolkit/components/places/nsINavHistoryService.idl
@@ -1171,17 +1171,17 @@ interface nsINavHistoryQueryOptions : ns
   attribute boolean asyncEnabled;
 
   /**
    * Creates a new options item with the same parameters of this one.
    */
   nsINavHistoryQueryOptions clone();
 };
 
-[scriptable, uuid(47f7b08b-71e0-492e-a2be-9a9fbfc75250)]
+[scriptable, uuid(8a1f527e-c9d7-4a51-bf0c-d86f0379b701)]
 interface nsINavHistoryService : nsISupports
 {
   /**
    * System Notifications:
    *
    * places-init-complete - Sent once the History service is completely
    *                        initialized successfully.
    * places-database-locked - Sent if initialization of the History service
@@ -1369,16 +1369,22 @@ interface nsINavHistoryService : nsISupp
   void addObserver(in nsINavHistoryObserver observer, in boolean ownsWeak);
 
   /**
    * Removes a history observer.
    */
   void removeObserver(in nsINavHistoryObserver observer);
 
   /**
+   * Gets an array of registered nsINavHistoryObserver objects.
+   */
+  void getObservers([optional] out unsigned long count,
+                    [retval, array, size_is(count)] out nsINavHistoryObserver observers);
+
+  /**
    * Runs the passed callback in batch mode. Use this when a lot of things
    * are about to change. Calls can be nested, observers will only be
    * notified when all batches begin/end.
    *
    * @param aCallback
    *        nsINavHistoryBatchCallback interface to call.
    * @param aUserData
    *        Opaque parameter passed to nsINavBookmarksBatchCallback
--- a/toolkit/components/places/nsNavHistory.cpp
+++ b/toolkit/components/places/nsNavHistory.cpp
@@ -2232,41 +2232,75 @@ nsNavHistory::GetQueryResults(nsNavHisto
   } else {
     rv = ResultsAsList(statement, aOptions, aResults);
     NS_ENSURE_SUCCESS(rv, rv);
   } 
 
   return NS_OK;
 }
 
-
-// nsNavHistory::AddObserver
-
 NS_IMETHODIMP
 nsNavHistory::AddObserver(nsINavHistoryObserver* aObserver, bool aOwnsWeak)
 {
   NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
   NS_ENSURE_ARG(aObserver);
 
   return mObservers.AppendWeakElement(aObserver, aOwnsWeak);
 }
 
-
-// nsNavHistory::RemoveObserver
-
 NS_IMETHODIMP
 nsNavHistory::RemoveObserver(nsINavHistoryObserver* aObserver)
 {
   NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
   NS_ENSURE_ARG(aObserver);
 
   return mObservers.RemoveWeakElement(aObserver);
 }
 
-// nsNavHistory::BeginUpdateBatch
+NS_IMETHODIMP
+nsNavHistory::GetObservers(uint32_t* _count,
+                           nsINavHistoryObserver*** _observers)
+{
+  NS_ENSURE_ARG_POINTER(_count);
+  NS_ENSURE_ARG_POINTER(_observers);
+
+  *_count = 0;
+  *_observers = nullptr;
+
+  if (!mCanNotify)
+    return NS_OK;
+
+  nsCOMArray<nsINavHistoryObserver> observers;
+
+  // First add the category cache observers.
+  mCacheObservers.GetEntries(observers);
+
+  // Then add the other observers.
+  for (uint32_t i = 0; i < mObservers.Length(); ++i) {
+    const nsCOMPtr<nsINavHistoryObserver> &observer = mObservers.ElementAt(i);
+    // Skip nullified weak observers.
+    if (observer)
+      observers.AppendElement(observer);
+  }
+
+  if (observers.Count() == 0)
+    return NS_OK;
+
+  *_observers = static_cast<nsINavHistoryObserver**>
+    (nsMemory::Alloc(observers.Count() * sizeof(nsINavHistoryObserver*)));
+  NS_ENSURE_TRUE(*_observers, NS_ERROR_OUT_OF_MEMORY);
+
+  *_count = observers.Count();
+  for (uint32_t i = 0; i < *_count; ++i) {
+    NS_ADDREF((*_observers)[i] = observers[i]);
+  }
+
+  return NS_OK;
+}
+
 // See RunInBatchMode
 nsresult
 nsNavHistory::BeginUpdateBatch()
 {
   if (mBatchLevel++ == 0) {
     mBatchDBTransaction = new mozStorageTransaction(mDB->MainConn(), false,
                                                     mozIStorageConnection::TRANSACTION_DEFERRED,
                                                     true);
--- a/toolkit/components/places/tests/unit/nsDummyObserver.js
+++ b/toolkit/components/places/tests/unit/nsDummyObserver.js
@@ -1,46 +1,41 @@
 /* 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/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 
 // Dummy boomark/history observer
 function DummyObserver() {
-  let os = Cc["@mozilla.org/observer-service;1"].
-           getService(Ci.nsIObserverService);
-  os.notifyObservers(null, "dummy-observer-created", null);
+  Services.obs.notifyObservers(null, "dummy-observer-created", null);
 }
 
 DummyObserver.prototype = {
   // history observer
   onBeginUpdateBatch: function () {},
   onEndUpdateBatch: function () {},
   onVisit: function (aURI, aVisitID, aTime, aSessionID, aReferringID, aTransitionType) {
-    let os = Cc["@mozilla.org/observer-service;1"].
-             getService(Ci.nsIObserverService);
-    os.notifyObservers(null, "dummy-observer-visited", null);
+    Services.obs.notifyObservers(null, "dummy-observer-visited", null);
   },
   onTitleChanged: function () {},
   onDeleteURI: function () {},
   onClearHistory: function () {},
   onPageChanged: function () {},
   onDeleteVisits: function () {},
 
   // bookmark observer
   //onBeginUpdateBatch: function() {},
   //onEndUpdateBatch: function() {},
   onItemAdded: function(aItemId, aParentId, aIndex, aItemType, aURI) {
-    let os = Cc["@mozilla.org/observer-service;1"].
-             getService(Ci.nsIObserverService);
-    os.notifyObservers(null, "dummy-observer-item-added", null);
+    Services.obs.notifyObservers(null, "dummy-observer-item-added", null);
   },
   onItemChanged: function () {},
   onItemRemoved: function() {},
   onItemVisited: function() {},
   onItemMoved: function() {},
 
   classID: Components.ID("62e221d3-68c3-4e1a-8943-a27beb5005fe"),
 
--- a/toolkit/components/places/tests/unit/test_history_catobs.js
+++ b/toolkit/components/places/tests/unit/test_history_catobs.js
@@ -1,46 +1,55 @@
-/* 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/XPCOMUtils.jsm");
-
-// Get services.
-let os = Cc["@mozilla.org/observer-service;1"].
-         getService(Ci.nsIObserverService);
-
-let gDummyCreated = false;
-let gDummyVisited = false;
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
 
-let observer = {
-  observe: function(subject, topic, data) {
-    if (topic == "dummy-observer-created")
-      gDummyCreated = true;
-    else if (topic == "dummy-observer-visited")
-      gDummyVisited = true;
-  },
 
-  QueryInterface: XPCOMUtils.generateQI([
-    Ci.nsIObserver,
-    Ci.nsISupportsWeakReference,
-  ])
-};
-
-function verify() {
-  do_check_true(gDummyCreated);
-  do_check_true(gDummyVisited);
-  do_test_finished();
+function run_test() {
+  run_next_test();
 }
 
-// main
-function run_test() {
+add_task(function* () {
   do_load_manifest("nsDummyObserver.manifest");
 
-  os.addObserver(observer, "dummy-observer-created", true);
-  os.addObserver(observer, "dummy-observer-visited", true);
+  let dummyCreated = false;
+  let dummyReceivedOnVisit = false;
 
-  do_test_pending();
+  Services.obs.addObserver(function created() {
+    Services.obs.removeObserver(created, "dummy-observer-created");
+    dummyCreated = true;
+  }, "dummy-observer-created", false);
+  Services.obs.addObserver(function visited() {
+    Services.obs.removeObserver(visited, "dummy-observer-visited");
+    dummyReceivedOnVisit = true;
+  }, "dummy-observer-visited", false);
+
+  let initialObservers = PlacesUtils.history.getObservers();
+
+  // Add a common observer, it should be invoked after the category observer.
+  let notificationsPromised = new Promise((resolve, reject) => {
+    PlacesUtils.history.addObserver({
+      __proto__: NavHistoryObserver.prototype,
+      onVisit() {
+        let observers = PlacesUtils.history.getObservers();
+        Assert.equal(observers.length, initialObservers.length + 1);
 
-  // Add a visit
-  promiseAddVisits(uri("http://typed.mozilla.org")).then(
-            function () do_timeout(1000, verify));
-}
+        // Check the common observer is the last one.
+        for (let i = 0; i < initialObservers.length; ++i) {
+          Assert.equal(initialObservers[i], observers[i]);
+        }
+
+        PlacesUtils.history.removeObserver(this);
+        observers = PlacesUtils.history.getObservers();
+        Assert.equal(observers.length, initialObservers.length);
+
+        // Check the category observer has been invoked before this one.
+        Assert.ok(dummyCreated);
+        Assert.ok(dummyReceivedOnVisit);
+        resolve();
+      }
+    }, false);
+  });
+
+  // Add a visit.
+  yield promiseAddVisits(uri("http://typed.mozilla.org"));
+
+  yield notificationsPromised;
+});
--- a/toolkit/components/places/tests/unit/test_null_interfaces.js
+++ b/toolkit/components/places/tests/unit/test_null_interfaces.js
@@ -8,17 +8,17 @@
 
 let Cr = Components.results;
 
 // Make an array of services to test, each specifying a class id, interface
 // and an array of function names that don't throw when passed nulls
 let testServices = [
   ["browser/nav-history-service;1", "nsINavHistoryService",
     ["queryStringToQueries", "removePagesByTimeframe", "removePagesFromHost",
-     "removeVisitsByTimeframe"]],
+     "removeVisitsByTimeframe", "getObservers"]],
   ["browser/nav-bookmarks-service;1","nsINavBookmarksService",
     ["createFolder", "getObservers"]],
   ["browser/livemark-service;2","mozIAsyncLivemarks", ["reloadLivemarks"]],
   ["browser/annotation-service;1","nsIAnnotationService", []],
   ["browser/favicon-service;1","nsIFaviconService", []],
   ["browser/tagging-service;1","nsITaggingService", []],
 ];
 do_print(testServices.join("\n"));