Bug 702810 - mozIAsyncHistory should expose an async isURIVisited method.
authorMarco Bonardo <mbonardo@mozilla.com>
Wed, 30 Nov 2011 00:48:47 +0100
changeset 82617 9bdf6415e2dbb485236f754faba432d13ec834db
parent 82616 bbda472dc34be7538a04998dfee6f335ea113dd0
child 82618 47500d6cbffd73121223dd637ddb29d244b8fb11
push id519
push userakeybl@mozilla.com
push dateWed, 01 Feb 2012 00:38:35 +0000
treeherdermozilla-beta@788ea1ef610b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs702810
milestone11.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 702810 - mozIAsyncHistory should expose an async isURIVisited method. r=dietrich sr=gavin
toolkit/components/places/History.cpp
toolkit/components/places/mozIAsyncHistory.idl
toolkit/components/places/nsNavHistory.cpp
toolkit/components/places/tests/unit/test_isURIVisited.js
toolkit/components/places/tests/unit/xpcshell.ini
--- a/toolkit/components/places/History.cpp
+++ b/toolkit/components/places/History.cpp
@@ -324,34 +324,35 @@ GetJSObjectFromArray(JSContext* aCtx,
   NS_ENSURE_ARG(!JSVAL_IS_PRIMITIVE(value));
   *_rooter = JSVAL_TO_OBJECT(value);
   return NS_OK;
 }
 
 class VisitedQuery : public AsyncStatementCallback
 {
 public:
-  static nsresult Start(nsIURI* aURI)
+  static nsresult Start(nsIURI* aURI,
+                        mozIVisitedStatusCallback* aCallback=nsnull)
   {
     NS_PRECONDITION(aURI, "Null URI");
 
   // If we are a content process, always remote the request to the
   // parent process.
   if (XRE_GetProcessType() == GeckoProcessType_Content) {
     mozilla::dom::ContentChild* cpc =
       mozilla::dom::ContentChild::GetSingleton();
     NS_ASSERTION(cpc, "Content Protocol is NULL!");
     (void)cpc->SendStartVisitedQuery(aURI);
     return NS_OK;
   }
 
     nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
     NS_ENSURE_STATE(navHistory);
     if (navHistory->hasEmbedVisit(aURI)) {
-      nsRefPtr<VisitedQuery> callback = new VisitedQuery(aURI, true);
+      nsRefPtr<VisitedQuery> callback = new VisitedQuery(aURI, aCallback, true);
       NS_ENSURE_TRUE(callback, NS_ERROR_OUT_OF_MEMORY);
       // As per IHistory contract, we must notify asynchronously.
       nsCOMPtr<nsIRunnable> event =
         NS_NewRunnableMethod(callback, &VisitedQuery::NotifyVisitedStatus);
       NS_DispatchToMainThread(event);
 
       return NS_OK;
     }
@@ -360,17 +361,17 @@ public:
     NS_ENSURE_STATE(history);
     mozIStorageAsyncStatement* stmt = history->GetIsVisitedStatement();
     NS_ENSURE_STATE(stmt);
 
     // Bind by index for performance.
     nsresult rv = URIBinder::Bind(stmt, 0, aURI);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    nsRefPtr<VisitedQuery> callback = new VisitedQuery(aURI);
+    nsRefPtr<VisitedQuery> callback = new VisitedQuery(aURI, aCallback);
     NS_ENSURE_TRUE(callback, NS_ERROR_OUT_OF_MEMORY);
 
     nsCOMPtr<mozIStoragePendingStatement> handle;
     return stmt->ExecuteAsync(callback, getter_AddRefs(handle));
   }
 
   NS_IMETHOD HandleResult(mozIStorageResultSet* aResults)
   {
@@ -395,16 +396,22 @@ public:
 
     nsresult rv = NotifyVisitedStatus();
     NS_ENSURE_SUCCESS(rv, rv);
     return NS_OK;
   }
 
   nsresult NotifyVisitedStatus()
   {
+    // If an external handling callback is provided, just notify through it.
+    if (mCallback) {
+      mCallback->IsVisited(mURI, mIsVisited);
+      return NS_OK;
+    }
+
     if (mIsVisited) {
       History* history = History::GetService();
       NS_ENSURE_STATE(history);
       history->NotifyVisited(mURI);
     }
 
     nsCOMPtr<nsIObserverService> observerService =
       mozilla::services::GetObserverService();
@@ -420,23 +427,27 @@ public:
                                              URI_VISITED_RESOLUTION_TOPIC,
                                              status.get());
     }
 
     return NS_OK;
   }
 
 private:
-  VisitedQuery(nsIURI* aURI, bool aIsVisited=false)
+  VisitedQuery(nsIURI* aURI,
+               mozIVisitedStatusCallback *aCallback=nsnull,
+               bool aIsVisited=false)
   : mURI(aURI)
+  , mCallback(aCallback)
   , mIsVisited(aIsVisited)
   {
   }
 
   nsCOMPtr<nsIURI> mURI;
+  nsCOMPtr<mozIVisitedStatusCallback> mCallback;
   bool mIsVisited;
 };
 
 /**
  * Notifies observers about a visit.
  */
 class NotifyVisitObservers : public nsRunnable
 {
@@ -1399,20 +1410,20 @@ History::GetIsVisitedStatement()
     NS_ENSURE_TRUE(dbConn, nsnull);
 
     (void)dbConn->Clone(true, getter_AddRefs(mReadOnlyDBConn));
     NS_ENSURE_TRUE(mReadOnlyDBConn, nsnull);
   }
 
   // Now we can create our cached statement.
   nsresult rv = mReadOnlyDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
-    "SELECT h.id "
+    "SELECT 1 "
     "FROM moz_places h "
     "WHERE url = ?1 "
-      "AND EXISTS(SELECT id FROM moz_historyvisits WHERE place_id = h.id LIMIT 1) "
+      "AND last_visit_date NOTNULL "
   ),  getter_AddRefs(mIsVisitedStatement));
   NS_ENSURE_SUCCESS(rv, nsnull);
   return mIsVisitedStatement;
 }
 
 nsresult
 History::InsertPlace(const VisitData& aPlace)
 {
@@ -2055,16 +2066,30 @@ History::UpdatePlaces(const jsval& aPlac
     NS_ENSURE_TRUE(backgroundThread, NS_ERROR_UNEXPECTED);
     nsCOMPtr<nsIRunnable> event = new NotifyCompletion(aCallback);
     (void)backgroundThread->Dispatch(event, NS_DISPATCH_NORMAL);
   }
 
   return NS_OK;
 }
 
+NS_IMETHODIMP
+History::IsURIVisited(nsIURI* aURI,
+                      mozIVisitedStatusCallback* aCallback)
+{
+  NS_ENSURE_STATE(NS_IsMainThread());
+  NS_ENSURE_ARG(aURI);
+  NS_ENSURE_ARG(aCallback);
+
+  nsresult rv = VisitedQuery::Start(aURI, aCallback);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 //// nsIObserver
 
 NS_IMETHODIMP
 History::Observe(nsISupports* aSubject, const char* aTopic,
                  const PRUnichar* aData)
 {
   if (strcmp(aTopic, TOPIC_PLACES_SHUTDOWN) == 0) {
--- a/toolkit/components/places/mozIAsyncHistory.idl
+++ b/toolkit/components/places/mozIAsyncHistory.idl
@@ -14,17 +14,18 @@
  * The Original Code is Places Async History API.
  *
  * The Initial Developer of the Original Code is
  * the Mozilla Foundation.
  * Portions created by the Initial Developer are Copyright (C) 2010
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
- *   Shawn Wilsher <me@shawnwilsher.com>
+ *   Shawn Wilsher <me@shawnwilsher.com> (Original Author)
+ *   Marco Bonardo <mak77@bonardo.net>
  *
  * 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
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -153,17 +154,35 @@ interface mozIVisitInfoCallback : nsISup
    */
   void handleCompletion();
 
 };
 
 /**
  * @status EXPERIMENTAL
  */
-[scriptable, uuid(f79ca67c-7e57-4511-a400-ea31001c762f)]
+[scriptable, function, uuid(994092bf-936f-449b-8dd6-0941e024360d)]
+interface mozIVisitedStatusCallback : nsISupports
+{
+  /**
+   * Notifies whether a certain URI has been visited.
+   *
+   * @param aURI
+   *        URI being notified about.
+   * @param aVisitedStatus
+   *        The visited status of aURI.
+   */
+  void isVisited(in nsIURI aURI,
+                 in boolean aVisitedStatus);
+};
+
+/**
+ * @status EXPERIMENTAL
+ */
+[scriptable, uuid(b7edc16e-9f3c-4bf5-981b-4e8000b02d89)]
 interface mozIAsyncHistory : nsISupports
 {
   /**
    * Adds a set of visits for one or more mozIPlaceInfo objects, and updates
    * each mozIPlaceInfo's title or guid.
    *
    * @param aPlaceInfo
    *        The mozIPlaceInfo object[s] containing the information to store or
@@ -181,9 +200,19 @@ interface mozIAsyncHistory : nsISupports
    *         - Not providing a visitDate and transitionType for each
    *           mozIVisitInfo.
    *         - Providing an invalid transitionType for a mozIVisitInfo.
    */
   [implicit_jscontext]
   void updatePlaces(in jsval aPlaceInfo,
                     [optional] in mozIVisitInfoCallback aCallback);
 
+  /**
+   * Checks if a given URI has been visited.
+   *
+   * @param aURI
+   *        The URI to check for.
+   * @param aCallback
+   *        A mozIVisitStatusCallback object which receives the visited status.
+   */
+  void isURIVisited(in nsIURI aURI,
+                    in mozIVisitedStatusCallback aCallback);
 };
--- a/toolkit/components/places/nsNavHistory.cpp
+++ b/toolkit/components/places/nsNavHistory.cpp
@@ -715,20 +715,20 @@ nsNavHistory::FindLastVisit(nsIURI* aURI
 //
 //    Takes a URL as a string and returns true if we've visited it.
 //
 //    Be careful to always reset the statement since it will be reused.
 
 bool nsNavHistory::IsURIStringVisited(const nsACString& aURIString)
 {
   nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
-    "SELECT h.id "
+    "SELECT 1 "
     "FROM moz_places h "
     "WHERE url = ?1 "
-      "AND EXISTS(SELECT id FROM moz_historyvisits WHERE place_id = h.id LIMIT 1) "
+      "AND last_visit_date NOTNULL "
   );
   NS_ENSURE_TRUE(stmt, false);
   mozStorageStatementScoper scoper(stmt);
 
   nsresult rv = URIBinder::Bind(stmt, 0, aURIString);
   NS_ENSURE_SUCCESS(rv, false);
 
   bool hasMore = false;
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/unit/test_isURIVisited.js
@@ -0,0 +1,93 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests functionality of the isURIVisited API.
+
+const SCHEMES = {
+  "http://": true,
+  "https://": true,
+  "ftp://": true,
+  "file:///": true,
+  "about:": false,
+  "imap://": false,
+  "news://": false,
+  "mailbox:": false,
+  "moz-anno:favicon:http://": false,
+  "view-source:http://": false,
+  "chrome://browser/content/browser.xul?": false,
+  "resource://": false,
+  "data:,": false,
+  "wyciwyg:/0/http://": false,
+  "javascript:": false,
+};
+
+const TRANSITIONS = [
+  TRANSITION_LINK,
+  TRANSITION_TYPED,
+  TRANSITION_BOOKMARK,
+  TRANSITION_EMBED,
+  TRANSITION_FRAMED_LINK,
+  TRANSITION_REDIRECT_PERMANENT,
+  TRANSITION_REDIRECT_TEMPORARY,
+  TRANSITION_DOWNLOAD,
+];
+
+let gRunner;
+function run_test()
+{
+  do_test_pending();
+  gRunner = step();
+  gRunner.next();
+}
+
+function step()
+{
+  let history = Cc["@mozilla.org/browser/history;1"]
+                  .getService(Ci.mozIAsyncHistory);
+
+  for (let scheme in SCHEMES) {
+    do_log_info("Testing scheme " + scheme);
+    for (let i = 0; i < TRANSITIONS.length; i++) {
+      let transition = TRANSITIONS[i];
+      do_log_info("With transition " + transition);
+
+      let uri = NetUtil.newURI(scheme + "mozilla.org/");
+
+      history.isURIVisited(uri, function(aURI, aIsVisited) {
+        do_check_true(uri.equals(aURI));
+        do_check_false(aIsVisited);
+
+        let callback = {
+          handleError:  function () {},
+          handleResult: function () {},
+          handleCompletion: function () {
+            do_log_info("Added visit to " + uri.spec);
+
+            history.isURIVisited(uri, function (aURI, aIsVisited) {
+              do_check_true(uri.equals(aURI));
+              let checker = SCHEMES[scheme] ? do_check_true : do_check_false;
+              checker(aIsVisited);
+
+              waitForClearHistory(function () {
+                history.isURIVisited(uri, function(aURI, aIsVisited) {
+                  do_check_true(uri.equals(aURI));
+                  do_check_false(aIsVisited);
+                  gRunner.next();
+                });
+              });
+            });
+          },
+        };
+
+        history.updatePlaces({ uri:    uri
+                             , visits: [ { transitionType: transition
+                                         , visitDate:      Date.now() * 1000
+                                         } ]
+                             }, callback);
+      });
+      yield;
+    }
+  }
+
+  do_test_finished();
+}
--- a/toolkit/components/places/tests/unit/xpcshell.ini
+++ b/toolkit/components/places/tests/unit/xpcshell.ini
@@ -82,16 +82,17 @@ skip-if = os == "android"
 [test_history_autocomplete_tags.js]
 [test_history_catobs.js]
 [test_history_notifications.js]
 [test_history_observer.js]
 [test_history_removeAllPages.js]
 # Bug 676989: test hangs consistently on Android
 skip-if = os == "android"
 [test_history_sidebar.js]
+[test_isURIVisited.js]
 [test_isvisited.js]
 [test_lastModified.js]
 [test_livemarkService_getLivemarkIdForFeedURI.js]
 [test_markpageas.js]
 [test_moz-anno_favicon_mime_type.js]
 [test_multi_queries.js]
 # Bug 676989: test fails consistently on Android
 fail-if = os == "android"