Bug 582703 - Improve the concurrency of location bar searches.
authorShawn Wilsher <me@shawnwilsher.com>, Marco Bonardo <mbonardo@mozilla.com>
Sat, 20 Nov 2010 01:39:55 +0100
changeset 59347 f118db7595c60b053b78d58cc64d97968cfcd556
parent 59346 202efe694803d3a3b1061d11a8134ecc6bb358d6
child 59348 ac0b81efdb004863874b237de25a2c8a5e0a0598
push id1
push usershaver@mozilla.com
push dateTue, 04 Jan 2011 17:58:04 +0000
bugs582703
milestone2.0b8pre
Bug 582703 - Improve the concurrency of location bar searches. Initial patch from sdwilsh is r=mak, further changes from mak are r=sdwilsh. switch-to-tab changes are r=unfocused sr=rstrong a=blocking
browser/base/content/tabbrowser.xml
browser/base/content/test/browser_tabMatchesInAwesomebar.js
toolkit/components/places/public/mozIPlacesAutoComplete.idl
toolkit/components/places/public/nsIBrowserHistory.idl
toolkit/components/places/src/SQLFunctions.h
toolkit/components/places/src/nsNavHistory.cpp
toolkit/components/places/src/nsNavHistory.h
toolkit/components/places/src/nsPlacesAutoComplete.js
toolkit/components/places/src/nsPlacesTables.h
toolkit/components/places/src/nsPlacesTriggers.h
toolkit/components/places/tests/autocomplete/test_tabmatches.js
toolkit/components/places/tests/head_common.js
toolkit/components/places/tests/unit/test_000_frecency.js
toolkit/components/places/tests/unit/test_frecency.js
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -100,19 +100,19 @@
       <field name="mURIFixup" readonly="true">
         Components.classes["@mozilla.org/docshell/urifixup;1"]
                   .getService(Components.interfaces.nsIURIFixup);
       </field>
       <field name="mFaviconService" readonly="true">
         Components.classes["@mozilla.org/browser/favicon-service;1"]
                   .getService(Components.interfaces.nsIFaviconService);
       </field>
-      <field name="mBrowserHistory" readonly="true">
-        Components.classes["@mozilla.org/browser/nav-history-service;1"]
-                  .getService(Components.interfaces.nsIBrowserHistory);
+      <field name="mPlacesAutocomplete" readonly="true">
+         Components.classes["@mozilla.org/autocomplete/search;1?name=history"]
+                   .getService(Components.interfaces.mozIPlacesAutoComplete);
       </field>
       <field name="mTabBox" readonly="true">
         document.getAnonymousElementByAttribute(this, "anonid", "tabbox");
       </field>
       <field name="mPanelContainer" readonly="true">
         document.getAnonymousElementByAttribute(this, "anonid", "panelcontainer");
       </field>
       <field name="mStringBundle">
@@ -514,23 +514,23 @@
                 this.mBrowser.missingPlugins = null;
 
                 // Don't clear the favicon if this onLocationChange was
                 // triggered by a pushState or a replaceState.  See bug 550565.
                 if (aWebProgress.isLoadingDocument &&
                     !(this.mBrowser.docShell.loadType & Ci.nsIDocShell.LOAD_CMD_PUSHSTATE))
                   this.mBrowser.mIconURL = null;
 
-                let browserHistory = this.mTabBrowser.mBrowserHistory;
+                let autocomplete = this.mTabBrowser.mPlacesAutocomplete;
                 if (this.mBrowser.registeredOpenURI) {
-                  browserHistory.unregisterOpenPage(this.mBrowser.registeredOpenURI);
+                  autocomplete.unregisterOpenPage(this.mBrowser.registeredOpenURI);
                   delete this.mBrowser.registeredOpenURI;
                 }
                 if (aLocation.spec != "about:blank") {
-                  browserHistory.registerOpenPage(aLocation);
+                  autocomplete.registerOpenPage(aLocation);
                   this.mBrowser.registeredOpenURI = aLocation;
                 }
               }
 
               if (!this.mBlank) {
                 this._callProgressListeners("onLocationChange",
                                             [aWebProgress, aRequest, aLocation]);
               }
@@ -1463,17 +1463,17 @@
 
             // Remove the tab's filter and progress listener.
             const filter = this.mTabFilters[aTab._tPos];
             browser.webProgress.removeProgressListener(filter);
             filter.removeProgressListener(this.mTabListeners[aTab._tPos]);
             this.mTabListeners[aTab._tPos].destroy();
 
             if (browser.registeredOpenURI && !aTabWillBeMoved) {
-              this.mBrowserHistory.unregisterOpenPage(browser.registeredOpenURI);
+              this.mPlacesAutocomplete.unregisterOpenPage(browser.registeredOpenURI);
               delete browser.registeredOpenURI;
             }
 
             // We are no longer the primary content area.
             browser.setAttribute("type", "content-targetable");
 
             // Remove this tab as the owner of any other tabs, since it's going away.
             Array.forEach(this.tabs, function (tab) {
@@ -1667,20 +1667,24 @@
             var ourBrowser = this.getBrowserForTab(aOurTab);
             ourBrowser.webProgress.removeProgressListener(filter);
             filter.removeProgressListener(tabListener);
             var tabListenerBlank = tabListener.mBlank;
 
             var otherBrowser = aOtherTab.linkedBrowser;
 
             // Restore current registered open URI.
-            if (ourBrowser.registeredOpenURI)
-              this.mBrowserHistory.unregisterOpenPage(ourBrowser.registeredOpenURI);
-            if (otherBrowser.registeredOpenURI)
+            if (ourBrowser.registeredOpenURI) {
+              this.mPlacesAutocomplete.unregisterOpenPage(ourBrowser.registeredOpenURI);
+              delete ourBrowser.registeredOpenURI;
+            }
+            if (otherBrowser.registeredOpenURI) {
               ourBrowser.registeredOpenURI = otherBrowser.registeredOpenURI;
+              delete otherBrowser.registeredOpenURI;
+            }
 
             // Workarounds for bug 458697
             // Icon might have been set on DOMLinkAdded, don't override that.
             if (!ourBrowser.mIconURL && otherBrowser.mIconURL)
               this.setIcon(aOurTab, otherBrowser.mIconURL);
             var isBusy = aOtherTab.hasAttribute("busy");
             if (isBusy) {
               aOurTab.setAttribute("busy", "true");
@@ -2368,17 +2372,17 @@
         ]]>
       </constructor>
 
       <destructor>
         <![CDATA[
           for (var i = 0; i < this.mTabListeners.length; ++i) {
             let browser = this.getBrowserAtIndex(i);
             if (browser.registeredOpenURI) {
-              this.mBrowserHistory.unregisterOpenPage(browser.registeredOpenURI);
+              this.mPlacesAutocomplete.unregisterOpenPage(browser.registeredOpenURI);
               delete browser.registeredOpenURI;
             }
             browser.webProgress.removeProgressListener(this.mTabFilters[i]);
             this.mTabFilters[i].removeProgressListener(this.mTabListeners[i]);
             this.mTabFilters[i] = null;
             this.mTabListeners[i].destroy();
             this.mTabListeners[i] = null;
           }
--- a/browser/base/content/test/browser_tabMatchesInAwesomebar.js
+++ b/browser/base/content/test/browser_tabMatchesInAwesomebar.js
@@ -40,17 +40,18 @@ Components.utils.import("resource://gre/
 
 const TEST_URL_BASES = [
   "http://example.org/browser/browser/base/content/test/dummy_page.html#tabmatch",
   "http://example.org/browser/browser/base/content/test/moz.png#tabmatch"
 ];
 
 var gPrivateBrowsing = Cc["@mozilla.org/privatebrowsing;1"].
                          getService(Ci.nsIPrivateBrowsingService);
-
+var gController = Cc["@mozilla.org/autocomplete/controller;1"].
+                  getService(Ci.nsIAutoCompleteController);
 
 var gTabWaitCount = 0;
 var gTabCounter = 0;
 
 var gTestSteps = [
   function() {
     info("Running step 1");
     for (let i = 0; i < 10; i++) {
@@ -76,105 +77,104 @@ var gTestSteps = [
     info("Running step 4");
     let ps = Services.prefs;
     ps.setBoolPref("browser.privatebrowsing.keep_current_session", true);
     ps.setBoolPref("browser.tabs.warnOnClose", false);
 
     gPrivateBrowsing.privateBrowsingEnabled = true;
 
     executeSoon(function() {
-      ensure_opentabs_match_db();
-      nextStep();
+      ensure_opentabs_match_db(nextStep);
     });
   },
   function() {
     info("Running step 5");
     gPrivateBrowsing.privateBrowsingEnabled = false;
 
     executeSoon(function() {
       let ps = Services.prefs;
       try {
         ps.clearUserPref("browser.privatebrowsing.keep_current_session");
       } catch (ex) {}
       try {
         ps.clearUserPref("browser.tabs.warnOnClose");
       } catch (ex) {}
 
-      ensure_opentabs_match_db();
-      nextStep()
+      ensure_opentabs_match_db(nextStep);
     });
   },
   function() {
     info("Running step 6 - ensure we don't register subframes as open pages");
     let tab = gBrowser.addTab();
     tab.linkedBrowser.addEventListener("load", function () {
       tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
       // Start the sub-document load.
       executeSoon(function () {
         tab.linkedBrowser.addEventListener("load", function (e) {
           tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
-            ensure_opentabs_match_db();
-            nextStep()
+            ensure_opentabs_match_db(nextStep);
         }, true);
         tab.linkedBrowser.contentDocument.querySelector("iframe").src = "http://test2.example.org/";
       });
     }, true);
     tab.linkedBrowser.loadURI('data:text/html,<body><iframe src=""></iframe></body>');
   },
   function() {
     info("Running step 7 - remove tab immediately");
     let tab = gBrowser.addTab("about:logo");
     gBrowser.removeTab(tab);
-    ensure_opentabs_match_db();
-    nextStep();
+    ensure_opentabs_match_db(nextStep);
   },
   function() {
     info("Running step 8 - check swapBrowsersAndCloseOther preserves registered switch-to-tab result");
     let tabToKeep = gBrowser.addTab();
     let tab = gBrowser.addTab();
     tab.linkedBrowser.addEventListener("load", function () {
       tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
       gBrowser.swapBrowsersAndCloseOther(tabToKeep, tab);
-      ensure_opentabs_match_db();
-      gBrowser.removeTab(tabToKeep);
-      ensure_opentabs_match_db();
-      nextStep();
+      ensure_opentabs_match_db(function () {
+        gBrowser.removeTab(tabToKeep);
+        ensure_opentabs_match_db(nextStep);
+      });
     }, true);
     tab.linkedBrowser.loadURI('about:robots');
   },
 ];
 
 
 
 function test() {
   waitForExplicitFinish();
   nextStep();
 }
 
 function loadTab(tab, url) {
   // Because adding visits is async, we will not be notified immediately.
   let visited = false;
+  let loaded = false;
+
+  function maybeCheckResults() {
+    if (visited && loaded && --gTabWaitCount == 0) {
+      ensure_opentabs_match_db(nextStep);
+    }
+  }
+
+  tab.linkedBrowser.addEventListener("load", function () {
+    tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
+    loaded = true;
+    maybeCheckResults();
+  }, true);
 
   Services.obs.addObserver(
     function (aSubject, aTopic, aData) {
       if (url != aSubject.QueryInterface(Ci.nsIURI).spec)
         return;
       Services.obs.removeObserver(arguments.callee, aTopic);
-      if (--gTabWaitCount > 0)
-        return;
-      is(gTabWaitCount, 0,
-         "sanity check, gTabWaitCount should not be decremented below 0");
-
-      try {
-        ensure_opentabs_match_db();
-      } catch (e) {
-        ok(false, "exception from ensure_openpages_match_db: " + e);
-      }
-
-      executeSoon(nextStep);
+      visited = true;
+      maybeCheckResults();
     },
     "uri-visit-saved",
     false
   );
 
   gTabWaitCount++;
   tab.linkedBrowser.loadURI(url);
 }
@@ -190,17 +190,17 @@ function nextStep() {
 
     return;
   }
 
   var stepFunc = gTestSteps.shift();
   stepFunc();
 }
 
-function ensure_opentabs_match_db() {
+function ensure_opentabs_match_db(aCallback) {
   var tabs = {};
 
   var winEnum = Services.wm.getEnumerator("navigator:browser");
   while (winEnum.hasMoreElements()) {
     let browserWin = winEnum.getNext();
     // skip closed-but-not-destroyed windows
     if (browserWin.closed)
       continue;
@@ -212,63 +212,73 @@ function ensure_opentabs_match_db() {
         continue;
       if (!(url in tabs))
         tabs[url] = 1;
       else
         tabs[url]++;
     }
   }
 
-  var db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
-                              .DBConnection;
-
-  try {
-    var stmt = db.createStatement(
-                          "SELECT t.url, open_count, p.id " +
-                          "FROM moz_openpages_temp t " +
-                          "LEFT JOIN moz_places p ON p.url = t.url ");
-  } catch (e) {
-    ok(false, "error creating db statement: " + e);
-    return;
-  }
-
-  var dbtabs = [];
-  try {
-    while (stmt.executeStep()) {
-      ok(stmt.row.url in tabs,
-         "url is in db, should be in tab: " + stmt.row.url);
-      is(tabs[stmt.row.url], stmt.row.open_count,
-         "db count (" + stmt.row.open_count + ") " +
-         "should match actual open tab count " +
-         "(" + tabs[stmt.row.url] + "): " + stmt.row.url);
-      dbtabs.push(stmt.row.url);
-    }
-  } finally {
-    stmt.finalize();
-  }
-
-  for (let url in tabs) {
-    ok(dbtabs.indexOf(url) > -1,
-       "tab is open (" + tabs[url] + " times) and should recorded in db: " + url);
-  }
-  dbtabs.forEach(function (url) {
-    ok(url in tabs,
-       "db-recorded tab should actually exist: " + url);
-  });
+  checkAutocompleteResults(tabs, aCallback);
 }
 
 /**
  * Clears history invoking callback when done.
  */
 function waitForClearHistory(aCallback) {
   const TOPIC_EXPIRATION_FINISHED = "places-expiration-finished";
   let observer = {
     observe: function(aSubject, aTopic, aData) {
       Services.obs.removeObserver(this, TOPIC_EXPIRATION_FINISHED);
       aCallback();
     }
   };
   Services.obs.addObserver(observer, TOPIC_EXPIRATION_FINISHED, false);
 
-  let hs = Cc["@mozilla.org/browser/nav-history-service;1"].
-           getService(Ci.nsINavHistoryService);
-  hs.QueryInterface(Ci.nsIBrowserHistory).removeAllPages();
+  PlacesUtils.bhistory.removeAllPages();
 }
+
+function checkAutocompleteResults(aExpected, aCallback)
+{
+  gController.input = {
+    timeout: 10,
+    textValue: "",
+    searches: ["history"],
+    searchParam: "enable-actions",
+    popupOpen: false,
+    minResultsForPopup: 0,
+    invalidate: function() {},
+    disableAutoComplete: false,
+    completeDefaultIndex: false,
+    get popup() { return this; },
+    onSearchBegin: function() {},
+    onSearchComplete:  function ()
+    {
+      info("Found " + gController.matchCount + " matches.");
+      // Check to see the expected uris and titles match up (in any order)
+      for (let i = 0; i < gController.matchCount; i++) {
+        let uri = gController.getValueAt(i).replace(/^moz-action:[^,]+,/i, "");
+
+        info("Search for '" + uri + "' in open tabs.");
+        ok(uri in aExpected, "Registered open page found in autocomplete.");
+        // Remove the found entry from expected results.
+        delete aExpected[uri];
+      }
+
+      // Make sure there is no reported open page that is not open.
+      for (let entry in aExpected) {
+        ok(false, "'" + entry + "' not found in autocomplete.");
+      }
+
+      executeSoon(aCallback);
+    },
+    setSelectedIndex: function() {},
+    get searchCount() { return this.searches.length; },
+    getSearchAt: function(aIndex) this.searches[aIndex],
+    QueryInterface: XPCOMUtils.generateQI([
+      Ci.nsIAutoCompleteInput,
+      Ci.nsIAutoCompletePopup,
+    ])
+  };
+
+  info("Searching open pages.");
+  gController.startSearch(Services.prefs.getCharPref("browser.urlbar.restrict.openpage"));
+}
--- a/toolkit/components/places/public/mozIPlacesAutoComplete.idl
+++ b/toolkit/components/places/public/mozIPlacesAutoComplete.idl
@@ -11,17 +11,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 Corporation.
+ * 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
@@ -34,21 +34,24 @@
  * 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 ***** */
 
 #include "nsISupports.idl"
 
+interface nsIURI;
+
 /**
  * This interface provides some constants used by the Places AutoComplete
- * search provider.
+ * search provider as well as methods to track opened pages for AutoComplete
+ * purposes.
  */
-[scriptable, uuid(a5ae8332-333c-412a-bb02-a35df8247714)]
+[scriptable, uuid(3bf895a0-d6d9-4ba7-b8db-f2f0e0f32d23)]
 interface mozIPlacesAutoComplete : nsISupports
 {
   //////////////////////////////////////////////////////////////////////////////
   //// Matching Constants
 
   /**
    * Match anywhere in each searchable term.
    */
@@ -105,15 +108,37 @@ interface mozIPlacesAutoComplete : nsISu
 
   /**
    * Search javascript: URLs.
    */
   const long BEHAVIOR_JAVASCRIPT = 1 << 6;
 
   /**
    * Search for pages that have been marked as being opened, such as a tab
-   * in a tabbrowser, via:
-   *   nsIBrowserHistory.registerOpenPage(url)
-   *
-   * @see nsIBrowserHistory.idl
+   * in a tabbrowser.
    */
   const long BEHAVIOR_OPENPAGE = 1 << 7;
+
+  /**
+   * Mark a page as being currently open.
+   *
+   * @note Pages will not be automatically unregistered when Private Browsing
+   *       mode is entered or exited.  Therefore, consumers MUST unregister or
+   *       register themselves.
+   *
+   * @param aURI
+   *        The URI to register as an open page.
+   */
+  void registerOpenPage(in nsIURI aURI);
+
+  /**
+   * Mark a page as no longer being open (either by closing the window or tab,
+   * or by navigating away from that page).
+   *
+   * @note Pages will not be automatically unregistered when Private Browsing
+   *       mode is entered or exited.  Therefore, consumers MUST unregister or
+   *       register themselves.
+   *
+   * @param aURI
+   *        The URI to unregister as an open page.
+   */
+  void unregisterOpenPage(in nsIURI aURI);
 };
--- a/toolkit/components/places/public/nsIBrowserHistory.idl
+++ b/toolkit/components/places/public/nsIBrowserHistory.idl
@@ -154,20 +154,34 @@ interface nsIBrowserHistory : nsIGlobalH
      *
      * Designate the url as coming from a link explicitly followed by
      * the user (for example by clicking on it).
      */
     void markPageAsFollowedLink(in nsIURI aURI);
 
     /**
      * Mark a page as being currently open.
+     *
+     * @note Pages will not be automatically unregistered when Private Browsing
+     *       mode is entered or exited.  Therefore, consumers MUST unregister or
+     *       register themselves.
+     *
+     * @note This is just an alias for mozIPlacesAutoComplete::registerOpenPage.
+     *
+     * @status DEPRECATED
      */
-    void registerOpenPage(in nsIURI aURI);
+    [deprecated] void registerOpenPage(in nsIURI aURI);
 
     /**
      * Mark a page as no longer being open (either by closing the window or tab,
      * or by navigating away from that page).
      *
-     * Note that when Private Browsing mode is entered/exited, pages need to be
-     * manually unregistered/registered.
+     * @note Pages will not be automatically unregistered when Private Browsing
+     *       mode is entered or exited.  Therefore, consumers MUST unregister or
+     *       register themselves.
+     *
+     * @note This is just an alias for
+     *       mozIPlacesAutoComplete::unregisterOpenPage.
+     *
+     * @status DEPRECATED
      */
-    void unregisterOpenPage(in nsIURI aURI);
+    [deprecated] void unregisterOpenPage(in nsIURI aURI);
 };
--- a/toolkit/components/places/src/SQLFunctions.h
+++ b/toolkit/components/places/src/SQLFunctions.h
@@ -75,17 +75,17 @@ namespace places {
  * @param aVisitCount
  *        The number of visits aURL has.
  * @param aTyped
  *        Indicates if aURL is a typed URL or not.  Treated as a boolean.
  * @param aBookmark
  *        Indicates if aURL is a bookmark or not.  Treated as a boolean.
  * @param aOpenPageCount
  *        The number of times aURL has been registered as being open.  (See
- *        nsIBrowserHistory.registerOpenPage.)
+ *        mozIPlacesAutoComplete::registerOpenPage.)
  * @param aMatchBehavior
  *        The match behavior to use for this search.
  * @param aSearchBehavior
  *        A bitfield dictating the search behavior.
  */
 class MatchAutoCompleteFunction : public mozIStorageFunction
 {
 public:
--- a/toolkit/components/places/src/nsNavHistory.cpp
+++ b/toolkit/components/places/src/nsNavHistory.cpp
@@ -61,16 +61,17 @@
 #include "prtime.h"
 #include "nsEscape.h"
 #include "nsIEffectiveTLDService.h"
 #include "nsIClassInfoImpl.h"
 #include "nsThreadUtils.h"
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsMathUtils.h"
 #include "mozIStorageAsyncStatement.h"
+#include "mozIPlacesAutoComplete.h"
 
 #include "nsNavBookmarks.h"
 #include "nsAnnotationService.h"
 #include "nsILivemarkService.h"
 #include "nsFaviconService.h"
 
 #include "nsPlacesTables.h"
 #include "nsPlacesIndexes.h"
@@ -984,19 +985,17 @@ nsNavHistory::InitDB()
 
   return NS_OK;
 }
 
 
 nsresult
 nsNavHistory::InitAdditionalDBItems()
 {
-  nsresult rv = InitTempTables();
-  NS_ENSURE_SUCCESS(rv, rv);
-  rv = InitFunctions();
+  nsresult rv = InitFunctions();
   NS_ENSURE_SUCCESS(rv, rv);
 
   // These statements are used by frecency calculation.  Since frecency runs in
   // the storage async thread, it needs to access them through a const history
   // object, for thread-safety.  Due to const correctness it's not possible for
   // the statements getter to lazily initialize them on first use, thus they
   // are initialized here.
   (void*)GetStatement(mDBPageInfoForFrecency);
@@ -1084,27 +1083,16 @@ mozStorageFunctionGetUnreversedHost::OnF
   } else {
     result->SetAsAString(EmptyString());
   }
   NS_ADDREF(*_retval = result);
   return NS_OK;
 }
 
 nsresult
-nsNavHistory::InitTempTables()
-{
-  nsresult rv = mDBConn->ExecuteSimpleSQL(CREATE_MOZ_OPENPAGES_TEMP);
-  NS_ENSURE_SUCCESS(rv, rv);
-  rv = mDBConn->ExecuteSimpleSQL(CREATE_REMOVEOPENPAGE_CLEANUP_TRIGGER);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  return NS_OK;
-}
-
-nsresult
 nsNavHistory::InitFunctions()
 {
   nsCOMPtr<mozIStorageFunction> func =
     new mozStorageFunctionGetUnreversedHost;
   NS_ENSURE_TRUE(func, NS_ERROR_OUT_OF_MEMORY);
   nsresult rv = mDBConn->CreateFunction(
     NS_LITERAL_CSTRING("get_unreversed_host"), 1, func
   );
@@ -1208,32 +1196,16 @@ nsNavHistory::GetStatement(const nsCOMPt
   ));
 
   RETURN_IF_STMT(mDBSetPlaceTitle, NS_LITERAL_CSTRING(
     "UPDATE moz_places "
     "SET title = :page_title "
     "WHERE url = :page_url "
   ));
 
-  RETURN_IF_STMT(mDBRegisterOpenPage, NS_LITERAL_CSTRING(
-      "INSERT OR REPLACE INTO moz_openpages_temp (url, open_count) "
-      "VALUES (:page_url, "
-        "IFNULL("
-          "(SELECT open_count + 1 FROM moz_openpages_temp WHERE url = :page_url), "
-          "1"
-        ")"
-      ")"
-  ));
-
-  RETURN_IF_STMT(mDBUnregisterOpenPage, NS_LITERAL_CSTRING(
-      "UPDATE moz_openpages_temp "
-      "SET open_count = open_count - 1 "
-      "WHERE url = :page_url"
-  ));
-
   // NOTE: This is not limited to visits with "visit_type NOT IN (0,4,7,8)"
   // because otherwise mDBVisitsForFrecency would return no visits
   // for places with only embed (or undefined) visits.  That would
   // cause an incorrect frecency, see CalculateFrecencyInternal().
   // In such a case a place with only EMBED visits would result in a non-zero
   // frecency.
   // In case of a temporary or permanent redirect, calculate the frecency as if
   // the original page was visited.
@@ -4795,48 +4767,38 @@ nsNavHistory::MarkPageAsFollowedLink(nsI
 
 
 NS_IMETHODIMP
 nsNavHistory::RegisterOpenPage(nsIURI* aURI)
 {
   NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
   NS_ENSURE_ARG(aURI);
 
-  // Don't add any pages while in Private Browsing mode, so as to avoid leaking
-  // information about other windows that might otherwise stay hidden
-  // and private.
-  if (InPrivateBrowsingMode())
-    return NS_OK;
-
-  DECLARE_AND_ASSIGN_SCOPED_LAZY_STMT(stmt, mDBRegisterOpenPage);
-  nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI);
-  NS_ENSURE_SUCCESS(rv, rv);
-  rv = stmt->Execute();
+  nsCOMPtr<mozIPlacesAutoComplete> ac =
+    do_GetService("@mozilla.org/autocomplete/search;1?name=history");
+  NS_ENSURE_STATE(ac);
+
+  nsresult rv = ac->RegisterOpenPage(aURI);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 
 NS_IMETHODIMP
 nsNavHistory::UnregisterOpenPage(nsIURI* aURI)
 {
   NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
   NS_ENSURE_ARG(aURI);
 
-  // Entering Private Browsing mode will unregister all open pages, therefore
-  // there shouldn't be anything in the moz_openpages_temp table. So we can stop
-  // now without doing any unnecessary work.
-  if (InPrivateBrowsingMode())
-    return NS_OK;
-
-  DECLARE_AND_ASSIGN_SCOPED_LAZY_STMT(stmt, mDBUnregisterOpenPage);
-  nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI);
-  NS_ENSURE_SUCCESS(rv, rv);
-  rv = stmt->Execute();
+  nsCOMPtr<mozIPlacesAutoComplete> ac =
+    do_GetService("@mozilla.org/autocomplete/search;1?name=history");
+  NS_ENSURE_STATE(ac);
+
+  nsresult rv = ac->UnregisterOpenPage(aURI);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 
 // nsNavHistory::SetCharsetForURI
 //
@@ -7364,18 +7326,16 @@ nsNavHistory::FinalizeStatements() {
     mDBVisitToURLResult,
     mDBVisitToVisitResult,
     mDBBookmarkToUrlResult,
     mDBVisitsForFrecency,
     mDBUpdateFrecency,
     mDBUpdateHiddenOnFrecency,
     mDBGetPlaceVisitStats,
     mDBPageInfoForFrecency,
-    mDBRegisterOpenPage,
-    mDBUnregisterOpenPage,
     mDBAsyncThreadPageInfoForFrecency,
     mDBAsyncThreadVisitsForFrecency,
   };
 
   for (PRUint32 i = 0; i < NS_ARRAY_LENGTH(stmts); i++) {
     nsresult rv = nsNavHistory::FinalizeStatement(stmts[i]);
     NS_ENSURE_SUCCESS(rv, rv);
   }
--- a/toolkit/components/places/src/nsNavHistory.h
+++ b/toolkit/components/places/src/nsNavHistory.h
@@ -641,18 +641,16 @@ protected:
   nsCOMPtr<mozIStorageStatement> mDBInsertVisit; // used by AddVisit
   nsCOMPtr<mozIStorageStatement> mDBGetPageVisitStats; // used by AddVisit
   nsCOMPtr<mozIStorageStatement> mDBIsPageVisited; // used by IsURIStringVisited
   nsCOMPtr<mozIStorageStatement> mDBUpdatePageVisitStats; // used by AddVisit
   nsCOMPtr<mozIStorageStatement> mDBAddNewPage; // used by InternalAddNewPage
   nsCOMPtr<mozIStorageStatement> mDBGetTags; // used by GetTags
   nsCOMPtr<mozIStorageStatement> mDBGetItemsWithAnno; // used by AutoComplete::StartSearch and FilterResultSet
   nsCOMPtr<mozIStorageStatement> mDBSetPlaceTitle; // used by SetPageTitleInternal
-  nsCOMPtr<mozIStorageStatement> mDBRegisterOpenPage; // used by RegisterOpenPage
-  nsCOMPtr<mozIStorageStatement> mDBUnregisterOpenPage; // used by UnregisterOpenPage
   nsCOMPtr<mozIStorageStatement> mDBVisitToURLResult; // kGetInfoIndex_* results
   nsCOMPtr<mozIStorageStatement> mDBVisitToVisitResult; // kGetInfoIndex_* results
   nsCOMPtr<mozIStorageStatement> mDBBookmarkToUrlResult; // kGetInfoIndex_* results
   nsCOMPtr<mozIStorageStatement> mDBUpdateFrecency;
   nsCOMPtr<mozIStorageStatement> mDBUpdateHiddenOnFrecency;
   nsCOMPtr<mozIStorageStatement> mDBGetPlaceVisitStats;
   // Cached statements used in frecency calculation.  Since it could happen on
   // both main thread or storage async thread, we keep two versions of them
@@ -709,21 +707,20 @@ protected:
    * Initializes the database.  This performs any necessary migrations for the
    * database.  All migration is done inside a transaction that is rolled back
    * if any error occurs.  Upon initialization, history is imported, and some
    * preferences that are used are set.
    */
   nsresult InitDB();
 
   /**
-   * Initializes additional database items like: views, temp tables, functions
-   * and statements.
+   * Initializes additional database items like functions, triggers, and
+   * statements.
    */
   nsresult InitAdditionalDBItems();
-  nsresult InitTempTables();
   nsresult InitFunctions();
   nsresult InitStatements();
   nsresult ForceMigrateBookmarksDB(mozIStorageConnection *aDBConn);
   nsresult MigrateV3Up(mozIStorageConnection *aDBConn);
   nsresult MigrateV6Up(mozIStorageConnection *aDBConn);
   nsresult MigrateV7Up(mozIStorageConnection *aDBConn);
   nsresult MigrateV8Up(mozIStorageConnection *aDBConn);
   nsresult MigrateV9Up(mozIStorageConnection *aDBConn);
--- a/toolkit/components/places/src/nsPlacesAutoComplete.js
+++ b/toolkit/components/places/src/nsPlacesAutoComplete.js
@@ -11,17 +11,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 mozilla.org code.
  *
  * The Initial Developer of the Original Code is
- * Mozilla Corporation.
+ * the Mozilla Foundation.
  * Portions created by the Initial Developer are Copyright (C) 2008
  * 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
@@ -102,16 +102,65 @@ const kQueryTypeFiltered = 1;
 // This separator is used as an RTL-friendly way to split the title and tags.
 // It can also be used by an nsIAutoCompleteResult consumer to re-split the
 // "comment" back into the title and the tag.
 const kTitleTagsSeparator = " \u2013 ";
 
 const kBrowserUrlbarBranch = "browser.urlbar.";
 
 ////////////////////////////////////////////////////////////////////////////////
+//// Globals and Lazy Getters
+
+XPCOMUtils.defineLazyServiceGetter(this, "pb",
+                                   "@mozilla.org/privatebrowsing;1",
+                                   "nsIPrivateBrowsingService");
+
+////////////////////////////////////////////////////////////////////////////////
+//// Helpers
+
+/**
+ * Initializes our temporary table on a given database.
+ *
+ * @param aDatabase
+ *        The mozIStorageConnection to set up the temp table on.
+ */
+function initTempTable(aDatabase)
+{
+  // Keep our temporary table in memory.
+  aDatabase.executeSimpleSQL("PRAGMA temp_store = MEMORY");
+
+  // Note: this should be kept up-to-date with the definition in
+  //       nsPlacesTables.h.
+  let sql = "CREATE TEMP TABLE moz_openpages_temp ("
+          + "  url TEXT PRIMARY KEY"
+          + ", open_count INTEGER"
+          + ")";
+  aDatabase.executeSimpleSQL(sql);
+
+  // Note: this should be kept up-to-date with the definition in
+  //       nsPlacesTriggers.h.
+  sql = "CREATE TEMPORARY TRIGGER moz_openpages_temp_afterupdate_trigger "
+      + "AFTER UPDATE OF open_count ON moz_openpages_temp FOR EACH ROW "
+      + "WHEN NEW.open_count = 0 "
+      + "BEGIN "
+      +   "DELETE FROM moz_openpages_temp "
+      +   "WHERE url = NEW.url;"
+      + "END";
+  aDatabase.executeSimpleSQL(sql);
+}
+
+/**
+ * @return true if private browsing is active, false otherwise.
+ */
+function inPrivateBrowsingMode()
+{
+  return pb.privateBrowsingEnabled;
+}
+
+////////////////////////////////////////////////////////////////////////////////
 //// AutoCompleteStatementCallbackWrapper class
 
 /**
  * Wraps a callback and ensures that handleCompletion is not dispatched if the
  * query is no longer tracked.
  *
  * @param aCallback
  *        A reference to a nsPlacesAutoComplete.
@@ -200,19 +249,29 @@ function nsPlacesAutoComplete()
                  +  "{ADDITIONAL_CONDITIONS} "
                  + "ORDER BY h.frecency DESC, h.id DESC "
                  + "LIMIT :maxResults";
 
   //////////////////////////////////////////////////////////////////////////////
   //// Smart Getters
 
   XPCOMUtils.defineLazyGetter(this, "_db", function() {
-    return Cc["@mozilla.org/browser/nav-history-service;1"].
-           getService(Ci.nsPIPlacesDatabase).
-           DBConnection;
+    // Get a cloned, read-only version of the database.  We'll only ever write
+    // to our own in-memory temp table, and having a cloned copy means we do not
+    // run the risk of our queries taking longer due to the main database
+    // connection performing a long-running task.
+    let db = Cc["@mozilla.org/browser/nav-history-service;1"].
+             getService(Ci.nsPIPlacesDatabase).
+             DBConnection.
+             clone(true);
+
+    // Create our in-memory tables for tab tracking.
+    initTempTable(db);
+
+    return db;
   });
 
   XPCOMUtils.defineLazyServiceGetter(this, "_bh",
                                      "@mozilla.org/browser/global-history;2",
                                      "nsIBrowserHistory");
 
   XPCOMUtils.defineLazyServiceGetter(this, "_textURIService",
                                      "@mozilla.org/intl/texttosuburi;1",
@@ -304,17 +363,17 @@ function nsPlacesAutoComplete()
     +   ") AS rank, place_id "
     +   "FROM moz_inputhistory i "
     +   "GROUP BY i.place_id "
     +   "HAVING rank > 0 "
     + ") AS i "
     + "JOIN moz_places h ON h.id = i.place_id "
     + "LEFT JOIN moz_favicons f ON f.id = h.favicon_id "
     + "LEFT JOIN moz_openpages_temp t ON t.url = h.url "
-    + "WHERE AUTOCOMPLETE_MATCH(:searchString, h.url, "
+    + "WHERE AUTOCOMPLETE_MATCH(NULL, h.url, "
     +                          "IFNULL(bookmark, h.title), tags, "
     +                          "h.visit_count, h.typed, parent, "
     +                          "t.open_count, "
     +                          ":matchBehavior, :searchBehavior) "
     + "ORDER BY rank DESC, h.frecency DESC "
     );
   });
 
@@ -337,16 +396,40 @@ function nsPlacesAutoComplete()
     +  "LEFT JOIN moz_places h ON h.url = search_url "
     +  "LEFT JOIN moz_favicons f ON f.id = h.favicon_id "
     +  "LEFT JOIN moz_openpages_temp t ON t.url = search_url "
     +  "WHERE LOWER(k.keyword) = LOWER(:keyword) "
     +  "ORDER BY h.frecency DESC "
     );
   });
 
+  XPCOMUtils.defineLazyGetter(this, "_registerOpenPageQuery", function() {
+    return this._db.createAsyncStatement(
+      "INSERT OR REPLACE INTO moz_openpages_temp (url, open_count) "
+    + "VALUES (:page_url, "
+    +   "IFNULL("
+    +     "("
+    +        "SELECT open_count + 1 "
+    +        "FROM moz_openpages_temp "
+    +        "WHERE url = :page_url "
+    +      "), "
+    +     "1"
+    +   ")"
+    + ")"
+    );
+  });
+
+  XPCOMUtils.defineLazyGetter(this, "_unregisterOpenPageQuery", function() {
+    return this._db.createAsyncStatement(
+      "UPDATE moz_openpages_temp "
+    + "SET open_count = open_count - 1 "
+    + "WHERE url = :page_url"
+    );
+  });
+
   //////////////////////////////////////////////////////////////////////////////
   //// Initialization
 
   // load preferences
   this._prefs = Cc["@mozilla.org/preferences-service;1"].
                 getService(Ci.nsIPrefService).
                 getBranch(kBrowserUrlbarBranch);
   this._loadPrefs(true);
@@ -408,25 +491,25 @@ nsPlacesAutoComplete.prototype = {
     // 3) openPages (this._openPagesQuery)
     // 4) query from this._getSearch
     // (1) only gets ran if we get any filtered tokens from this._getSearch,
     // since if there are no tokens, there is nothing to match, so there is no
     // reason to run the query).
     let {query, tokens} =
       this._getSearch(this._getUnfilteredSearchTokens(this._currentSearchString));
     let queries = tokens.length ?
-      [this._getBoundKeywordQuery(tokens), this._getBoundAdaptiveQuery(), this._getBoundOpenPagesQuery(), query] :
-      [this._getBoundAdaptiveQuery(), this._getBoundOpenPagesQuery(), query];
+      [this._getBoundKeywordQuery(tokens), this._getBoundAdaptiveQuery(), this._getBoundOpenPagesQuery(tokens), query] :
+      [this._getBoundAdaptiveQuery(), this._getBoundOpenPagesQuery(tokens), query];
 
     // Start executing our queries.
     this._executeQueries(queries);
 
     // Set up our persistent state for the duration of the search.
     this._searchTokens = tokens;
-    this._usedPlaceIds = {};
+    this._usedPlaces = {};
   },
 
   stopSearch: function PAC_stopSearch()
   {
     // We need to cancel our searches so we do not get any [more] results.
     // However, it's possible we haven't actually started any searches, so this
     // method may throw because this._pendingQuery may be undefined.
     if (this._pendingQuery)
@@ -440,16 +523,49 @@ nsPlacesAutoComplete.prototype = {
 
   onValueRemoved: function PAC_onValueRemoved(aResult, aURISpec, aRemoveFromDB)
   {
     if (aRemoveFromDB)
       this._bh.removePage(this._ioService.newURI(aURISpec, null, null));
   },
 
   //////////////////////////////////////////////////////////////////////////////
+  //// mozIPlacesAutoComplete
+
+  registerOpenPage: function PAC_registerOpenPage(aURI)
+  {
+    // Don't add any pages while in Private Browsing mode, so as to avoid
+    // leaking information about other windows that might otherwise stay hidden
+    // and private.
+    if (inPrivateBrowsingMode()) {
+      return;
+    }
+
+    let stmt = this._registerOpenPageQuery;
+    stmt.params.page_url = aURI.spec;
+
+    stmt.executeAsync();
+  },
+
+  unregisterOpenPage: function PAC_unregisterOpenPage(aURI)
+  {
+    // Entering Private Browsing mode will unregister all open pages, therefore
+    // there should not be anything in the moz_openpages_temp table.  As a
+    // result, we can stop now without doing any unnecessary work.
+    if (inPrivateBrowsingMode()) {
+      return;
+    }
+
+    let stmt = this._unregisterOpenPageQuery;
+    stmt.params.page_url = aURI.spec;
+
+    stmt.executeAsync();
+  },
+
+  //////////////////////////////////////////////////////////////////////////////
   //// mozIStorageStatementCallback
 
   handleResult: function PAC_handleResult(aResultSet)
   {
     let row, haveMatches = false;
     while (row = aResultSet.getNextRow()) {
       let match = this._processRow(row);
       haveMatches = haveMatches || match;
@@ -516,16 +632,18 @@ nsPlacesAutoComplete.prototype = {
         "_defaultQuery",
         "_historyQuery",
         "_bookmarkQuery",
         "_tagsQuery",
         "_openPagesQuery",
         "_typedQuery",
         "_adaptiveQuery",
         "_keywordQuery",
+        "_registerOpenPageQuery",
+        "_unregisterOpenPageQuery",
       ];
       for (let i = 0; i < stmts.length; i++) {
         // We do not want to create any query we haven't already created, so
         // see if it is a getter first.  __lookupGetter__ returns null if it is
         // actually a statement.
         if (!this.__lookupGetter__(stmts[i]))
           this[stmts[i]].finalize();
       }
@@ -592,17 +710,17 @@ nsPlacesAutoComplete.prototype = {
       this._notifyResults(false);
 
     // Clear our state
     delete this._originalSearchString;
     delete this._currentSearchString;
     delete this._searchTokens;
     delete this._listener;
     delete this._result;
-    delete this._usedPlaceIds;
+    delete this._usedPlaces;
     delete this._pendingQuery;
     this._secondPass = false;
     this._enableActions = false;
   },
 
   /**
    * Executes the given queries asynchronously.
    *
@@ -808,27 +926,29 @@ nsPlacesAutoComplete.prototype = {
       // Limit the query to the the maximum number of desired results.
       // This way we can avoid doing more work than needed.
       params.maxResults = this._maxRichResults;
     }
 
     return query;
   },
 
-  _getBoundOpenPagesQuery: function PAC_getBoundOpenPagesQuery()
+  _getBoundOpenPagesQuery: function PAC_getBoundOpenPagesQuery(aTokens)
   {
     let query = this._openPagesQuery;
 
     // Bind the needed parameters to the query so consumers can use it.
     let (params = query.params) {
       params.parent = this._bs.tagsFolder;
       params.query_type = kQueryTypeFiltered;
       params.matchBehavior = this._matchBehavior;
       params.searchBehavior = this._behavior;
-      params.searchString = this._currentSearchString;
+      // We only want to search the tokens that we are left with - not the
+      // original search string.
+      params.searchString = aTokens.join(" ");
       params.maxResults = this._maxRichResults;
     }
 
     return query;
   },
 
   /**
    * Obtains the keyword query with the properly bound parameters.
@@ -894,27 +1014,35 @@ nsPlacesAutoComplete.prototype = {
    * @param aRow
    *        The row to process.
    * @return true if the row is accepted, and false if not.
    */
   _processRow: function PAC_processRow(aRow)
   {
     // Before we do any work, make sure this entry isn't already in our results.
     let entryId = aRow.getResultByIndex(kQueryIndexPlaceId);
-    if (this._inResults(entryId))
+    let escapedEntryURL = aRow.getResultByIndex(kQueryIndexURL);
+    let openPageCount = aRow.getResultByIndex(kQueryIndexOpenPageCount) || 0;
+
+    // If actions are enabled and the page is open, add only the switch-to-tab
+    // result.  Otherwise, add the normal result.
+    let [url, action] = this._enableActions && openPageCount > 0 ?
+                        ["moz-action:switchtab," + escapedEntryURL, "action "] :
+                        [escapedEntryURL, ""];
+
+    if (this._inResults(entryId || url)) {
       return false;
+    }
 
-    let escapedEntryURL = aRow.getResultByIndex(kQueryIndexURL);
     let entryTitle = aRow.getResultByIndex(kQueryIndexTitle) || "";
     let entryFavicon = aRow.getResultByIndex(kQueryIndexFaviconURL) || "";
     let entryParentId = aRow.getResultByIndex(kQueryIndexParentId);
     let entryBookmarkTitle = entryParentId ?
       aRow.getResultByIndex(kQueryIndexBookmarkTitle) : null;
     let entryTags = aRow.getResultByIndex(kQueryIndexTags) || "";
-    let openPageCount = aRow.getResultByIndex(kQueryIndexOpenPageCount) || 0;
 
     // Always prefer the bookmark title unless it is empty
     let title = entryBookmarkTitle || entryTitle;
 
     let style;
     if (aRow.getResultByIndex(kQueryIndexQueryType) == kQueryTypeKeyword) {
       // If we do not have a title, then we must have a keyword, so let the UI
       // know it is a keyword.  Otherwise, we found an exact page match, so just
@@ -951,35 +1079,30 @@ nsPlacesAutoComplete.prototype = {
       if (showTags)
         style = "tag";
       else if (entryParentId)
         style = "bookmark";
       else
         style = "favicon";
     }
 
-    // If actions are enabled and the page is open, add only the switch-to-tab
-    // result.  Otherwise, add the normal result.
-    let [url, action] = this._enableActions && openPageCount > 0 ?
-                        ["moz-action:switchtab," + escapedEntryURL, "action "] :
-                        [escapedEntryURL, ""];
     this._addToResults(entryId, url, title, entryFavicon, action + style);
     return true;
   },
 
   /**
    * Checks to see if the given place has already been added to the results.
    *
-   * @param aPlaceId
-   *        The place_id to check for.
+   * @param aPlaceIdOrUrl
+   *        The place id or url (if the entry does not have a id) to check for.
    * @return true if the place has been added, false otherwise.
    */
-  _inResults: function PAC_inResults(aPlaceId)
+  _inResults: function PAC_inResults(aPlaceIdOrUrl)
   {
-    return (aPlaceId in this._usedPlaceIds);
+    return aPlaceIdOrUrl in this._usedPlaces;
   },
 
   /**
    * Adds a result to the AutoComplete results.  Also tracks that we've added
    * this place_id into the result set.
    *
    * @param aPlaceId
    *        The place_id of the item to be added to the result set.  This is
@@ -992,18 +1115,22 @@ nsPlacesAutoComplete.prototype = {
    *        The favicon to give to the entry.
    * @param aStyle
    *        Indicates how the entry should be styled when displayed.
    */
   _addToResults: function PAC_addToResults(aPlaceId, aURISpec, aTitle,
                                            aFaviconSpec, aStyle)
   {
     // Add this to our internal tracker to ensure duplicates do not end up in
-    // the result.  _usedPlaceIds is an Object that is being used as a set.
-    this._usedPlaceIds[aPlaceId] = true;
+    // the result.  _usedPlaces is an Object that is being used as a set.
+    // Not all entries have a place id, thus we fallback to the url for them.
+    // We cannot use only the url since keywords entries are modified to
+    // include the search string, and would be returned multiple times.  Ids
+    // are faster too.
+    this._usedPlaces[aPlaceId || aURISpec] = true;
 
     // Obtain the favicon for this URI.
     let favicon;
     if (aFaviconSpec) {
       let uri = this._ioService.newURI(aFaviconSpec, null, null);
       favicon = this._faviconService.getFaviconLinkForIcon(uri).spec;
     }
     favicon = favicon || this._faviconService.defaultFavicon.spec;
@@ -1065,15 +1192,16 @@ nsPlacesAutoComplete.prototype = {
   //////////////////////////////////////////////////////////////////////////////
   //// nsISupports
 
   classID: Components.ID("d0272978-beab-4adc-a3d4-04b76acfa4e7"),
 
   QueryInterface: XPCOMUtils.generateQI([
     Ci.nsIAutoCompleteSearch,
     Ci.nsIAutoCompleteSimpleResultListener,
+    Ci.mozIPlacesAutoComplete,
     Ci.mozIStorageStatementCallback,
     Ci.nsIObserver,
   ])
 };
 
 let components = [nsPlacesAutoComplete];
 const NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
--- a/toolkit/components/places/src/nsPlacesTables.h
+++ b/toolkit/components/places/src/nsPlacesTables.h
@@ -153,16 +153,18 @@
 
 #define CREATE_MOZ_KEYWORDS NS_LITERAL_CSTRING( \
   "CREATE TABLE moz_keywords (" \
     "  id INTEGER PRIMARY KEY AUTOINCREMENT" \
     ", keyword TEXT UNIQUE" \
   ")" \
 )
 
+// Note: this should be kept up-to-date with the definition in
+//       nsPlacesAutoComplete.js.
 #define CREATE_MOZ_OPENPAGES_TEMP NS_LITERAL_CSTRING( \
   "CREATE TEMP TABLE moz_openpages_temp (" \
     "  url TEXT PRIMARY KEY" \
     ", open_count INTEGER" \
   ")" \
 )
 
 #endif // __nsPlacesTables_h__
--- a/toolkit/components/places/src/nsPlacesTriggers.h
+++ b/toolkit/components/places/src/nsPlacesTriggers.h
@@ -97,16 +97,19 @@
                          "WHERE place_id = OLD.place_id " \
                          "ORDER BY visit_date DESC LIMIT 1) " \
     "WHERE id = OLD.place_id;" \
   "END" \
 )
 
 /**
  * This trigger removes a row from moz_openpages_temp when open_count reaches 0.
+ *
+ * @note this should be kept up-to-date with the definition in
+ *       nsPlacesAutoComplete.js
  */
 #define CREATE_REMOVEOPENPAGE_CLEANUP_TRIGGER NS_LITERAL_CSTRING( \
   "CREATE TEMPORARY TRIGGER moz_openpages_temp_afterupdate_trigger " \
   "AFTER UPDATE OF open_count ON moz_openpages_temp FOR EACH ROW " \
   "WHEN NEW.open_count = 0 " \
   "BEGIN " \
     "DELETE FROM moz_openpages_temp " \
     "WHERE url = NEW.url;" \
--- a/toolkit/components/places/tests/autocomplete/test_tabmatches.js
+++ b/toolkit/components/places/tests/autocomplete/test_tabmatches.js
@@ -39,31 +39,42 @@
 let gTabRestrictChar = prefs.getCharPref("browser.urlbar.restrict.openpage");
 
 let kSearchParam = "enable-actions";
 
 let kURIs = [
   "http://abc.com/",
   "moz-action:switchtab,http://abc.com/",
   "http://xyz.net/",
-  "moz-action:switchtab,http://xyz.net/"
+  "moz-action:switchtab,http://xyz.net/",
+  "about:robots",
+  "moz-action:switchtab,about:robots",
+  "data:text/html,test",
+  "moz-action:switchtab,data:text/html,test"
 ];
 
 let kTitles = [
   "ABC rocks",
-  "xyz.net - we're better than ABC"
+  "xyz.net - we're better than ABC",
+  "about:robots",
+  "data:text/html,test"
 ];
 
 addPageBook(0, 0);
 gPages[1] = [1, 0];
 addPageBook(2, 1);
 gPages[3] = [3, 1];
 
 addOpenPages(0, 1);
 
+// PAges that cannot be registered in history.
+addOpenPages(4, 1);
+gPages[5] = [5, 2];
+addOpenPages(6, 1);
+gPages[7] = [7, 3];
 
 let gTests = [
   ["0: single result, that is also a tab match",
    "abc.com", [1]],
   ["1: two results, one tab match",
    "abc", [1,2]],
   ["2: two results, both tab matches",
    "abc", [1,3],
@@ -80,25 +91,35 @@ let gTests = [
    function() {
      removeOpenPages(0, 1);
      removeOpenPages(2, 6);
    }],
   ["5: tab match search with restriction character",
    gTabRestrictChar + " abc", [1],
    function() {
     addOpenPages(0, 1);
-   }]
+   }],
+  ["6: tab match with not-addable pages",
+   "robots", [5]],
+  ["7: tab match with not-addable pages and restriction character",
+   gTabRestrictChar + " robots", [5]],
+  ["8: tab match with not-addable pages and only restriction character",
+   gTabRestrictChar, [1, 5, 7]],
 ];
 
 
 function addOpenPages(aUri, aCount) {
   let num = aCount || 1;
+  let acprovider = Cc["@mozilla.org/autocomplete/search;1?name=history"].
+                   getService(Ci.mozIPlacesAutoComplete);
   for (let i = 0; i < num; i++) {
-    bhist.registerOpenPage(toURI(kURIs[aUri]));
+    acprovider.registerOpenPage(toURI(kURIs[aUri]));
   }
 }
 
 function removeOpenPages(aUri, aCount) {
   let num = aCount || 1;
+  let acprovider = Cc["@mozilla.org/autocomplete/search;1?name=history"].
+                   getService(Ci.mozIPlacesAutoComplete);
   for (let i = 0; i < num; i++) {
-    bhist.unregisterOpenPage(toURI(kURIs[aUri]));
+    acprovider.unregisterOpenPage(toURI(kURIs[aUri]));
   }
 }
--- a/toolkit/components/places/tests/head_common.js
+++ b/toolkit/components/places/tests/head_common.js
@@ -458,55 +458,57 @@ function check_JSON_backup() {
   do_check_true(profileBookmarksJSONFile.exists());
   return profileBookmarksJSONFile;
 }
 
 
 /**
  * Waits for a frecency update then calls back.
  *
- * @param aUrl
- *        Address of the page we are waiting frecency for.
+ * @param aURI
+ *        URI or spec of the page we are waiting frecency for.
  * @param aValidator
  *        Validator function for the current frecency. If it returns true we
  *        have the expected frecency, otherwise we wait for next update.
  * @param aCallback
  *        function invoked when frecency update finishes.
  * @param aCbScope
  *        "this" scope for the callback
  * @param aCbArguments
  *        array of arguments to be passed to the callback
  *
  * @note since frecency is something that can be changed by a bunch of stuff
  *       like adding and removing visits, bookmarks we use a polling strategy.
  */
-function waitForFrecency(aUrl, aValidator, aCallback, aCbScope, aCbArguments) {
+function waitForFrecency(aURI, aValidator, aCallback, aCbScope, aCbArguments) {
   Services.obs.addObserver(function (aSubject, aTopic, aData) {
-    let frecency = frecencyForUrl(aUrl);
+    let frecency = frecencyForUrl(aURI);
     if (!aValidator(frecency)) {
+      print("Has to wait for frecency...");
       return;
     }
     Services.obs.removeObserver(arguments.callee, aTopic);
     aCallback.apply(aCbScope, aCbArguments);
   }, "places-frecency-updated", false);
 }
 
 /**
  * Returns the frecency of a url.
  *
- * @param  aURL
- *         the URL to get frecency for.
+ * @param  aURI
+ *         The URI or spec to get frecency for.
  * @return the frecency value.
  */
-function frecencyForUrl(aUrl)
+function frecencyForUrl(aURI)
 {
+  let url = aURI instanceof Ci.nsIURI ? aURI.spec : aURI;
   let stmt = DBConn().createStatement(
     "SELECT frecency FROM moz_places WHERE url = ?1"
   );
-  stmt.bindUTF8StringParameter(0, aUrl);
+  stmt.bindUTF8StringParameter(0, url);
   if (!stmt.executeStep())
     throw "No result for frecency.";
   let frecency = stmt.getInt32(0);
   stmt.finalize();
 
   return frecency;
 }
 
@@ -525,8 +527,40 @@ function is_time_ordered(before, after) 
   // PR_Now() use different code atm, the results can be unordered by this
   // amount of time.  See bug 558745 and bug 557406.
   let isWindows = ("@mozilla.org/windows-registry-key;1" in Cc);
   // Just to be safe we consider 20ms.
   let skew = isWindows ? 20000000 : 0;
   return after - before > -skew;
 }
 
+/**
+ * Waits for all pending async statements on the default connection, before
+ * proceeding with aCallback.
+ *
+ * @param aCallback
+ *        Function to be called when done.
+ * @param aScope
+ *        Scope for the callback.
+ * @param aArguments
+ *        Arguments array for the callback.
+ *
+ * @note The result is achieved by asynchronously executing a query requiring
+ *       a write lock.  Since all statements on the same connection are
+ *       serialized, the end of this write operation means that all writes are
+ *       complete.  Note that WAL makes so that writers don't block readers, but
+ *       this is a problem only across different connections.
+ */
+function waitForAsyncUpdates(aCallback, aScope, aArguments)
+{
+  let scope = aScope || this;
+  let argument = aArguments || [];
+  let db = DBConn();
+  db.createAsyncStatement("BEGIN EXCLUSIVE").executeAsync();
+  db.createAsyncStatement("COMMIT").executeAsync({
+    handleResult: function() {},
+    handleError: function() {},
+    handleCompletion: function(aReason)
+    {
+      aCallback.apply(scope, arguments);
+    }
+  });
+}
--- a/toolkit/components/places/tests/unit/test_000_frecency.js
+++ b/toolkit/components/places/tests/unit/test_000_frecency.js
@@ -238,32 +238,34 @@ AutoCompleteInput.prototype = {
         iid.equals(Ci.nsIAutoCompleteInput))
       return this;
 
     throw Components.results.NS_ERROR_NO_INTERFACE;
   }
 }
 
 function run_test() {
+  do_test_pending();
+  waitForAsyncUpdates(continue_test);
+}
+
+function continue_test() {
   var controller = Components.classes["@mozilla.org/autocomplete/controller;1"].
                    getService(Components.interfaces.nsIAutoCompleteController);
 
   // Make an AutoCompleteInput that uses our searches
   // and confirms results on search complete
   var input = new AutoCompleteInput(["history"]);
 
   controller.input = input;
 
   // always search in history + bookmarks, no matter what the default is
   prefs.setIntPref("browser.urlbar.search.sources", 3);
   prefs.setIntPref("browser.urlbar.default.behavior", 0);
 
-  // Search is asynchronous, so don't let the test finish immediately
-  do_test_pending();
-
   var numSearchesStarted = 0;
   input.onSearchBegin = function() {
     numSearchesStarted++;
     do_check_eq(numSearchesStarted, 1);
   };
 
   input.onSearchComplete = function() {
     do_check_eq(numSearchesStarted, 1);
--- a/toolkit/components/places/tests/unit/test_frecency.js
+++ b/toolkit/components/places/tests/unit/test_frecency.js
@@ -103,16 +103,25 @@ AutoCompleteInput.prototype = {
       return this;
 
     throw Components.results.NS_ERROR_NO_INTERFACE;
   }
 }
 
 function ensure_results(uris, searchTerm)
 {
+  waitForFrecency(uris[0].spec, function () {
+    return uris.every(function (aURI) {
+      return frecencyForUrl(aURI.spec) > 0;
+    });
+  }, ensure_results_internal, this, arguments);
+}
+
+function ensure_results_internal(uris, searchTerm)
+{
   var controller = Components.classes["@mozilla.org/autocomplete/controller;1"].
                    getService(Components.interfaces.nsIAutoCompleteController);
 
   // Make an AutoCompleteInput that uses our searches
   // and confirms results on search complete
   var input = new AutoCompleteInput(["history"]);
 
   controller.input = input;
@@ -149,18 +158,19 @@ try {
                 getService(Ci.nsINavBookmarksService);
 } catch(ex) {
   do_throw("Could not get history service\n");
 } 
 
 function setCountDate(aURI, aCount, aDate)
 {
   // We need visits so that frecency can be computed over multiple visits
-  for (let i = 0; i < aCount; i++)
+  for (let i = 0; i < aCount; i++) {
     histsvc.addVisit(aURI, aDate, null, histsvc.TRANSITION_TYPED, false, 0);
+  }
 }
 
 function setBookmark(aURI)
 {
   bmksvc.insertBookmark(bmksvc.bookmarksMenuFolder, aURI, -1, "bleh");
 }
 
 function tagURI(aURI, aTags) {
@@ -180,113 +190,113 @@ var d1 = new Date(Date.now() - 1000 * 60
 var d2 = new Date(Date.now() - 1000 * 60 * 60 * 24 * 10) * 1000;
 // c1 is larger (should show up higher) than c2
 var c1 = 10;
 var c2 = 1;
 
 var tests = [
 // test things without a search term
 function() {
-  print("Test 0: same count, different date");
+  print("TEST-INFO | Test 0: same count, different date");
   setCountDate(uri1, c1, d1);
   setCountDate(uri2, c1, d2);
   tagURI(uri1, ["site"]);
   ensure_results([uri1, uri2], "");
 },
 function() {
-  print("Test 1: same count, different date");
+  print("TEST-INFO | Test 1: same count, different date");
   setCountDate(uri1, c1, d2);
   setCountDate(uri2, c1, d1);
   tagURI(uri1, ["site"]);
   ensure_results([uri2, uri1], "");
 },
 function() {
-  print("Test 2: different count, same date");
+  print("TEST-INFO | Test 2: different count, same date");
   setCountDate(uri1, c1, d1);
   setCountDate(uri2, c2, d1);
   tagURI(uri1, ["site"]);
   ensure_results([uri1, uri2], "");
 },
 function() {
-  print("Test 3: different count, same date");
+  print("TEST-INFO | Test 3: different count, same date");
   setCountDate(uri1, c2, d1);
   setCountDate(uri2, c1, d1);
   tagURI(uri1, ["site"]);
   ensure_results([uri2, uri1], "");
 },
 
 // test things with a search term
 function() {
-  print("Test 4: same count, different date");
+  print("TEST-INFO | Test 4: same count, different date");
   setCountDate(uri1, c1, d1);
   setCountDate(uri2, c1, d2);
   tagURI(uri1, ["site"]);
   ensure_results([uri1, uri2], "site");
 },
 function() {
-  print("Test 5: same count, different date");
+  print("TEST-INFO | Test 5: same count, different date");
   setCountDate(uri1, c1, d2);
   setCountDate(uri2, c1, d1);
   tagURI(uri1, ["site"]);
   ensure_results([uri2, uri1], "site");
 },
 function() {
-  print("Test 6: different count, same date");
+  print("TEST-INFO | Test 6: different count, same date");
   setCountDate(uri1, c1, d1);
   setCountDate(uri2, c2, d1);
   tagURI(uri1, ["site"]);
   ensure_results([uri1, uri2], "site");
 },
 function() {
-  print("Test 7: different count, same date");
+  print("TEST-INFO | Test 7: different count, same date");
   setCountDate(uri1, c2, d1);
   setCountDate(uri2, c1, d1);
   tagURI(uri1, ["site"]);
   ensure_results([uri2, uri1], "site");
 },
 // There are multiple tests for 8, hence the multiple functions
 // Bug 426166 section
 function() {
-  print("Test 8.1: same count, same date");  
+  print("TEST-INFO | Test 8.1a: same count, same date");
   setBookmark(uri3);
   setBookmark(uri4);
   ensure_results([uri4, uri3], "a");
 },
 function() {
-  print("Test 8.1: same count, same date");  
+  print("TEST-INFO | Test 8.1b: same count, same date");
   setBookmark(uri3);
   setBookmark(uri4);
   ensure_results([uri4, uri3], "aa");
 },
 function() {
-  print("Test 8.2: same count, same date");
+  print("TEST-INFO | Test 8.2: same count, same date");
   setBookmark(uri3);
   setBookmark(uri4);
   ensure_results([uri4, uri3], "aaa");
 },
 function() {
-  print("Test 8.3: same count, same date");
+  print("TEST-INFO | Test 8.3: same count, same date");
   setBookmark(uri3);
   setBookmark(uri4);
   ensure_results([uri4, uri3], "aaaa");
 },
 function() {
-  print("Test 8.4: same count, same date");
+  print("TEST-INFO | Test 8.4: same count, same date");
   setBookmark(uri3);
   setBookmark(uri4);
   ensure_results([uri4, uri3], "aaa");
 },
 function() {
-  print("Test 8.5: same count, same date");
+  print("TEST-INFO | Test 8.5: same count, same date");
   setBookmark(uri3);
   setBookmark(uri4);
   ensure_results([uri4, uri3], "aa");
 },
 function() {
-  print("Test 8.6: same count, same date");
+  print("TEST-INFO | Test 8.6: same count, same date");
   setBookmark(uri3);
   setBookmark(uri4);
   ensure_results([uri4, uri3], "a");
 }
 ];
 
 /**
  * Test adaptive autocomplete