Bug 606966 - Need an async history visit API exposed to JS
authorShawn Wilsher <me@shawnwilsher.com>
Tue, 25 Jan 2011 09:41:42 -0800
changeset 61389 8abbd360989e7806a442b1c853fe56c103f17a43
parent 61388 3962f456fd9a51ad9049aaf3872fa3499bd1becf
child 61390 5186a60fddfdf62ef9a3c6263fc55485e302085a
push id1
push userroot
push dateTue, 10 Dec 2013 15:46:25 +0000
bugs606966
milestone2.0b10pre
Bug 606966 - Need an async history visit API exposed to JS Part 24 - Dispatch observer topic when done r=mak a=blocking
toolkit/components/places/src/Helpers.cpp
toolkit/components/places/src/Helpers.h
toolkit/components/places/src/History.cpp
toolkit/components/places/src/nsNavHistory.cpp
toolkit/components/places/tests/unit/test_async_history_api.js
--- a/toolkit/components/places/src/Helpers.cpp
+++ b/toolkit/components/places/src/Helpers.cpp
@@ -10,17 +10,17 @@
  * Software distributed under the License is distributed on an "AS IS" basis,
  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  * for the specific language governing rights and limitations under the
  * License.
  *
  * The Original Code is Places code.
  *
  * The Initial Developer of the Original Code is
- * Mozilla Foundation.
+ * the Mozilla Foundation.
  * Portions created by the Initial Developer are Copyright (C) 2009
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Shawn Wilsher <me@shawnwilsher.com> (Original Author)
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
@@ -37,16 +37,17 @@
  * ***** END LICENSE BLOCK ***** */
 
 #include "Helpers.h"
 #include "mozIStorageError.h"
 #include "plbase64.h"
 #include "prio.h"
 #include "nsString.h"
 #include "nsNavHistory.h"
+#include "mozilla/Services.h"
 
 // The length of guids that are used by history and bookmarks.
 #define GUID_LENGTH 12
 
 namespace mozilla {
 namespace places {
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -352,10 +353,62 @@ bool
 GetHiddenState(bool aIsRedirect,
                PRUint32 aTransitionType)
 {
   return aTransitionType == nsINavHistoryService::TRANSITION_FRAMED_LINK ||
          aTransitionType == nsINavHistoryService::TRANSITION_EMBED ||
          aIsRedirect;
 }
 
+////////////////////////////////////////////////////////////////////////////////
+//// PlacesEvent
+
+PlacesEvent::PlacesEvent(const char* aTopic)
+: mTopic(aTopic)
+, mDoubleEnqueue(false)
+{
+}
+
+PlacesEvent::PlacesEvent(const char* aTopic,
+                         bool aDoubleEnqueue)
+: mTopic(aTopic)
+, mDoubleEnqueue(aDoubleEnqueue)
+{
+}
+
+NS_IMETHODIMP
+PlacesEvent::Run()
+{
+  Notify();
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PlacesEvent::Complete()
+{
+  Notify();
+  return NS_OK;
+}
+
+void
+PlacesEvent::Notify()
+{
+  if (mDoubleEnqueue) {
+    mDoubleEnqueue = false;
+    (void)NS_DispatchToMainThread(this);
+  }
+  else {
+    NS_ASSERTION(NS_IsMainThread(), "Must only be used on the main thread!");
+    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+    if (obs) {
+      (void)obs->NotifyObservers(nsnull, mTopic, nsnull);
+    }
+  }
+}
+
+NS_IMPL_THREADSAFE_ISUPPORTS2(
+  PlacesEvent
+, mozIStorageCompletionCallback
+, nsIRunnable
+)
+
 } // namespace places
 } // namespace mozilla
--- a/toolkit/components/places/src/Helpers.h
+++ b/toolkit/components/places/src/Helpers.h
@@ -275,12 +275,32 @@ void ForceWALCheckpoint(mozIStorageConne
  *        True if this visit was a redirect, false otherwise.
  * @param aTransitionType
  *        The transition type of the visit.
  * @return true if this visit should be hidden.
  */
 bool GetHiddenState(bool aIsRedirect,
                     PRUint32 aTransitionType);
 
+/**
+ * Notifies a specified topic via the observer service.
+ */
+class PlacesEvent : public nsRunnable
+                  , public mozIStorageCompletionCallback
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIRUNNABLE
+  NS_DECL_MOZISTORAGECOMPLETIONCALLBACK
+
+  PlacesEvent(const char* aTopic);
+  PlacesEvent(const char* aTopic, bool aDoubleEnqueue);
+protected:
+  void Notify();
+
+  const char* const mTopic;
+  bool mDoubleEnqueue;
+};
+
 } // namespace places
 } // namespace mozilla
 
 #endif // mozilla_places_Helpers_h_
--- a/toolkit/components/places/src/History.cpp
+++ b/toolkit/components/places/src/History.cpp
@@ -57,16 +57,19 @@
 #include "mozilla/Services.h"
 #include "nsThreadUtils.h"
 #include "nsNetUtil.h"
 #include "nsContentUtils.h"
 
 // Initial size for the cache holding visited status observers.
 #define VISIT_OBSERVERS_INITIAL_CACHE_SIZE 128
 
+// Topic used to notify that work in mozIAsyncHistory::updatePlaces is done.
+#define TOPIC_UPDATEPLACES_COMPLETE "places-updatePlaces-complete"
+
 using namespace mozilla::dom;
 
 namespace mozilla {
 namespace places {
 
 ////////////////////////////////////////////////////////////////////////////////
 //// Global Defines
 
@@ -1948,27 +1951,36 @@ History::UpdatePlaces(const jsval& aPlac
       nsCOMPtr<nsIURI> referrer = GetURIFromJSObject(aCtx, visit,
                                                      "referrerURI");
       if (referrer) {
         (void)referrer->GetSpec(data.referrerSpec);
       }
     }
   }
 
+  mozIStorageConnection* dbConn = GetDBConn();
+  NS_ENSURE_STATE(dbConn);
+
   // It is possible that all of the visits we were passed were dissallowed by
   // CanAddURI, which isn't an error.  If we have no visits to add, however,
   // we should not call InsertVisitedURIs::Start.
   if (visitData.Length()) {
-    mozIStorageConnection* dbConn = GetDBConn();
-    NS_ENSURE_STATE(dbConn);
-
     nsresult rv = InsertVisitedURIs::Start(dbConn, visitData, aCallback);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
+  // Be sure to notify that all of our operations are complete.  This is
+  // double enqueued to make sure that all database notifications and all embed
+  // or canAddURI notifications have finished.
+  nsCOMPtr<nsIEventTarget> backgroundThread = do_GetInterface(dbConn);
+  NS_ENSURE_TRUE(backgroundThread, NS_ERROR_UNEXPECTED);
+  nsRefPtr<PlacesEvent> completeEvent =
+    new PlacesEvent(TOPIC_UPDATEPLACES_COMPLETE, true);
+  (void)backgroundThread->Dispatch(completeEvent, 0);
+
   return NS_OK;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 //// nsIObserver
 
 NS_IMETHODIMP
 History::Observe(nsISupports* aSubject, const char* aTopic,
--- a/toolkit/components/places/src/nsNavHistory.cpp
+++ b/toolkit/components/places/src/nsNavHistory.cpp
@@ -311,72 +311,16 @@ public:
   {
     mNavHistory.EndUpdateBatch();
   }
 protected:
   nsNavHistory& mNavHistory;
 };
 
 
-class PlacesEvent : public nsRunnable
-                  , public mozIStorageCompletionCallback
-{
-public:
-  NS_DECL_ISUPPORTS
-
-  PlacesEvent(const char* aTopic)
-    : mTopic(aTopic)
-    , mDoubleEnqueue(false)
-  {
-  }
-
-  PlacesEvent(const char* aTopic,
-              bool aDoubleEnqueue)
-    : mTopic(aTopic)
-    , mDoubleEnqueue(aDoubleEnqueue)
-  {
-  }
-
-  NS_IMETHODIMP Run()
-  {
-    Notify();
-    return NS_OK;
-  }
-
-  NS_IMETHODIMP Complete()
-  {
-    Notify();
-    return NS_OK;
-  }
-
-protected:
-  void Notify()
-  {
-    if (mDoubleEnqueue) {
-      mDoubleEnqueue = false;
-      (void)NS_DispatchToMainThread(this);
-    }
-    else {
-      nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
-      if (obs)
-        (void)obs->NotifyObservers(nsnull, mTopic, nsnull);
-    }
-  }
-
-  const char* const mTopic;
-  bool mDoubleEnqueue;
-};
-
-NS_IMPL_ISUPPORTS2(
-  PlacesEvent
-, mozIStorageCompletionCallback
-, nsIRunnable
-)
-
-
 // Used to notify a topic to system observers on async execute completion.
 class AsyncStatementCallbackNotifier : public AsyncStatementCallback
 {
 public:
   AsyncStatementCallbackNotifier(const char* aTopic)
     : mTopic(aTopic)
   {
   }
--- a/toolkit/components/places/tests/unit/test_async_history_api.js
+++ b/toolkit/components/places/tests/unit/test_async_history_api.js
@@ -11,17 +11,18 @@
 XPCOMUtils.defineLazyServiceGetter(this, "gHistory",
                                    "@mozilla.org/browser/history;1",
                                    "mozIAsyncHistory");
 
 XPCOMUtils.defineLazyServiceGetter(this, "gGlobalHistory",
                                    "@mozilla.org/browser/nav-history-service;1",
                                    "nsIGlobalHistory2");
 
-const TEST_DOMAIN = "http://mozilla.org/"
+const TEST_DOMAIN = "http://mozilla.org/";
+const TOPIC_UPDATEPLACES_COMPLETE = "places-updatePlaces-complete";
 
 ////////////////////////////////////////////////////////////////////////////////
 //// Helpers
 
 /**
  * Object that represents a mozIVisitInfo object.
  *
  * @param [optional] aTransitionType
@@ -402,16 +403,60 @@ function test_non_addable_uri_errors()
 
     // If we have had all of our callbacks, continue running tests.
     if (++callbackCount == places.length) {
       run_next_test();
     }
   });
 }
 
+function test_observer_topic_dispatched_when_complete()
+{
+  // We test a normal visit, and embeded visit, and a uri that would fail
+  // the canAddURI test to make sure that the notification happens after *all*
+  // of them have had a callback.
+  let places = [
+    { uri: NetUtil.newURI(TEST_DOMAIN +
+                          "test_observer_topic_dispatched_when_complete"),
+      visits: [
+        new VisitInfo(),
+        new VisitInfo(TRANSITION_EMBED),
+      ],
+    },
+    { uri: NetUtil.newURI("data:,Hello%2C%20World!"),
+      visits: [
+        new VisitInfo(),
+      ],
+    },
+  ];
+  do_check_false(gGlobalHistory.isVisited(places[0].uri));
+  do_check_false(gGlobalHistory.isVisited(places[1].uri));
+
+  const EXPECTED_COUNT = 3;
+  let callbackCount = 0;
+
+  gHistory.updatePlaces(places, function(aResultCode, aPlaceInfo) {
+    let checker = PlacesUtils.history.canAddURI(aPlaceInfo.uri) ?
+      do_check_true : do_check_false;
+    checker(Components.isSuccessCode(aResultCode));
+    callbackCount++;
+  });
+
+  let observer = {
+    observe: function(aSubject, aTopic, aData)
+    {
+      do_check_eq(aTopic, TOPIC_UPDATEPLACES_COMPLETE);
+      do_check_eq(callbackCount, EXPECTED_COUNT);
+      Services.obs.removeObserver(observer, TOPIC_UPDATEPLACES_COMPLETE);
+      run_next_test();
+    },
+  };
+  Services.obs.addObserver(observer, TOPIC_UPDATEPLACES_COMPLETE, false);
+}
+
 function test_add_visit()
 {
   const VISIT_TIME = Date.now() * 1000;
   let place = {
     uri: NetUtil.newURI(TEST_DOMAIN + "test_add_visit"),
     title: "test_add_visit title",
     visits: [],
   };
@@ -830,16 +875,17 @@ let gTests = [
   test_invalid_places_throws,
   test_invalid_id_throws,
   test_invalid_guid_throws,
   test_no_id_or_guid_no_visits_throws,
   test_add_visit_no_date_throws,
   test_add_visit_no_transitionType_throws,
   test_add_visit_invalid_transitionType_throws,
   test_non_addable_uri_errors,
+  test_observer_topic_dispatched_when_complete,
   test_add_visit,
   test_properties_saved,
   test_guid_saved,
   test_referrer_saved,
   test_sessionId_saved,
   test_guid_change_saved,
   test_title_change_saved,
   test_no_title_does_not_clear_title,