merge backout on CLOSED TREE
authorMarco Bonardo <mbonardo@mozilla.com>
Tue, 10 Aug 2010 00:15:18 +0200
changeset 49240 0edbfca5081d6886c4f75983f86260cd64d556d2
parent 49238 47f7c62e8556b155cd0dbcaca07ca2a30233c50b (current diff)
parent 49239 88a6756b7f4b29aa0ffcce3aee772dbade5c35b8 (diff)
child 49242 34c42da84827087df90afde763cf57b1f76f499a
push id14956
push usermak77@bonardo.net
push dateMon, 09 Aug 2010 22:32:41 +0000
treeherderautoland@34c42da84827 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone2.0b4pre
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
merge backout on CLOSED TREE
browser/base/content/test/browser_tabMatchesInAwesomebar.js
browser/components/nsBrowserGlue.js
browser/components/places/tests/unit/test_clearHistory_shutdown.js
browser/installer/package-manifest.in
browser/installer/removed-files.in
docshell/test/bug94514-postpage.html
docshell/test/test_bug94514.html
toolkit/components/places/src/Makefile.in
toolkit/components/places/src/PlacesDBUtils.jsm
toolkit/components/places/src/PlacesUtils.jsm
toolkit/components/places/src/nsAnnotationService.cpp
toolkit/components/places/src/nsFaviconService.cpp
toolkit/components/places/src/nsFaviconService.h
toolkit/components/places/src/nsNavBookmarks.cpp
toolkit/components/places/src/nsNavHistory.cpp
toolkit/components/places/src/nsNavHistory.h
toolkit/components/places/src/nsPlacesAutoComplete.js
toolkit/components/places/src/nsPlacesExpiration.js
toolkit/components/places/src/nsPlacesIndexes.h
toolkit/components/places/src/nsPlacesTables.h
toolkit/components/places/src/nsPlacesTriggers.h
toolkit/components/places/src/toolkitplaces.manifest
toolkit/components/places/tests/Makefile.in
toolkit/components/places/tests/autocomplete/test_autocomplete_on_value_removed_479089.js
toolkit/components/places/tests/browser/browser_settitle.js
toolkit/components/places/tests/browser/browser_visituri.js
toolkit/components/places/tests/cpp/places_test_harness.h
toolkit/components/places/tests/expiration/test_annos_expire_policy.js
toolkit/components/places/tests/expiration/test_removeAllPages.js
toolkit/components/places/tests/head_common.js
toolkit/components/places/tests/mochitest/bug_411966/redirect.js
toolkit/components/places/tests/network/test_history_redirects.js
toolkit/components/places/tests/queries/head_queries.js
toolkit/components/places/tests/queries/test_transitions.js
toolkit/components/places/tests/unit/test_248970.js
toolkit/components/places/tests/unit/test_412132.js
toolkit/components/places/tests/unit/test_454977.js
toolkit/components/places/tests/unit/test_doSetAndLoadFaviconForPage_failures.js
toolkit/components/places/tests/unit/test_history_removeAllPages.js
toolkit/components/places/tests/unit/test_migrateFrecency.js
toolkit/components/places/tests/unit/test_removeVisitsByTimeframe.js
toolkit/components/places/tests/unit/test_update_frecency_after_delete.js
--- a/browser/base/content/test/browser_tabMatchesInAwesomebar.js
+++ b/browser/base/content/test/browser_tabMatchesInAwesomebar.js
@@ -186,19 +186,20 @@ function ensure_opentabs_match_db() {
     }
   }
 
   var db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
                               .DBConnection;
 
   try {
     var stmt = db.createStatement(
-                          "SELECT p.url AS url, open_count, place_id " +
+                          "SELECT IFNULL(p_t.url, p.url) AS url, open_count, place_id " +
                           "FROM moz_openpages_temp " +
-                          "LEFT JOIN moz_places p ON p.id = place_id");
+                          "LEFT JOIN moz_places p ON p.id=place_id " +
+                          "LEFT JOIN moz_places_temp p_t ON p_t.id=place_id");
   } catch (e) {
     ok(false, "error creating db statement: " + e);
     return;
   }
 
   var dbtabs = [];
   try {
     while (stmt.executeStep()) {
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -351,17 +351,20 @@ BrowserGlue.prototype = {
       args.AppendElement(str);
       const EMURL = "chrome://mozapps/content/extensions/extensions.xul";
       const EMFEATURES = "chrome,menubar,extra-chrome,toolbar,dialog=no,resizable";
       Services.ww.openWindow(null, EMURL, "_blank", EMFEATURES, args);
       Services.prefs.clearUserPref(PREF_EM_NEW_ADDONS_LIST);
     }
 
     // Load the "more info" page for a locked places.sqlite
-    // This property is set earlier by places-database-locked topic.
+    // This property is set earlier in the startup process:
+    // nsPlacesDBFlush loads after profile-after-change and initializes
+    // the history service, which sends out places-database-locked
+    // which sets this property.
     if (this._isPlacesDatabaseLocked) {
       this._showPlacesLockedNotificationBox();
     }
 
     // If there are plugins installed that are outdated, and the user hasn't
     // been warned about them yet, open the plugins update page.
     if (Services.prefs.getBoolPref(PREF_PLUGINS_NOTIFYUSER))
       this._showPluginUpdatePage();
--- a/browser/components/places/tests/unit/test_clearHistory_shutdown.js
+++ b/browser/components/places/tests/unit/test_clearHistory_shutdown.js
@@ -53,17 +53,19 @@ let expirationObserver = {
     Services.obs.removeObserver(expirationObserver,
                                 PlacesUtils.TOPIC_EXPIRATION_FINISHED);
   
     let db = PlacesUtils.history
                         .QueryInterface(Ci.nsPIPlacesDatabase)
                         .DBConnection;
 
     let stmt = db.createStatement(
-      "SELECT id FROM moz_places WHERE url = :page_url "
+      "SELECT id FROM moz_places_temp WHERE url = :page_url "
+    + "UNION ALL "
+    + "SELECT id FROM moz_places WHERE url = :page_url "
     );
 
     try {
       URIS.forEach(function(aUrl) {
         stmt.params.page_url = aUrl;
         do_check_false(stmt.executeStep());
         stmt.reset();
       });
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -337,16 +337,17 @@
 #else
 @BINPATH@/components/brwsrcmp@DLL_SUFFIX@
 #endif
 @BINPATH@/components/txEXSLTRegExFunctions.manifest
 @BINPATH@/components/txEXSLTRegExFunctions.js
 @BINPATH@/components/toolkitplaces.manifest
 @BINPATH@/components/nsLivemarkService.js
 @BINPATH@/components/nsTaggingService.js
+@BINPATH@/components/nsPlacesDBFlush.js
 @BINPATH@/components/nsPlacesAutoComplete.manifest
 @BINPATH@/components/nsPlacesAutoComplete.js
 @BINPATH@/components/nsPlacesExpiration.js
 @BINPATH@/components/PlacesProtocolHandler.js
 @BINPATH@/components/nsDefaultCLH.manifest
 @BINPATH@/components/nsDefaultCLH.js
 @BINPATH@/components/nsContentPrefService.manifest
 @BINPATH@/components/nsContentPrefService.js
--- a/browser/installer/removed-files.in
+++ b/browser/installer/removed-files.in
@@ -859,9 +859,8 @@ extensions/inspector@mozilla.org/chrome.
 extensions/inspector@mozilla.org/chrome/inspector.jar
 extensions/inspector@mozilla.org/defaults/preferences/inspector.js
 extensions/inspector@mozilla.org/platform/WINNT/chrome/icons/default/winInspectorMain.ico
 extensions/inspector@mozilla.org/components/inspector.xpt
 extensions/inspector@mozilla.org/components/@DLL_PREFIX@inspector@DLL_SUFFIX@
 extensions/inspector@mozilla.org/chrome/icons/default/winInspectorMain.ico
 components/nsPlacesTransactionsService.js
 components/browserplaces.xpt
-components/nsPlacesDBFlush.js
--- a/docshell/test/bug94514-postpage.html
+++ b/docshell/test/bug94514-postpage.html
@@ -17,14 +17,14 @@ Specifically, this is a test page that a
 
 netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
 if (!window.location.href.match("posted=1")) {
   // Here we just submit the form
   var form = $("testForm");
   form.action = window.location.href + "?posted=1";
   form.submit();
 } else {
-  window.location.href = "http://mochi.test:8888/tests/docshell/test/bug94514-postpage.html";
+  window.opener.finishTest();
 }
 
 </script>
 </body>
 </html>
--- a/docshell/test/test_bug94514.html
+++ b/docshell/test/test_bug94514.html
@@ -39,34 +39,16 @@ var postedPage = ios.newURI(postedURI, n
 var gh = Cc["@mozilla.org/browser/global-history;2"].
          getService(Ci.nsIGlobalHistory2);
 SimpleTest.ok(!gh.isVisited(startPage),
               "Initial page does not start in global history.  Note: " +
               "this will also fail if you run the test twice.");
 SimpleTest.ok(!gh.isVisited(postedPage),
               "Posted page does not start in global history.");
 
-// Because adding visits is async, we will not be notified imemdiately.
-var os = Cc["@mozilla.org/observer-service;1"].
-         getService(Ci.nsIObserverService);
-var visitObserver = {
-  _visitCount: 0,
-  observe: function(aSubject, aTopic, aData)
-  {
-    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
-    if (!startPage.equals(aSubject.QueryInterface(Ci.nsIURI)) ||
-        ++this._visitCount < 2) {
-      return;
-    }
-    os.removeObserver(this, aTopic, false);
-    finishTest();
-  },
-};
-os.addObserver(visitObserver, "uri-visit-saved", false);
-
 var w = window.open(startURI, "", "width=10,height=10");
 
 function finishTest()
 {
   // We need to check that this was not added to global history.
   SimpleTest.ok(gh.isVisited(startPage),
                 "Initial page was added to global history.");
   SimpleTest.ok(!gh.isVisited(postedPage),
--- a/toolkit/components/places/src/Makefile.in
+++ b/toolkit/components/places/src/Makefile.in
@@ -81,16 +81,17 @@ EXTRA_DSO_LDOPTS += \
 	$(NULL)
 
 LOCAL_INCLUDES += -I$(srcdir)/../../build
 
 EXTRA_COMPONENTS = \
   toolkitplaces.manifest \
   nsLivemarkService.js \
   nsTaggingService.js \
+  nsPlacesDBFlush.js \
   nsPlacesExpiration.js \
   nsMicrosummaryService.js \
   $(NULL)
 
 ifdef MOZ_XUL
 EXTRA_COMPONENTS += nsPlacesAutoComplete.js nsPlacesAutoComplete.manifest
 endif
 
--- a/toolkit/components/places/src/PlacesDBUtils.jsm
+++ b/toolkit/components/places/src/PlacesDBUtils.jsm
@@ -207,16 +207,18 @@ nsPlacesDBUtils.prototype = {
       ")");
     cleanupStatements.push(deleteInvalidAttributeAnnos);
 
     // B.2 remove orphan annos
     let deleteOrphanAnnos = this._dbConn.createStatement(
       "DELETE FROM moz_annos WHERE id IN ( " +
         "SELECT id FROM moz_annos a " +
         "WHERE NOT EXISTS " +
+          "(SELECT id FROM moz_places_temp WHERE id = a.place_id LIMIT 1) " +
+        "AND NOT EXISTS " +
           "(SELECT id FROM moz_places WHERE id = a.place_id LIMIT 1) " +
       ")");
     cleanupStatements.push(deleteOrphanAnnos);
 
     // MOZ_BOOKMARKS_ROOTS
     // C.1 fix missing Places root
     //     Bug 477739 shows a case where the root could be wrongly removed
     //     due to an endianness issue.  We try to fix broken roots here.
@@ -281,16 +283,17 @@ nsPlacesDBUtils.prototype = {
     // D.1 remove items without a valid place
     // if fk IS NULL we fix them in D.7
     let deleteNoPlaceItems = this._dbConn.createStatement(
       "DELETE FROM moz_bookmarks WHERE id NOT IN ( " +
         "SELECT folder_id FROM moz_bookmarks_roots " + // skip roots
       ") AND id IN (" +
         "SELECT b.id FROM moz_bookmarks b " +
         "WHERE fk NOT NULL AND b.type = :bookmark_type " +
+          "AND NOT EXISTS (SELECT url FROM moz_places_temp WHERE id = b.fk LIMIT 1) " +
           "AND NOT EXISTS (SELECT url FROM moz_places WHERE id = b.fk LIMIT 1) " +
       ")");
     deleteNoPlaceItems.params["bookmark_type"] = this._bms.TYPE_BOOKMARK;
     cleanupStatements.push(deleteNoPlaceItems);
 
     // D.2 remove items that are not uri bookmarks from tag containers
     let deleteBogusTagChildren = this._dbConn.createStatement(
       "DELETE FROM moz_bookmarks WHERE id NOT IN ( " +
@@ -439,51 +442,58 @@ nsPlacesDBUtils.prototype = {
       fixPositionsForParent.params["parent"] = parent;
       cleanupStatements.push(fixPositionsForParent);
     }
 */
 
     // D.11 remove old livemarks status items
     //      Livemark status items are now static but some livemark has still old
     //      status items bookmarks inside it. We should remove them.
+    //      Note: This does not need to query the temp table.
     let removeLivemarkStaticItems = this._dbConn.createStatement(
       "DELETE FROM moz_bookmarks WHERE type = :bookmark_type AND fk IN ( " +
         "SELECT id FROM moz_places WHERE url = :lmloading OR url = :lmfailed " +
       ")");
     removeLivemarkStaticItems.params["bookmark_type"] = this._bms.TYPE_BOOKMARK;
     removeLivemarkStaticItems.params["lmloading"] = "about:livemark-loading";
     removeLivemarkStaticItems.params["lmfailed"] = "about:livemark-failed";
     cleanupStatements.push(removeLivemarkStaticItems);
 
     // MOZ_FAVICONS
     // E.1 remove orphan icons
     let deleteOrphanIcons = this._dbConn.createStatement(
       "DELETE FROM moz_favicons WHERE id IN (" +
         "SELECT id FROM moz_favicons f " +
         "WHERE NOT EXISTS " +
+          "(SELECT id FROM moz_places_temp WHERE favicon_id = f.id LIMIT 1) " +
+          "AND NOT EXISTS" +
           "(SELECT id FROM moz_places WHERE favicon_id = f.id LIMIT 1) " +
       ")");
     cleanupStatements.push(deleteOrphanIcons);
 
     // MOZ_HISTORYVISITS
     // F.1 remove orphan visits
     let deleteOrphanVisits = this._dbConn.createStatement(
       "DELETE FROM moz_historyvisits WHERE id IN (" +
         "SELECT id FROM moz_historyvisits v " +
         "WHERE NOT EXISTS " +
+          "(SELECT id FROM moz_places_temp WHERE id = v.place_id LIMIT 1) " +
+          "AND NOT EXISTS " +
           "(SELECT id FROM moz_places WHERE id = v.place_id LIMIT 1) " +
       ")");
     cleanupStatements.push(deleteOrphanVisits);
 
     // MOZ_INPUTHISTORY
     // G.1 remove orphan input history
     let deleteOrphanInputHistory = this._dbConn.createStatement(
       "DELETE FROM moz_inputhistory WHERE place_id IN (" +
         "SELECT place_id FROM moz_inputhistory i " +
         "WHERE NOT EXISTS " +
+          "(SELECT id FROM moz_places_temp WHERE id = i.place_id LIMIT 1) " +
+          "AND NOT EXISTS " +
           "(SELECT id FROM moz_places WHERE id = i.place_id LIMIT 1) " +
       ")");
     cleanupStatements.push(deleteOrphanInputHistory);
 
     // MOZ_ITEMS_ANNOS
     // H.1 remove item annos with an invalid attribute
     let deleteInvalidAttributeItemsAnnos = this._dbConn.createStatement(
       "DELETE FROM moz_items_annos WHERE id IN ( " +
@@ -521,27 +531,32 @@ nsPlacesDBUtils.prototype = {
         "WHERE favicon_id NOT NULL " +
           "AND NOT EXISTS " +
             "(SELECT id FROM moz_favicons WHERE id = h.favicon_id LIMIT 1) " +
       ")");
     cleanupStatements.push(fixInvalidFaviconIds);
 
 /* XXX needs test
     // L.2 recalculate visit_count
+    // We're detecting errors only in disk table since temp tables could have
+    // different values based on the number of visits not yet synced to disk.
     let detectWrongCountPlaces = this._dbConn.createStatement(
       "SELECT id FROM moz_places h " +
-      "WHERE h.visit_count <> " +
+      "WHERE id NOT IN (SELECT id FROM moz_places_temp) " +
+        "AND h.visit_count <> " +
           "(SELECT count(*) FROM moz_historyvisits " +
             "WHERE place_id = h.id AND visit_type NOT IN (0,4,7,8))");
     while (detectWrongCountPlaces.executeStep()) {
       let placeId = detectWrongCountPlaces.getInt64(0);
       let fixCountForPlace = this._dbConn.createStatement(
-        "UPDATE moz_places SET visit_count = ( " +
+        "UPDATE moz_places_view SET visit_count = ( " +
           "(SELECT count(*) FROM moz_historyvisits " +
-            "WHERE place_id = :place_id AND visit_type NOT IN (0,4,7,8)) + "
+            "WHERE place_id = :place_id AND visit_type NOT IN (0,4,7,8)) + " +
+          "(SELECT count(*) FROM moz_historyvisits_temp " +
+            "WHERE place_id = :place_id AND visit_type NOT IN (0,4,7,8)) + " +
         ") WHERE id = :place_id");
       fixCountForPlace.params["place_id"] = placeId;
       cleanupStatements.push(fixCountForPlace);
     }
 */
 
     // MAINTENANCE STATEMENTS SHOULD GO ABOVE THIS POINT!
 
--- a/toolkit/components/places/src/PlacesUtils.jsm
+++ b/toolkit/components/places/src/PlacesUtils.jsm
@@ -145,16 +145,17 @@ var PlacesUtils = {
   POST_DATA_ANNO: "bookmarkProperties/POSTData",
   READ_ONLY_ANNO: "placesInternal/READ_ONLY",
 
   TOPIC_SHUTDOWN: "places-shutdown",
   TOPIC_INIT_COMPLETE: "places-init-complete",
   TOPIC_DATABASE_LOCKED: "places-database-locked",
   TOPIC_EXPIRATION_FINISHED: "places-expiration-finished",
   TOPIC_FEEDBACK_UPDATED: "places-autocomplete-feedback-updated",
+  TOPIC_SYNC_FINISHED: "places-sync-finished",
   TOPIC_FAVICONS_EXPIRED: "places-favicons-expired",
   TOPIC_VACUUM_STARTING: "places-vacuum-starting",
 
   asVisit: function(aNode) asVisit(aNode),
   asFullVisit: function(aNode) asFullVisit(aNode),
   asContainer: function(aNode) asContainer(aNode),
   asQuery: function(aNode) asQuery(aNode),
 
--- a/toolkit/components/places/src/nsAnnotationService.cpp
+++ b/toolkit/components/places/src/nsAnnotationService.cpp
@@ -131,27 +131,33 @@ nsAnnotationService::GetStatement(const 
     "FROM moz_anno_attributes n "
     "JOIN moz_items_annos a ON a.anno_attribute_id = n.id "
     "WHERE a.item_id = :item_id"));
 
   RETURN_IF_STMT(mDBGetAnnotationsForPage, NS_LITERAL_CSTRING(
     "SELECT n.name "
     "FROM moz_anno_attributes n "
     "JOIN moz_annos a ON a.anno_attribute_id = n.id "
-    "JOIN moz_places h ON h.id = a.place_id "
-    "WHERE h.url = :page_url"));
+    "JOIN ( "
+      "SELECT id FROM moz_places_temp WHERE url = :page_url "
+      "UNION "
+      "SELECT id FROM moz_places WHERE url = :page_url "
+    ") AS h ON h.id = a.place_id"));
 
   RETURN_IF_STMT(mDBGetPageAnnotationValue, NS_LITERAL_CSTRING(
     "SELECT a.id, a.place_id, :anno_name, a.mime_type, a.content, a.flags, "
            "a.expiration, a.type "
     "FROM moz_anno_attributes n "
     "JOIN moz_annos a ON n.id = a.anno_attribute_id "
-    "JOIN moz_places h ON h.id = a.place_id "
-    "WHERE h.url = :page_url "
-      "AND n.name = :anno_name"));
+    "JOIN ( "
+      "SELECT id FROM moz_places_temp WHERE url = :page_url "
+      "UNION "
+      "SELECT id FROM moz_places WHERE url = :page_url "
+    ") AS h ON h.id = a.place_id "
+    "WHERE n.name = :anno_name"));
 
   RETURN_IF_STMT(mDBGetItemAnnotationValue, NS_LITERAL_CSTRING(
     "SELECT a.id, a.item_id, :anno_name, a.mime_type, a.content, a.flags, "
            "a.expiration, a.type "
     "FROM moz_anno_attributes n "
     "JOIN moz_items_annos a ON a.anno_attribute_id = n.id "
     "WHERE a.item_id = :item_id "
     "AND n.name = :anno_name"));
@@ -170,47 +176,53 @@ nsAnnotationService::GetStatement(const 
     "INSERT OR REPLACE INTO moz_items_annos "
       "(id, item_id, anno_attribute_id, mime_type, content, flags, "
        "expiration, type, dateAdded, lastModified) "
     "VALUES (:id, :fk, :name_id, :mime_type, :content, :flags, "
     ":expiration, :type, :date_added, :last_modified)"));
 
   RETURN_IF_STMT(mDBRemovePageAnnotation, NS_LITERAL_CSTRING(
     "DELETE FROM moz_annos "
-    "WHERE place_id = (SELECT id FROM moz_places WHERE url = :page_url) "
-      "AND anno_attribute_id = "
-        "(SELECT id FROM moz_anno_attributes WHERE name = :anno_name)"));
+    "WHERE place_id = ( "
+      "SELECT id FROM moz_places_temp WHERE url = :page_url "
+      "UNION "
+      "SELECT id FROM moz_places WHERE url = :page_url) "
+    "AND anno_attribute_id = "
+      "(SELECT id FROM moz_anno_attributes WHERE name = :anno_name)"));
 
   RETURN_IF_STMT(mDBRemoveItemAnnotation, NS_LITERAL_CSTRING(
     "DELETE FROM moz_items_annos "
     "WHERE item_id = :item_id "
-      "AND anno_attribute_id = "
-        "(SELECT id FROM moz_anno_attributes WHERE name = :anno_name)"));
+    "AND anno_attribute_id = "
+      "(SELECT id FROM moz_anno_attributes WHERE name = :anno_name)"));
 
   RETURN_IF_STMT(mDBGetPagesWithAnnotation, NS_LITERAL_CSTRING(
-    "SELECT h.url "
+    "SELECT IFNULL(h_t.url, h.url) AS coalesced_url "
     "FROM moz_anno_attributes n "
     "JOIN moz_annos a ON n.id = a.anno_attribute_id "
-    "JOIN moz_places h ON h.id = a.place_id "
-    "WHERE n.name = :anno_name"));
+    "LEFT JOIN moz_places h ON h.id = a.place_id "
+    "LEFT JOIN moz_places_temp h_t ON h_t.id = a.place_id "
+    "WHERE n.name = :anno_name AND coalesced_url NOT NULL"));
 
   RETURN_IF_STMT(mDBGetItemsWithAnnotation, NS_LITERAL_CSTRING(
     "SELECT a.item_id "
     "FROM moz_anno_attributes n "
     "JOIN moz_items_annos a ON n.id = a.anno_attribute_id "
     "WHERE n.name = :anno_name"));
 
   RETURN_IF_STMT(mDBCheckPageAnnotation, NS_LITERAL_CSTRING(
     "SELECT h.id, "
            "(SELECT id FROM moz_anno_attributes WHERE name = :anno_name) AS nameid, "
            "a.id, a.dateAdded "
-    "FROM moz_places h "
+    "FROM (SELECT id FROM moz_places_temp WHERE url = :page_url "
+          "UNION "
+          "SELECT id FROM moz_places WHERE url = :page_url "
+          ") AS h "
     "LEFT JOIN moz_annos a ON a.place_id = h.id "
-                         "AND a.anno_attribute_id = nameid "
-    "WHERE h.url = :page_url"));
+                         "AND a.anno_attribute_id = nameid"));
 
   RETURN_IF_STMT(mDBCheckItemAnnotation, NS_LITERAL_CSTRING(
     "SELECT b.id, "
            "(SELECT id FROM moz_anno_attributes WHERE name = :anno_name) AS nameid, "
            "a.id, a.dateAdded "
     "FROM moz_bookmarks b "
     "LEFT JOIN moz_items_annos a ON a.item_id = b.id "
                                "AND a.anno_attribute_id = nameid "
@@ -1565,17 +1577,19 @@ NS_IMETHODIMP
 nsAnnotationService::RemovePageAnnotations(nsIURI* aURI)
 {
   NS_ENSURE_ARG(aURI);
 
   // Should this be precompiled or a getter?
   nsCOMPtr<mozIStorageStatement> statement;
   nsresult rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
       "DELETE FROM moz_annos WHERE place_id = "
-        "(SELECT id FROM moz_places WHERE url = :page_url)"),
+        "(SELECT id FROM moz_places_temp WHERE url = :page_url "
+         "UNION "
+         "SELECT id FROM moz_places WHERE url = :page_url)"),
     getter_AddRefs(statement));
   NS_ENSURE_SUCCESS(rv, rv);
   rv = URIBinder::Bind(statement, NS_LITERAL_CSTRING("page_url"), aURI);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = statement->Execute();
   NS_ENSURE_SUCCESS(rv, rv);
 
@@ -1625,39 +1639,47 @@ nsAnnotationService::CopyPageAnnotations
   if (InPrivateBrowsingMode())
     return NS_OK;
 
   mozStorageTransaction transaction(mDBConn, PR_FALSE);
 
   nsCOMPtr<mozIStorageStatement> sourceStmt;
   nsresult rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
     "SELECT h.id, n.id, n.name, a2.id "
-    "FROM moz_places h "
+    "FROM ( "
+      "SELECT id from moz_places_temp WHERE url = :source_url "
+      "UNION "
+      "SELECT id FROM moz_places WHERE url = :source_url "
+    ") AS h "
     "JOIN moz_annos a ON a.place_id = h.id "
     "JOIN moz_anno_attributes n ON n.id = a.anno_attribute_id "
-    "LEFT JOIN moz_annos a2 ON a2.place_id = "
-      "(SELECT id FROM moz_places WHERE url = :dest_url) "
-                          "AND a2.anno_attribute_id = n.id "
-    "WHERE url = :source_url"),
+    "LEFT JOIN moz_annos a2 ON a2.place_id = ( "
+      "SELECT id FROM moz_places_temp WHERE url = :dest_url "
+      "UNION "
+      "SELECT id FROM moz_places WHERE url = :dest_url "
+    ") AND a2.anno_attribute_id = n.id"),
     getter_AddRefs(sourceStmt));
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = URIBinder::Bind(sourceStmt, NS_LITERAL_CSTRING("source_url"), aSourceURI);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = URIBinder::Bind(sourceStmt, NS_LITERAL_CSTRING("dest_url"), aDestURI);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<mozIStorageStatement> copyStmt;
   rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
       "INSERT INTO moz_annos "
       "(place_id, anno_attribute_id, mime_type, content, flags, expiration, "
        "type, dateAdded, lastModified) "
-      "SELECT (SELECT id FROM moz_places WHERE url = :page_url), "
-             "anno_attribute_id, mime_type, content, flags, expiration, type, "
-             ":date, :date "
+      "SELECT ( "
+        "SELECT id FROM moz_places_temp WHERE url = :page_url "
+        "UNION "
+        "SELECT id FROM moz_places WHERE url = :page_url "
+      "), anno_attribute_id, mime_type, content, flags, expiration, type, "
+        ":date, :date "
       "FROM moz_annos "
       "WHERE place_id = :page_id "
       "AND anno_attribute_id = :name_id"),
     getter_AddRefs(copyStmt));
   NS_ENSURE_SUCCESS(rv, rv);
 
   PRBool hasResult;
   while (NS_SUCCEEDED(sourceStmt->ExecuteStep(&hasResult)) && hasResult) {
--- a/toolkit/components/places/src/nsFaviconService.cpp
+++ b/toolkit/components/places/src/nsFaviconService.cpp
@@ -166,57 +166,74 @@ nsFaviconService::GetStatement(const nsC
     "SELECT id, length(data), expiration FROM moz_favicons "
     "WHERE url = :icon_url"));
 
   // If the page does not exist url = NULL will return NULL instead of 0,
   // since (1 = NULL) is NULL.  Thus the need for the IFNULL.
   RETURN_IF_STMT(mDBGetIconInfoWithPage, NS_LITERAL_CSTRING(
     "SELECT id, length(data), expiration, data, mime_type, "
     "IFNULL(url = (SELECT f.url "
-                  "FROM moz_places h "
+                  "FROM ( "
+                    "SELECT favicon_id FROM moz_places_temp "
+                    "WHERE url = :page_url "
+                    "UNION ALL "
+                    "SELECT favicon_id FROM moz_places "
+                    "WHERE url = :page_url "
+                  ") AS h "
                   "JOIN moz_favicons f ON h.favicon_id = f.id "
-                  "WHERE h.url = :page_url "
                   "LIMIT 1), "
-           "0) "
+           "0)"
     "FROM moz_favicons WHERE url = :icon_url"));
 
   RETURN_IF_STMT(mDBGetURL, NS_LITERAL_CSTRING(
     "SELECT f.id, f.url, length(f.data), f.expiration "
-    "FROM moz_places h "
-    "JOIN moz_favicons f ON h.favicon_id = f.id "
-    "WHERE h.url = :page_url "
+    "FROM ( "
+      "SELECT " MOZ_PLACES_COLUMNS " FROM moz_places_temp "
+      "WHERE url = :page_url "
+      "UNION ALL "
+      "SELECT " MOZ_PLACES_COLUMNS " FROM moz_places "
+      "WHERE url = :page_url "
+    ") AS h JOIN moz_favicons f ON h.favicon_id = f.id "
     "LIMIT 1"));
 
+
   RETURN_IF_STMT(mDBGetData, NS_LITERAL_CSTRING(
     "SELECT f.data, f.mime_type FROM moz_favicons f WHERE url = :icon_url"));
 
   RETURN_IF_STMT(mDBInsertIcon, NS_LITERAL_CSTRING(
     "INSERT OR REPLACE INTO moz_favicons (id, url, data, mime_type, expiration) "
       "VALUES (:icon_id, :icon_url, :data, :mime_type, :expiration)"));
 
   RETURN_IF_STMT(mDBUpdateIcon, NS_LITERAL_CSTRING(
     "UPDATE moz_favicons SET data = :data, mime_type = :mime_type, "
                             "expiration = :expiration "
     "WHERE id = :icon_id"));
 
   RETURN_IF_STMT(mDBSetPageFavicon, NS_LITERAL_CSTRING(
-    "UPDATE moz_places SET favicon_id = :icon_id WHERE id = :page_id"));
+    "UPDATE moz_places_view SET favicon_id = :icon_id WHERE id = :page_id"));
 
   RETURN_IF_STMT(mDBAssociateFaviconURIToPageURI, NS_LITERAL_CSTRING(
-    "UPDATE moz_places "
+    "UPDATE moz_places_view "
     "SET favicon_id = (SELECT id FROM moz_favicons WHERE url = :icon_url) "
     "WHERE url = :page_url"));
 
   RETURN_IF_STMT(mDBRemoveOnDiskReferences, NS_LITERAL_CSTRING(
     "UPDATE moz_places "
     "SET favicon_id = NULL "
     "WHERE favicon_id NOT NULL"));
 
+  RETURN_IF_STMT(mDBRemoveTempReferences, NS_LITERAL_CSTRING(
+    "UPDATE moz_places_temp "
+    "SET favicon_id = NULL "
+    "WHERE favicon_id NOT NULL"));
+
   RETURN_IF_STMT(mDBRemoveAllFavicons, NS_LITERAL_CSTRING(
     "DELETE FROM moz_favicons WHERE id NOT IN ("
+      "SELECT favicon_id FROM moz_places_temp WHERE favicon_id NOT NULL "
+      "UNION ALL "
       "SELECT favicon_id FROM moz_places WHERE favicon_id NOT NULL "
     ")"));
 
   return nsnull;
 }
 
 
 // nsFaviconService::InitTables
@@ -240,21 +257,26 @@ nsFaviconService::InitTables(mozIStorage
 }
 
 
 NS_IMETHODIMP
 nsFaviconService::ExpireAllFavicons()
 {
   mFaviconsExpirationRunning = true;
 
+  // We do this in 2 steps, first we null-out all favicons in the disk table,
+  // then we do the same in the temp table.  This is because the view UPDATE
+  // trigger does not allow setting a NULL value to prevent dataloss.
+
   mozIStorageBaseStatement *stmts[] = {
     GetStatement(mDBRemoveOnDiskReferences),
+    GetStatement(mDBRemoveTempReferences),
     GetStatement(mDBRemoveAllFavicons),
   };
-  NS_ENSURE_STATE(stmts[0] && stmts[1]);
+  NS_ENSURE_STATE(stmts[0] && stmts[1] && stmts[2]);
   nsCOMPtr<mozIStoragePendingStatement> ps;
   nsCOMPtr<ExpireFaviconsStatementCallbackNotifier> callback =
     new ExpireFaviconsStatementCallbackNotifier(&mFaviconsExpirationRunning);
   nsresult rv = mDBConn->ExecuteAsync(stmts, NS_ARRAY_LENGTH(stmts), callback,
                                       getter_AddRefs(ps));
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
@@ -1024,16 +1046,17 @@ nsFaviconService::FinalizeStatements() {
     mDBGetURL,
     mDBGetData,
     mDBGetIconInfo,
     mDBGetIconInfoWithPage,
     mDBInsertIcon,
     mDBUpdateIcon,
     mDBSetPageFavicon,
     mDBRemoveOnDiskReferences,
+    mDBRemoveTempReferences,
     mDBRemoveAllFavicons,
     mDBAssociateFaviconURIToPageURI,
   };
 
   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/nsFaviconService.h
+++ b/toolkit/components/places/src/nsFaviconService.h
@@ -185,16 +185,17 @@ private:
   nsCOMPtr<mozIStorageStatement> mDBGetData; // returns actual data given URL
   nsCOMPtr<mozIStorageStatement> mDBGetIconInfo;
   nsCOMPtr<mozIStorageStatement> mDBGetIconInfoWithPage;
   nsCOMPtr<mozIStorageStatement> mDBInsertIcon;
   nsCOMPtr<mozIStorageStatement> mDBUpdateIcon;
   nsCOMPtr<mozIStorageStatement> mDBSetPageFavicon;
   nsCOMPtr<mozIStorageStatement> mDBAssociateFaviconURIToPageURI;
   nsCOMPtr<mozIStorageStatement> mDBRemoveOnDiskReferences;
+  nsCOMPtr<mozIStorageStatement> mDBRemoveTempReferences;
   nsCOMPtr<mozIStorageStatement> mDBRemoveAllFavicons;
 
   static nsFaviconService* gFaviconService;
 
   /**
    * A cached URI for the default icon. We return this a lot, and don't want to
    * re-parse and normalize our unchanging string many times.  Important: do
    * not return this directly; use Clone() since callers may change the object
--- a/toolkit/components/places/src/nsNavBookmarks.cpp
+++ b/toolkit/components/places/src/nsNavBookmarks.cpp
@@ -246,35 +246,45 @@ nsNavBookmarks::InitTables(mozIStorageCo
 mozIStorageStatement*
 nsNavBookmarks::GetStatement(const nsCOMPtr<mozIStorageStatement>& aStmt)
 {
   if (mShuttingDown)
     return nsnull;
 
   // Double ordering covers possible lastModified ties, that could happen when
   // importing, syncing or due to extensions.
-  // Note: not using a JOIN is cheaper in this case.
   RETURN_IF_STMT(mDBFindURIBookmarks, NS_LITERAL_CSTRING(
     "SELECT b.id "
     "FROM moz_bookmarks b "
-    "WHERE b.fk = (SELECT id FROM moz_places WHERE url = :page_url) "
-      "AND b.fk NOTNULL "
+    "WHERE b.type = :item_type AND b.fk = ( "
+      "SELECT id FROM moz_places_temp "
+      "WHERE url = :page_url "
+      "UNION "
+      "SELECT id FROM moz_places "
+      "WHERE url = :page_url "
+      "LIMIT 1 "
+    ") "
     "ORDER BY b.lastModified DESC, b.id DESC "));
 
   // Select all children of a given folder, sorted by position.
   // This is a LEFT OUTER JOIN with moz_places since folders does not have
   // a reference into that table.
   // We construct a result where the first columns exactly match those returned
   // by mDBGetURLPageInfo, and additionally contains columns for position,
   // item_child, and folder_child from moz_bookmarks.
   RETURN_IF_STMT(mDBGetChildren, NS_LITERAL_CSTRING(
-    "SELECT h.id, h.url, IFNULL(b.title, h.title), h.rev_host, h.visit_count, "
-           "h.last_visit_date, f.url, null, b.id, b.dateAdded, b.lastModified, "
-           "b.parent, null, b.position, b.type, b.fk, b.folder_type "
+    "SELECT IFNULL(h_t.id, h.id), IFNULL(h_t.url, h.url), "
+           "COALESCE(b.title, h_t.title, h.title), "
+           "IFNULL(h_t.rev_host, h.rev_host), "
+           "IFNULL(h_t.visit_count, h.visit_count), "
+           "IFNULL(h_t.last_visit_date, h.last_visit_date), "
+           "f.url, null, b.id, b.dateAdded, b.lastModified, b.parent, null, "
+           "b.position, b.type, b.fk, b.folder_type "
     "FROM moz_bookmarks b "
+    "LEFT JOIN moz_places_temp h_t ON b.fk = h_t.id "
     "LEFT JOIN moz_places h ON b.fk = h.id "
     "LEFT JOIN moz_favicons f ON h.favicon_id = f.id "
     "WHERE b.parent = :parent "
     "ORDER BY b.position ASC"));
 
   // Count all of the children of a given folder and checks that it exists.
   RETURN_IF_STMT(mDBFolderCount, NS_LITERAL_CSTRING(
     "SELECT COUNT(*), "
@@ -283,17 +293,18 @@ nsNavBookmarks::GetStatement(const nsCOM
 
   RETURN_IF_STMT(mDBGetChildAt, NS_LITERAL_CSTRING(
     "SELECT id, fk, type FROM moz_bookmarks "
     "WHERE parent = :parent AND position = :item_index"));
 
   // Get bookmark/folder/separator properties.
   RETURN_IF_STMT(mDBGetItemProperties, NS_LITERAL_CSTRING(
     "SELECT b.id, "
-           "(SELECT url FROM moz_places WHERE id = b.fk), "
+           "IFNULL((SELECT url FROM moz_places_temp WHERE id = b.fk), "
+                  "(SELECT url FROM moz_places WHERE id = b.fk)), "
            "b.title, b.position, b.fk, b.parent, b.type, b.folder_type, "
            "b.dateAdded, b.lastModified "
     "FROM moz_bookmarks b "
     "WHERE b.id = :item_id"));
 
   RETURN_IF_STMT(mDBGetItemIdForGUID, NS_LITERAL_CSTRING(
     "SELECT item_id FROM moz_items_annos "
     "WHERE content = :guid "
@@ -307,19 +318,22 @@ nsNavBookmarks::GetStatement(const nsCOM
             ":item_title, :folder_type, :date_added, :last_modified)"));
 
   // Just select position since it's just an int32 and may be faster.
   // We don't actually care about the data, just whether there is any.
   RETURN_IF_STMT(mDBIsBookmarkedInDatabase, NS_LITERAL_CSTRING(
     "SELECT 1 FROM moz_bookmarks WHERE fk = :page_id"));
 
   RETURN_IF_STMT(mDBIsURIBookmarkedInDatabase, NS_LITERAL_CSTRING(
-    "SELECT 1 FROM moz_bookmarks b "
-    "JOIN moz_places h ON b.fk = h.id "
-    "WHERE h.url = :page_url"));
+    "SELECT 1 FROM moz_bookmarks WHERE fk = ("
+      "SELECT id FROM moz_places_temp WHERE url = :page_url "
+      "UNION ALL "
+      "SELECT id FROM moz_places WHERE url = :page_url "
+      "LIMIT 1"
+    ")"));
 
   // Checks to make sure a place id is a bookmark, and isn't a livemark.
   RETURN_IF_STMT(mDBIsRealBookmark, NS_LITERAL_CSTRING(
     "SELECT id "
     "FROM moz_bookmarks "
     "WHERE fk = :page_id "
       "AND type = :item_type "
       "AND parent NOT IN ("
@@ -344,22 +358,29 @@ nsNavBookmarks::GetStatement(const nsCOM
     "WHERE id = :item_id"));
 
   RETURN_IF_STMT(mDBSetItemLastModified, NS_LITERAL_CSTRING(
     "UPDATE moz_bookmarks SET lastModified = :date WHERE id = :item_id"));
 
   RETURN_IF_STMT(mDBSetItemIndex, NS_LITERAL_CSTRING(
     "UPDATE moz_bookmarks SET position = :item_index WHERE id = :item_id"));
 
+  // Get keyword text for bookmarked URI.
   RETURN_IF_STMT(mDBGetKeywordForURI, NS_LITERAL_CSTRING(
     "SELECT k.keyword "
-    "FROM FROM moz_places h "
+    "FROM ( "
+      "SELECT id FROM moz_places_temp "
+      "WHERE url = :page_url "
+      "UNION ALL "
+      "SELECT id FROM moz_places "
+      "WHERE url = :page_url "
+      "LIMIT 1 "
+    ") AS h "
     "JOIN moz_bookmarks b ON b.fk = h.id "
-    "JOIN moz_keywords k ON k.id = b.keyword_id "
-    "WHERE h.url = :page_url "));
+    "JOIN moz_keywords k ON k.id = b.keyword_id"));
 
   RETURN_IF_STMT(mDBAdjustPosition, NS_LITERAL_CSTRING(
     "UPDATE moz_bookmarks SET position = position + :delta "
     "WHERE parent = :parent "
       "AND position >= :from_index AND position <= :to_index"));
 
   RETURN_IF_STMT(mDBRemoveItem, NS_LITERAL_CSTRING(
     "DELETE FROM moz_bookmarks WHERE id = :item_id"));
@@ -396,35 +417,61 @@ nsNavBookmarks::GetStatement(const nsCOM
   //    a visit then we could just join this table and get all visit_id that
   //    are in the same redirect_session as our visit.  This has the drawback
   //    that we can't ensure data integrity in the downgrade -> upgrade path,
   //    since an old version would not update the table on new visits.
   //
   // For most cases these levels of redirects should be fine though, it's hard
   // to hit a page that is 4 or 5 levels of redirects below a bookmarked page.
   //
+  // Moreover this query does not mix-up all possible cases of disk and temp
+  // tables.  This is because we expect a redirects chain to be completely on
+  // disk or completely in memory.  We never bring back visits from disk to
+  // memory, we sync visits on a timer (the chained visits have narrow times),
+  // or on bookmarks changes.  The likely possiblity that we break a chain in
+  // the middle is so much smaller than the perf and readability hit we would
+  // get making complete crossing joins.
+  //
   // As a bonus the query also checks first if place_id is already a bookmark,
   // so you don't have to check that apart.
 
 #define COALESCE_PLACEID \
   "COALESCE(greatgrandparent.place_id, grandparent.place_id, parent.place_id) "
 
   nsCString redirectsFragment =
     nsPrintfCString(3, "%d,%d",
                     nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT,
                     nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY);
 
   RETURN_IF_STMT(mDBFindRedirectedBookmark, NS_LITERAL_CSTRING(
-    "SELECT "
+    "SELECT IFNULL( "
+      "(SELECT url FROM moz_places_temp WHERE id = :page_id), "
       "(SELECT url FROM moz_places WHERE id = :page_id) "
+    ") "
     "FROM moz_bookmarks b "
     "WHERE b.fk = :page_id "
     "UNION ALL " // Not directly bookmarked.
-    "SELECT "
+    "SELECT IFNULL( "
+      "(SELECT url FROM moz_places_temp WHERE id = " COALESCE_PLACEID "), "
       "(SELECT url FROM moz_places WHERE id = " COALESCE_PLACEID ") "
+    ") "
+    "FROM moz_historyvisits_temp self "
+    "JOIN moz_bookmarks b ON b.fk = " COALESCE_PLACEID
+    "LEFT JOIN moz_historyvisits_temp parent ON parent.id = self.from_visit "
+    "LEFT JOIN moz_historyvisits_temp grandparent ON parent.from_visit = grandparent.id "
+      "AND parent.visit_type IN (") + redirectsFragment + NS_LITERAL_CSTRING(") "
+    "LEFT JOIN moz_historyvisits_temp greatgrandparent ON grandparent.from_visit = greatgrandparent.id "
+      "AND grandparent.visit_type IN (") + redirectsFragment + NS_LITERAL_CSTRING(") "
+    "WHERE self.visit_type IN (") + redirectsFragment + NS_LITERAL_CSTRING(") "
+      "AND self.place_id = :page_id "
+    "UNION ALL " // Not in the temp table.
+    "SELECT IFNULL( "
+      "(SELECT url FROM moz_places_temp WHERE id = " COALESCE_PLACEID "), "
+      "(SELECT url FROM moz_places WHERE id = " COALESCE_PLACEID ") "
+    ") "
     "FROM moz_historyvisits self "
     "JOIN moz_bookmarks b ON b.fk = " COALESCE_PLACEID
     "LEFT JOIN moz_historyvisits parent ON parent.id = self.from_visit "
     "LEFT JOIN moz_historyvisits grandparent ON parent.from_visit = grandparent.id "
       "AND parent.visit_type IN (") + redirectsFragment + NS_LITERAL_CSTRING(") "
     "LEFT JOIN moz_historyvisits greatgrandparent ON grandparent.from_visit = greatgrandparent.id "
       "AND grandparent.visit_type IN (") + redirectsFragment + NS_LITERAL_CSTRING(") "
     "WHERE self.visit_type IN (") + redirectsFragment + NS_LITERAL_CSTRING(") "
@@ -2579,16 +2626,18 @@ nsresult
 nsNavBookmarks::GetBookmarkIdsForURITArray(nsIURI* aURI,
                                            nsTArray<PRInt64>& aResult)
 {
   NS_ENSURE_ARG(aURI);
 
   DECLARE_AND_ASSIGN_SCOPED_LAZY_STMT(stmt, mDBFindURIBookmarks);
   nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI);
   NS_ENSURE_SUCCESS(rv, rv);
+  rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("item_type"), TYPE_BOOKMARK);
+  NS_ENSURE_SUCCESS(rv, rv);
 
   PRBool more;
   while (NS_SUCCEEDED((rv = stmt->ExecuteStep(&more))) && more) {
     PRInt64 bookmarkId;
     rv = stmt->GetInt64(kFindBookmarksIndex_ID, &bookmarkId);
     NS_ENSURE_SUCCESS(rv, rv);
     NS_ENSURE_TRUE(aResult.AppendElement(bookmarkId), NS_ERROR_OUT_OF_MEMORY);
   }
--- a/toolkit/components/places/src/nsNavHistory.cpp
+++ b/toolkit/components/places/src/nsNavHistory.cpp
@@ -60,17 +60,16 @@
 #include "prsystem.h"
 #include "prtime.h"
 #include "nsEscape.h"
 #include "nsIEffectiveTLDService.h"
 #include "nsIClassInfoImpl.h"
 #include "nsThreadUtils.h"
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsMathUtils.h"
-#include "mozIStorageCompletionCallback.h"
 
 #include "nsNavBookmarks.h"
 #include "nsAnnotationService.h"
 #include "nsILivemarkService.h"
 #include "nsFaviconService.h"
 
 #include "nsPlacesTables.h"
 #include "nsPlacesIndexes.h"
@@ -129,17 +128,17 @@ using namespace mozilla::places;
 
 // Default integer value for PREF_CACHE_TO_MEMORY_PERCENTAGE.
 // This is 6% of machine memory, giving 15MB for a user with 256MB of memory.
 // Out of this cache, SQLite will use at most the size of the database file.
 #define DATABASE_DEFAULT_CACHE_TO_MEMORY_PERCENTAGE 6
 
 // This is the schema version, update it at any schema change and add a
 // corresponding migrateVxx method below.
-#define DATABASE_SCHEMA_VERSION 11
+#define DATABASE_SCHEMA_VERSION 10
 
 // Filename of the database.
 #define DATABASE_FILENAME NS_LITERAL_STRING("places.sqlite")
 
 // Filename used to backup corrupt databases.
 #define DATABASE_CORRUPT_FILENAME NS_LITERAL_STRING("places.sqlite.corrupt")
 
 // We use the TRUNCATE journal mode to reduce the number of fsyncs.  Without
@@ -308,21 +307,18 @@ 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)
@@ -332,22 +328,16 @@ public:
   }
 
   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 {
@@ -356,22 +346,16 @@ protected:
         (void)obs->NotifyObservers(nsnull, mTopic, nsnull);
     }
   }
 
   const char* mTopic;
   bool mDoubleEnqueue;
 };
 
-NS_IMPL_ISUPPORTS2(
-  PlacesEvent
-, mozIStorageCompletionCallback
-, nsIRunnable
-)
-
 } // anonymouse namespace
 
 
 // Queries rows indexes to bind or get values, if adding a new one, be sure to
 // update nsNavBookmarks statements and its kGetChildrenIndex_* constants
 const PRInt32 nsNavHistory::kGetInfoIndex_PageID = 0;
 const PRInt32 nsNavHistory::kGetInfoIndex_URL = 1;
 const PRInt32 nsNavHistory::kGetInfoIndex_Title = 2;
@@ -781,22 +765,16 @@ nsNavHistory::InitDB()
       }
 
       // Migrate places up to V10
       if (currentSchemaVersion < 10) {
         rv = MigrateV10Up(mDBConn);
         NS_ENSURE_SUCCESS(rv, rv);
       }
 
-      // Migrate places up to V11
-      if (currentSchemaVersion < 11) {
-        rv = MigrateV11Up(mDBConn);
-        NS_ENSURE_SUCCESS(rv, rv);
-      }
-
       // Schema Upgrades must add migration code here.
 
     } else {
       // Downgrading
 
       // XXX Need to prompt user or otherwise notify of 
       // potential dataloss when downgrading.
 
@@ -842,22 +820,16 @@ nsNavHistory::InitDB()
 
     // CREATE TABLE moz_historyvisits.
     rv = mDBConn->ExecuteSimpleSQL(CREATE_MOZ_HISTORYVISITS);
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = mDBConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_HISTORYVISITS_PLACEDATE);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    // Visits triggers.
-    rv = mDBConn->ExecuteSimpleSQL(CREATE_HISTORYVISITS_AFTERINSERT_TRIGGER);
-    NS_ENSURE_SUCCESS(rv, rv);
-    rv = mDBConn->ExecuteSimpleSQL(CREATE_HISTORYVISITS_AFTERDELETE_TRIGGER);
-    NS_ENSURE_SUCCESS(rv, rv);
-
     // This makes a big difference in startup time for large profiles because of
     // finding bookmark redirects using the referring page. 
     rv = mDBConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_HISTORYVISITS_FROMVISIT);
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = mDBConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_HISTORYVISITS_VISITDATE);
     NS_ENSURE_SUCCESS(rv, rv);
 
@@ -878,16 +850,18 @@ nsNavHistory::InitDB()
 }
 
 
 nsresult
 nsNavHistory::InitAdditionalDBItems()
 {
   nsresult rv = InitTempTables();
   NS_ENSURE_SUCCESS(rv, rv);
+  rv = InitViews();
+  NS_ENSURE_SUCCESS(rv, rv);
   rv = InitFunctions();
   NS_ENSURE_SUCCESS(rv, rv);
   rv = InitStatements();
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
@@ -970,25 +944,98 @@ mozStorageFunctionGetUnreversedHost::OnF
   }
   NS_ADDREF(*_retval = result);
   return NS_OK;
 }
 
 nsresult
 nsNavHistory::InitTempTables()
 {
-  nsresult rv = mDBConn->ExecuteSimpleSQL(CREATE_MOZ_OPENPAGES_TEMP);
-  NS_ENSURE_SUCCESS(rv, rv);
+  nsresult rv;
+
+  // moz_places_temp
+  rv = mDBConn->ExecuteSimpleSQL(CREATE_MOZ_PLACES_TEMP);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = mDBConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_TEMP_URL);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = mDBConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_TEMP_FAVICON);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = mDBConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_TEMP_REVHOST);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = mDBConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_TEMP_VISITCOUNT);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = mDBConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_TEMP_FRECENCY);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = mDBConn->ExecuteSimpleSQL(CREATE_MOZ_PLACES_SYNC_TRIGGER);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+
+  // moz_historyvisits_temp
+  rv = mDBConn->ExecuteSimpleSQL(CREATE_MOZ_HISTORYVISITS_TEMP);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = mDBConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_HISTORYVISITS_TEMP_PLACEDATE);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = mDBConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_HISTORYVISITS_TEMP_FROMVISIT);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = mDBConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_HISTORYVISITS_TEMP_VISITDATE);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = mDBConn->ExecuteSimpleSQL(CREATE_MOZ_HISTORYVISITS_SYNC_TRIGGER);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // moz_openpages_temp
+  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::InitViews()
+{
+  nsresult rv;
+
+  // moz_places_view
+  rv = mDBConn->ExecuteSimpleSQL(CREATE_MOZ_PLACES_VIEW);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = mDBConn->ExecuteSimpleSQL(CREATE_PLACES_VIEW_INSERT_TRIGGER);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = mDBConn->ExecuteSimpleSQL(CREATE_PLACES_VIEW_DELETE_TRIGGER);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = mDBConn->ExecuteSimpleSQL(CREATE_PLACES_VIEW_UPDATE_TRIGGER);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // moz_historyvisits_view
+  rv = mDBConn->ExecuteSimpleSQL(CREATE_MOZ_HISTORYVISITS_VIEW);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = mDBConn->ExecuteSimpleSQL(CREATE_HISTORYVISITS_VIEW_INSERT_TRIGGER);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = mDBConn->ExecuteSimpleSQL(CREATE_HISTORYVISITS_VIEW_DELETE_TRIGGER);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = mDBConn->ExecuteSimpleSQL(CREATE_HISTORYVISITS_VIEW_UPDATE_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
   );
@@ -1002,174 +1049,285 @@ nsNavHistory::InitFunctions()
 
 
 /**
  * Called after InitDB, this creates our stored statements.
  */
 nsresult
 nsNavHistory::InitStatements()
 {
+  // mDBGetURLPageInfo
+  // We are not checking for duplicated ids into the unified table
+  // for perf reasons, LIMIT 1 will discard duplicates faster since we
+  // have unique urls.
   nsresult rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
     "SELECT id, url, title, rev_host, visit_count "
+    "FROM moz_places_temp "
+    "WHERE url = :page_url "
+    "UNION ALL "
+    "SELECT id, url, title, rev_host, visit_count "
     "FROM moz_places "
     "WHERE url = :page_url "
-  ), getter_AddRefs(mDBGetURLPageInfo));
-  NS_ENSURE_SUCCESS(rv, rv);
-
+    "LIMIT 1"),
+    getter_AddRefs(mDBGetURLPageInfo));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // mDBGetIdPageInfo
+  // We are not checking for duplicated ids into the unified table
+  // for perf reasons, LIMIT 1 will discard duplicates faster since we
+  // have unique place ids.
   rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
-    "SELECT id, url, title, rev_host, visit_count "
-    "FROM moz_places "
-    "WHERE id = :page_id "
-  ), getter_AddRefs(mDBGetIdPageInfo));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
-    "SELECT id, session, visit_date "
-    "FROM moz_historyvisits "
-    "WHERE place_id = (SELECT id FROM moz_places WHERE url = :page_url) "
-    "ORDER BY visit_date DESC "
-  ), getter_AddRefs(mDBRecentVisitOfURL));
-  NS_ENSURE_SUCCESS(rv, rv);
-
+      "SELECT id, url, title, rev_host, visit_count "
+      "FROM moz_places_temp "
+      "WHERE id = :page_id "
+      "UNION ALL "
+      "SELECT id, url, title, rev_host, visit_count "
+      "FROM moz_places "
+      "WHERE id = :page_id "
+      "LIMIT 1"),
+    getter_AddRefs(mDBGetIdPageInfo));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // mDBRecentVisitOfURL
+  // We are not checking for duplicated ids into the unified table
+  // for perf reasons, LIMIT 1 will discard duplicates faster since we
+  // expect visits in temp table being the most recent.
   rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
-    "SELECT id FROM moz_historyvisits "
-    "WHERE place_id = :page_id "
-      "AND visit_date = :visit_date "
-      "AND session = :session "
-  ), getter_AddRefs(mDBRecentVisitOfPlace));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
-    "INSERT INTO moz_historyvisits "
-      "(from_visit, place_id, visit_date, visit_type, session) "
-    "VALUES (:from_visit, :page_id, :visit_date, :visit_type, :session) "
-  ), getter_AddRefs(mDBInsertVisit));
-  NS_ENSURE_SUCCESS(rv, rv);
-
+      "SELECT id, session, visit_date "
+      "FROM moz_historyvisits_temp "
+      "WHERE place_id = IFNULL( "
+        "(SELECT id FROM moz_places_temp WHERE url = :page_url), "
+        "(SELECT id FROM moz_places WHERE url = :page_url) "
+      ") "
+      "UNION ALL "
+      "SELECT id, session, visit_date "
+      "FROM moz_historyvisits "
+      "WHERE place_id = IFNULL( "
+        "(SELECT id FROM moz_places_temp WHERE url = :page_url), "
+        "(SELECT id FROM moz_places WHERE url = :page_url) "
+      ") "
+      "ORDER BY visit_date DESC "
+      "LIMIT 1 "),
+    getter_AddRefs(mDBRecentVisitOfURL));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // mDBRecentVisitOfPlace
+  // We are not checking for duplicated ids into the unified table
+  // for perf reasons, LIMIT 1 will discard duplicates faster since we
+  // expect visits in temp table being the most recent.  
   rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
-    "SELECT id, visit_count, typed, hidden "
-    "FROM moz_places "
-    "WHERE url = :page_url "
-  ), getter_AddRefs(mDBGetPageVisitStats));
-  NS_ENSURE_SUCCESS(rv, rv);
-
+      "SELECT id FROM moz_historyvisits_temp "
+      "WHERE place_id = :page_id "
+        "AND visit_date = :visit_date "
+        "AND session = :session "
+      "UNION ALL "
+      "SELECT id FROM moz_historyvisits "
+      "WHERE place_id = :page_id "
+        "AND visit_date = :visit_date "
+        "AND session = :session "
+      "LIMIT 1"),
+    getter_AddRefs(mDBRecentVisitOfPlace));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // mDBInsertVisit
   rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
-    "SELECT h.id "
-    "FROM moz_places h "
-    "WHERE url = ?1 "
-      "AND EXISTS(SELECT id FROM moz_historyvisits WHERE place_id = h.id LIMIT 1) "
-  ), getter_AddRefs(mDBIsPageVisited));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  // We don't need to update visit_count since it's maintained in sync by
-  // triggers. Indeed we must NEVER touch it!
+      "INSERT INTO moz_historyvisits_view "
+        "(from_visit, place_id, visit_date, visit_type, session) "
+      "VALUES (:from_visit, :page_id, :visit_date, :visit_type, :session)"),
+    getter_AddRefs(mDBInsertVisit));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // mDBGetPageVisitStats (see InternalAdd)
+  // We are not checking for duplicated ids into the unified table
+  // for perf reasons, LIMIT 1 will discard duplicates faster since we
+  // have unique place ids.
+  rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+      "SELECT id, visit_count, typed, hidden "
+      "FROM moz_places_temp "
+      "WHERE url = :page_url "
+      "UNION ALL "
+      "SELECT id, visit_count, typed, hidden "
+      "FROM moz_places "
+      "WHERE url = :page_url "
+      "LIMIT 1"),
+    getter_AddRefs(mDBGetPageVisitStats));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // mDBIsPageVisited
+  // We are not checking for duplicated ids into the unified table
+  // for perf reasons, LIMIT 1 will discard duplicates faster since we
+  // only need to know if a visit exists.
+  // Use indexed params here for performance.
   rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
-    "UPDATE moz_places "
-    "SET hidden = :hidden, typed = :typed "
-    "WHERE id = :page_id "
-  ), getter_AddRefs(mDBUpdatePageVisitStats));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
-    "INSERT INTO moz_places "
-      "(url, title, rev_host, hidden, typed, frecency) "
-    "VALUES (:page_url, :page_title, :rev_host, :hidden, :typed, :frecency) "
-  ), getter_AddRefs(mDBAddNewPage));
-  NS_ENSURE_SUCCESS(rv, rv);
-
+      "SELECT h.id "
+      "FROM moz_places_temp h "
+      "WHERE url = ?1 " 
+        "AND ( "
+          "EXISTS(SELECT id FROM moz_historyvisits_temp WHERE place_id = h.id LIMIT 1) "
+          "OR EXISTS(SELECT id FROM moz_historyvisits WHERE place_id = h.id LIMIT 1) "
+        ") "
+      "UNION ALL "
+      "SELECT h.id "
+      "FROM moz_places h "
+      "WHERE url = ?1 "
+      "AND ( "
+        "EXISTS(SELECT id FROM moz_historyvisits_temp WHERE place_id = h.id LIMIT 1) "
+        "OR EXISTS(SELECT id FROM moz_historyvisits WHERE place_id = h.id LIMIT 1) "
+      ") "
+      "LIMIT 1"), 
+    getter_AddRefs(mDBIsPageVisited));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // mDBUpdatePageVisitStats (see InternalAdd)
+  // we don't need to update visit_count since it's maintained
+  // in sync by triggers, and we must NEVER touch it
   rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
-    "/* do not warn (bug 487594) */ "
-    "SELECT GROUP_CONCAT(tag_title, ', ') "
-    "FROM ( "
-      "SELECT t.title AS tag_title "
-      "FROM moz_bookmarks b "
-      "JOIN moz_bookmarks t ON t.id = b.parent "
-      "WHERE b.fk = (SELECT id FROM moz_places WHERE url = :page_url) "
-        "AND LENGTH(t.title) > 0 "
-        "AND b.type = ") +
-          nsPrintfCString("%d", nsINavBookmarksService::TYPE_BOOKMARK) +
-        NS_LITERAL_CSTRING(" AND t.parent = :tags_folder "
-      "ORDER BY t.title COLLATE NOCASE ASC) "
-  ), getter_AddRefs(mDBGetTags));
-  NS_ENSURE_SUCCESS(rv, rv);
-
+      "UPDATE moz_places_view "
+      "SET hidden = :hidden, typed = :typed "
+      "WHERE id = :page_id"),
+    getter_AddRefs(mDBUpdatePageVisitStats));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // mDBAddNewPage (see InternalAddNewPage)
+  rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+      "INSERT INTO moz_places_view "
+        "(url, title, rev_host, hidden, typed, frecency) "
+      "VALUES (:page_url, :page_title, :rev_host, :hidden, :typed, :frecency)"),
+    getter_AddRefs(mDBAddNewPage));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // mDBGetTags
   rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
-    "SELECT a.item_id, a.content "
-    "FROM moz_anno_attributes n "
-    "JOIN moz_items_annos a ON n.id = a.anno_attribute_id "
-    "WHERE n.name = :anno_name "
-  ), getter_AddRefs(mDBGetItemsWithAnno));
-  NS_ENSURE_SUCCESS(rv, rv);
-
+      "/* do not warn (bug 487594) */ "
+      "SELECT GROUP_CONCAT(tag_title, ', ') "
+      "FROM ( "
+        "SELECT t.title AS tag_title "
+        "FROM moz_bookmarks b "
+        "JOIN moz_bookmarks t ON t.id = b.parent "
+        "WHERE b.fk = IFNULL((SELECT id FROM moz_places_temp WHERE url = :page_url), "
+                            "(SELECT id FROM moz_places WHERE url = :page_url)) "
+          "AND LENGTH(t.title) > 0 "
+          "AND b.type = ") +
+            nsPrintfCString("%d", nsINavBookmarksService::TYPE_BOOKMARK) +
+          NS_LITERAL_CSTRING(" AND t.parent = :tags_folder "
+        "ORDER BY t.title COLLATE NOCASE ASC)"),
+    getter_AddRefs(mDBGetTags));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // mDBGetItemsWithAnno
   rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
-    "UPDATE moz_places "
-    "SET title = :page_title "
-    "WHERE url = :page_url "
-  ), getter_AddRefs(mDBSetPlaceTitle));
-  NS_ENSURE_SUCCESS(rv, rv);
-
+      "SELECT a.item_id, a.content "
+      "FROM moz_anno_attributes n "
+      "JOIN moz_items_annos a ON n.id = a.anno_attribute_id "
+      "WHERE n.name = :anno_name"),
+    getter_AddRefs(mDBGetItemsWithAnno));
+   NS_ENSURE_SUCCESS(rv, rv);
+
+  // mDBSetPlaceTitle
   rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
-    "INSERT OR REPLACE INTO moz_openpages_temp (place_id, open_count) "
-    "VALUES (:page_id, "
-      "IFNULL("
-        "(SELECT open_count + 1 FROM moz_openpages_temp WHERE place_id = :page_id), "
-        "1"
-      ")"
-    ")"
-  ), getter_AddRefs(mDBRegisterOpenPage));
-  NS_ENSURE_SUCCESS(rv, rv);
-
+      "UPDATE moz_places_view "
+      "SET title = :page_title "
+      "WHERE url = :page_url"),
+    getter_AddRefs(mDBSetPlaceTitle));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // mDBRegisterOpenPage
   rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
-    "UPDATE moz_openpages_temp "
-    "SET open_count = open_count - 1 "
-    "WHERE place_id = :page_id "
-  ), getter_AddRefs(mDBUnregisterOpenPage));
-  NS_ENSURE_SUCCESS(rv, rv);
-
+      "INSERT OR REPLACE INTO moz_openpages_temp (place_id, open_count) "
+      "VALUES (:page_id, "
+        "IFNULL("
+          "(SELECT open_count + 1 FROM moz_openpages_temp WHERE place_id = :page_id), "
+          "1"
+        ")"
+      ")"),
+    getter_AddRefs(mDBRegisterOpenPage));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // mDBUnregisterOpenPage
+  rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+      "UPDATE moz_openpages_temp "
+      "SET open_count = open_count - 1 "
+      "WHERE place_id = :page_id"),
+    getter_AddRefs(mDBUnregisterOpenPage));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // mDBVisitsForFrecency
   // 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.
   rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
-    "SELECT v.visit_date, COALESCE( "
-      "(SELECT r.visit_type FROM moz_historyvisits r "
-        "WHERE v.visit_type IN ") +
-          nsPrintfCString("(%d,%d) ", TRANSITION_REDIRECT_PERMANENT,
-                                      TRANSITION_REDIRECT_TEMPORARY) +
-          NS_LITERAL_CSTRING(" AND r.id = v.from_visit), "
-      "visit_type) "
-    "FROM moz_historyvisits v "
-    "WHERE v.place_id = :page_id "
-    "ORDER BY visit_date DESC LIMIT "
-  ) + nsPrintfCString("%d", mNumVisitsForFrecency)
-  , getter_AddRefs(mDBVisitsForFrecency));
-  NS_ENSURE_SUCCESS(rv, rv);
-
+      "SELECT v.visit_date, COALESCE( "
+        "(SELECT r.visit_type FROM moz_historyvisits_temp r "
+          "WHERE v.visit_type IN ") +
+            nsPrintfCString("(%d,%d) ", TRANSITION_REDIRECT_PERMANENT,
+                                        TRANSITION_REDIRECT_TEMPORARY) +
+            NS_LITERAL_CSTRING(" AND r.id = v.from_visit), "
+        "(SELECT r.visit_type FROM moz_historyvisits r "
+          "WHERE v.visit_type IN ") +
+            nsPrintfCString("(%d,%d) ", TRANSITION_REDIRECT_PERMANENT,
+                                        TRANSITION_REDIRECT_TEMPORARY) +
+            NS_LITERAL_CSTRING(" AND r.id = v.from_visit), "
+        "visit_type) "
+      "FROM moz_historyvisits_temp v "
+      "WHERE v.place_id = :page_id "
+      "UNION ALL "
+      "SELECT v.visit_date, COALESCE( "
+        "(SELECT r.visit_type FROM moz_historyvisits_temp r "
+          "WHERE v.visit_type IN ") +
+            nsPrintfCString("(%d,%d) ", TRANSITION_REDIRECT_PERMANENT,
+                                        TRANSITION_REDIRECT_TEMPORARY) +
+            NS_LITERAL_CSTRING(" AND r.id = v.from_visit), "
+        "(SELECT r.visit_type FROM moz_historyvisits r "
+          "WHERE v.visit_type IN ") +
+            nsPrintfCString("(%d,%d) ", TRANSITION_REDIRECT_PERMANENT,
+                                        TRANSITION_REDIRECT_TEMPORARY) +
+            NS_LITERAL_CSTRING(" AND r.id = v.from_visit), "
+        "visit_type) "
+      "FROM moz_historyvisits v "
+      "WHERE v.place_id = :page_id "
+        "AND v.id NOT IN (SELECT id FROM moz_historyvisits_temp) "
+      "ORDER BY visit_date DESC LIMIT ") +
+        nsPrintfCString("%d", mNumVisitsForFrecency),
+    getter_AddRefs(mDBVisitsForFrecency));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // mDBUpdateFrecencyAndHidden
   rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
-    "UPDATE moz_places "
-    "SET frecency = :frecency, hidden = :hidden "
-    "WHERE id = :page_id "
-  ), getter_AddRefs(mDBUpdateFrecencyAndHidden));
-  NS_ENSURE_SUCCESS(rv, rv);
-
+      "UPDATE moz_places_view SET frecency = :frecency, hidden = :hidden "
+      "WHERE id = :page_id"),
+    getter_AddRefs(mDBUpdateFrecencyAndHidden));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // mDBGetPlaceVisitStats
+  // We are not checking for duplicated ids into the unified table
+  // for perf reasons, LIMIT 1 will discard duplicates faster since we
+  // have unique place ids.
   rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
-    "SELECT typed, hidden, frecency "
-    "FROM moz_places "
-    "WHERE id = :page_id "
-  ), getter_AddRefs(mDBGetPlaceVisitStats));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  // When calculating frecency, we want the visit count to be all the visits.
+      "SELECT typed, hidden, frecency "
+      "FROM moz_places_temp WHERE id = :page_id "
+      "UNION ALL "
+      "SELECT typed, hidden, frecency "
+      "FROM moz_places WHERE id = :page_id "
+      "LIMIT 1"),
+    getter_AddRefs(mDBGetPlaceVisitStats));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // when calculating frecency, we want the visit count to be 
+  // all the visits.
   rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
-    "SELECT COUNT(*) FROM moz_historyvisits WHERE place_id = :page_id "
-  ), getter_AddRefs(mDBFullVisitCount));
+      "SELECT "
+        "(SELECT COUNT(*) FROM moz_historyvisits WHERE place_id = :page_id) + "
+        "(SELECT COUNT(*) FROM moz_historyvisits_temp WHERE place_id = :page_id "
+            "AND id NOT IN (SELECT id FROM moz_historyvisits))"),
+    getter_AddRefs(mDBFullVisitCount));
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 
 /**
  * This dumps all bookmarks-related tables, and recreates them,
@@ -1592,17 +1750,18 @@ nsNavHistory::MigrateV8Up(mozIStorageCon
 
 nsresult
 nsNavHistory::MigrateV9Up(mozIStorageConnection *aDBConn)
 {
   mozStorageTransaction transaction(aDBConn, PR_FALSE);
   // Added in Bug 488966.  The last_visit_date column caches the last
   // visit date, this enhances SELECT performances when we
   // need to sort visits by visit date.
-  // The cached value is synced by triggers on every added or removed visit.
+  // The cached value is synced by INSERT and DELETE triggers on
+  // moz_historyvisits_view, on every added or removed visit.
   // See nsPlacesTriggers.h for details on the triggers.
   PRBool oldIndexExists = PR_FALSE;
   nsresult rv = mDBConn->IndexExists(
     NS_LITERAL_CSTRING("moz_places_lastvisitdateindex"), &oldIndexExists);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (!oldIndexExists) {
     // Add last_visit_date column to moz_places.
@@ -1649,70 +1808,17 @@ nsNavHistory::MigrateV10Up(mozIStorageCo
   // This way we can use lastModified index to sort.
   nsresult rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
       "UPDATE moz_bookmarks SET lastModified = dateAdded "
       "WHERE lastModified IS NULL"));
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
-
-
-nsresult
-nsNavHistory::MigrateV11Up(mozIStorageConnection *aDBConn)
-{
-  // Temp tables are going away, so we need the visit_count triggers to return.
-
-  // Regardless triggers existance, every time we pass through this migration
-  // step, we must ensure correctness of visit_count values.
-  nsresult rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    "UPDATE moz_places SET visit_count = "
-      "(SELECT count(*) FROM moz_historyvisits "
-       "WHERE place_id = moz_places.id "
-        "AND visit_type NOT IN ") +
-          nsPrintfCString("(0,%d,%d,%d) ",
-                          nsINavHistoryService::TRANSITION_EMBED,
-                          nsINavHistoryService::TRANSITION_FRAMED_LINK,
-                          nsINavHistoryService::TRANSITION_DOWNLOAD) +
-      NS_LITERAL_CSTRING(")")
-  );
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  // Now create triggers if needed.
-  nsCOMPtr<mozIStorageStatement> triggerDetection;
-  rv = aDBConn->CreateStatement(NS_LITERAL_CSTRING(
-      "SELECT name "
-      "FROM sqlite_master "
-      "WHERE type = 'trigger' "
-      "AND name = :trigger_name"),
-    getter_AddRefs(triggerDetection));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  // Both visits triggers were removed in the past at the same time, so we can
-  // check just one of them.
-  PRBool triggerExists;
-  rv = triggerDetection->BindUTF8StringByName(
-    NS_LITERAL_CSTRING("trigger_name"),
-    NS_LITERAL_CSTRING("moz_historyvisits_afterinsert_v2_trigger")
-  );
-  NS_ENSURE_SUCCESS(rv, rv);
-  rv = triggerDetection->ExecuteStep(&triggerExists);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  if (!triggerExists) {
-    rv = mDBConn->ExecuteSimpleSQL(CREATE_HISTORYVISITS_AFTERINSERT_TRIGGER);
-    NS_ENSURE_SUCCESS(rv, rv);
-    rv = mDBConn->ExecuteSimpleSQL(CREATE_HISTORYVISITS_AFTERDELETE_TRIGGER);
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
-
-  return NS_OK;
-}
-
-
+   
 // nsNavHistory::GetUrlIdFor
 //
 //    Called by the bookmarks and annotation services, this function returns the
 //    ID of the row for the given URL, optionally creating one if it doesn't
 //    exist. A newly created entry will have no visits.
 //
 //    If aAutoCreate is false and the item doesn't exist, the entry ID will be
 //    zero.
@@ -2023,21 +2129,22 @@ PRInt64
 nsNavHistory::GetNewSessionID()
 {
   // Use cached value if already initialized.
   if (mLastSessionID)
     return ++mLastSessionID;
 
   // Extract the last session ID, so we know where to pick up. There is no
   // index over sessions so we use the visit_date index.
+  // This happens on the first visit, so we don't care about temp tables.
   nsCOMPtr<mozIStorageStatement> selectSession;
   nsresult rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
-    "SELECT session FROM moz_historyvisits "
-    "ORDER BY visit_date DESC "
-  ), getter_AddRefs(selectSession));
+      "SELECT session FROM moz_historyvisits "
+      "ORDER BY visit_date DESC LIMIT 1"),
+    getter_AddRefs(selectSession));
   NS_ENSURE_SUCCESS(rv, rv);
   PRBool hasSession;
   if (NS_SUCCEEDED(selectSession->ExecuteStep(&hasSession)) && hasSession)
     mLastSessionID = selectSession->AsInt64(0) + 1;
   else
     mLastSessionID = 1;
 
   return mLastSessionID;
@@ -2070,16 +2177,18 @@ PRInt32
 nsNavHistory::GetDaysOfHistory() {
   PRInt32 daysOfHistory = 0;
   nsCOMPtr<mozIStorageStatement> statement;
   nsresult rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
       "SELECT ROUND(( "
         "strftime('%s','now','localtime','utc') - "
         "( "
           "SELECT visit_date FROM moz_historyvisits "
+          "UNION ALL "
+          "SELECT visit_date FROM moz_historyvisits_temp "
           "ORDER BY visit_date ASC LIMIT 1 "
         ")/1000000 "
       ")/86400) AS daysOfHistory "),
     getter_AddRefs(statement));
   NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Unable to create statement.");
   NS_ENSURE_SUCCESS(rv, 0);
   PRBool hasResult;
   if (NS_SUCCEEDED(statement->ExecuteStep(&hasResult)) && hasResult)
@@ -2440,17 +2549,19 @@ nsNavHistory::GetHasHistoryEntries(PRBoo
   // Use cached value if it's been set
   if (mHasHistoryEntries != -1) {
     *aHasEntries = (mHasHistoryEntries == 1);
     return NS_OK;
   }
 
   nsCOMPtr<mozIStorageStatement> dbSelectStatement;
   nsresult rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
-      "SELECT 1 FROM moz_historyvisits "),
+      "SELECT 1 "
+      "WHERE EXISTS (SELECT id FROM moz_historyvisits_temp LIMIT 1) "
+        "OR EXISTS (SELECT id FROM moz_historyvisits LIMIT 1)"),
     getter_AddRefs(dbSelectStatement));
   NS_ENSURE_SUCCESS(rv, rv);
   rv = dbSelectStatement->ExecuteStep(aHasEntries);
   NS_ENSURE_SUCCESS(rv, rv);
 
   mHasHistoryEntries = *aHasEntries ? 1 : 0;
   return NS_OK;
 }
@@ -2458,29 +2569,34 @@ nsNavHistory::GetHasHistoryEntries(PRBoo
 nsresult
 nsNavHistory::FixInvalidFrecenciesForExcludedPlaces()
 {
   // for every moz_place that has an invalid frecency (< 0) and
   // is an unvisited child of a livemark feed, or begins with "place:",
   // set frecency to 0 so that it is excluded from url bar autocomplete.
   nsCOMPtr<mozIStorageStatement> dbUpdateStatement;
   nsresult rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
-      "UPDATE moz_places "
+      "UPDATE moz_places_view "
       "SET frecency = 0 WHERE id IN ("
         "SELECT h.id FROM moz_places h "
         "WHERE h.url >= 'place:' AND h.url < 'place;' "
         "UNION "
+        "SELECT h.id FROM moz_places_temp h "
+        "WHERE  h.url >= 'place:' AND h.url < 'place;' "
+        "UNION "
         // Unvisited child of a livemark
         "SELECT b.fk FROM moz_bookmarks b "
         "JOIN moz_bookmarks bp ON bp.id = b.parent "
         "JOIN moz_items_annos a ON a.item_id = bp.id "
         "JOIN moz_anno_attributes n ON n.id = a.anno_attribute_id "
         "WHERE n.name = :anno_name "
         "AND b.fk IN( "
           "SELECT id FROM moz_places WHERE visit_count = 0 AND frecency < 0 "
+          "UNION ALL "
+          "SELECT id FROM moz_places_temp WHERE visit_count = 0 AND frecency < 0 "
         ") "
       ")"),
     getter_AddRefs(dbUpdateStatement));
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = dbUpdateStatement->BindUTF8StringByName(
     NS_LITERAL_CSTRING("anno_name"), NS_LITERAL_CSTRING(LMANNO_FEEDURI)
   );
@@ -3148,21 +3264,55 @@ PlacesSQLQueryBuilder::SelectAsURI()
                          NS_LITERAL_CSTRING("h.id"),
                          mHasSearchTerms,
                          tagsSqlFragment);
 
       mQueryString = NS_LITERAL_CSTRING(
         "SELECT h.id, h.url, h.title, h.rev_host, h.visit_count, "
         "h.last_visit_date, f.url, v.session, null, null, null, null, ") +
         tagsSqlFragment + NS_LITERAL_CSTRING(
+        "FROM moz_places_temp h "
+        "JOIN moz_historyvisits_temp v ON h.id = v.place_id "
+        "LEFT JOIN moz_favicons f ON h.favicon_id = f.id "
+        // WHERE 1 is a no-op since additonal conditions will start with AND.
+        "WHERE 1 "
+          "{QUERY_OPTIONS_VISITS} {QUERY_OPTIONS_PLACES} "
+          "{ADDITIONAL_CONDITIONS} "
+        "GROUP BY h.id "
+        "UNION ALL "
+        "SELECT h.id, h.url, h.title, h.rev_host, h.visit_count, "
+        "h.last_visit_date, f.url, v.session, null, null, null, null, ") +
+        tagsSqlFragment + NS_LITERAL_CSTRING(
+        "FROM moz_places_temp h "
+        "JOIN moz_historyvisits v ON h.id = v.place_id "
+        "LEFT JOIN moz_favicons f ON h.favicon_id = f.id "
+        "WHERE h.id NOT IN (SELECT place_id FROM moz_historyvisits_temp) "
+          "{QUERY_OPTIONS_VISITS} {QUERY_OPTIONS_PLACES} "
+          "{ADDITIONAL_CONDITIONS} "
+        "GROUP BY h.id "
+        "UNION ALL "
+        "SELECT h.id, h.url, h.title, h.rev_host, h.visit_count, "
+        "h.last_visit_date, f.url, v.session, null, null, null, null, ") +
+        tagsSqlFragment + NS_LITERAL_CSTRING(
+        "FROM moz_places h "
+        "JOIN moz_historyvisits_temp v ON h.id = v.place_id "
+        "LEFT JOIN moz_favicons f ON h.favicon_id = f.id "
+        "WHERE h.id NOT IN (SELECT id FROM moz_places_temp) "
+          "{QUERY_OPTIONS_VISITS} {QUERY_OPTIONS_PLACES} "
+          "{ADDITIONAL_CONDITIONS} "
+        "GROUP BY h.id "
+        "UNION ALL "
+        "SELECT h.id, h.url, h.title, h.rev_host, h.visit_count, "
+        "h.last_visit_date, f.url, v.session, null, null, null, null, ") +
+        tagsSqlFragment + NS_LITERAL_CSTRING(
         "FROM moz_places h "
         "JOIN moz_historyvisits v ON h.id = v.place_id "
         "LEFT JOIN moz_favicons f ON h.favicon_id = f.id "
-        // WHERE 1 is a no-op since additonal conditions will start with AND.
-        "WHERE 1 "
+        "WHERE h.id NOT IN (SELECT id FROM moz_places_temp) "
+          "AND h.id NOT IN (SELECT place_id FROM moz_historyvisits_temp) "
           "{QUERY_OPTIONS_VISITS} {QUERY_OPTIONS_PLACES} "
           "{ADDITIONAL_CONDITIONS} "
         "GROUP BY h.id ");
       break;
 
     case nsINavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS:
       if (mResultType == nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS) {
         // Order-by clause is hardcoded because we need to discard duplicates
@@ -3182,38 +3332,71 @@ PlacesSQLQueryBuilder::SelectAsURI()
             "b2.dateAdded, b2.lastModified, b2.parent, ") +
             tagsSqlFragment + NS_LITERAL_CSTRING(
           "FROM moz_bookmarks b2 "
           "JOIN (SELECT b.fk "
                 "FROM moz_bookmarks b "
                 // ADDITIONAL_CONDITIONS will filter on parent.
                 "WHERE b.type = 1 {ADDITIONAL_CONDITIONS} "
                 ") AS seed ON b2.fk = seed.fk "
+          "JOIN moz_places_temp h ON h.id = b2.fk "
+          "LEFT OUTER JOIN moz_favicons f ON h.favicon_id = f.id "
+          "WHERE NOT EXISTS ( "
+            "SELECT id FROM moz_bookmarks WHERE id = b2.parent AND parent = ") +
+                nsPrintfCString("%lld", history->GetTagsFolder()) +
+          NS_LITERAL_CSTRING(") "
+          "UNION ALL "
+          "SELECT b2.fk, h.url, COALESCE(b2.title, h.title), h.rev_host, "
+            "h.visit_count, h.last_visit_date, f.url, null, b2.id, "
+            "b2.dateAdded, b2.lastModified, b2.parent, ") +
+            tagsSqlFragment + NS_LITERAL_CSTRING(
+          "FROM moz_bookmarks b2 "
+          "JOIN (SELECT b.fk "
+                "FROM moz_bookmarks b "
+                // ADDITIONAL_CONDITIONS will filter on parent.
+                "WHERE b.type = 1 {ADDITIONAL_CONDITIONS} "
+                ") AS seed ON b2.fk = seed.fk "
           "JOIN moz_places h ON h.id = b2.fk "
           "LEFT OUTER JOIN moz_favicons f ON h.favicon_id = f.id "
           "WHERE NOT EXISTS ( "
             "SELECT id FROM moz_bookmarks WHERE id = b2.parent AND parent = ") +
                 nsPrintfCString("%lld", history->GetTagsFolder()) +
           NS_LITERAL_CSTRING(") "
+            "AND h.id NOT IN (SELECT id FROM moz_places_temp) "
           "ORDER BY b2.fk DESC, b2.lastModified DESC");
       }
       else {
         GetTagsSqlFragment(history->GetTagsFolder(),
                            NS_LITERAL_CSTRING("b.fk"),
                            mHasSearchTerms,
                            tagsSqlFragment);
         mQueryString = NS_LITERAL_CSTRING(
           "SELECT b.fk, h.url, COALESCE(b.title, h.title), h.rev_host, "
             "h.visit_count, h.last_visit_date, f.url, null, b.id, "
             "b.dateAdded, b.lastModified, b.parent, ") +
             tagsSqlFragment + NS_LITERAL_CSTRING(
           "FROM moz_bookmarks b "
+          "JOIN moz_places_temp h ON b.fk = h.id AND b.type = 1 "
+          "LEFT OUTER JOIN moz_favicons f ON h.favicon_id = f.id "
+          "WHERE NOT EXISTS "
+            "(SELECT id FROM moz_bookmarks "
+              "WHERE id = b.parent AND parent = ") +
+                nsPrintfCString("%lld", history->GetTagsFolder()) +
+            NS_LITERAL_CSTRING(") "
+            "{ADDITIONAL_CONDITIONS}"
+          "UNION ALL "
+          "SELECT b.fk, h.url, COALESCE(b.title, h.title), h.rev_host, "
+            "h.visit_count, h.last_visit_date, f.url, null, b.id, "
+            "b.dateAdded, b.lastModified, b.parent, ") +
+            tagsSqlFragment + NS_LITERAL_CSTRING(
+          "FROM moz_bookmarks b "
           "JOIN moz_places h ON b.fk = h.id AND b.type = 1 "
           "LEFT OUTER JOIN moz_favicons f ON h.favicon_id = f.id "
-          "WHERE NOT EXISTS "
+          "WHERE h.id NOT IN (SELECT id FROM moz_places_temp) "
+            "AND NOT EXISTS "
               "(SELECT id FROM moz_bookmarks "
                 "WHERE id = b.parent AND parent = ") +
                   nsPrintfCString("%lld", history->GetTagsFolder()) +
               NS_LITERAL_CSTRING(") "
             "{ADDITIONAL_CONDITIONS}");
       }
       break;
 
@@ -3232,22 +3415,53 @@ PlacesSQLQueryBuilder::SelectAsVisit()
   GetTagsSqlFragment(history->GetTagsFolder(),
                      NS_LITERAL_CSTRING("h.id"),
                      mHasSearchTerms,
                      tagsSqlFragment);
   mQueryString = NS_LITERAL_CSTRING(
     "SELECT h.id, h.url, h.title, h.rev_host, h.visit_count, "
       "v.visit_date, f.url, v.session, null, null, null, null, ") +
       tagsSqlFragment + NS_LITERAL_CSTRING(
-    "FROM moz_places h "
+    "FROM moz_places_temp h "
+    "JOIN moz_historyvisits_temp v ON h.id = v.place_id "
+    "LEFT JOIN moz_favicons f ON h.favicon_id = f.id "
+    // WHERE 1 is a no-op since additonal conditions will start with AND.
+    "WHERE 1 "
+      "{QUERY_OPTIONS_VISITS} {QUERY_OPTIONS_PLACES} "
+      "{ADDITIONAL_CONDITIONS} "
+    "UNION ALL "
+    "SELECT h.id, h.url, h.title, h.rev_host, h.visit_count, "
+      "v.visit_date, f.url, v.session, null, null, null, null, ") +
+      tagsSqlFragment + NS_LITERAL_CSTRING(
+    "FROM moz_places_temp h "
     "JOIN moz_historyvisits v ON h.id = v.place_id "
     "LEFT JOIN moz_favicons f ON h.favicon_id = f.id "
     // WHERE 1 is a no-op since additonal conditions will start with AND.
     "WHERE 1 "
       "{QUERY_OPTIONS_VISITS} {QUERY_OPTIONS_PLACES} "
+      "{ADDITIONAL_CONDITIONS} "
+    "UNION ALL "
+    "SELECT h.id, h.url, h.title, h.rev_host, h.visit_count, "
+      "v.visit_date, f.url, v.session, null, null, null, null, ") +
+      tagsSqlFragment + NS_LITERAL_CSTRING(
+    "FROM moz_places h "
+    "JOIN moz_historyvisits_temp v ON h.id = v.place_id "
+    "LEFT JOIN moz_favicons f ON h.favicon_id = f.id "
+    "WHERE h.id NOT IN (SELECT id FROM moz_places_temp) "
+      "{QUERY_OPTIONS_VISITS} {QUERY_OPTIONS_PLACES} "
+      "{ADDITIONAL_CONDITIONS} "
+    "UNION ALL "
+    "SELECT h.id, h.url, h.title, h.rev_host, h.visit_count, "
+      "v.visit_date, f.url, v.session, null, null, null, null, ") +
+      tagsSqlFragment + NS_LITERAL_CSTRING(
+    "FROM moz_places h "
+    "JOIN moz_historyvisits v ON h.id = v.place_id "
+    "LEFT JOIN moz_favicons f ON h.favicon_id = f.id "
+    "WHERE h.id NOT IN (SELECT id FROM moz_places_temp) "
+      "{QUERY_OPTIONS_VISITS} {QUERY_OPTIONS_PLACES} "
       "{ADDITIONAL_CONDITIONS} ");
 
   return NS_OK;
 }
 
 nsresult
 PlacesSQLQueryBuilder::SelectAsDay()
 {
@@ -3411,28 +3625,38 @@ PlacesSQLQueryBuilder::SelectAsDay()
     nsPrintfCString dateParam("dayTitle%d", i);
     mAddParams.Put(dateParam, dateName);
 
      nsPrintfCString dayRange(1024,
         "SELECT :%s AS dayTitle, "
                "%s AS beginTime, "
                "%s AS endTime "
          "WHERE EXISTS ( "
+           "SELECT id FROM moz_historyvisits_temp "
+          "WHERE visit_date >= %s "
+            "AND visit_date < %s "
+            "AND visit_type NOT IN (0,%d,%d) "
+            "{QUERY_OPTIONS_VISITS} "
+          "UNION ALL "
           "SELECT id FROM moz_historyvisits "
           "WHERE visit_date >= %s "
             "AND visit_date < %s "
              "AND visit_type NOT IN (0,%d,%d) "
              "{QUERY_OPTIONS_VISITS} "
            "LIMIT 1 "
         ") ",
       dateParam.get(),
       sqlFragmentContainerBeginTime.get(),
       sqlFragmentContainerEndTime.get(),
       sqlFragmentSearchBeginTime.get(),
       sqlFragmentSearchEndTime.get(),
+       nsINavHistoryService::TRANSITION_EMBED,
+       nsINavHistoryService::TRANSITION_FRAMED_LINK,
+      sqlFragmentSearchBeginTime.get(),
+      sqlFragmentSearchEndTime.get(),
       nsINavHistoryService::TRANSITION_EMBED,
       nsINavHistoryService::TRANSITION_FRAMED_LINK);
 
     mQueryString.Append(dayRange);
 
     if (i < HISTORY_DATE_CONT_NUM(daysOfHistory))
       mQueryString.Append(NS_LITERAL_CSTRING(" UNION ALL "));
   }
@@ -3455,31 +3679,44 @@ PlacesSQLQueryBuilder::SelectAsSite()
 
   // We want just sites, but from whole database.
   if (mConditions.IsEmpty()) {
     mQueryString = nsPrintfCString(2048,
       "SELECT DISTINCT null, "
              "'place:type=%ld&sort=%ld&domain=&domainIsHost=true', "
              ":localhost, :localhost, null, null, null, null, null, null, null "
       "WHERE EXISTS ( "
+        "SELECT id FROM moz_places_temp "
+        "WHERE hidden <> 1 "
+          "AND rev_host = '.' "
+          "AND visit_count > 0 "
+          "AND url BETWEEN 'file://' AND 'file:/~' "
+        "UNION ALL "
         "SELECT id FROM moz_places "
-        "WHERE hidden <> 1 "
+        "WHERE id NOT IN (SELECT id FROM moz_places_temp) "
+          "AND hidden <> 1 "
           "AND rev_host = '.' "
           "AND visit_count > 0 "
           "AND url BETWEEN 'file://' AND 'file:/~' "
       ") "
       "UNION ALL "
       "SELECT DISTINCT null, "
              "'place:type=%ld&sort=%ld&domain='||host||'&domainIsHost=true', "
              "host, host, null, null, null, null, null, null, null "
       "FROM ( "
         "SELECT get_unreversed_host(rev_host) host "
         "FROM ( "
+          "SELECT DISTINCT rev_host FROM moz_places_temp "
+          "WHERE hidden <> 1 "
+            "AND rev_host <> '.' "
+            "AND visit_count > 0 "
+          "UNION ALL "
           "SELECT DISTINCT rev_host FROM moz_places "
-          "WHERE hidden <> 1 "
+          "WHERE id NOT IN (SELECT id FROM moz_places_temp) "
+            "AND hidden <> 1 "
             "AND rev_host <> '.' "
             "AND visit_count > 0 "
         ") "
       "ORDER BY 1 ASC) ",
       nsINavHistoryQueryOptions::RESULTS_AS_URI,
       mSortingMode,
       nsINavHistoryQueryOptions::RESULTS_AS_URI,
       mSortingMode);
@@ -3495,30 +3732,81 @@ PlacesSQLQueryBuilder::SelectAsSite()
         "SELECT h.id "
         "FROM moz_places h "
         "JOIN moz_historyvisits v ON v.place_id = h.id "
         "WHERE h.hidden <> 1 AND h.rev_host = '.' "
           "AND h.visit_count > 0 "
           "AND h.url BETWEEN 'file://' AND 'file:/~' "
           "{QUERY_OPTIONS_VISITS} {QUERY_OPTIONS_PLACES} "
           "{ADDITIONAL_CONDITIONS} "
+        "UNION "
+        "SELECT h.id "
+        "FROM moz_places_temp h "
+        "JOIN moz_historyvisits v ON v.place_id = h.id "
+        "WHERE h.hidden <> 1 AND h.rev_host = '.' "
+          "AND h.visit_count > 0 "
+          "AND h.url BETWEEN 'file://' AND 'file:/~' "
+          "{QUERY_OPTIONS_VISITS} {QUERY_OPTIONS_PLACES} "
+          "{ADDITIONAL_CONDITIONS} "
+        "UNION "
+        "SELECT h.id "
+        "FROM moz_places h "
+        "JOIN moz_historyvisits_temp v ON v.place_id = h.id "
+        "WHERE h.hidden <> 1 AND h.rev_host = '.' "
+          "AND h.visit_count > 0 "
+          "AND h.url BETWEEN 'file://' AND 'file:/~' "
+          "{QUERY_OPTIONS_VISITS} {QUERY_OPTIONS_PLACES} "
+          "{ADDITIONAL_CONDITIONS} "
+        "UNION "
+        "SELECT h.id "
+        "FROM moz_places_temp h "
+        "JOIN moz_historyvisits_temp v ON v.place_id = h.id "
+        "WHERE h.hidden <> 1 AND h.rev_host = '.' "
+          "AND h.visit_count > 0 "
+          "AND h.url BETWEEN 'file://' AND 'file:/~' "
+          "{QUERY_OPTIONS_VISITS}  {QUERY_OPTIONS_PLACES} "
+          "{ADDITIONAL_CONDITIONS} "        
       ") "
       "UNION ALL "
       "SELECT DISTINCT null, "
              "'place:type=%ld&sort=%ld&domain='||host||'&domainIsHost=true"
                "&beginTime='||:begin_time||'&endTime='||:end_time, "
              "host, host, null, null, null, null, null, null, null "
       "FROM ( "
         "SELECT DISTINCT get_unreversed_host(rev_host) AS host "
         "FROM moz_places h "
         "JOIN moz_historyvisits v ON v.place_id = h.id "
         "WHERE h.rev_host <> '.' "
           "AND h.visit_count > 0 "
           "{QUERY_OPTIONS_VISITS} {QUERY_OPTIONS_PLACES} "
           "{ADDITIONAL_CONDITIONS} "
+        "UNION "
+        "SELECT DISTINCT get_unreversed_host(rev_host) AS host "
+        "FROM moz_places_temp h "
+        "JOIN moz_historyvisits v ON v.place_id = h.id "
+        "WHERE h.rev_host <> '.' "
+          "AND h.visit_count > 0 "
+          "{QUERY_OPTIONS_VISITS} {QUERY_OPTIONS_PLACES} "
+          "{ADDITIONAL_CONDITIONS} "
+        "UNION "
+        "SELECT DISTINCT get_unreversed_host(rev_host) AS host "
+        "FROM moz_places h "
+        "JOIN moz_historyvisits_temp v ON v.place_id = h.id "
+        "WHERE h.rev_host <> '.' "
+          "AND h.visit_count > 0 "
+          "{QUERY_OPTIONS_VISITS} {QUERY_OPTIONS_PLACES} "
+          "{ADDITIONAL_CONDITIONS} "
+        "UNION "
+        "SELECT DISTINCT get_unreversed_host(rev_host) AS host "
+        "FROM moz_places_temp h "
+        "JOIN moz_historyvisits_temp v ON v.place_id = h.id "        
+        "WHERE h.rev_host <> '.' "
+          "AND h.visit_count > 0 "
+          "{QUERY_OPTIONS_VISITS} {QUERY_OPTIONS_PLACES} "
+          "{ADDITIONAL_CONDITIONS} "        
         "ORDER BY 1 ASC "
       ") ",
       nsINavHistoryQueryOptions::RESULTS_AS_URI,
       mSortingMode,
       nsINavHistoryQueryOptions::RESULTS_AS_URI,
       mSortingMode);
   }
 
@@ -3560,16 +3848,21 @@ PlacesSQLQueryBuilder::Where()
     additionalVisitsConditions += NS_LITERAL_CSTRING(
       "AND visit_type NOT IN ") +
       nsPrintfCString("(%d,%d) ", nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT,
                                   nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY);
   }
   else if (mRedirectsMode == nsINavHistoryQueryOptions::REDIRECTS_MODE_TARGET) {
     additionalVisitsConditions += NS_LITERAL_CSTRING(
       "AND NOT EXISTS ( "
+        "SELECT id FROM moz_historyvisits_temp WHERE from_visit = v.id "
+        "AND visit_type IN ") +
+        nsPrintfCString("(%d,%d) ", nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT,
+                                    nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY) +
+      NS_LITERAL_CSTRING(") AND NOT EXISTS ( "
         "SELECT id FROM moz_historyvisits WHERE from_visit = v.id "
         "AND visit_type IN ") +
         nsPrintfCString("(%d,%d) ", nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT,
                                     nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY) +
       NS_LITERAL_CSTRING(") ");
   }
 
   if (!mIncludeHidden) {
@@ -3626,17 +3919,19 @@ PlacesSQLQueryBuilder::OrderBy()
     return NS_OK;
 
   // Sort clause: we will sort later, but if it comes out of the DB sorted,
   // our later sort will be basically free. The DB can sort these for free
   // most of the time anyway, because it has indices over these items.
   switch(mSortingMode)
   {
     case nsINavHistoryQueryOptions::SORT_BY_NONE:
-      // Ensure sorting does not change based on tables status.
+      // If this is a URI query the sorting could change based on the
+      // sync status of disk and temp tables, we must ensure sorting does not
+      // change between queries.
       if (mResultType == nsINavHistoryQueryOptions::RESULTS_AS_URI) {
         if (mQueryType == nsINavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS)
           mQueryString += NS_LITERAL_CSTRING(" ORDER BY b.id ASC ");
         else if (mQueryType == nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY)
           mQueryString += NS_LITERAL_CSTRING(" ORDER BY h.id ASC ");
       }
       break;
     case nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING:
@@ -3769,20 +4064,47 @@ nsNavHistory::ConstructQueryString(
       IsOptimizableHistoryQuery(aQueries, aOptions,
         nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_DESCENDING)) {
     // Generate an optimized query for the history menu and most visited
     // smart bookmark.
     queryString = NS_LITERAL_CSTRING(
       "SELECT h.id, h.url, h.title, h.rev_host, h.visit_count, h.last_visit_date, "
           "f.url, null, null, null, null, null, ") +
           tagsSqlFragment + NS_LITERAL_CSTRING(
+        "FROM moz_places_temp h "
+        "LEFT OUTER JOIN moz_favicons f ON h.favicon_id = f.id "
+        "WHERE h.hidden <> 1 "
+          "AND EXISTS (SELECT id FROM moz_historyvisits_temp WHERE place_id = h.id "
+                       "AND visit_type NOT IN ") +
+                       nsPrintfCString("(0,%d,%d) ",
+                                       nsINavHistoryService::TRANSITION_EMBED,
+                                       nsINavHistoryService::TRANSITION_FRAMED_LINK) +
+                       NS_LITERAL_CSTRING("UNION ALL "
+                       "SELECT id FROM moz_historyvisits WHERE place_id = h.id "
+                       "AND visit_type NOT IN ") +
+                       nsPrintfCString("(0,%d,%d) ",
+                                       nsINavHistoryService::TRANSITION_EMBED,
+                                       nsINavHistoryService::TRANSITION_FRAMED_LINK) +
+                       NS_LITERAL_CSTRING("LIMIT 1) "
+          "{QUERY_OPTIONS} "
+      "UNION ALL "
+      "SELECT h.id, h.url, h.title, h.rev_host, h.visit_count, h.last_visit_date, "
+          "f.url, null, null, null, null, null, ") +
+          tagsSqlFragment + NS_LITERAL_CSTRING(
         "FROM moz_places h "
         "LEFT OUTER JOIN moz_favicons f ON h.favicon_id = f.id "
         "WHERE h.hidden <> 1 "
-          "AND EXISTS (SELECT id FROM moz_historyvisits WHERE place_id = h.id "
+          "AND h.id NOT IN (SELECT id FROM moz_places_temp) "
+          "AND EXISTS (SELECT id FROM moz_historyvisits_temp WHERE place_id = h.id "
+                       "AND visit_type NOT IN ") +
+                       nsPrintfCString("(0,%d,%d) ",
+                                       nsINavHistoryService::TRANSITION_EMBED,
+                                       nsINavHistoryService::TRANSITION_FRAMED_LINK) +
+                       NS_LITERAL_CSTRING("UNION ALL "
+                       "SELECT id FROM moz_historyvisits WHERE place_id = h.id "
                        "AND visit_type NOT IN ") +
                        nsPrintfCString("(0,%d,%d) ",
                                        nsINavHistoryService::TRANSITION_EMBED,
                                        nsINavHistoryService::TRANSITION_FRAMED_LINK) +
                        NS_LITERAL_CSTRING("LIMIT 1) "
           "{QUERY_OPTIONS} "
         );
 
@@ -3795,33 +4117,57 @@ nsNavHistory::ConstructQueryString(
     queryString.Append(NS_LITERAL_CSTRING("LIMIT "));
     queryString.AppendInt(aOptions->MaxResults());
 
     nsCAutoString additionalQueryOptions;
     if (aOptions->RedirectsMode() ==
           nsINavHistoryQueryOptions::REDIRECTS_MODE_SOURCE) {
       additionalQueryOptions +=  nsPrintfCString(256,
         "AND NOT EXISTS ( "
+          "SELECT id FROM moz_historyvisits_temp WHERE place_id = h.id "
+                                             "AND visit_type IN (%d,%d)"
+        ") "
+        "AND NOT EXISTS ( "
           "SELECT id FROM moz_historyvisits WHERE place_id = h.id "
                                              "AND visit_type IN (%d,%d)"
         ") ",
         TRANSITION_REDIRECT_PERMANENT,
+        TRANSITION_REDIRECT_TEMPORARY,
+        TRANSITION_REDIRECT_PERMANENT,
         TRANSITION_REDIRECT_TEMPORARY);
     }
     else if (aOptions->RedirectsMode() ==
               nsINavHistoryQueryOptions::REDIRECTS_MODE_TARGET) {
       additionalQueryOptions += nsPrintfCString(1024,
         "AND NOT EXISTS ( "
           "SELECT id "
+          "FROM moz_historyvisits_temp v "
+          "WHERE place_id = h.id "
+            "AND EXISTS(SELECT id FROM moz_historyvisits_temp "
+                           "WHERE from_visit = v.id AND visit_type IN (%d,%d) "
+                        "UNION ALL "
+                        "SELECT id FROM moz_historyvisits "
+                           "WHERE from_visit = v.id AND visit_type IN (%d,%d)) "
+          "UNION ALL "
+          "SELECT id "
           "FROM moz_historyvisits v "
           "WHERE place_id = h.id "
-            "AND EXISTS(SELECT id FROM moz_historyvisits "
+            "AND EXISTS(SELECT id FROM moz_historyvisits_temp "
+                           "WHERE from_visit = v.id AND visit_type IN (%d,%d) "
+                        "UNION ALL "
+                        "SELECT id FROM moz_historyvisits "
                            "WHERE from_visit = v.id AND visit_type IN (%d,%d)) "
         ") ",
         TRANSITION_REDIRECT_PERMANENT,
+        TRANSITION_REDIRECT_TEMPORARY,
+        TRANSITION_REDIRECT_PERMANENT,
+        TRANSITION_REDIRECT_TEMPORARY,
+        TRANSITION_REDIRECT_PERMANENT,
+        TRANSITION_REDIRECT_TEMPORARY,
+        TRANSITION_REDIRECT_PERMANENT,
         TRANSITION_REDIRECT_TEMPORARY);
     }
     queryString.ReplaceSubstring("{QUERY_OPTIONS}",
                                   additionalQueryOptions.get());
     return NS_OK;
   }
 
   nsCAutoString conditions;
@@ -4055,21 +4401,37 @@ nsNavHistory::AddPageWithDetails(nsIURI 
 //    the statement.
 
 NS_IMETHODIMP
 nsNavHistory::GetLastPageVisited(nsACString & aLastPageVisited)
 {
   NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
 
   nsCOMPtr<mozIStorageStatement> statement;
+  // We are not checking for duplicated ids into the unified table
+  // for perf reasons, LIMIT 1 will discard duplicates faster since we
+  // expect newest visits being in temp table.
   nsresult rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+      "SELECT url, visit_date FROM moz_historyvisits_temp v "
+      "JOIN moz_places_temp h ON v.place_id = h.id "
+      "WHERE h.hidden <> 1 "
+      "UNION ALL "
+      "SELECT url, visit_date FROM moz_historyvisits_temp v "
+      "JOIN moz_places h ON v.place_id = h.id "
+      "WHERE h.hidden <> 1 "
+      "UNION ALL "
+      "SELECT url, visit_date FROM moz_historyvisits v "
+      "JOIN moz_places_temp h ON v.place_id = h.id "
+      "WHERE h.hidden <> 1 "
+      "UNION ALL "
       "SELECT url, visit_date FROM moz_historyvisits v "
       "JOIN moz_places h ON v.place_id = h.id "
       "WHERE h.hidden <> 1 "
-      "ORDER BY visit_date DESC "),
+      "ORDER BY visit_date DESC "
+      "LIMIT 1 "),
     getter_AddRefs(statement));
   NS_ENSURE_SUCCESS(rv, rv);
 
   PRBool hasMatch = PR_FALSE;
   if (NS_SUCCEEDED(statement->ExecuteStep(&hasMatch)) && hasMatch)
     return statement->GetUTF8String(0, aLastPageVisited);
 
   aLastPageVisited.Truncate(0);
@@ -4117,17 +4479,17 @@ nsNavHistory::RemovePagesInternal(const 
 
   mozStorageTransaction transaction(mDBConn, PR_FALSE);
 
   nsresult rv = PreparePlacesForVisitsDelete(aPlaceIdsQueryString);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Delete all visits for the specified place ids.
   rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-      "DELETE FROM moz_historyvisits WHERE place_id IN (") +
+      "DELETE FROM moz_historyvisits_view WHERE place_id IN (") +
         aPlaceIdsQueryString +
         NS_LITERAL_CSTRING(")"));
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = CleanupPlacesOnVisitsDelete(aPlaceIdsQueryString);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Invalidate the cached value for whether there's history or not.
@@ -4156,22 +4518,31 @@ nsNavHistory::PreparePlacesForVisitsDele
 
   // if a moz_place is annotated or was a bookmark,
   // we won't delete it, but we will delete the moz_visits
   // so we need to reset the frecency.  Note, we set frecency to
   // -visit_count, as we use that value in our "on idle" query
   // to figure out which places to recalculate frecency first.
   // Pay attention to not set frecency = 0 if visit_count = 0
   nsresult rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-      "UPDATE moz_places "
+      "UPDATE moz_places_view "
       "SET frecency = -MAX(visit_count, 1) "
       "WHERE id IN ( "
         "SELECT h.id " 
+        "FROM moz_places_temp h "
+        "WHERE h.id IN ( ") + aPlaceIdsQueryString + NS_LITERAL_CSTRING(") "
+          "AND ( "
+            "EXISTS (SELECT b.id FROM moz_bookmarks b WHERE b.fk =h.id) "
+            "OR EXISTS (SELECT a.id FROM moz_annos a WHERE a.place_id = h.id) "
+          ") "
+        "UNION ALL "
+        "SELECT h.id " 
         "FROM moz_places h "
         "WHERE h.id IN ( ") + aPlaceIdsQueryString + NS_LITERAL_CSTRING(") "
+          "AND h.id NOT IN (SELECT id FROM moz_places_temp) "
           "AND ( "
             "EXISTS (SELECT b.id FROM moz_bookmarks b WHERE b.fk =h.id) "
             "OR EXISTS (SELECT a.id FROM moz_annos a WHERE a.place_id = h.id) "
           ") "        
       ")"));
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
@@ -4196,19 +4567,26 @@ nsNavHistory::CleanupPlacesOnVisitsDelet
   if (aPlaceIdsQueryString.IsEmpty())
     return NS_OK;
 
   // if the entry is not bookmarked and is not a place: uri
   // then we can remove it from moz_places.
   // Note that we do NOT delete favicons. Any unreferenced favicons will be
   // deleted next time the browser is shut down.
   nsresult rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-      "DELETE FROM moz_places WHERE id IN ( "
+      "DELETE FROM moz_places_view WHERE id IN ("
+        "SELECT h.id FROM moz_places_temp h "
+        "WHERE h.id IN ( ") + aPlaceIdsQueryString + NS_LITERAL_CSTRING(") "
+          "AND SUBSTR(h.url, 1, 6) <> 'place:' "
+          "AND NOT EXISTS "
+            "(SELECT b.id FROM moz_bookmarks b WHERE b.fk = h.id LIMIT 1) "
+        "UNION ALL "
         "SELECT h.id FROM moz_places h "
-        "WHERE h.id IN ( ") + aPlaceIdsQueryString + NS_LITERAL_CSTRING(") "
+        "WHERE h.id NOT IN (SELECT id FROM moz_places_temp) "
+          "AND h.id IN ( ") + aPlaceIdsQueryString + NS_LITERAL_CSTRING(") "
           "AND SUBSTR(h.url, 1, 6) <> 'place:' "
           "AND NOT EXISTS "
             "(SELECT b.id FROM moz_bookmarks b WHERE b.fk = h.id LIMIT 1) "
     ")"));
   NS_ENSURE_SUCCESS(rv, rv);
 
   // If we have removed all visits to a livemark's child, we need to fix its
   // frecency, or it would appear in the url bar autocomplete.
@@ -4337,18 +4715,22 @@ nsNavHistory::RemovePagesFromHost(const 
     conditionString.AssignLiteral("rev_host >= ?1 AND rev_host < ?2 ");
   else
     conditionString.AssignLiteral("rev_host = ?1 ");
 
   nsCOMPtr<mozIStorageStatement> statement;
 
   // create statement depending on delete type
   rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+      "SELECT id FROM moz_places_temp "
+      "WHERE ") + conditionString + NS_LITERAL_CSTRING(
+      "UNION ALL "
       "SELECT id FROM moz_places "
-      "WHERE ") + conditionString,
+      "WHERE id NOT IN (SELECT id FROM moz_places_temp) "
+        "AND ") + conditionString,
     getter_AddRefs(statement));
   NS_ENSURE_SUCCESS(rv, rv);
   rv = statement->BindStringByIndex(0, revHostDot);
   NS_ENSURE_SUCCESS(rv, rv);
   if (aEntireDomain) {
     rv = statement->BindStringByIndex(1, revHostSlash);
     NS_ENSURE_SUCCESS(rv, rv);
   }
@@ -4390,19 +4772,30 @@ nsNavHistory::RemovePagesByTimeframe(PRT
   nsresult rv;
   // build a list of place ids to delete
   nsCString deletePlaceIdsQueryString;
 
   // we only need to know if a place has a visit into the given timeframe
   // this query is faster than actually selecting in moz_historyvisits
   nsCOMPtr<mozIStorageStatement> selectByTime;
   rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+      "SELECT h.id FROM moz_places_temp h WHERE "
+        "EXISTS "
+          "(SELECT id FROM moz_historyvisits v WHERE v.place_id = h.id "
+            "AND v.visit_date >= :from_date AND v.visit_date <= :to_date LIMIT 1)"
+        "OR EXISTS "
+          "(SELECT id FROM moz_historyvisits_temp v WHERE v.place_id = h.id "
+            "AND v.visit_date >= :from_date AND v.visit_date <= :to_date LIMIT 1) "
+      "UNION "
       "SELECT h.id FROM moz_places h WHERE "
         "EXISTS "
           "(SELECT id FROM moz_historyvisits v WHERE v.place_id = h.id "
+            "AND v.visit_date >= :from_date AND v.visit_date <= :to_date LIMIT 1)"
+        "OR EXISTS "
+          "(SELECT id FROM moz_historyvisits_temp v WHERE v.place_id = h.id "
             "AND v.visit_date >= :from_date AND v.visit_date <= :to_date LIMIT 1)"),
     getter_AddRefs(selectByTime));
   NS_ENSURE_SUCCESS(rv, rv);
   rv = selectByTime->BindInt64ByName(NS_LITERAL_CSTRING("from_date"), aBeginTime);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = selectByTime->BindInt64ByName(NS_LITERAL_CSTRING("to_date"), aEndTime);
   NS_ENSURE_SUCCESS(rv, rv);
 
@@ -4453,20 +4846,28 @@ nsNavHistory::RemoveVisitsByTimeframe(PR
   // These places will be deleted by the call to CleanupPlacesOnVisitsDelete
   // below.
   nsCString deletePlaceIdsQueryString;
   {
     nsCOMPtr<mozIStorageStatement> selectByTime;
     mozStorageStatementScoper scope(selectByTime);
     rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
         "SELECT place_id "
+        "FROM moz_historyvisits_temp "
+        "WHERE :from_date <= visit_date AND visit_date <= :to_date "
+        "UNION "
+        "SELECT place_id "
         "FROM moz_historyvisits "
         "WHERE :from_date <= visit_date AND visit_date <= :to_date "
         "EXCEPT "
         "SELECT place_id "
+        "FROM moz_historyvisits_temp "
+        "WHERE visit_date < :from_date OR :to_date < visit_date "
+        "EXCEPT "
+        "SELECT place_id "
         "FROM moz_historyvisits "
         "WHERE visit_date < :from_date OR :to_date < visit_date"),
       getter_AddRefs(selectByTime));
     NS_ENSURE_SUCCESS(rv, rv);
     rv = selectByTime->BindInt64ByName(NS_LITERAL_CSTRING("from_date"), aBeginTime);
     NS_ENSURE_SUCCESS(rv, rv);
     rv = selectByTime->BindInt64ByName(NS_LITERAL_CSTRING("to_date"), aEndTime);
     NS_ENSURE_SUCCESS(rv, rv);
@@ -4491,17 +4892,17 @@ nsNavHistory::RemoveVisitsByTimeframe(PR
   mozStorageTransaction transaction(mDBConn, PR_FALSE);
 
   rv = PreparePlacesForVisitsDelete(deletePlaceIdsQueryString);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Delete all visits within the timeframe.
   nsCOMPtr<mozIStorageStatement> deleteVisitsStmt;
   rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
-      "DELETE FROM moz_historyvisits "
+      "DELETE FROM moz_historyvisits_view "
       "WHERE :from_date <= visit_date AND visit_date <= :to_date"),
     getter_AddRefs(deleteVisitsStmt));
   NS_ENSURE_SUCCESS(rv, rv);
   rv = deleteVisitsStmt->BindInt64ByName(NS_LITERAL_CSTRING("from_date"), aBeginTime);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = deleteVisitsStmt->BindInt64ByName(NS_LITERAL_CSTRING("to_date"), aEndTime);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = deleteVisitsStmt->Execute();
@@ -4531,26 +4932,29 @@ nsNavHistory::RemoveAllPages()
 
   mozStorageTransaction transaction(mDBConn, PR_FALSE);
 
   // reset frecency for all items that will _not_ be deleted
   // Note, we set frecency to -visit_count since we use that value in our
   // idle query to figure out which places to recalcuate frecency first.
   // We must do this before deleting visits.
   nsresult rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    "UPDATE moz_places SET frecency = -MAX(visit_count, 1) "
+    "UPDATE moz_places_view SET frecency = -MAX(visit_count, 1) "
     "WHERE id IN("
+      "SELECT h.id FROM moz_places_temp h "
+      "WHERE EXISTS (SELECT id FROM moz_bookmarks WHERE fk = h.id) "
+      "UNION ALL "
       "SELECT h.id FROM moz_places h "
       "WHERE EXISTS (SELECT id FROM moz_bookmarks WHERE fk = h.id) "
     ")"));
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Expire visits, then let the paranoid functions do the cleanup for us.
   rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-      "DELETE FROM moz_historyvisits"));
+      "DELETE FROM moz_historyvisits_view"));
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Some of the remaining places could be place: urls or
   // unvisited livemark items, so setting the frecency to -1
   // will cause them to show up in the url bar autocomplete
   // call FixInvalidFrecenciesForExcludedPlaces to handle that scenario.
   rv = FixInvalidFrecenciesForExcludedPlaces();
   if (NS_FAILED(rv))
@@ -5342,21 +5746,18 @@ nsNavHistory::Observe(nsISupports *aSubj
     nsCOMPtr<nsIPrefService> prefService = do_QueryInterface(mPrefBranch);
     if (prefService)
       prefService->SavePrefFile(nsnull);
 
     // Finalize all statements.
     nsresult rv = FinalizeInternalStatements();
     NS_ENSURE_SUCCESS(rv, rv);
 
-    // Finally, close the connection.
-    nsRefPtr<PlacesEvent> closeListener =
-      new PlacesEvent(TOPIC_PLACES_DATABASE_CLOSED);
-    (void)mDBConn->AsyncClose(closeListener);
-    mDBConn = nsnull;
+    // NOTE: We don't close the connection because the sync service could still
+    // need it for a final flush.
   }
 
 #ifdef MOZ_XUL
   else if (strcmp(aTopic, TOPIC_AUTOCOMPLETE_FEEDBACK_INCOMING) == 0) {
     nsCOMPtr<nsIAutoCompleteInput> input = do_QueryInterface(aSubject);
     if (!input)
       return NS_OK;
 
@@ -5540,18 +5941,19 @@ nsresult
 nsNavHistory::DecayFrecency()
 {
   // Update frecency values.
   nsresult rv = FixInvalidFrecencies();
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Globally decay places frecency rankings to estimate reduced frecency
   // values of pages that haven't been visited for a while, i.e., they do
-  // not get an updated frecency.  A scaling factor of .975 results in .5 the
-  // original value after 28 days.
+  // not get an updated frecency. We directly modify moz_places to avoid
+  // bringing the whole database into places_temp through places_view. A
+  // scaling factor of .975 results in .5 the original value after 28 days.
   nsCOMPtr<mozIStorageStatement> decayFrecency;
   rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
       "UPDATE moz_places SET frecency = ROUND(frecency * .975) "
       "WHERE frecency > 0"),
     getter_AddRefs(decayFrecency));
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Decay potentially unused adaptive entries (e.g. those that are at 1)
@@ -5752,16 +6154,19 @@ nsNavHistory::QueryToSelectClause(nsNavH
   if (transitions.Length() == 1) {
     clause.Condition("v.visit_type =").Param(":transition0_");
   }
   else if (transitions.Length() > 1) {
     for (PRUint32 i = 0; i < transitions.Length(); ++i) {
       nsPrintfCString param(":transition%d_", i);
       clause.Str("EXISTS (SELECT 1 FROM moz_historyvisits "
                          "WHERE place_id = h.id AND visit_type = "
+                ).Param(param.get()).Str(" UNION ALL "
+                         "SELECT 1 FROM moz_historyvisits_temp "
+                         "WHERE place_id = h.id AND visit_type = "
                 ).Param(param.get()).Str(" LIMIT 1)");
       if (i < transitions.Length() - 1)
         clause.Str("AND");
     }
   }
 
   // parent parameter is used in tag contents queries.
   // Only one folder should be defined for them.
@@ -7338,17 +7743,17 @@ nsNavHistory::FixInvalidFrecencies()
   // are not livemark items and not place: queries.)
   //
   // Note, we are not limiting ourselves to places with visits because we may
   // not have any if the place is a bookmark and we expired or deleted all the
   // visits.
   nsCOMPtr<mozIStorageStatement> invalidFrecencies;
   nsresult rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
       "SELECT id, typed, hidden, frecency, url "
-      "FROM moz_places "
+      "FROM moz_places_view "
       "WHERE frecency < 0"),
     getter_AddRefs(invalidFrecencies));
   NS_ENSURE_SUCCESS(rv, rv);
 
   PRBool hasMore = PR_FALSE;
   while (NS_SUCCEEDED(invalidFrecencies->ExecuteStep(&hasMore)) && hasMore) {
     PRInt64 placeId = invalidFrecencies->AsInt64(0);
     PRInt32 typed = invalidFrecencies->AsInt32(1);
@@ -7454,19 +7859,25 @@ nsNavHistory::GetDBFeedbackIncrease()
   if (mDBFeedbackIncrease)
     return mDBFeedbackIncrease;
 
   nsresult rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
     // Leverage the PRIMARY KEY (place_id, input) to insert/update entries.
     "INSERT OR REPLACE INTO moz_inputhistory "
       // use_count will asymptotically approach the max of 10.
       "SELECT h.id, IFNULL(i.input, :input_text), IFNULL(i.use_count, 0) * .9 + 1 "
+      "FROM moz_places_temp h "
+      "LEFT JOIN moz_inputhistory i ON i.place_id = h.id AND i.input = :input_text "
+      "WHERE url = :page_url "
+      "UNION ALL "
+      "SELECT h.id, IFNULL(i.input, :input_text), IFNULL(i.use_count, 0) * .9 + 1 "
       "FROM moz_places h "
       "LEFT JOIN moz_inputhistory i ON i.place_id = h.id AND i.input = :input_text "
-      "WHERE url = :page_url "),
+      "WHERE url = :page_url "
+        "AND h.id NOT IN (SELECT id FROM moz_places_temp)"),
     getter_AddRefs(mDBFeedbackIncrease));
   NS_ENSURE_SUCCESS(rv, nsnull);
 
   return mDBFeedbackIncrease;
 }
 #endif
 
 
@@ -7524,44 +7935,70 @@ nsNavHistory::GetDateFormatBundle()
 }
 
 mozIStorageStatement *
 nsNavHistory::GetDBVisitToVisitResult()
 {
   if (mDBVisitToVisitResult)
     return mDBVisitToVisitResult;
 
-  // Should match kGetInfoIndex_* (see GetQueryResults)
+  // mDBVisitToVisitResult, should match kGetInfoIndex_* (see GetQueryResults)
+  // We are not checking for duplicated ids into the unified table
+  // for perf reasons, LIMIT 1 will discard duplicates faster since we
+  // have unique visit ids.
   nsresult rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
       "SELECT h.id, h.url, h.title, h.rev_host, h.visit_count, "
           "v.visit_date, f.url, v.session, null, null, null, null "
+        "FROM moz_places_temp h "
+        "LEFT JOIN moz_historyvisits_temp v_t ON h.id = v_t.place_id "
+        "LEFT JOIN moz_historyvisits v ON h.id = v.place_id "
+        "LEFT JOIN moz_favicons f ON h.favicon_id = f.id "
+        "WHERE v.id = :visit_id OR v_t.id = :visit_id "
+      "UNION ALL "
+      "SELECT h.id, h.url, h.title, h.rev_host, h.visit_count, "
+          "v.visit_date, f.url, v.session, null, null, null, null "
         "FROM moz_places h "
-        "JOIN moz_historyvisits v ON h.id = v.place_id "
+        "LEFT JOIN moz_historyvisits_temp v_t ON h.id = v_t.place_id "
+        "LEFT JOIN moz_historyvisits v ON h.id = v.place_id "
         "LEFT JOIN moz_favicons f ON h.favicon_id = f.id "
-        "WHERE v.id = :visit_id "),
+        "WHERE v.id = :visit_id OR v_t.id = :visit_id "
+      "LIMIT 1"),
     getter_AddRefs(mDBVisitToVisitResult));
   NS_ENSURE_SUCCESS(rv, nsnull);
 
   return mDBVisitToVisitResult;
 }
 
 mozIStorageStatement *
 nsNavHistory::GetDBVisitToURLResult()
 {
   if (mDBVisitToURLResult)
     return mDBVisitToURLResult;
 
-  // Should match kGetInfoIndex_* (see GetQueryResults)
+  // mDBVisitToURLResult, should match kGetInfoIndex_* (see GetQueryResults)
+  // We are not checking for duplicated ids into the unified table
+  // for perf reasons, LIMIT 1 will discard duplicates faster since we
+  // have unique visit ids.
   nsresult rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
       "SELECT h.id, h.url, h.title, h.rev_host, h.visit_count, "
              "h.last_visit_date, f.url, null, null, null, null, null, null "
+        "FROM moz_places_temp h "
+        "LEFT JOIN moz_historyvisits_temp v_t ON h.id = v_t.place_id "
+        "LEFT JOIN moz_historyvisits v ON h.id = v.place_id "
+        "LEFT JOIN moz_favicons f ON h.favicon_id = f.id "
+        "WHERE v.id = :visit_id OR v_t.id = :visit_id "
+      "UNION ALL "
+      "SELECT h.id, h.url, h.title, h.rev_host, h.visit_count, "
+             "h.last_visit_date, f.url, null, null, null, null, null, null "
         "FROM moz_places h "
-        "JOIN moz_historyvisits v ON h.id = v.place_id "
+        "LEFT JOIN moz_historyvisits_temp v_t ON h.id = v_t.place_id "
+        "LEFT JOIN moz_historyvisits v ON h.id = v.place_id "
         "LEFT JOIN moz_favicons f ON h.favicon_id = f.id "
-        "WHERE v.id = :visit_id "),
+        "WHERE v.id = :visit_id OR v_t.id = :visit_id "
+      "LIMIT 1"),
     getter_AddRefs(mDBVisitToURLResult));
   NS_ENSURE_SUCCESS(rv, nsnull);
 
   return mDBVisitToURLResult;
 }
 
 mozIStorageStatement *
 nsNavHistory::GetDBBookmarkToUrlResult()
@@ -7573,16 +8010,24 @@ nsNavHistory::GetDBBookmarkToUrlResult()
   // We are not checking for duplicated ids into the unified table
   // for perf reasons, LIMIT 1 will discard duplicates faster since we
   // have unique place ids.
   nsresult rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
       "SELECT b.fk, h.url, COALESCE(b.title, h.title), "
         "h.rev_host, h.visit_count, h.last_visit_date, f.url, null, b.id, "
         "b.dateAdded, b.lastModified, b.parent, null "
       "FROM moz_bookmarks b "
+      "JOIN moz_places_temp h ON b.fk = h.id "
+      "LEFT OUTER JOIN moz_favicons f ON h.favicon_id = f.id "
+      "WHERE b.id = :item_id "
+      "UNION ALL "
+      "SELECT b.fk, h.url, COALESCE(b.title, h.title), "
+        "h.rev_host, h.visit_count, h.last_visit_date, f.url, null, b.id, "
+        "b.dateAdded, b.lastModified, b.parent, null "
+      "FROM moz_bookmarks b "
       "JOIN moz_places h ON b.fk = h.id "
       "LEFT OUTER JOIN moz_favicons f ON h.favicon_id = f.id "
       "WHERE b.id = :item_id "
       "LIMIT 1"),
     getter_AddRefs(mDBBookmarkToUrlResult));
   NS_ENSURE_SUCCESS(rv, nsnull);
 
   return mDBBookmarkToUrlResult;
--- a/toolkit/components/places/src/nsNavHistory.h
+++ b/toolkit/components/places/src/nsNavHistory.h
@@ -94,18 +94,16 @@
 #define TOPIC_AUTOCOMPLETE_FEEDBACK_UPDATED "places-autocomplete-feedback-updated"
 #endif
 // Fired when Places is shutting down.
 #define TOPIC_PLACES_SHUTDOWN "places-shutdown"
 // Internal notification, called after places-shutdown.
 // If you need to listen for Places shutdown, you should really use
 // places-shutdown, because places-teardown is guaranteed to break your code.
 #define TOPIC_PLACES_TEARDOWN "places-teardown"
-// Fired when the database connection has been closed.
-#define TOPIC_PLACES_DATABASE_CLOSED "places-database-closed"
 // Fired when Places found a locked database while initing.
 #define TOPIC_DATABASE_LOCKED "places-database-locked"
 // Fired after Places inited.
 #define TOPIC_PLACES_INIT_COMPLETE "places-init-complete"
 // Fired before starting a VACUUM operation.
 #define TOPIC_DATABASE_VACUUM_STARTING "places-vacuum-starting"
 
 namespace mozilla {
@@ -547,26 +545,26 @@ protected:
   nsresult InitDB();
 
   /**
    * Initializes additional database items like: views, temp tables, functions
    * and statements.
    */
   nsresult InitAdditionalDBItems();
   nsresult InitTempTables();
+  nsresult InitViews();
   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);
   nsresult MigrateV10Up(mozIStorageConnection *aDBConn);
-  nsresult MigrateV11Up(mozIStorageConnection *aDBConn);
 
   nsresult RemovePagesInternal(const nsCString& aPlaceIdsQueryString);
   nsresult PreparePlacesForVisitsDelete(const nsCString& aPlaceIdsQueryString);
   nsresult CleanupPlacesOnVisitsDelete(const nsCString& aPlaceIdsQueryString);
 
   nsresult AddURIInternal(nsIURI* aURI, PRTime aTime, PRBool aRedirect,
                           PRBool aToplevel, nsIURI* aReferrer);
 
--- a/toolkit/components/places/src/nsPlacesAutoComplete.js
+++ b/toolkit/components/places/src/nsPlacesAutoComplete.js
@@ -104,16 +104,43 @@ 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.";
 
 ////////////////////////////////////////////////////////////////////////////////
+//// Global Functions
+
+/**
+ * Generates the SQL subquery to get the best favicon for a given revhost.  This
+ * is the favicon for the most recent visit.
+ *
+ * @param aTableName
+ *        The table to join to the moz_favicons table with.  This must have a
+ *        column called favicon_id.
+ * @return the SQL subquery (in string form) to get the best favicon.
+ */
+function best_favicon_for_revhost(aTableName)
+{
+  return "(" +
+    "SELECT f.url " +
+    "FROM " + aTableName + " " +
+    "JOIN moz_favicons f ON f.id = favicon_id " +
+    "WHERE rev_host = IFNULL( " +
+      "(SELECT rev_host FROM moz_places_temp WHERE id = b.fk), " +
+      "(SELECT rev_host FROM moz_places WHERE id = b.fk) " +
+    ") " +
+    "ORDER BY frecency DESC " +
+    "LIMIT 1 " +
+  ")";
+}
+
+////////////////////////////////////////////////////////////////////////////////
 //// 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.
@@ -179,34 +206,42 @@ AutoCompleteStatementCallbackWrapper.pro
 ////////////////////////////////////////////////////////////////////////////////
 //// nsPlacesAutoComplete class
 
 function nsPlacesAutoComplete()
 {
   //////////////////////////////////////////////////////////////////////////////
   //// Shared Constants for Smart Getters
 
+  // Define common pieces of various queries.
   // TODO bug 412736 in case of a frecency tie, break it with h.typed and
   // h.visit_count which is better than nothing.  This is slow, so not doing it
   // yet...
-  const SQL_BASE = "SELECT h.url, h.title, f.url, " + kBookTagSQLFragment + ", "
-                 +        "h.visit_count, h.typed, h.id, :query_type, "
-                 +        "t.open_count "
-                 + "FROM moz_places h "
-                 + "LEFT JOIN moz_favicons f ON f.id = h.favicon_id "
-                 + "LEFT JOIN moz_openpages_temp t ON t.place_id = h.id "
-                 + "WHERE h.frecency <> 0 "
-                 +   "AND AUTOCOMPLETE_MATCH(:searchString, h.url, "
-                 +                          "IFNULL(bookmark, h.title), tags, "
-                 +                          "h.visit_count, h.typed, parent, "
-                 +                          "t.open_count, "
-                 +                          ":matchBehavior, :searchBehavior) "
-                 +  "{ADDITIONAL_CONDITIONS} "
-                 + "ORDER BY h.frecency DESC, h.id DESC "
-                 + "LIMIT :maxResults";
+  // Note: h.frecency is only selected because we need it for ordering.
+  function sql_base_fragment(aTableName) {
+    return "SELECT h.url, h.title, f.url, " + kBookTagSQLFragment + ", " +
+                  "h.visit_count, h.typed, h.id, :query_type, t.open_count, " +
+                  "h.frecency " +
+           "FROM " + aTableName + " h " +
+           "LEFT OUTER JOIN moz_favicons f ON f.id = h.favicon_id " +
+           "LEFT OUTER JOIN moz_openpages_temp t ON t.place_id = h.id " +
+           "WHERE h.frecency <> 0 " +
+           "AND AUTOCOMPLETE_MATCH(:searchString, h.url, " +
+                                  "IFNULL(bookmark, h.title), tags, " +
+                                  "h.visit_count, h.typed, parent, " +
+                                  "t.open_count, " +
+                                  ":matchBehavior, :searchBehavior) " +
+          "{ADDITIONAL_CONDITIONS} ";
+  }
+  const SQL_BASE = sql_base_fragment("moz_places_temp") +
+                   "UNION ALL " +
+                   sql_base_fragment("moz_places") +
+                   "AND +h.id NOT IN (SELECT id FROM moz_places_temp) " +
+                   "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;
@@ -229,109 +264,112 @@ function nsPlacesAutoComplete()
                                      "nsIIOService");
 
   XPCOMUtils.defineLazyServiceGetter(this, "_faviconService",
                                      "@mozilla.org/browser/favicon-service;1",
                                      "nsIFaviconService");
 
   XPCOMUtils.defineLazyGetter(this, "_defaultQuery", function() {
     let replacementText = "";
-    return this._db.createAsyncStatement(
+    return this._db.createStatement(
       SQL_BASE.replace("{ADDITIONAL_CONDITIONS}", replacementText, "g")
     );
   });
 
   XPCOMUtils.defineLazyGetter(this, "_historyQuery", function() {
     let replacementText = "AND h.visit_count > 0";
-    return this._db.createAsyncStatement(
+    return this._db.createStatement(
       SQL_BASE.replace("{ADDITIONAL_CONDITIONS}", replacementText, "g")
     );
   });
 
   XPCOMUtils.defineLazyGetter(this, "_bookmarkQuery", function() {
     let replacementText = "AND bookmark IS NOT NULL";
-    return this._db.createAsyncStatement(
+    return this._db.createStatement(
       SQL_BASE.replace("{ADDITIONAL_CONDITIONS}", replacementText, "g")
     );
   });
 
   XPCOMUtils.defineLazyGetter(this, "_tagsQuery", function() {
     let replacementText = "AND tags IS NOT NULL";
-    return this._db.createAsyncStatement(
+    return this._db.createStatement(
       SQL_BASE.replace("{ADDITIONAL_CONDITIONS}", replacementText, "g")
     );
   });
 
   XPCOMUtils.defineLazyGetter(this, "_openPagesQuery", function() {
     let replacementText = "AND t.open_count > 0";
-    return this._db.createAsyncStatement(
+    return this._db.createStatement(
       SQL_BASE.replace("{ADDITIONAL_CONDITIONS}", replacementText, "g")
     );
   });
 
   XPCOMUtils.defineLazyGetter(this, "_typedQuery", function() {
     let replacementText = "AND h.typed = 1";
-    return this._db.createAsyncStatement(
+    return this._db.createStatement(
       SQL_BASE.replace("{ADDITIONAL_CONDITIONS}", replacementText, "g")
     );
   });
 
   XPCOMUtils.defineLazyGetter(this, "_adaptiveQuery", function() {
     // In this query, we are taking kBookTagSQLFragment only for h.id because it
     // uses data from the moz_bookmarks table and we sync tables on bookmark
     // insert.  So, most likely, h.id will always be populated when we have any
-    // bookmark.
-    return this._db.createAsyncStatement(
-      "/* do not warn (bug 487789) */ "
-    + "SELECT h.url, h.title, f.url, " + kBookTagSQLFragment + ", "
-    +         "h.visit_count, h.typed, h.id, :query_type, t.open_count, rank "
-    + "FROM ( "
-    +   "SELECT ROUND( "
-    +     "MAX(((i.input = :search_string) + "
-    +          "(SUBSTR(i.input, 1, LENGTH(:search_string)) = :search_string) "
-    +         ") * i.use_count "
-    +     ") , 1 " // Round at first decimal.
-    +   ") 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.place_id = i.place_id "
-    + "WHERE AUTOCOMPLETE_MATCH(:searchString, 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 "
+    // bookmark.  We still need to join on moz_places_temp for other data (eg.
+    // title).
+    return this._db.createStatement(
+      "/* do not warn (bug 487789) */ " +
+      "SELECT IFNULL(h_t.url, h.url) AS c_url, " +
+             "IFNULL(h_t.title, h.title) AS c_title, f.url, " +
+              kBookTagSQLFragment + ", " +
+              "IFNULL(h_t.visit_count, h.visit_count) AS c_visit_count, " +
+              "IFNULL(h_t.typed, h.typed) AS c_typed, " +
+              "IFNULL(h_t.id, h.id), :query_type, t.open_count, rank " +
+      "FROM ( " +
+        "SELECT ROUND(MAX(((i.input = :search_string) + " +
+                          "(SUBSTR(i.input, 1, LENGTH(:search_string)) = :search_string)) * " +
+                          "i.use_count), 1) AS rank, place_id " +
+        "FROM moz_inputhistory i " +
+        "GROUP BY i.place_id " +
+        "HAVING rank > 0 " +
+      ") AS i " +
+      "LEFT JOIN moz_places h ON h.id = i.place_id " +
+      "LEFT JOIN moz_places_temp h_t ON h_t.id = i.place_id " +
+      "LEFT JOIN moz_favicons f ON f.id = IFNULL(h_t.favicon_id, h.favicon_id) " + 
+      "LEFT JOIN moz_openpages_temp t ON t.place_id = i.place_id " +
+      "WHERE c_url NOTNULL " +
+      "AND AUTOCOMPLETE_MATCH(:searchString, c_url, " +
+                             "IFNULL(bookmark, c_title), tags, " +
+                             "c_visit_count, c_typed, parent, " +
+                             "t.open_count, " +
+                             ":matchBehavior, :searchBehavior) " +
+      "ORDER BY rank DESC, IFNULL(h_t.frecency, h.frecency) DESC"
     );
   });
 
   XPCOMUtils.defineLazyGetter(this, "_keywordQuery", function() {
-    return this._db.createAsyncStatement(
-      "/* do not warn (bug 487787) */ "
-    + "SELECT "
-    +  "(SELECT REPLACE(url, '%s', :query_string) FROM moz_places WHERE id = b.fk) "
-    +  "AS search_url, h.title, "
-    +  "IFNULL(f.url, (SELECT f.url "
-    +                 "FROM moz_places "
-    +                 "JOIN moz_favicons f ON f.id = favicon_id "
-    +                 "WHERE rev_host = (SELECT rev_host FROM moz_places WHERE id = b.fk) "
-    +                 "ORDER BY frecency DESC "
-    +                 "LIMIT 1) "
-    + "), b.parent, b.title, NULL, h.visit_count, h.typed, IFNULL(h.id, b.fk), "
-    +  ":query_type, t.open_count "
-    +  "FROM moz_keywords k "
-    +  "JOIN moz_bookmarks b ON b.keyword_id = k.id "
-    +  "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.place_id = h.id "
-    +  "WHERE LOWER(k.keyword) = LOWER(:keyword) "
-    +  "ORDER BY h.frecency DESC "
+    return this._db.createStatement(
+      "/* do not warn (bug 487787) */ " +
+      "SELECT IFNULL( " +
+          "(SELECT REPLACE(url, '%s', :query_string) FROM moz_places_temp WHERE id = b.fk), " +
+          "(SELECT REPLACE(url, '%s', :query_string) FROM moz_places WHERE id = b.fk) " +
+        ") AS search_url, IFNULL(h_t.title, h.title), " +
+        "COALESCE(f.url, " + best_favicon_for_revhost("moz_places_temp") + "," +
+                  best_favicon_for_revhost("moz_places") + "), b.parent, " +
+        "b.title, NULL, IFNULL(h_t.visit_count, h.visit_count), " +
+        "IFNULL(h_t.typed, h.typed), COALESCE(h_t.id, h.id, b.fk), " +
+        ":query_type, t.open_count " +
+      "FROM moz_keywords k " +
+      "JOIN moz_bookmarks b ON b.keyword_id = k.id " +
+      "LEFT JOIN moz_places AS h ON h.url = search_url " +
+      "LEFT JOIN moz_places_temp AS h_t ON h_t.url = search_url " +
+      "LEFT JOIN moz_favicons f ON f.id = IFNULL(h_t.favicon_id, h.favicon_id) " +
+      "LEFT JOIN moz_openpages_temp t ON t.place_id = IFNULL(h_t.id, h.id) " +
+      "WHERE LOWER(k.keyword) = LOWER(:keyword) " +
+      "ORDER BY IFNULL(h_t.frecency, h.frecency) DESC"
     );
   });
 
   //////////////////////////////////////////////////////////////////////////////
   //// Initialization
 
   // load preferences
   this._prefs = Cc["@mozilla.org/preferences-service;1"].
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/src/nsPlacesDBFlush.js
@@ -0,0 +1,399 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 sts=2 expandtab
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * 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.
+ * 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)
+ *   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
+ * decision by deleting the provisions above and replace them with the notice
+ * 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 ***** */
+
+////////////////////////////////////////////////////////////////////////////////
+//// Constants
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+const Cu = Components.utils;
+
+// Use places-teardown to ensure we run last in the shutdown process.
+// Any other implementer should use places-shutdown instead, since teardown is
+// where things really break.
+const kTopicShutdown = "places-teardown";
+const kSyncFinished = "places-sync-finished";
+const kDebugStopSync = "places-debug-stop-sync";
+const kDebugStartSync = "places-debug-start-sync";
+
+const kSyncPrefName = "places.syncDBTableIntervalInSecs";
+const kDefaultSyncInterval = 120;
+
+// Query Constants.  These describe the queries we use.
+const kQuerySyncPlacesId = 0;
+const kQuerySyncHistoryVisitsId = 1;
+
+////////////////////////////////////////////////////////////////////////////////
+//// Modules
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/PlacesUtils.jsm");
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsPlacesDBFlush class
+
+function nsPlacesDBFlush()
+{
+  // Get our sync interval
+  try {
+    // We want to silently fail since getIntPref throws if it does not exist,
+    // and use a default to fallback to.
+    this._syncInterval = Services.prefs.getIntPref(kSyncPrefName);
+    if (this._syncInterval <= 0)
+      this._syncInterval = kDefaultSyncInterval;
+  }
+  catch (e) {
+    // The preference did not exist, so use the default.
+    this._syncInterval = kDefaultSyncInterval;
+  }
+
+  // Register observers
+  Services.obs.addObserver(this, kTopicShutdown, false);
+  Services.obs.addObserver(this, kDebugStopSync, false);
+  Services.obs.addObserver(this, kDebugStartSync, false);
+
+  let (pb2 = Services.prefs.QueryInterface(Ci.nsIPrefBranch2))
+    pb2.addObserver(kSyncPrefName, this, false);
+
+  // Create our timer to update everything
+  this._timer = this._newTimer();
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// Smart Getters
+
+  XPCOMUtils.defineLazyGetter(this, "_db", function() {
+    return PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
+                              .DBConnection;
+  });
+}
+
+nsPlacesDBFlush.prototype = {
+  //////////////////////////////////////////////////////////////////////////////
+  //// nsIObserver
+
+  observe: function DBFlush_observe(aSubject, aTopic, aData)
+  {
+    if (aTopic == kTopicShutdown) {
+      Services.obs.removeObserver(this, kTopicShutdown);
+      Services.obs.removeObserver(this, kDebugStopSync);
+      Services.obs.removeObserver(this, kDebugStartSync);
+
+      let (pb2 = Services.prefs.QueryInterface(Ci.nsIPrefBranch2))
+        pb2.removeObserver(kSyncPrefName, this);
+
+      if (this._timer) {
+        this._timer.cancel();
+        this._timer = null;
+      }
+
+      // Other components could still make changes to history at this point,
+      // for example to clear private data on shutdown, so here we dispatch
+      // an event to the main thread so that we will sync after
+      // Places shutdown ensuring all data have been saved.
+      Services.tm.mainThread.dispatch({
+        _self: this,
+        run: function() {
+          // Flush any remaining change to disk tables.
+          this._self._flushWithQueries([kQuerySyncPlacesId, kQuerySyncHistoryVisitsId]);
+
+          // Close the database connection, this was the last sync and we can't
+          // ensure database coherence from now on.
+          this._self._finalizeInternalStatements();
+          this._self._db.asyncClose();
+        }
+      }, Ci.nsIThread.DISPATCH_NORMAL);
+    }
+    else if (aTopic == "nsPref:changed" && aData == kSyncPrefName) {
+      // Get the new pref value, and then update our timer
+      this._syncInterval = Services.prefs.getIntPref(kSyncPrefName);
+      if (this._syncInterval <= 0)
+        this._syncInterval = kDefaultSyncInterval;
+
+      // We may have canceled the timer already for batch updates, so we want to
+      // exit early.
+      if (!this._timer)
+        return;
+
+      this._timer.cancel();
+      this._timer = this._newTimer();
+    }
+    else if (aTopic == kDebugStopSync) {
+      this._syncStopped = true;
+    }
+    else if (aTopic == kDebugStartSync) {
+      if (_syncStopped in this)
+        delete this._syncStopped;
+    }
+  },
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// nsINavBookmarkObserver
+
+  onBeginUpdateBatch: function DBFlush_onBeginUpdateBatch()
+  {
+    this._inBatchMode = true;
+
+    // We do not want to sync while we are doing batch work.
+    this._timer.cancel();
+    this._timer = null;
+  },
+
+  onEndUpdateBatch: function DBFlush_onEndUpdateBatch()
+  {
+    this._inBatchMode = false;
+
+    // Restore our timer
+    this._timer = this._newTimer();
+
+    // We need to sync now
+    this._flushWithQueries([kQuerySyncPlacesId, kQuerySyncHistoryVisitsId]);
+  },
+
+  onItemAdded: function(aItemId, aParentId, aIndex, aItemType)
+  {
+    // Sync only if we added a TYPE_BOOKMARK item.  Note, we want to run the
+    // least amount of queries as possible here for performance reasons.
+    if (!this._inBatchMode && aItemType == PlacesUtils.bookmarks.TYPE_BOOKMARK)
+      this._flushWithQueries([kQuerySyncPlacesId]);
+  },
+
+  onItemChanged: function DBFlush_onItemChanged(aItemId, aProperty,
+                                                aIsAnnotationProperty,
+                                                aNewValue, aLastModified,
+                                                aItemType)
+  {
+    if (!this._inBatchMode && aProperty == "uri")
+      this._flushWithQueries([kQuerySyncPlacesId]);
+  },
+
+  onBeforeItemRemoved: function() { },
+  onItemRemoved: function() { },
+  onItemVisited: function() { },
+  onItemMoved: function() { },
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// nsINavHistoryObserver
+
+  // We currently only use the history observer to know when the history service
+  // is activated.  At that point, we actually get initialized, and our timer
+  // to sync history is added.
+
+  // These methods share the name of the ones on nsINavBookmarkObserver, so
+  // the implementations can be found above.
+  //onBeginUpdateBatch: function() { },
+  //onEndUpdateBatch: function() { },
+  onVisit: function() { },
+  onTitleChanged: function() { },
+  onBeforeDeleteURI: function() { },
+  onDeleteURI: function() { },
+  onClearHistory: function() { },
+  onPageChanged: function() { },
+  onDeleteVisits: function() { },
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// nsITimerCallback
+
+  notify: function DBFlush_timerCallback()
+  {
+    let queries = [
+      kQuerySyncPlacesId,
+      kQuerySyncHistoryVisitsId,
+    ];
+    this._flushWithQueries(queries);
+  },
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// mozIStorageStatementCallback
+
+  handleResult: function DBFlush_handleResult(aResultSet)
+  {
+  },
+
+  handleError: function DBFlush_handleError(aError)
+  {
+    Cu.reportError("Async statement execution returned with '" +
+                   aError.result + "', '" + aError.message + "'");
+  },
+
+  handleCompletion: function DBFlush_handleCompletion(aReason)
+  {
+    if (aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED) {
+      // Dispatch a notification that sync has finished.
+      Services.obs.notifyObservers(null, kSyncFinished, null);
+    }
+  },
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// nsPlacesDBFlush
+  _syncInterval: kDefaultSyncInterval,
+
+  /**
+   * Execute async statements to flush tables with the specified queries.
+   *
+   * @param aQueryNames
+   *        The names of the queries to use with this flush.
+   */
+  _flushWithQueries: function DBFlush_flushWithQueries(aQueryNames)
+  {
+    // No need to do extra work if we are in batch mode
+    if (this._inBatchMode || this._syncStopped)
+      return;
+
+    let statements = [];
+    for (let i = 0; i < aQueryNames.length; i++)
+      statements.push(this._getQuery(aQueryNames[i]));
+
+    // Execute sync statements async in a transaction
+    this._db.executeAsync(statements, statements.length, this);
+  },
+
+  /**
+   * Finalizes all of our mozIStorageStatements so we can properly close the
+   * database.
+   */
+  _finalizeInternalStatements: function DBFlush_finalizeInternalStatements()
+  {
+    this._cachedStatements.forEach(function(stmt) {
+      if (stmt instanceof Ci.mozIStorageAsyncStatement)
+        stmt.finalize();
+    });
+  },
+
+  /**
+   * Generate the statement to synchronizes the moz_{aTableName} and
+   * moz_{aTableName}_temp by copying all the data from the temporary table
+   * into the permanent one.
+   * Most of the work is done through triggers defined in nsPlacesTriggers.h,
+   * they sync back to disk, then delete the data in the temporary table.
+   *
+   * @param aQueryType
+   *        Type of the query to build statement for.
+   */
+  _cachedStatements: [],
+  _getQuery: function DBFlush_getQuery(aQueryType)
+  {
+    // Statement creating can be expensive, so always cache if we can.
+    if (aQueryType in this._cachedStatements) {
+      let stmt = this._cachedStatements[aQueryType];
+
+      // Bind the appropriate parameters.
+      let params = stmt.params;
+      switch (aQueryType) {
+        case kQuerySyncHistoryVisitsId:
+        case kQuerySyncPlacesId:
+          params.transition_type = Ci.nsINavHistoryService.TRANSITION_EMBED;
+          break;
+      }
+
+      return stmt;
+    }
+
+    switch(aQueryType) {
+      case kQuerySyncHistoryVisitsId:
+        // For history table we want to leave embed visits in memory, since
+        // those are expired with current session, so we are filtering them out.
+        // Notice that instead we _want_ to sync framed_link visits, since those
+        // come from user's actions.
+        this._cachedStatements[aQueryType] = this._db.createAsyncStatement(
+          "DELETE FROM moz_historyvisits_temp " +
+          "WHERE visit_type <> :transition_type"
+        );
+        break;
+
+      case kQuerySyncPlacesId:
+        // For places table we want to leave places associated with embed visits
+        // in memory, they usually have hidden = 1 and at least an embed visit
+        // in historyvisits_temp table.
+        this._cachedStatements[aQueryType] = this._db.createAsyncStatement(
+          "DELETE FROM moz_places_temp " +
+          "WHERE id IN ( " +
+            "SELECT id FROM moz_places_temp h " +
+            "WHERE h.hidden <> 1 OR NOT EXISTS ( " +
+              "SELECT id FROM moz_historyvisits_temp " +
+              "WHERE place_id = h.id AND visit_type = :transition_type " +
+              "LIMIT 1 " +
+            ") " +
+          ")"
+        );
+        break;
+
+      default:
+        throw "Unexpected statement!";
+    }
+
+    // We only bind our own parameters when we have a cached statement, so we
+    // call ourself since we now have a cached statement.
+    return this._getQuery(aQueryType);
+  },
+
+  /**
+   * Creates a new timer based on this._syncInterval.
+   *
+   * @returns a REPEATING_SLACK nsITimer that runs every this._syncInterval.
+   */
+  _newTimer: function DBFlush_newTimer()
+  {
+    let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+    timer.initWithCallback(this, this._syncInterval * 1000,
+                           Ci.nsITimer.TYPE_REPEATING_SLACK);
+    return timer;
+  },
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// nsISupports
+
+  classID: Components.ID("c1751cfc-e8f1-4ade-b0bb-f74edfb8ef6a"),
+
+  QueryInterface: XPCOMUtils.generateQI([
+    Ci.nsIObserver,
+    Ci.nsINavBookmarkObserver,
+    Ci.nsINavHistoryObserver,
+    Ci.nsITimerCallback,
+    Ci.mozIStorageStatementCallback,
+  ])
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// Module Registration
+
+let components = [nsPlacesDBFlush];
+var NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
--- a/toolkit/components/places/src/nsPlacesExpiration.js
+++ b/toolkit/components/places/src/nsPlacesExpiration.js
@@ -205,96 +205,140 @@ const ACTION = {
 // The queries we use to expire.
 const EXPIRATION_QUERIES = {
 
   // Finds visits to be expired.  Will return nothing if we are not over the
   // unique URIs limit.
   QUERY_FIND_VISITS_TO_EXPIRE: {
     sql: "INSERT INTO expiration_notify "
        +   "(v_id, url, visit_date, expected_results) "
-       + "SELECT v.id, h.url, v.visit_date, :limit_visits "
+       + "SELECT v.id, IFNULL(h_t.url, h.url) AS url, "
+       +         "v.visit_date AS visit_date, "
+       +         ":limit_visits AS expected_results "
+       + "FROM moz_historyvisits_temp v "
+       + "LEFT JOIN moz_places_temp AS h_t ON h_t.id = v.place_id "
+       + "LEFT JOIN moz_places AS h ON h.id = v.place_id "
+       + "WHERE ((SELECT COUNT(*) FROM moz_places_temp) + "
+       +        "(SELECT COUNT(*) FROM moz_places)) > :max_uris "
+       + "UNION ALL "
+       + "SELECT v.id, IFNULL(h_t.url, h.url) AS url, "
+       +        "v.visit_date AS visit_date, "
+       +        ":limit_visits AS expected_results "
        + "FROM moz_historyvisits v "
-       + "JOIN moz_places h ON h.id = v.place_id "
-       + "WHERE (SELECT COUNT(*) FROM moz_places) > :max_uris "
+       + "LEFT JOIN moz_places_temp AS h_t ON h_t.id = v.place_id "
+       + "LEFT JOIN moz_places AS h ON h.id = v.place_id "
+       + "WHERE ((SELECT COUNT(*) FROM moz_places_temp) + "
+       +        "(SELECT COUNT(*) FROM moz_places)) > :max_uris "
        + "ORDER BY v.visit_date ASC "
        + "LIMIT :limit_visits",
     actions: ACTION.TIMED_OVERLIMIT | ACTION.SHUTDOWN | ACTION.IDLE |
              ACTION.DEBUG
   },
 
   // Removes the previously found visits.
   QUERY_EXPIRE_VISITS: {
-    sql: "DELETE FROM moz_historyvisits WHERE id IN ( "
+    sql: "DELETE FROM moz_historyvisits_view WHERE id IN ( "
        +   "SELECT v_id FROM expiration_notify WHERE v_id NOTNULL "
        + ")",
     actions: ACTION.TIMED_OVERLIMIT | ACTION.SHUTDOWN | ACTION.IDLE |
              ACTION.DEBUG
   },
 
   // Finds orphan URIs in the database.
   // Notice we won't notify single removed URIs on removeAllPages, so we don't
   // run this query in such a case, but just delete URIs.
   QUERY_FIND_URIS_TO_EXPIRE: {
     sql: "INSERT INTO expiration_notify "
        +   "(p_id, url, visit_date, expected_results) "
-       + "SELECT h.id, h.url, h.last_visit_date, :limit_uris "
+       + "SELECT h.id, h.url, h.last_visit_date AS visit_date, "
+       +        ":limit_uris AS expected_results "
+       + "FROM moz_places_temp h "
+       + "LEFT JOIN moz_historyvisits v ON h.id = v.place_id "
+       + "LEFT JOIN moz_historyvisits_temp v_t ON h.id = v_t.place_id "
+       + "LEFT JOIN moz_bookmarks b ON h.id = b.fk "
+       + "WHERE v.id IS NULL "
+       +   "AND v_t.id IS NULL "
+       +   "AND b.id IS NULL "
+       +   "AND h.ROWID <> IFNULL(:null_skips_last, (SELECT MAX(ROWID) FROM moz_places_temp)) "
+       + "UNION ALL "
+       + "SELECT h.id, h.url, h.last_visit_date AS visit_date, "
+       +       ":limit_uris AS expected_results "
        + "FROM moz_places h "
        + "LEFT JOIN moz_historyvisits v ON h.id = v.place_id "
+       + "LEFT JOIN moz_historyvisits_temp v_t ON h.id = v_t.place_id "
        + "LEFT JOIN moz_bookmarks b ON h.id = b.fk "
        + "WHERE v.id IS NULL "
+       +   "AND v_t.id IS NULL "
        +   "AND b.id IS NULL "
-       +   "AND h.ROWID <> IFNULL(:null_skips_last, (SELECT MAX(ROWID) FROM moz_places)) "
        + "LIMIT :limit_uris",
     actions: ACTION.TIMED | ACTION.TIMED_OVERLIMIT | ACTION.SHUTDOWN |
              ACTION.IDLE | ACTION.DEBUG
   },
 
   // Expire found URIs from the database.
   QUERY_EXPIRE_URIS: {
-    sql: "DELETE FROM moz_places WHERE id IN ( "
+    sql: "DELETE FROM moz_places_view WHERE id IN ( "
        +   "SELECT p_id FROM expiration_notify WHERE p_id NOTNULL "
        + ")",
     actions: ACTION.TIMED | ACTION.TIMED_OVERLIMIT | ACTION.SHUTDOWN |
              ACTION.IDLE | ACTION.DEBUG
   },
 
   // Expire orphan URIs from the database.
   QUERY_SILENT_EXPIRE_ORPHAN_URIS: {
-    sql: "DELETE FROM moz_places WHERE id IN ( "
+    sql: "DELETE FROM moz_places_view WHERE id IN ( "
+       +   "SELECT h.id "
+       +   "FROM moz_places_temp h "
+       +   "LEFT JOIN moz_historyvisits v ON h.id = v.place_id "
+       +   "LEFT JOIN moz_historyvisits_temp v_t ON h.id = v_t.place_id "
+       +   "LEFT JOIN moz_bookmarks b ON h.id = b.fk "
+       +   "WHERE v.id IS NULL "
+       +     "AND v_t.id IS NULL "
+       +     "AND b.id IS NULL "
+       +     "AND SUBSTR(h.url, 1, 6) <> 'place:' "
+       +   "UNION ALL "
        +   "SELECT h.id "
        +   "FROM moz_places h "
        +   "LEFT JOIN moz_historyvisits v ON h.id = v.place_id "
+       +   "LEFT JOIN moz_historyvisits_temp v_t ON h.id = v_t.place_id "
        +   "LEFT JOIN moz_bookmarks b ON h.id = b.fk "
        +   "WHERE v.id IS NULL "
+       +     "AND v_t.id IS NULL "
        +     "AND b.id IS NULL "
+       +     "AND SUBSTR(h.url, 1, 6) <> 'place:' "
        +   "LIMIT :limit_uris "
        + ")",
     actions: ACTION.CLEAR_HISTORY
   },
 
   // Expire orphan icons from the database.
   QUERY_EXPIRE_FAVICONS: {
     sql: "DELETE FROM moz_favicons WHERE id IN ( "
        +   "SELECT f.id FROM moz_favicons f "
        +   "LEFT JOIN moz_places h ON f.id = h.favicon_id "
+       +   "LEFT JOIN moz_places_temp h_t ON f.id = h_t.favicon_id "
        +   "WHERE h.favicon_id IS NULL "
+       +     "AND h_t.favicon_id IS NULL "
        +   "LIMIT :limit_favicons "
        + ")",
     actions: ACTION.TIMED | ACTION.TIMED_OVERLIMIT | ACTION.CLEAR_HISTORY |
              ACTION.SHUTDOWN | ACTION.IDLE | ACTION.DEBUG
   },
 
   // Expire orphan page annotations from the database.
   QUERY_EXPIRE_ANNOS: {
     sql: "DELETE FROM moz_annos WHERE id in ( "
        +   "SELECT a.id FROM moz_annos a "
        +   "LEFT JOIN moz_places h ON a.place_id = h.id "
+       +   "LEFT JOIN moz_places_temp h_t ON a.place_id = h_t.id "
        +   "LEFT JOIN moz_historyvisits v ON a.place_id = v.place_id "
-       +   "WHERE h.id IS NULL "
-       +      "OR (v.id IS NULL AND a.expiration <> :expire_never) "
+       +   "LEFT JOIN moz_historyvisits_temp v_t ON a.place_id = v_t.place_id "
+       +   "WHERE (h.id IS NULL AND h_t.id IS NULL) "
+       +      "OR (v.id IS NULL AND v_t.id IS NULL AND "
+       +          "a.expiration <> :expire_never) "
        +   "LIMIT :limit_annos "
        + ")",
     actions: ACTION.TIMED | ACTION.TIMED_OVERLIMIT | ACTION.CLEAR_HISTORY |
              ACTION.SHUTDOWN | ACTION.IDLE | ACTION.DEBUG
   },
 
   // Expire page annotations based on expiration policy.
   QUERY_EXPIRE_ANNOS_WITH_POLICY: {
@@ -321,16 +365,18 @@ const EXPIRATION_QUERIES = {
     actions: ACTION.TIMED | ACTION.TIMED_OVERLIMIT | ACTION.CLEAR_HISTORY |
              ACTION.SHUTDOWN | ACTION.IDLE | ACTION.DEBUG
   },
 
   // Expire page annotations based on expiration policy.
   QUERY_EXPIRE_ANNOS_WITH_HISTORY: {
     sql: "DELETE FROM moz_annos "
        + "WHERE expiration = :expire_with_history "
+       +   "AND NOT EXISTS (SELECT id FROM moz_historyvisits_temp "
+       +                   "WHERE place_id = moz_annos.place_id LIMIT 1) "
        +   "AND NOT EXISTS (SELECT id FROM moz_historyvisits "
        +                   "WHERE place_id = moz_annos.place_id LIMIT 1)",
     actions: ACTION.TIMED | ACTION.TIMED_OVERLIMIT | ACTION.CLEAR_HISTORY |
              ACTION.SHUTDOWN | ACTION.IDLE | ACTION.DEBUG
   },
 
   // Expire item annos without a corresponding item id.
   QUERY_EXPIRE_ITEMS_ANNOS: {
@@ -356,17 +402,19 @@ const EXPIRATION_QUERIES = {
     actions: ACTION.CLEAR_HISTORY | ACTION.SHUTDOWN | ACTION.IDLE | ACTION.DEBUG
   },
 
   // Expire orphan inputhistory.
   QUERY_EXPIRE_INPUTHISTORY: {
     sql: "DELETE FROM moz_inputhistory WHERE place_id IN ( "
        +   "SELECT i.place_id FROM moz_inputhistory i "
        +   "LEFT JOIN moz_places h ON h.id = i.place_id "
+       +   "LEFT JOIN moz_places_temp h_t ON h_t.id = i.place_id "
        +   "WHERE h.id IS NULL "
+       +     "AND h_t.id IS NULL "
        +   "LIMIT :limit_inputhistory "
        + ")",
     actions: ACTION.CLEAR_HISTORY | ACTION.SHUTDOWN | ACTION.IDLE | ACTION.DEBUG
   },
 
   // Expire all session annotations.  Should only be called at shutdown.
   QUERY_EXPIRE_ANNOS_SESSION: {
     sql: "DELETE FROM moz_annos WHERE expiration = :expire_session",
@@ -469,18 +517,18 @@ nsPlacesExpiration.prototype = {
 
       this.expireOnIdle = false;
 
       if (this._timer) {
         this._timer.cancel();
         this._timer = null;
       }
 
-      // We must be sure to run after all other shutdown observers thus we
-      // enqueue the calls.
+      // We must be sure to run after all other shutdown observers (but DBFlush)
+      // thus we enqueue the calls.
       let self = this;
       Services.tm.mainThread.dispatch({
         run: function() {
           // If we ran a clearHistory recently, or database id not dirty, we don't want to spend
           // time expiring on shutdown.  In such a case just expire session annotations.
           let hasRecentClearHistory =
             Date.now() - self._lastClearHistoryTime <
               SHUTDOWN_WITH_RECENT_CLEARHISTORY_TIMEOUT_SECONDS * 1000;
@@ -564,17 +612,18 @@ nsPlacesExpiration.prototype = {
   //////////////////////////////////////////////////////////////////////////////
   //// nsITimerCallback
 
   notify: function PEX_timerCallback()
   {
     // Check if we are over history capacity, if so visits must be expired.
     if (!this._cachedStatements["LIMIT_COUNT"]) {
       this._cachedStatements["LIMIT_COUNT"] = this._db.createAsyncStatement(
-        "SELECT COUNT(*) FROM moz_places"
+        "SELECT (SELECT COUNT(*) FROM moz_places_temp) + "
+      +        "(SELECT COUNT(*) FROM moz_places)"
       );
     }
     let self = this;
     this._cachedStatements["LIMIT_COUNT"].executeAsync({
       handleResult: function(aResults) {
         let row = aResults.getNextRow();
         self._overLimit = row.getResultByIndex(0) > self._urisLimit;
       },
--- a/toolkit/components/places/src/nsPlacesIndexes.h
+++ b/toolkit/components/places/src/nsPlacesIndexes.h
@@ -44,60 +44,96 @@
   NS_LITERAL_CSTRING( \
     "CREATE " __type " INDEX IF NOT EXISTS " __table "_" __name \
       " ON " __table " (" __columns ")" \
   )
 
 /**
  * moz_places
  */
+#define CREATE_IDX_MOZ_PLACES_TEMP_URL \
+  CREATE_PLACES_IDX( \
+    "url_uniqueindex", "moz_places_temp", "url", "UNIQUE" \
+  )
 #define CREATE_IDX_MOZ_PLACES_URL \
   CREATE_PLACES_IDX( \
     "url_uniqueindex", "moz_places", "url", "UNIQUE" \
   )
 
+#define CREATE_IDX_MOZ_PLACES_TEMP_FAVICON \
+  CREATE_PLACES_IDX( \
+    "faviconindex", "moz_places_temp", "favicon_id", "" \
+  )
 #define CREATE_IDX_MOZ_PLACES_FAVICON \
   CREATE_PLACES_IDX( \
     "faviconindex", "moz_places", "favicon_id", "" \
   )
 
+#define CREATE_IDX_MOZ_PLACES_TEMP_REVHOST \
+  CREATE_PLACES_IDX( \
+    "hostindex", "moz_places_temp", "rev_host", "" \
+  )
 #define CREATE_IDX_MOZ_PLACES_REVHOST \
   CREATE_PLACES_IDX( \
     "hostindex", "moz_places", "rev_host", "" \
   )
 
+#define CREATE_IDX_MOZ_PLACES_TEMP_VISITCOUNT \
+  CREATE_PLACES_IDX( \
+    "visitcount", "moz_places_temp", "visit_count", "" \
+  )
 #define CREATE_IDX_MOZ_PLACES_VISITCOUNT \
   CREATE_PLACES_IDX( \
     "visitcount", "moz_places", "visit_count", "" \
   )
 
+#define CREATE_IDX_MOZ_PLACES_TEMP_FRECENCY \
+  CREATE_PLACES_IDX( \
+    "frecencyindex", "moz_places_temp", "frecency", "" \
+  )
 #define CREATE_IDX_MOZ_PLACES_FRECENCY \
   CREATE_PLACES_IDX( \
     "frecencyindex", "moz_places", "frecency", "" \
   )
 
+#define CREATE_IDX_MOZ_PLACES_TEMP_LASTVISITDATE \
+  CREATE_PLACES_IDX( \
+    "lastvisitdateindex", "moz_places_temp", "last_visit_date", "" \
+  )
 #define CREATE_IDX_MOZ_PLACES_LASTVISITDATE \
   CREATE_PLACES_IDX( \
     "lastvisitdateindex", "moz_places", "last_visit_date", "" \
   )
 
 /**
  * moz_historyvisits
  */
 
+#define CREATE_IDX_MOZ_HISTORYVISITS_TEMP_PLACEDATE \
+  CREATE_PLACES_IDX( \
+    "placedateindex", "moz_historyvisits_temp", "place_id, visit_date", "" \
+  )
 #define CREATE_IDX_MOZ_HISTORYVISITS_PLACEDATE \
   CREATE_PLACES_IDX( \
     "placedateindex", "moz_historyvisits", "place_id, visit_date", "" \
   )
 
+#define CREATE_IDX_MOZ_HISTORYVISITS_TEMP_FROMVISIT \
+  CREATE_PLACES_IDX( \
+    "fromindex", "moz_historyvisits_temp", "from_visit", "" \
+  )
 #define CREATE_IDX_MOZ_HISTORYVISITS_FROMVISIT \
   CREATE_PLACES_IDX( \
     "fromindex", "moz_historyvisits", "from_visit", "" \
   )
 
+#define CREATE_IDX_MOZ_HISTORYVISITS_TEMP_VISITDATE \
+  CREATE_PLACES_IDX( \
+    "dateindex", "moz_historyvisits_temp", "visit_date", "" \
+  )
 #define CREATE_IDX_MOZ_HISTORYVISITS_VISITDATE \
   CREATE_PLACES_IDX( \
     "dateindex", "moz_historyvisits", "visit_date", "" \
   )
 
 /**
  * moz_bookmarks
  */
--- a/toolkit/components/places/src/nsPlacesTables.h
+++ b/toolkit/components/places/src/nsPlacesTables.h
@@ -35,48 +35,67 @@
  * 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 ***** */
 
 #ifndef __nsPlacesTables_h__
 #define __nsPlacesTables_h__
 
-
-#define CREATE_MOZ_PLACES NS_LITERAL_CSTRING( \
-    "CREATE TABLE moz_places ( " \
+#define CREATE_MOZ_PLACES_BASE(__name, __temporary) NS_LITERAL_CSTRING( \
+  "CREATE " __temporary " TABLE " __name " ( " \
     "  id INTEGER PRIMARY KEY" \
     ", url LONGVARCHAR" \
     ", title LONGVARCHAR" \
     ", rev_host LONGVARCHAR" \
     ", visit_count INTEGER DEFAULT 0" \
     ", hidden INTEGER DEFAULT 0 NOT NULL" \
     ", typed INTEGER DEFAULT 0 NOT NULL" \
     ", favicon_id INTEGER" \
     ", frecency INTEGER DEFAULT -1 NOT NULL" \
     ", last_visit_date INTEGER " \
   ")" \
 )
+#define CREATE_MOZ_PLACES CREATE_MOZ_PLACES_BASE("moz_places", "")
+#define CREATE_MOZ_PLACES_TEMP CREATE_MOZ_PLACES_BASE("moz_places_temp", "TEMP")
 #define MOZ_PLACES_COLUMNS \
   "id, url, title, rev_host, visit_count, hidden, typed, favicon_id, " \
   "frecency, last_visit_date"
+#define CREATE_MOZ_PLACES_VIEW NS_LITERAL_CSTRING( \
+  "CREATE TEMPORARY VIEW moz_places_view AS " \
+  "SELECT " MOZ_PLACES_COLUMNS " FROM moz_places_temp " \
+  "UNION ALL " \
+  "SELECT " MOZ_PLACES_COLUMNS " FROM moz_places " \
+  "WHERE id NOT IN (SELECT id FROM moz_places_temp) " \
+)
 
-#define CREATE_MOZ_HISTORYVISITS NS_LITERAL_CSTRING( \
-  "CREATE TABLE moz_historyvisits (" \
+
+#define CREATE_MOZ_HISTORYVISITS_BASE(__name, __temporary) NS_LITERAL_CSTRING( \
+  "CREATE " __temporary " TABLE " __name " (" \
     "  id INTEGER PRIMARY KEY" \
     ", from_visit INTEGER" \
     ", place_id INTEGER" \
     ", visit_date INTEGER" \
     ", visit_type INTEGER" \
     ", session INTEGER" \
   ")" \
 )
+#define CREATE_MOZ_HISTORYVISITS \
+  CREATE_MOZ_HISTORYVISITS_BASE("moz_historyvisits", "")
+#define CREATE_MOZ_HISTORYVISITS_TEMP \
+  CREATE_MOZ_HISTORYVISITS_BASE("moz_historyvisits_temp", "TEMP")
 #define MOZ_HISTORYVISITS_COLUMNS \
   "id, from_visit, place_id, visit_date, visit_type, session"
-
+#define CREATE_MOZ_HISTORYVISITS_VIEW NS_LITERAL_CSTRING( \
+  "CREATE TEMPORARY VIEW moz_historyvisits_view AS " \
+  "SELECT " MOZ_HISTORYVISITS_COLUMNS " FROM moz_historyvisits_temp " \
+  "UNION ALL " \
+  "SELECT " MOZ_HISTORYVISITS_COLUMNS " FROM moz_historyvisits " \
+  "WHERE id NOT IN (SELECT id FROM moz_historyvisits_temp) " \
+)
 
 #define CREATE_MOZ_INPUTHISTORY NS_LITERAL_CSTRING( \
   "CREATE TABLE moz_inputhistory (" \
     "  place_id INTEGER NOT NULL" \
     ", input LONGVARCHAR NOT NULL" \
     ", use_count INTEGER" \
     ", PRIMARY KEY (place_id, input)" \
   ")" \
--- a/toolkit/components/places/src/nsPlacesTriggers.h
+++ b/toolkit/components/places/src/nsPlacesTriggers.h
@@ -67,45 +67,204 @@
       "FROM moz_bookmarks " \
       "WHERE keyword_id = OLD.keyword_id " \
       "AND id <> OLD.id " \
       "LIMIT 1 " \
     ");" \
   "END" \
 )
 
+/**
+ * This trigger allows for an insertion into moz_places_view.  It enters the new
+ * data into the temporary table, ensuring that the new id is one greater than
+ * the largest id value found.
+ */
+#define CREATE_PLACES_VIEW_INSERT_TRIGGER NS_LITERAL_CSTRING( \
+  "CREATE TEMPORARY TRIGGER moz_places_view_insert_trigger " \
+  "INSTEAD OF INSERT " \
+  "ON moz_places_view " \
+  "BEGIN " \
+    "INSERT INTO moz_places_temp (" MOZ_PLACES_COLUMNS ") " \
+    "VALUES (MAX(IFNULL((SELECT MAX(id) FROM moz_places_temp), 0), " \
+                "IFNULL((SELECT MAX(id) FROM moz_places), 0)) + 1," \
+            "NEW.url, NEW.title, NEW.rev_host, " \
+            "IFNULL(NEW.visit_count, 0), " /* enforce having a value */ \
+            "NEW.hidden, NEW.typed, NEW.favicon_id, NEW.frecency, " \
+            "NEW.last_visit_date);" \
+  "END" \
+)
+
+/**
+ * This trigger allows for the deletion of a record in moz_places_view.  It
+ * removes any entry in the temporary table, the permanent table, and any
+ * associated entry in moz_openpages_temp.
+ */
+#define CREATE_PLACES_VIEW_DELETE_TRIGGER NS_LITERAL_CSTRING( \
+  "CREATE TEMPORARY TRIGGER moz_places_view_delete_trigger " \
+  "INSTEAD OF DELETE " \
+  "ON moz_places_view " \
+  "BEGIN " \
+    "DELETE FROM moz_places_temp " \
+    "WHERE id = OLD.id; " \
+    "DELETE FROM moz_places " \
+    "WHERE id = OLD.id; " \
+    "DELETE FROM moz_openpages_temp " \
+    "WHERE place_id = OLD.id; " \
+  "END" \
+)
 
 /**
- * This triggers update visit_count and last_visit_date based on historyvisits
- * table changes.
+ * This trigger allows for updates to a record in moz_places_view.  It first
+ * copies the row from the permanent table over to the temp table if it does not
+ * exist in the temporary table.  Then, it will update the temporary table with
+ * the new data.
+ * We use INSERT OR IGNORE to avoid looking if the place already exists in the
+ * temp table.
  */
-#define CREATE_HISTORYVISITS_AFTERINSERT_TRIGGER NS_LITERAL_CSTRING( \
-  "CREATE TRIGGER moz_historyvisits_afterinsert_v2_trigger " \
-  "AFTER INSERT ON moz_historyvisits FOR EACH ROW " \
+#define CREATE_PLACES_VIEW_UPDATE_TRIGGER NS_LITERAL_CSTRING( \
+  "CREATE TEMPORARY TRIGGER moz_places_view_update_trigger " \
+  "INSTEAD OF UPDATE " \
+  "ON moz_places_view " \
   "BEGIN " \
-    "UPDATE moz_places SET " \
-      "visit_count = visit_count + (SELECT NEW.visit_type NOT IN (" EXCLUDED_VISIT_TYPES ")), "\
-      "last_visit_date = MAX(IFNULL(last_visit_date, 0), NEW.visit_date) " \
+    "INSERT OR IGNORE INTO moz_places_temp (" MOZ_PLACES_COLUMNS ") " \
+    "SELECT " MOZ_PLACES_COLUMNS " FROM moz_places " \
+    "WHERE id = OLD.id; " \
+    "UPDATE moz_places_temp " \
+    "SET url = IFNULL(NEW.url, OLD.url), " \
+        "title = IFNULL(NEW.title, OLD.title), " \
+        "rev_host = IFNULL(NEW.rev_host, OLD.rev_host), " \
+        "visit_count = IFNULL(NEW.visit_count, OLD.visit_count), " \
+        "hidden = IFNULL(NEW.hidden, OLD.hidden), " \
+        "typed = IFNULL(NEW.typed, OLD.typed), " \
+        "favicon_id = IFNULL(NEW.favicon_id, OLD.favicon_id), " \
+        "frecency = IFNULL(NEW.frecency, OLD.frecency), " \
+        "last_visit_date = IFNULL(NEW.last_visit_date, OLD.last_visit_date) " \
+    "WHERE id = OLD.id; " \
+  "END" \
+)
+
+/**
+ * This trigger allows for an insertion into  moz_historyvisits_view.  It enters
+ * the new data into the temporary table, ensuring that the new id is one
+ * greater than the largest id value found.  It then updates moz_places_view
+ * with the new visit count.
+ * We use INSERT OR IGNORE to avoid looking if the place already exists in the
+ * temp table.
+ * In this case when updating last_visit_date we can simply check the maximum
+ * between new visit date and the actual cached value (or 0 if it is NULL).
+ */
+#define CREATE_HISTORYVISITS_VIEW_INSERT_TRIGGER NS_LITERAL_CSTRING( \
+  "CREATE TEMPORARY TRIGGER moz_historyvisits_view_insert_trigger " \
+  "INSTEAD OF INSERT " \
+  "ON moz_historyvisits_view " \
+  "BEGIN " \
+    "INSERT INTO moz_historyvisits_temp (" MOZ_HISTORYVISITS_COLUMNS ") " \
+    "VALUES (MAX(IFNULL((SELECT MAX(id) FROM moz_historyvisits_temp), 0), " \
+                "IFNULL((SELECT MAX(id) FROM moz_historyvisits), 0)) + 1, " \
+            "NEW.from_visit, NEW.place_id, NEW.visit_date, NEW.visit_type, " \
+            "NEW.session); " \
+    "INSERT OR IGNORE INTO moz_places_temp (" MOZ_PLACES_COLUMNS ") " \
+    "SELECT " MOZ_PLACES_COLUMNS " FROM moz_places " \
+    "WHERE id = NEW.place_id " \
+    "AND NEW.visit_type NOT IN (" EXCLUDED_VISIT_TYPES "); " \
+    "UPDATE moz_places_temp " \
+    "SET visit_count = visit_count + 1 " \
+    "WHERE id = NEW.place_id " \
+    "AND NEW.visit_type NOT IN (" EXCLUDED_VISIT_TYPES "); " \
+    "UPDATE moz_places_temp " \
+    "SET last_visit_date = MAX(IFNULL(last_visit_date, 0), NEW.visit_date)" \
     "WHERE id = NEW.place_id;" \
   "END" \
 )
 
-#define CREATE_HISTORYVISITS_AFTERDELETE_TRIGGER NS_LITERAL_CSTRING( \
-  "CREATE TRIGGER moz_historyvisits_afterdelete_v2_trigger " \
-  "AFTER DELETE ON moz_historyvisits FOR EACH ROW " \
+/**
+ * This trigger allows for the deletion of a record in moz_historyvisits_view.
+ * It removes any entry in the temporary table, and removes any entry in the
+ * permanent table as well.  It then updates moz_places_view with the new visit
+ * count.
+ * We use INSERT OR IGNORE to avoid looking if the place already exists in the
+ * temp table.
+ */
+#define CREATE_HISTORYVISITS_VIEW_DELETE_TRIGGER NS_LITERAL_CSTRING( \
+  "CREATE TEMPORARY TRIGGER moz_historyvisits_view_delete_trigger " \
+  "INSTEAD OF DELETE " \
+  "ON moz_historyvisits_view " \
   "BEGIN " \
-    "UPDATE moz_places SET " \
-      "visit_count = visit_count - (SELECT OLD.visit_type NOT IN (" EXCLUDED_VISIT_TYPES ")), "\
-      "last_visit_date = (SELECT visit_date FROM moz_historyvisits " \
-                         "WHERE place_id = OLD.place_id " \
-                         "ORDER BY visit_date DESC LIMIT 1) " \
-    "WHERE id = OLD.place_id;" \
+    "DELETE FROM moz_historyvisits_temp " \
+    "WHERE id = OLD.id; " \
+    "DELETE FROM moz_historyvisits " \
+    "WHERE id = OLD.id; " \
+    "INSERT OR IGNORE INTO moz_places_temp (" MOZ_PLACES_COLUMNS ") " \
+    "SELECT " MOZ_PLACES_COLUMNS " FROM moz_places " \
+    "WHERE id = OLD.place_id " \
+    "AND OLD.visit_type NOT IN (" EXCLUDED_VISIT_TYPES "); " \
+    "UPDATE moz_places_temp " \
+    "SET visit_count = visit_count - 1 " \
+    "WHERE id = OLD.place_id " \
+    "AND OLD.visit_type NOT IN (" EXCLUDED_VISIT_TYPES "); " \
+    "UPDATE moz_places_temp " \
+    "SET last_visit_date = " \
+      "(SELECT visit_date FROM moz_historyvisits_temp " \
+       "WHERE place_id = OLD.place_id " \
+       "UNION ALL " \
+       "SELECT visit_date FROM moz_historyvisits " \
+       "WHERE place_id = OLD.place_id " \
+       "ORDER BY visit_date DESC LIMIT 1) " \
+    "WHERE id = OLD.place_id; " \
   "END" \
 )
 
+/**
+ * This trigger allows for updates to a record in moz_historyvisits_view.  It
+ * first copies the row from the permanent table over to the temp table if it
+ * does not exist in the temporary table.  Then it will update the temporary
+ * table with the new data.
+ * We use INSERT OR IGNORE to avoid looking if the visit already exists in the
+ * temp table.
+ */
+#define CREATE_HISTORYVISITS_VIEW_UPDATE_TRIGGER NS_LITERAL_CSTRING( \
+  "CREATE TEMPORARY TRIGGER moz_historyvisits_view_update_trigger " \
+  "INSTEAD OF UPDATE " \
+  "ON moz_historyvisits_view " \
+  "BEGIN " \
+    "INSERT OR IGNORE INTO moz_historyvisits_temp (" MOZ_HISTORYVISITS_COLUMNS ") " \
+    "SELECT " MOZ_HISTORYVISITS_COLUMNS " FROM moz_historyvisits " \
+    "WHERE id = OLD.id; " \
+    "UPDATE moz_historyvisits_temp " \
+    "SET from_visit = IFNULL(NEW.from_visit, OLD.from_visit), " \
+        "place_id = IFNULL(NEW.place_id, OLD.place_id), " \
+        "visit_date = IFNULL(NEW.visit_date, OLD.visit_date), " \
+        "visit_type = IFNULL(NEW.visit_type, OLD.visit_type), " \
+        "session = IFNULL(NEW.session, OLD.session) " \
+    "WHERE id = OLD.id; " \
+  "END" \
+)
+
+/**
+ * This trigger moves the data out of a temporary table into the permanent one
+ * before deleting from the temporary table.
+ *
+ * Note - it's OK to use an INSERT OR REPLACE here because the only conflict
+ * that will happen is the primary key.  As a result, the row will be deleted,
+ * and the replacement will be inserted with the same id.
+ */
+#define CREATE_TEMP_SYNC_TRIGGER_BASE(__table, __columns) NS_LITERAL_CSTRING( \
+  "CREATE TEMPORARY TRIGGER " __table "_beforedelete_trigger " \
+  "BEFORE DELETE ON " __table "_temp FOR EACH ROW " \
+  "BEGIN " \
+    "INSERT OR REPLACE INTO " __table " (" __columns ") " \
+    "SELECT " __columns " FROM " __table "_temp " \
+    "WHERE id = OLD.id;" \
+  "END" \
+)
+#define CREATE_MOZ_PLACES_SYNC_TRIGGER \
+  CREATE_TEMP_SYNC_TRIGGER_BASE("moz_places", MOZ_PLACES_COLUMNS)
+#define CREATE_MOZ_HISTORYVISITS_SYNC_TRIGGER \
+  CREATE_TEMP_SYNC_TRIGGER_BASE("moz_historyvisits", MOZ_HISTORYVISITS_COLUMNS)
+
 
 /**
  * This trigger removes a row from moz_openpages_temp when open_count reaches 0.
  */
 #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 " \
--- a/toolkit/components/places/src/toolkitplaces.manifest
+++ b/toolkit/components/places/src/toolkitplaces.manifest
@@ -3,16 +3,22 @@ component {dca61eb5-c7cd-4df1-b0fb-d0722
 contract @mozilla.org/browser/livemark-service;2 {dca61eb5-c7cd-4df1-b0fb-d0722baba251}
 
 # nsTaggingService.js
 component {bbc23860-2553-479d-8b78-94d9038334f7} nsTaggingService.js
 contract @mozilla.org/browser/tagging-service;1 {bbc23860-2553-479d-8b78-94d9038334f7}
 component {1dcc23b0-d4cb-11dc-9ad6-479d56d89593} nsTaggingService.js
 contract @mozilla.org/autocomplete/search;1?name=places-tag-autocomplete {1dcc23b0-d4cb-11dc-9ad6-479d56d89593}
 
+# nsPlacesDBFlush.js
+component {c1751cfc-e8f1-4ade-b0bb-f74edfb8ef6a} nsPlacesDBFlush.js
+contract @mozilla.org/places/sync;1 {c1751cfc-e8f1-4ade-b0bb-f74edfb8ef6a}
+category bookmark-observers nsPlacesDBFlush @mozilla.org/places/sync;1
+category history-observers nsPlacesDBFlush @mozilla.org/places/sync;1
+
 # nsPlacesExpiration.js
 component {705a423f-2f69-42f3-b9fe-1517e0dee56f} nsPlacesExpiration.js
 contract @mozilla.org/places/expiration;1 {705a423f-2f69-42f3-b9fe-1517e0dee56f}
 category history-observers nsPlacesExpiration @mozilla.org/places/expiration;1
 
 # nsMicrosummaryService.js
 component {460a9792-b154-4f26-a922-0f653e2c8f91} nsMicrosummaryService.js
 contract @mozilla.org/microsummary/service;1 {460a9792-b154-4f26-a922-0f653e2c8f91}
--- a/toolkit/components/places/tests/Makefile.in
+++ b/toolkit/components/places/tests/Makefile.in
@@ -44,16 +44,17 @@ relativesrcdir	= toolkit/components/plac
 
 include $(DEPTH)/config/autoconf.mk
 
 MODULE		= test_places
 
 XPCSHELL_TESTS = \
 	autocomplete \
 	expiration \
+	sync \
 	bookmarks \
 	queries \
 	unit \
 	network \
 	$(NULL)
 
 # Files in the main "tests" folder.
 XPCSHELL_TESTS_COMMON = \
--- a/toolkit/components/places/tests/autocomplete/test_autocomplete_on_value_removed_479089.js
+++ b/toolkit/components/places/tests/autocomplete/test_autocomplete_on_value_removed_479089.js
@@ -67,17 +67,17 @@ function run_test()
   var options = histsvc.getNewQueryOptions();
   // look for this uri only
   query.uri = uri;
   // execute
   var queryRes = histsvc.executeQuery(query, options);
   // open the result container
   queryRes.root.containerOpen = true;
   // debug queries
-  // dump_table("moz_places");
+  // dump_table("moz_places_view");
   do_check_eq(queryRes.root.childCount, 1);  
   // call the untested code path
   listener.onValueRemoved(null, uri.spec, true);
   // make sure it is GONE from the DB
   do_check_eq(queryRes.root.childCount, 0);
   // close the container
   queryRes.root.containerOpen = false;
 }
--- a/toolkit/components/places/tests/browser/browser_settitle.js
+++ b/toolkit/components/places/tests/browser/browser_settitle.js
@@ -31,16 +31,18 @@ function load(href, callback)
 var conn = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase).DBConnection;
 
 /**
  * Gets a single column value from either the places or historyvisits table.
  */
 function getColumn(table, column, fromColumnName, fromColumnValue)
 {
   var stmt = conn.createStatement(
+    "SELECT " + column + " FROM " + table + "_temp WHERE " + fromColumnName + "=:val " +
+    "UNION ALL " +
     "SELECT " + column + " FROM " + table + " WHERE " + fromColumnName + "=:val " +
     "LIMIT 1");
   try {
     stmt.params.val = fromColumnValue;
     stmt.executeStep();
     return stmt.row[column];
   }
   finally {
--- a/toolkit/components/places/tests/browser/browser_visituri.js
+++ b/toolkit/components/places/tests/browser/browser_visituri.js
@@ -46,16 +46,18 @@ function waitForLoad(callback)
 var conn = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase).DBConnection;
 
 /**
  * Gets a single column value from either the places or historyvisits table.
  */
 function getColumn(table, column, fromColumnName, fromColumnValue)
 {
   var stmt = conn.createStatement(
+    "SELECT " + column + " FROM " + table + "_temp WHERE " + fromColumnName + "=:val " +
+    "UNION ALL " +
     "SELECT " + column + " FROM " + table + " WHERE " + fromColumnName + "=:val " +
     "LIMIT 1");
   try {
     stmt.params.val = fromColumnValue;
     stmt.executeStep();
     return stmt.row[column];
   }
   finally {
--- a/toolkit/components/places/tests/cpp/places_test_harness.h
+++ b/toolkit/components/places/tests/cpp/places_test_harness.h
@@ -177,22 +177,26 @@ do_get_place(nsIURI* aURI, PlaceRecord& 
   nsCOMPtr<mozIStorageConnection> dbConn = do_get_db();
   nsCOMPtr<mozIStorageStatement> stmt;
 
   nsCString spec;
   nsresult rv = aURI->GetSpec(spec);
   do_check_success(rv);
 
   rv = dbConn->CreateStatement(NS_LITERAL_CSTRING(
+    "SELECT id, hidden, typed, visit_count FROM moz_places_temp "
+    "WHERE url=?1 "
+    "UNION ALL "
     "SELECT id, hidden, typed, visit_count FROM moz_places "
     "WHERE url=?1 "
+    "LIMIT 1"
   ), getter_AddRefs(stmt));
   do_check_success(rv);
 
-  rv = stmt->BindUTF8StringByIndex(0, spec);
+  rv = stmt->BindUTF8StringParameter(0, spec);
   do_check_success(rv);
 
   PRBool hasResults;
   rv = stmt->ExecuteStep(&hasResults);
   do_check_true(hasResults);
   do_check_success(rv);
 
   rv = stmt->GetInt64(0, &result.id);
@@ -213,23 +217,26 @@ do_get_place(nsIURI* aURI, PlaceRecord& 
  */
 void
 do_get_lastVisit(PRInt64 placeId, VisitRecord& result)
 {
   nsCOMPtr<mozIStorageConnection> dbConn = do_get_db();
   nsCOMPtr<mozIStorageStatement> stmt;
 
   nsresult rv = dbConn->CreateStatement(NS_LITERAL_CSTRING(
+    "SELECT id, from_visit, visit_type FROM moz_historyvisits_temp "
+    "WHERE place_id=?1 "
+    "UNION ALL "
     "SELECT id, from_visit, visit_type FROM moz_historyvisits "
     "WHERE place_id=?1 "
     "LIMIT 1"
   ), getter_AddRefs(stmt));
   do_check_success(rv);
 
-  rv = stmt->BindInt64ByIndex(0, placeId);
+  rv = stmt->BindInt64Parameter(0, placeId);
   do_check_success(rv);
 
   PRBool hasResults;
   rv = stmt->ExecuteStep(&hasResults);
   do_check_true(hasResults);
   do_check_success(rv);
 
   rv = stmt->GetInt64(0, &result.id);
--- a/toolkit/components/places/tests/expiration/test_annos_expire_policy.js
+++ b/toolkit/components/places/tests/expiration/test_annos_expire_policy.js
@@ -85,17 +85,17 @@ function add_old_anno(aIdentifier, aName
                       "ORDER BY dateAdded DESC LIMIT 1)";
   }
   else if (aIdentifier instanceof Ci.nsIURI){
     // Page annotation.
     as.setPageAnnotation(aIdentifier, aName, aValue, 0, aExpirePolicy);
     // Update dateAdded for the last added annotation.
     sql = "UPDATE moz_annos SET dateAdded = :expire_date, lastModified = :last_modified " +
           "WHERE id = (SELECT a.id FROM moz_annos a " +
-                      "LEFT JOIN moz_places h on h.id = a.place_id " +
+                      "LEFT JOIN moz_places_view h on h.id = a.place_id " +
                       "WHERE h.url = :id " +
                       "ORDER BY a.dateAdded DESC LIMIT 1)";
   }
   else
     do_throw("Wrong identifier type");
 
   let stmt = DBConn().createStatement(sql);
   stmt.params.id = (typeof(aIdentifier) == "number") ? aIdentifier
--- a/toolkit/components/places/tests/expiration/test_removeAllPages.js
+++ b/toolkit/components/places/tests/expiration/test_removeAllPages.js
@@ -83,17 +83,17 @@ function add_old_anno(aIdentifier, aName
   else if (aIdentifier instanceof Ci.nsIURI){
     // Page annotation.
     as.setPageAnnotation(aIdentifier, aName, aValue, 0, aExpirePolicy);
     // Update dateAdded for the last added annotation.
     sql = "UPDATE moz_annos SET dateAdded = :expire_date, lastModified = :last_modified " +
           "WHERE id = ( " +
             "SELECT a.id FROM moz_annos a " +
             "JOIN moz_anno_attributes n ON n.id = a.anno_attribute_id " +
-            "JOIN moz_places h on h.id = a.place_id " +
+            "JOIN moz_places_view h on h.id = a.place_id " +
             "WHERE h.url = :id " +
             "AND n.name = :anno_name " +
             "ORDER BY a.dateAdded DESC LIMIT 1 " +
           ")";
   }
   else
     do_throw("Wrong identifier type");
 
--- a/toolkit/components/places/tests/head_common.js
+++ b/toolkit/components/places/tests/head_common.js
@@ -452,8 +452,39 @@ function is_time_ordered(before, after) 
   // Windows has an estimated 16ms timers precision, since Date.now() and
   // 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;
 }
+
+
+// These tests are known to randomly fail due to bug 507790 when database
+// flushes are active, so we turn off syncing for them.
+let (randomFailingSyncTests = [
+  "test_multi_word_tags.js",
+  "test_removeVisitsByTimeframe.js",
+  "test_utils_getURLsForContainerNode.js",
+  "test_exclude_livemarks.js",
+  "test_402799.js",
+  "test_results-as-visit.js",
+  "test_sorting.js",
+  "test_redirectsMode.js",
+  "test_384228.js",
+  "test_395593.js",
+  "test_containersQueries_sorting.js",
+  "test_browserGlue_smartBookmarks.js",
+  "test_browserGlue_distribution.js",
+  "test_331487.js",
+  "test_tags.js",
+  "test_385829.js",
+  "test_405938_restore_queries.js",
+]) {
+  let currentTestFilename = do_get_file(_TEST_FILE[0], true).leafName;
+  if (randomFailingSyncTests.indexOf(currentTestFilename) != -1) {
+    print("Test " + currentTestFilename +
+          " is known random due to bug 507790, disabling PlacesDBFlush.");
+    let sync = Cc["@mozilla.org/places/sync;1"].getService(Ci.nsIObserver);
+    sync.observe(null, "places-debug-stop-sync", null);
+  }
+}
--- a/toolkit/components/places/tests/mochitest/bug_411966/redirect.js
+++ b/toolkit/components/places/tests/mochitest/bug_411966/redirect.js
@@ -153,21 +153,21 @@ StreamListener.prototype = {
 
 // Check Callback.
 function checkDB(data){
   netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
   var referrer = this.mChannel.QueryInterface(Ci.nsIHttpChannel).referrer;
   ghist.addURI(this.mChannel.URI, true, true, referrer);
 
   // Get all pages visited from the original typed one
-  var sql = "SELECT url FROM moz_historyvisits " +
-            "JOIN moz_places h ON h.id = place_id " +
+  var sql = "SELECT url FROM moz_historyvisits_view " +
+            "JOIN moz_places_view h ON h.id = place_id " +
             "WHERE from_visit IN " +
-              "(SELECT v.id FROM moz_historyvisits v " +
-              "JOIN moz_places p ON p.id = v.place_id " +
+              "(SELECT v.id FROM moz_historyvisits_view v " +
+              "JOIN moz_places_view p ON p.id = v.place_id " +
               "WHERE p.url = ?1)";
   var stmt = mDBConn.createStatement(sql);
   stmt.bindUTF8StringParameter(0, typedURI.spec);
 
   var empty = true;
   while (stmt.executeStep()) {
     empty = false;
     var visitedURI = stmt.getUTF8String(0);
--- a/toolkit/components/places/tests/network/test_history_redirects.js
+++ b/toolkit/components/places/tests/network/test_history_redirects.js
@@ -104,18 +104,18 @@ function run_test() {
   chan.notificationCallbacks = listener;
   chan.asyncOpen(listener, null);
   // The test will continue on onStopRequest.
 }
 
 function continue_test() {
   let stmt = DBConn().createStatement(
     "SELECT v.id, h.url, v.from_visit, v.visit_date, v.visit_type, v.session " +
-    "FROM moz_historyvisits v " +
-    "JOIN moz_places h on h.id = v.place_id " +
+    "FROM moz_historyvisits_view v " +
+    "JOIN moz_places_view h on h.id = v.place_id " +
     "ORDER BY v.id ASC");
   const EXPECTED = [
     { id: 1,
       url: PERMA_REDIR_URL,
       from_visit: 0,
       visit_type: Ci.nsINavHistoryService.TRANSITION_LINK,
       session: 1 },
     { id: 2,
--- a/toolkit/components/places/tests/queries/head_queries.js
+++ b/toolkit/components/places/tests/queries/head_queries.js
@@ -80,34 +80,34 @@ function populateDB(aArray) {
         var referrer = qdata.referrer ? uri(qdata.referrer) : null;
         var visitId = PlacesUtils.history.addVisit(uri(qdata.uri), qdata.lastVisit,
                                                    referrer, qdata.transType,
                                                    qdata.isRedirect, qdata.sessionID);
         do_check_true(visitId > 0);
         if (qdata.title && !qdata.isDetails) {
           // Set the page title synchronously, otherwise setPageTitle is LAZY.
           let stmt = DBConn().createStatement(
-            "UPDATE moz_places SET title = :title WHERE url = :url"
+            "UPDATE moz_places_view SET title = :title WHERE url = :url"
           );
           stmt.params.title = qdata.title;
           stmt.params.url = qdata.uri;
           try {
             stmt.execute();
             // Force a notification so results are updated.
             PlacesUtils.history.runInBatchMode({runBatched: function(){}}, null);
           }
           finally {
             stmt.finalize();
           }
         }
         if (qdata.visitCount && !qdata.isDetails) {
           // Set a fake visit_count, this is not a real count but can be used
           // to test sorting by visit_count.
           let stmt = DBConn().createStatement(
-            "UPDATE moz_places SET visit_count = :vc WHERE url = :url");
+            "UPDATE moz_places_view SET visit_count = :vc WHERE url = :url");
           stmt.params.vc = qdata.visitCount;
           stmt.params.url = qdata.uri;
           try {
             stmt.execute();
             // Force a notification so results are updated.
             PlacesUtils.history.runInBatchMode({runBatched: function(){}}, null);
           }
           finally {
--- a/toolkit/components/places/tests/queries/test_transitions.js
+++ b/toolkit/components/places/tests/queries/test_transitions.js
@@ -95,18 +95,20 @@ function run_test() {
     PlacesUtils.history.addVisit(uri(item.uri), timeNow++ * 1000, null,
       item.transType, false, 0);
   }
 
   for (let i in testData) {
     testData[i].title = null;
   }
 
-  //dump_table("moz_places");
-  //dump_table("moz_historyvisits");
+  dump_table("moz_places");
+  dump_table("moz_places_temp");
+  dump_table("moz_historyvisits");
+  dump_table("moz_historyvisits_temp");
 
   var numSortFunc = function (a,b) { return (a - b); };
   var arrs = testDataTyped.concat(testDataDownload).concat(testDataBookmark)
               .sort(numSortFunc);
 
   // Four tests which compare the result of a query to an expected set.
   var data = arrs.filter(function (index) {
       return (testData[index].uri.match(/arewefastyet\.com/) &&
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/sync/head_sync.js
@@ -0,0 +1,136 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * 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.
+ *
+ * The Initial Developer of the Original Code is
+ * Google Inc.
+ * Portions created by the Initial Developer are Copyright (C) 2005
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Brian Ryner <bryner@brianryner.com>
+ *  Dietrich Ayala <dietrich@mozilla.com>
+ *  Shawn Wilsher <me@shawnwilsher.com>
+ *  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
+ * decision by deleting the provisions above and replace them with the notice
+ * 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 ***** */
+
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cr = Components.results;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+// Import common head.
+let (commonFile = do_get_file("../head_common.js", false)) {
+  let uri = Services.io.newFileURI(commonFile);
+  Services.scriptloader.loadSubScript(uri.spec, this);
+}
+
+// Put any other stuff relative to this test folder below.
+
+
+/**
+ * Function tests to see if the place associated with the bookmark with id
+ * aBookmarkId has the uri aExpectedURI.  The event will call do_test_finished()
+ *  if aFinish is true.
+ *
+ * @param aBookmarkId
+ *        The bookmark to check against.
+ * @param aExpectedURI
+ *        The URI we expect to be in moz_places.
+ * @param aExpected
+ *        Indicates if we expect to get a result or not.
+ * @param [optional] aFinish
+ *        Indicates if the test should be completed or not.
+ */
+function new_test_bookmark_uri_event(aBookmarkId, aExpectedURI, aExpected, aFinish)
+{
+  let stmt = DBConn().createStatement(
+    "SELECT moz_places.url " +
+    "FROM moz_bookmarks INNER JOIN moz_places " +
+    "ON moz_bookmarks.fk = moz_places.id " +
+    "WHERE moz_bookmarks.id = ?1"
+  );
+  stmt.bindInt64Parameter(0, aBookmarkId);
+
+  if (aExpected) {
+    do_check_true(stmt.executeStep());
+    do_check_eq(stmt.getUTF8String(0), aExpectedURI);
+  }
+  else {
+    do_check_false(stmt.executeStep());
+  }
+  stmt.reset();
+  stmt.finalize();
+  stmt = null;
+
+  if (aFinish)
+    do_test_finished();
+}
+
+
+/**
+ * Function tests to see if the place associated with the visit with id aVisitId
+ * has the uri aExpectedURI.  The event will call do_test_finished() if aFinish is
+ * true.
+ *
+ * @param aVisitId
+ *        The visit to check against.
+ * @param aExpectedURI
+ *        The URI we expect to be in moz_places.
+ * @param aExpected
+ *        Indicates if we expect to get a result or not.
+ * @param [optional] aFinish
+ *        Indicates if the test should be completed or not.
+ */
+function new_test_visit_uri_event(aVisitId, aExpectedURI, aExpected, aFinish)
+{
+  let stmt = DBConn().createStatement(
+    "SELECT moz_places.url " +
+    "FROM moz_historyvisits INNER JOIN moz_places " +
+    "ON moz_historyvisits.place_id = moz_places.id " +
+    "WHERE moz_historyvisits.id = ?1"
+  );
+  stmt.bindInt64Parameter(0, aVisitId);
+
+  if (aExpected) {
+    do_check_true(stmt.executeStep());
+    do_check_eq(stmt.getUTF8String(0), aExpectedURI);
+  }
+  else {
+    do_check_false(stmt.executeStep());
+  }
+  stmt.reset();
+  stmt.finalize();
+  stmt = null;
+
+  if (aFinish)
+    do_test_finished();
+}
+
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/sync/test_bookmarks_sorted_by_none.js
@@ -0,0 +1,126 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 sts=2 expandtab
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * 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.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Marco Bonardo <mak77@bonardo.net> (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
+ * 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
+ * decision by deleting the provisions above and replace them with the notice
+ * 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 ***** */
+
+/**
+ * Bug 493933 - When sorting bookmarks BY NONE we should take in count partitioning.
+ *
+ * When we have a bookmarks results sorted BY NONE, could be the order depends
+ * on current status of disk and temp tables due to how the query executes the
+ * UNION. We must ensure their order is correct.
+ */
+ 
+var hs = Cc["@mozilla.org/browser/nav-history-service;1"].
+         getService(Ci.nsINavHistoryService);
+var bs = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
+         getService(Ci.nsINavBookmarksService);
+var prefs = Cc["@mozilla.org/preferences-service;1"].
+            getService(Ci.nsIPrefService).
+            getBranch("places.");
+var os = Cc["@mozilla.org/observer-service;1"].
+         getService(Ci.nsIObserverService);
+
+const kSyncPrefName = "syncDBTableIntervalInSecs";
+const SYNC_INTERVAL = 600;
+const kSyncFinished = "places-sync-finished";
+
+var observer = {
+  _runCount: 0,
+  one: null,
+  two: null,
+  observe: function(aSubject, aTopic, aData) {
+    if (aTopic == kSyncFinished) {
+      // Skip the first sync.
+      if (++this._runCount < 2)
+        return;
+
+      os.removeObserver(this, kSyncFinished);
+
+      // Sanity check positions.
+      do_check_true(this.one < this.two);
+      do_check_eq(bs.getItemIndex(this.one), 0);
+      do_check_eq(bs.getItemIndex(this.two), 1);
+
+      check_results();
+
+      // Now add a visit to the second bookmark, so that it is in temp table.
+      hs.addVisit(uri("http://2.mozilla.org/"), Date.now() * 1000, null,
+                  hs.TRANSITION_TYPED, false, 0);
+
+      // Nothing should change in results.
+      check_results();
+
+      // Cleanup.
+      bs.removeFolderChildren(bs.toolbarFolder);
+      do_test_finished();
+    }
+  }
+}
+os.addObserver(observer, kSyncFinished, false);
+
+function check_results() {
+  // Create a bookmarks query.
+  var options = hs.getNewQueryOptions();
+  options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS;
+  var query = hs.getNewQuery();
+  query.setFolders([bs.toolbarFolder], 1);
+  query.searchTerms = "mozilla";
+  var result = hs.executeQuery(query, options);
+  var root = result.root;
+  root.containerOpen = true;
+  do_check_eq(root.childCount, 2);
+  do_check_eq(root.getChild(0).title, "mozilla 1");
+  do_check_eq(root.getChild(1).title, "mozilla 2");
+  root.containerOpen = false;
+}
+
+function run_test()
+{
+  // First set the preference for the timer to a large value so we don't sync
+  prefs.setIntPref(kSyncPrefName, SYNC_INTERVAL);
+
+  // Add two bookmarks and check their index
+  observer.one = bs.insertBookmark(bs.toolbarFolder,
+                                   uri("http://1.mozilla.org/"),
+                                   bs.DEFAULT_INDEX, "mozilla 1");
+  observer.two = bs.insertBookmark(bs.toolbarFolder,
+                                   uri("http://2.mozilla.org/"),
+                                   bs.DEFAULT_INDEX, "mozilla 2");
+
+  do_test_pending();
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/sync/test_database_sync_after_addBookmark.js
@@ -0,0 +1,89 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 sts=2 expandtab
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * 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.
+ * 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)
+ *   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
+ * decision by deleting the provisions above and replace them with the notice
+ * 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 ***** */
+
+var bs = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
+         getService(Ci.nsINavBookmarksService);
+var os = Cc["@mozilla.org/observer-service;1"].
+         getService(Ci.nsIObserverService);
+var prefs = Cc["@mozilla.org/preferences-service;1"].
+            getService(Ci.nsIPrefService).
+            getBranch("places.");
+
+const TEST_URI = "http://test.com/";
+
+const SYNC_INTERVAL = 600; // ten minutes
+const kSyncPrefName = "syncDBTableIntervalInSecs";
+const kSyncFinished = "places-sync-finished";
+
+// Used to update observer itemId
+var bookmarksObserver = {
+  onItemAdded: function(aItemId, aNewParent, aNewIndex) {
+    observer.itemId = aItemId;
+  }
+}
+bs.addObserver(bookmarksObserver, false);
+
+var observer = {
+  itemId: -1,
+  observe: function(aSubject, aTopic, aData) {
+    if (aTopic == kSyncFinished) {
+      do_check_neq(this.itemId, -1);
+      // remove the observer, we don't need to observe sync on quit
+      os.removeObserver(this, kSyncFinished);
+      bs.removeObserver(bookmarksObserver);
+      // Check that moz_places table has been correctly synced
+      new_test_bookmark_uri_event(this.itemId, TEST_URI, true, true);
+    }
+  }
+}
+os.addObserver(observer, kSyncFinished, false);
+
+function run_test()
+{
+  // First set the preference for the timer to a really large value so it won't
+  // run before the test finishes.
+  prefs.setIntPref(kSyncPrefName, SYNC_INTERVAL);
+
+  // Insert a new bookmark
+  bs.insertBookmark(bs.unfiledBookmarksFolder, uri(TEST_URI),
+                    bs.DEFAULT_INDEX, "test");
+
+  do_test_pending();
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/sync/test_database_sync_after_addBookmark_batched.js
@@ -0,0 +1,110 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 sts=2 expandtab
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * 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.
+ * 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)
+ *   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
+ * decision by deleting the provisions above and replace them with the notice
+ * 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 ***** */
+
+var bs = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
+         getService(Ci.nsINavBookmarksService);
+var os = Cc["@mozilla.org/observer-service;1"].
+         getService(Ci.nsIObserverService);
+var prefs = Cc["@mozilla.org/preferences-service;1"].
+            getService(Ci.nsIPrefService).
+            getBranch("places.");
+
+const TEST_URI = "http://test.com/";
+
+const SYNC_INTERVAL = 600; // ten minutes
+const kSyncPrefName = "syncDBTableIntervalInSecs";
+const kSyncFinished = "places-sync-finished";
+
+// Used to check if we are batching and to update observer itemId
+var bookmarksObserver = {
+  _batching: false,
+  onBeginUpdateBatch: function() {
+    this._batching = true;
+  },
+  onEndUpdateBatch: function() {
+    this._batching = false;
+  },
+  onItemAdded: function(aItemId, aNewParent, aNewIndex) {
+    observer.itemId = aItemId;
+  }
+}
+bs.addObserver(bookmarksObserver, false);
+
+var observer = {
+  itemId: -1,
+  observe: function(aSubject, aTopic, aData) {
+    if (aTopic == kSyncFinished) {
+      dump(this.itemId);
+      // item id must be valid
+      do_check_neq(this.itemId, -1);
+      // Check that we are not in a batch
+      do_check_false(bookmarksObserver._batching);
+      // remove the observer, we don't need to observe sync on quit
+      os.removeObserver(this, kSyncFinished);
+      bs.removeObserver(bookmarksObserver);
+      // Check that tables have been correctly synced
+      new_test_bookmark_uri_event(this.itemId, TEST_URI, true, true);
+    }
+  }
+}
+os.addObserver(observer, kSyncFinished, false);
+
+function run_test()
+{
+  // Set the preference for the timer to a really large value, so it won't
+  // run before the test finishes.
+  prefs.setIntPref(kSyncPrefName, SYNC_INTERVAL);
+
+  // Add a bookmark in batch mode
+  let id = -1;
+  bs.runInBatchMode({
+    runBatched: function(aUserData)
+    {
+      id = bs.insertBookmark(bs.unfiledBookmarksFolder, uri(TEST_URI),
+                             bs.DEFAULT_INDEX, "test");
+      // We should not sync during a batch
+      new_test_bookmark_uri_event(id, TEST_URI, false);
+    }
+  }, null);
+  // Ensure the bookmark has been added
+  do_check_neq(id, -1);
+
+  do_test_pending();
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/sync/test_database_sync_after_addVisit.js
@@ -0,0 +1,96 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 sts=2 expandtab
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * 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.
+ * 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)
+ *   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
+ * decision by deleting the provisions above and replace them with the notice
+ * 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 ***** */
+
+var hs = Cc["@mozilla.org/browser/nav-history-service;1"].
+         getService(Ci.nsINavHistoryService);
+var bs = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
+         getService(Ci.nsINavBookmarksService);
+var prefs = Cc["@mozilla.org/preferences-service;1"].
+            getService(Ci.nsIPrefService).
+            getBranch("places.");
+var os = Cc["@mozilla.org/observer-service;1"].
+         getService(Ci.nsIObserverService);
+
+const TEST_URI = "http://test.com/";
+
+const kSyncPrefName = "syncDBTableIntervalInSecs";
+const SYNC_INTERVAL = 1;
+const kSyncFinished = "places-sync-finished";
+
+var observer = {
+  visitId: -1,
+  observe: function(aSubject, aTopic, aData) {
+    if (aTopic == kSyncFinished && this.visitId != -1) {
+      // sanity check: visitId set by history observer should be the same we
+      // have added
+      do_check_eq(this.visitId, visitId);
+      // remove the observer, we don't need to observe sync on quit
+      os.removeObserver(this, kSyncFinished);
+      // Check the visit
+      new_test_visit_uri_event(this.visitId, TEST_URI, true, true);
+    }
+  }
+}
+os.addObserver(observer, kSyncFinished, false);
+
+// Used to update observer visitId
+var historyObserver = {
+  onVisit: function(aURI, aVisitId, aTime, aSessionId, aReferringId,
+                    aTransitionType, aAdded) {
+    observer.visitId = aVisitId;
+    hs.removeObserver(this, false);
+  }
+}
+hs.addObserver(historyObserver, false);
+
+// used for sanity check
+var visitId = -1;
+
+function run_test()
+{
+  // First set the preference for the timer to a small value
+  prefs.setIntPref(kSyncPrefName, SYNC_INTERVAL);
+
+  // Now add the visit
+  visitId = hs.addVisit(uri(TEST_URI), Date.now() * 1000, null,
+                        hs.TRANSITION_TYPED, false, 0);
+
+  do_test_pending();
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/sync/test_database_sync_after_addVisit_batched.js
@@ -0,0 +1,109 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 sts=2 expandtab
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * 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.
+ * 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)
+ *   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
+ * decision by deleting the provisions above and replace them with the notice
+ * 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 ***** */
+
+  var bs = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
+           getService(Ci.nsINavBookmarksService);
+  var hs = Cc["@mozilla.org/browser/nav-history-service;1"].
+           getService(Ci.nsINavHistoryService);
+  var os = Cc["@mozilla.org/observer-service;1"].
+           getService(Ci.nsIObserverService);
+
+const TEST_URI = "http://test.com/";
+
+const kSyncFinished = "places-sync-finished";
+
+// Used to check if we are batching
+var bookmarksObserver = {
+  _batching: false,
+  onBeginUpdateBatch: function() {
+    this._batching = true;
+  },
+  onEndUpdateBatch: function() {
+    this._batching = false;
+  }
+}
+bs.addObserver(bookmarksObserver, false);
+
+// Used to update observer visitId
+var historyObserver = {
+  onVisit: function(aURI, aVisitId, aTime, aSessionId, aReferringId,
+                    aTransitionType, aAdded) {
+    observer.visitId = aVisitId;
+  }
+}
+hs.addObserver(historyObserver, false);
+
+var observer = {
+  visitId: -1,
+  observe: function(aSubject, aTopic, aData) {
+    if (aTopic == kSyncFinished) {
+      // visit id must be valid
+      do_check_neq(this.visitId, -1);
+      // Check that we are not in a batch
+      do_check_false(bookmarksObserver._batching);
+      // remove the observer, we don't need to observe sync on quit
+      os.removeObserver(this, kSyncFinished);
+      bs.removeObserver(bookmarksObserver);
+      hs.removeObserver(historyObserver);
+      // Check that tables have been correctly synced
+      new_test_visit_uri_event(this.visitId, TEST_URI, true, true);
+    }
+  }
+}
+os.addObserver(observer, kSyncFinished, false);
+
+function run_test()
+{
+  // Add a visit in batch mode
+  let id = -1;
+  bs.runInBatchMode({
+    runBatched: function(aUserData)
+    {
+      id = hs.addVisit(uri(TEST_URI), Date.now() * 1000, null,
+                       hs.TRANSITION_TYPED, false, 0);
+      // We should not sync during a batch
+      new_test_visit_uri_event(id, TEST_URI, false);
+    }
+  }, null);
+  // Ensure the visit has been added
+  do_check_neq(id, -1);
+
+  do_test_pending();
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/sync/test_database_sync_after_modifyBookmark.js
@@ -0,0 +1,109 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 sts=2 expandtab
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * 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.
+ * 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)
+ *   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
+ * decision by deleting the provisions above and replace them with the notice
+ * 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 ***** */
+
+var bs = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
+         getService(Ci.nsINavBookmarksService);
+var os = Cc["@mozilla.org/observer-service;1"].
+         getService(Ci.nsIObserverService);
+var prefs = Cc["@mozilla.org/preferences-service;1"].
+            getService(Ci.nsIPrefService).
+            getBranch("places.");
+
+const TEST_URI = "http://test.com/";
+const MODIFIED_URI = "http://test.com/index.html";
+
+const SYNC_INTERVAL = 600; // ten minutes
+const kSyncPrefName = "syncDBTableIntervalInSecs";
+const kSyncFinished = "places-sync-finished";
+
+// Used to update observer itemId
+var bookmarksObserver = {
+  onItemAdded: function(aItemId, aNewParent, aNewIndex, aItemType) {
+    observer.itemId = aItemId;
+  },
+  onItemChanged: function(aItemId, aProperty, aNewValue, aLastModified,
+                          aItemType) {
+    if (aProperty == "uri")
+      do_check_eq(observer.itemId, aItemId);
+  }
+}
+bs.addObserver(bookmarksObserver, false);
+
+var observer = {
+  itemId: -1,
+  _runCount: 0,
+  observe: function(aSubject, aTopic, aData) {
+    if (aTopic == kSyncFinished) {
+      // item id must be valid
+      do_check_neq(this.itemId, -1);
+      if (++this._runCount == 1) {
+        // First sync is fired by adding the bookmark
+        // Check that tables have been synced after insertBookmark
+        new_test_bookmark_uri_event(this.itemId, TEST_URI, true);
+        // Now modify the bookmark
+        bs.changeBookmarkURI(this.itemId, uri(MODIFIED_URI));
+      }
+      else if (this._runCount == 2) {
+        // Second sync is fired by changing the bookmark's uri
+        // remove the observer, we don't need to observe sync on quit
+        os.removeObserver(this, kSyncFinished);
+        bs.removeObserver(bookmarksObserver);
+        // Check that tables have been synced after changeBookmarkURI
+        new_test_bookmark_uri_event(this.itemId, MODIFIED_URI, true, true);
+      }
+      else
+        do_throw("Too many places sync calls");
+    }
+  }
+}
+os.addObserver(observer, kSyncFinished, false);
+
+function run_test()
+{
+  // Set the preference for the timer to a really large value, so it won't
+  // run before the test finishes.
+  prefs.setIntPref(kSyncPrefName, SYNC_INTERVAL);
+
+  // Insert a new bookmark
+  bs.insertBookmark(bs.unfiledBookmarksFolder, uri(TEST_URI),
+                    bs.DEFAULT_INDEX, "test");
+
+  do_test_pending();
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/sync/test_database_sync_after_shutdown.js
@@ -0,0 +1,95 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 sts=2 expandtab
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * 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.
+ * 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)
+ *   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
+ * decision by deleting the provisions above and replace them with the notice
+ * 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 ***** */
+
+var os = Cc["@mozilla.org/observer-service;1"].
+         getService(Ci.nsIObserverService);
+var prefs = Cc["@mozilla.org/preferences-service;1"].
+            getService(Ci.nsIPrefService).
+            getBranch("places.");
+var hs = Cc["@mozilla.org/browser/nav-history-service;1"].
+         getService(Ci.nsINavHistoryService);
+
+const TEST_URI = "http://test.com/";
+
+const kSyncPrefName = "syncDBTableIntervalInSecs";
+const SYNC_INTERVAL = 600; // ten minutes
+const kSyncFinished = "places-sync-finished";
+
+var historyObserver = {
+  onVisit: function(aURI, aVisitId, aTime, aSessionId, aReferringId,
+                    aTransitionType, aAdded) {
+    observer.visitId = aVisitId;
+    hs.removeObserver(this);
+  }
+}
+hs.addObserver(historyObserver, false);
+
+var observer = {
+  visitId: -1,
+  observe: function(aSubject, aTopic, aData) {
+    if (aTopic == kSyncFinished) {
+      // remove the observer, we don't need to observe sync on quit
+      os.removeObserver(this, kSyncFinished);
+
+      // visit id must be valid
+      do_check_neq(this.visitId, -1);
+      // Check that tables have been correctly synced
+      new_test_visit_uri_event(this.visitId, TEST_URI, true, true);
+    }
+  }
+}
+os.addObserver(observer, kSyncFinished, false);
+
+function run_test()
+{
+  // Set the preference for the timer to a really large value, so it won't
+  // run before the test finishes.
+  prefs.setIntPref(kSyncPrefName, SYNC_INTERVAL);
+
+  // Now add a visit
+  hs.addVisit(uri(TEST_URI), Date.now() * 1000, null,
+              hs.TRANSITION_TYPED, false, 0);
+
+  // Notify that we are quitting the app - we should sync!
+  shutdownPlaces();
+
+  // Test will continue on sync notification.
+  do_test_pending();
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/sync/test_database_sync_after_shutdown_with_removeAllPages.js
@@ -0,0 +1,164 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 sts=2 expandtab
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * 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.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Marco Bonardo <mak77@bonardo.net> (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
+ * 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
+ * decision by deleting the provisions above and replace them with the notice
+ * 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 ***** */
+
+var os = Cc["@mozilla.org/observer-service;1"].
+         getService(Ci.nsIObserverService);
+var prefs = Cc["@mozilla.org/preferences-service;1"].
+            getService(Ci.nsIPrefService).
+            getBranch("places.");
+var hs = Cc["@mozilla.org/browser/nav-history-service;1"].
+         getService(Ci.nsINavHistoryService);
+var bh = hs.QueryInterface(Ci.nsIBrowserHistory);
+var bs = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
+         getService(Ci.nsINavBookmarksService);
+
+const TEST_URI = "http://test.com/";
+
+const PREF_SYNC_INTERVAL = "syncDBTableIntervalInSecs";
+const SYNC_INTERVAL = 600; // ten minutes
+const TOPIC_SYNC_FINISHED = "places-sync-finished";
+
+// Polling constants to check the connection closed status.
+const POLLING_TIMEOUT_MS = 100;
+const POLLING_MAX_PASSES = 20;
+
+var historyObserver = {
+  visitId: -1,
+  cleared: false,
+  onVisit: function(aURI, aVisitId, aTime, aSessionId, aReferringId,
+                    aTransitionType, aAdded) {
+    this.visitId = aVisitId;
+  },
+  onClearHistory: function() {
+    // check browserHistory returns no entries
+    do_check_eq(0, bh.count);
+    this.cleared = true;
+    hs.removeObserver(this);
+  }
+}
+hs.addObserver(historyObserver, false);
+
+var observer = {
+  _runCount: 0,
+  observe: function(aSubject, aTopic, aData) {
+    if (aTopic == TOPIC_SYNC_FINISHED) {
+      if (++this._runCount == 1) {
+        // The first sync is due to the insert bookmark.
+        // Simulate a clear private data just before shutdown.
+        bh.removeAllPages();
+        // Immediately notify shutdown.
+        shutdownPlaces();
+        return;
+      }
+
+      // Remove the observer, we don't need it anymore.
+      os.removeObserver(this, TOPIC_SYNC_FINISHED);
+
+      // Visit id must be valid.
+      do_check_neq(historyObserver.visitId, -1);
+      // History must have been cleared.
+      do_check_true(historyObserver.cleared);
+
+      // The database connection will be closed after this sync, but we can't
+      // know how much time it will take, so we use a polling strategy.
+      do_timeout(POLLING_TIMEOUT_MS, check_results);
+    }
+  }
+}
+os.addObserver(observer, TOPIC_SYNC_FINISHED, false);
+
+var gPasses = 0;
+function check_results() {
+    if (++gPasses >= POLLING_MAX_PASSES) {
+      do_throw("Maximum time elapsdes waiting for Places database connection to close");
+      do_test_finished();
+    }
+
+    if (PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
+                           .DBConnection.connectionReady) {
+      do_timeout(POLLING_TIMEOUT_MS, check_results);
+      return;
+    }
+
+    let dbConn = DBConn();
+    do_check_true(dbConn.connectionReady);
+
+    // Check that frecency for not cleared items (bookmarks) has been
+    // converted to -MAX(visit_count, 1), so we will be able to
+    // recalculate frecency starting from most frecent bookmarks.
+    let stmt = dbConn.createStatement(
+      "SELECT id FROM moz_places WHERE frecency > 0 LIMIT 1");
+    do_check_false(stmt.executeStep());
+    stmt.finalize();
+
+    stmt = dbConn.createStatement(
+      "SELECT h.id FROM moz_places h WHERE h.frecency = -2 " +
+        "AND EXISTS (SELECT id FROM moz_bookmarks WHERE fk = h.id) LIMIT 1");
+    do_check_true(stmt.executeStep());
+    stmt.finalize();
+
+    // Check that all visit_counts have been brought to 0
+    stmt = dbConn.createStatement(
+      "SELECT id FROM moz_places WHERE visit_count <> 0 LIMIT 1");
+    do_check_false(stmt.executeStep());
+    stmt.finalize();
+
+    dbConn.asyncClose(function() {
+      do_check_false(dbConn.connectionReady);
+
+      do_test_finished();
+    });
+}
+
+function run_test()
+{
+  do_test_pending();
+
+  // Set the preference for the timer to a really large value, so it won't
+  // run before the test finishes.
+  prefs.setIntPref(PREF_SYNC_INTERVAL, SYNC_INTERVAL);
+
+  // Now add a visit before creating bookmark, and one later
+  hs.addVisit(uri(TEST_URI), Date.now() * 1000, null,
+              hs.TRANSITION_TYPED, false, 0);
+  bs.insertBookmark(bs.unfiledBookmarksFolder, uri(TEST_URI),
+                    bs.DEFAULT_INDEX, "bookmark");
+  hs.addVisit(uri(TEST_URI), Date.now() * 1000, null,
+              hs.TRANSITION_TYPED, false, 0);
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/sync/test_database_sync_embed_visits.js
@@ -0,0 +1,173 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 sts=2 expandtab
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * 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.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Marco Bonardo <mak77@bonardo.net> (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
+ * 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
+ * decision by deleting the provisions above and replace them with the notice
+ * 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 ***** */
+
+ /*
+  * This test checks that embed visits are not synced down to disk, we hold
+  * them in memory since they're going to be purged at session close
+  *
+  * We instead sync to disk FRAMED_LINK visits.
+  */
+
+var hs = Cc["@mozilla.org/browser/nav-history-service;1"].
+         getService(Ci.nsINavHistoryService);
+var dbConn = hs.QueryInterface(Ci.nsPIPlacesDatabase).DBConnection;
+var bs = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
+         getService(Ci.nsINavBookmarksService);
+var prefs = Cc["@mozilla.org/preferences-service;1"].
+            getService(Ci.nsIPrefService).
+            getBranch("places.");
+var os = Cc["@mozilla.org/observer-service;1"].
+         getService(Ci.nsIObserverService);
+
+const TEST_URI = "http://test.com/";
+const EMBED_URI = "http://embed.test.com/";
+const PLACE_URI = "place:test.com/";
+
+const kSyncPrefName = "syncDBTableIntervalInSecs";
+const SYNC_INTERVAL = 1;
+const kSyncFinished = "places-sync-finished";
+
+var transitions = [ hs.TRANSITION_LINK,
+                    hs.TRANSITION_TYPED,
+                    hs.TRANSITION_BOOKMARK,
+                    hs.TRANSITION_EMBED,
+                    hs.TRANSITION_FRAMED_LINK,
+                    hs.TRANSITION_REDIRECT_PERMANENT,
+                    hs.TRANSITION_REDIRECT_TEMPORARY,
+                    hs.TRANSITION_DOWNLOAD ];
+
+var observer = {
+  observe: function(aSubject, aTopic, aData) {
+    if (aTopic == kSyncFinished && this.visitId != -1) {
+      // remove the observer, we don't need to observe sync on quit
+      os.removeObserver(this, kSyncFinished);
+
+      // Check that moz_places table has been correctly synced
+      var stmt = dbConn.createStatement(
+        "SELECT id FROM moz_places WHERE url = :url");
+      stmt.params["url"] = TEST_URI;
+      do_check_true(stmt.executeStep());
+      stmt.finalize();
+      stmt = dbConn.createStatement(
+        "SELECT id FROM moz_places_temp WHERE url = :url");
+      stmt.params["url"] = TEST_URI;
+      do_check_false(stmt.executeStep());
+      stmt.finalize();
+
+      stmt = dbConn.createStatement(
+        "SELECT id FROM moz_places WHERE url = :url");
+      stmt.params["url"] = EMBED_URI;
+      do_check_false(stmt.executeStep());
+      stmt.finalize();
+      stmt = dbConn.createStatement(
+        "SELECT id FROM moz_places_temp WHERE url = :url");
+      stmt.params["url"] = EMBED_URI;
+      do_check_true(stmt.executeStep());
+      stmt.finalize();
+
+      stmt = dbConn.createStatement(
+        "SELECT id FROM moz_places WHERE url = :url");
+      stmt.params["url"] = PLACE_URI;
+      do_check_true(stmt.executeStep());
+      stmt.finalize();
+      stmt = dbConn.createStatement(
+        "SELECT id FROM moz_places_temp WHERE url = :url");
+      stmt.params["url"] = PLACE_URI;
+      do_check_false(stmt.executeStep());
+      stmt.finalize();
+
+      // Check that all visits but embed ones are in disk table
+      stmt = dbConn.createStatement(
+        "SELECT count(*) FROM moz_historyvisits h WHERE visit_type <> :t_embed");
+      stmt.params["t_embed"] = hs.TRANSITION_EMBED;
+      do_check_true(stmt.executeStep());
+      do_check_eq(stmt.getInt32(0), (transitions.length - 1) * 2);
+      stmt.finalize();
+      stmt = dbConn.createStatement(
+        "SELECT id FROM moz_historyvisits h WHERE visit_type = :t_embed");
+      stmt.params["t_embed"] = hs.TRANSITION_EMBED;
+      do_check_false(stmt.executeStep());
+      stmt.finalize();
+      stmt = dbConn.createStatement(
+        "SELECT id FROM moz_historyvisits_temp h WHERE visit_type = :t_embed");
+      stmt.params["t_embed"] = hs.TRANSITION_EMBED;
+      do_check_true(stmt.executeStep());
+      stmt.finalize();
+      stmt = dbConn.createStatement(
+        "SELECT id FROM moz_historyvisits_temp h WHERE visit_type <> :t_embed");
+      stmt.params["t_embed"] = hs.TRANSITION_EMBED;
+      do_check_false(stmt.executeStep());
+      stmt.finalize();
+
+      do_test_finished();
+    }
+  }
+}
+
+function run_test()
+{
+  // First set the preference for the timer to a small value
+  prefs.setIntPref(kSyncPrefName, SYNC_INTERVAL);
+
+  // Add a visit for every transition type on TEST_URI
+  // Embed visit should stay in temp table, while other should be synced
+  // Place entry for this uri should be synced to disk table
+  transitions.forEach(function addVisit(aTransition) {
+                        hs.addVisit(uri(TEST_URI), Date.now() * 1000, null,
+                                    aTransition, false, 0);
+                      });
+
+  // Add an embed visit for EMBED_URI
+  // Embed visit should stay in temp table
+  // Place entry for this uri should stay in temp table
+  hs.addVisit(uri(EMBED_URI), Date.now() * 1000, null,
+              hs.TRANSITION_EMBED, false, 0);
+
+  // Add a visit for every transition type on PLACE_URI
+  // Embed visit should stay in temp table
+  // Place entry for this uri should be synced to disk table
+  transitions.forEach(function addVisit(aTransition) {
+                        hs.addVisit(uri(PLACE_URI), Date.now() * 1000, null,
+                                    aTransition, false, 0);
+                      });
+
+  os.addObserver(observer, kSyncFinished, false);
+
+  do_test_pending();
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/sync/test_database_sync_expireAllFavicons.js
@@ -0,0 +1,134 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 sts=2 expandtab
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * 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.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Marco Bonardo <mak77@bonardo.net> (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
+ * 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
+ * decision by deleting the provisions above and replace them with the notice
+ * 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 ***** */
+
+/*
+ * This test ensures favicons are correctly expired by expireAllFavicons API,
+ * both synced and unsynced favicons should be expired.
+ */
+var hs = Cc["@mozilla.org/browser/nav-history-service;1"].
+         getService(Ci.nsINavHistoryService);
+var dbConn = hs.QueryInterface(Ci.nsPIPlacesDatabase).DBConnection;
+var bs = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
+         getService(Ci.nsINavBookmarksService);
+var prefs = Cc["@mozilla.org/preferences-service;1"].
+            getService(Ci.nsIPrefService).
+            getBranch("places.");
+var os = Cc["@mozilla.org/observer-service;1"].
+         getService(Ci.nsIObserverService);
+var icons = Cc["@mozilla.org/browser/favicon-service;1"].
+            getService(Ci.nsIFaviconService);
+
+const TEST_URI = "http://test.com/";
+const TEST_ICON_URI = "http://test.com/favicon.ico";
+const TEST_NOSYNC_URI = "http://nosync.test.com/";
+const TEST_NOSYNC_ICON_URI = "http://nosync.test.com/favicon.ico";
+
+const kSyncPrefName = "syncDBTableIntervalInSecs";
+const SYNC_INTERVAL = 600;
+const kSyncFinished = "places-sync-finished";
+
+const kExpirationFinished = "places-favicons-expired";
+
+var observer = {
+  observe: function(aSubject, aTopic, aData) {
+    if (aTopic == kSyncFinished) {
+      os.removeObserver(this, kSyncFinished);
+
+      // Add a not synced page with a visit.
+      hs.addVisit(uri(TEST_NOSYNC_URI), Date.now() * 1000, null,
+                  hs.TRANSITION_TYPED, false, 0);
+      // Set a favicon for the page.
+      icons.setFaviconUrlForPage(uri(TEST_NOSYNC_URI), uri(TEST_NOSYNC_ICON_URI));
+
+      // Check both a synced and a not-synced favicons exist.
+      let stmt = dbConn.createStatement(
+        "SELECT id FROM moz_places WHERE url = :url AND favicon_id NOT NULL");
+      stmt.params["url"] = TEST_URI;
+      do_check_true(stmt.executeStep());
+
+      stmt.finalize();
+
+      stmt = dbConn.createStatement(
+        "SELECT id FROM moz_places_temp WHERE url = :url AND favicon_id NOT NULL");
+      stmt.params["url"] = TEST_NOSYNC_URI;
+      do_check_true(stmt.executeStep());
+
+      stmt.finalize();
+
+      // Expire all favicons.
+      icons.expireAllFavicons();
+    }
+    else if (aTopic == kExpirationFinished) {
+      os.removeObserver(this, kExpirationFinished);
+      // Check all favicons have been removed.
+      let stmt = dbConn.createStatement(
+        "SELECT id FROM moz_favicons");
+      do_check_false(stmt.executeStep());
+
+      stmt.finalize();
+
+      stmt = dbConn.createStatement(
+        "SELECT id FROM moz_places_view WHERE favicon_id NOT NULL");
+      do_check_false(stmt.executeStep());
+
+      stmt.finalize();
+
+      do_test_finished();
+    }
+  }
+}
+os.addObserver(observer, kSyncFinished, false);
+os.addObserver(observer, kExpirationFinished, false);
+
+function run_test()
+{
+  // First set the preference for the timer to a large value so we don't sync.
+  prefs.setIntPref(kSyncPrefName, SYNC_INTERVAL);
+
+  // Add a page with a visit.
+  let visitId = hs.addVisit(uri(TEST_URI), Date.now() * 1000, null,
+                            hs.TRANSITION_TYPED, false, 0);
+  // Set a favicon for the page.
+  icons.setFaviconUrlForPage(uri(TEST_URI), uri(TEST_ICON_URI));
+
+  // Now add a bookmark to force a sync.
+  bs.insertBookmark(bs.toolbarFolder, uri(TEST_URI), bs.DEFAULT_INDEX, "visited");
+
+  do_test_pending();
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/sync/test_database_sync_onitemadded.js
@@ -0,0 +1,108 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 sts=2 expandtab
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * 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.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Marco Bonardo <mak77@bonardo.net> (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
+ * 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
+ * decision by deleting the provisions above and replace them with the notice
+ * 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 ***** */
+
+ /*
+  * This test checks that when we add an item we sync only if its type is
+  * TYPE_BOOKMARKS
+  */
+
+var bs = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
+         getService(Ci.nsINavBookmarksService);
+var prefs = Cc["@mozilla.org/preferences-service;1"].
+            getService(Ci.nsIPrefService).
+            getBranch("places.");
+var os = Cc["@mozilla.org/observer-service;1"].
+         getService(Ci.nsIObserverService);
+
+const TEST_URI = "http://test.com/";
+
+const kSyncPrefName = "syncDBTableIntervalInSecs";
+const SYNC_INTERVAL = 600;
+const kSyncFinished = "places-sync-finished";
+
+var syncObserver = {
+  _numSyncs: 0,
+  finalSync: false,
+  observe: function(aSubject, aTopic, aData) {
+    if (aTopic == kSyncFinished) {
+      if (++this._numSyncs > 1 || !this.finalSync)
+        do_throw("We synced too many times: " + this._numSyncs);
+
+      // remove the observers
+      os.removeObserver(this, kSyncFinished);
+      bs.removeObserver(bookmarksObserver, false);
+
+      do_test_finished();
+    }
+  }
+}
+os.addObserver(syncObserver, kSyncFinished, false);
+
+var bookmarksObserver = {
+  onItemAdded: function(aItemId, aNewParent, aNewIndex) {
+    if (bs.getItemType(aItemId) == bs.TYPE_BOOKMARK)
+      syncObserver.finalSync = true;
+  }
+}
+bs.addObserver(bookmarksObserver, false);
+
+function run_test()
+{
+  // dynamic container sample
+  do_load_manifest("../unit/nsDynamicContainerServiceSample.manifest");
+
+  // First set the preference for the timer to a large value, so it won't sync
+  prefs.setIntPref(kSyncPrefName, SYNC_INTERVAL);
+
+  // Add a folder
+  bs.createFolder(bs.toolbarFolder, "folder", bs.DEFAULT_INDEX);
+
+  // Add a dynamic container
+  bs.createDynamicContainer(bs.toolbarFolder, "dynamic",
+                                "@mozilla.org/browser/remote-container-sample;1",
+                                bs.DEFAULT_INDEX);
+
+  // Add a separator
+  bs.insertSeparator(bs.toolbarFolder, bs.DEFAULT_INDEX);
+
+  // Add a bookmark, this will trigger final sync
+  bs.insertBookmark(bs.toolbarFolder, uri(TEST_URI), bs.DEFAULT_INDEX, "bookmark");
+
+  do_test_pending();
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/sync/test_database_sync_with_specialHistoryQueries.js
@@ -0,0 +1,116 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 sts=2 expandtab
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * 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.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Marco Bonardo <mak77@bonardo.net> (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
+ * 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
+ * decision by deleting the provisions above and replace them with the notice
+ * 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 ***** */
+
+var hs = Cc["@mozilla.org/browser/nav-history-service;1"].
+         getService(Ci.nsINavHistoryService);
+var bs = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
+         getService(Ci.nsINavBookmarksService);
+var prefs = Cc["@mozilla.org/preferences-service;1"].
+            getService(Ci.nsIPrefService).
+            getBranch("places.");
+var os = Cc["@mozilla.org/observer-service;1"].
+         getService(Ci.nsIObserverService);
+
+const TEST_URI = "http://test.com/";
+
+const kSyncPrefName = "syncDBTableIntervalInSecs";
+const SYNC_INTERVAL = 600;
+const kSyncFinished = "places-sync-finished";
+
+var observer = {
+  observe: function(aSubject, aTopic, aData) {
+    if (aTopic == kSyncFinished) {
+      // Set the preference for the timer to a large value so we don't sync.
+      prefs.setIntPref(kSyncPrefName, SYNC_INTERVAL);
+
+      // Now add another visit, be sure to use a different session, so we
+      // will also test grouping by uri.
+      hs.addVisit(uri(TEST_URI), Date.now() * 1000, null,
+                  hs.TRANSITION_TYPED, false, 1);
+  
+      // Create the history menu query.
+      var options = hs.getNewQueryOptions();
+      options.maxResults = 10;
+      options.resultType = options.RESULTS_AS_URI;
+      options.sortingMode = options.SORT_BY_DATE_DESCENDING;
+      var query = hs.getNewQuery();
+      var result = hs.executeQuery(query, options);
+      var root = result.root;
+      root.containerOpen = true;
+      do_check_eq(root.childCount, 1);
+      root.containerOpen = false;
+
+      // Create the most visited query.
+      options = hs.getNewQueryOptions();
+      options.maxResults = 10;
+      options.resultType = options.RESULTS_AS_URI;
+      options.sortingMode = options.SORT_BY_VISITCOUNT_DESCENDING;
+      query = hs.getNewQuery();
+      result = hs.executeQuery(query, options);
+      root = result.root;
+      root.containerOpen = true;
+      do_check_eq(root.childCount, 1);
+      root.containerOpen = false;
+
+      // Create basic uri query.
+      options = hs.getNewQueryOptions();
+      query = hs.getNewQuery();
+      result = hs.executeQuery(query, options);
+      root = result.root;
+      root.containerOpen = true;
+      do_check_eq(root.childCount, 1);
+      root.containerOpen = false;
+
+      os.removeObserver(this, kSyncFinished);
+      do_test_finished();
+    }
+  }
+}
+os.addObserver(observer, kSyncFinished, false);
+
+function run_test()
+{
+  // First set the preference for the timer to a small value so we sync
+  prefs.setIntPref(kSyncPrefName, 1);
+
+  // Now add the visit
+  let visitId = hs.addVisit(uri(TEST_URI), Date.now() * 1000, null,
+                            hs.TRANSITION_TYPED, false, 0);
+  do_test_pending();
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/sync/test_multiple_bookmarks_around_sync.js
@@ -0,0 +1,150 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 sts=2 expandtab
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * 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.
+ * 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)
+ *   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
+ * decision by deleting the provisions above and replace them with the notice
+ * 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 ***** */
+
+/**
+ * This test ensures that adding a bookmark (which has an implicit sync), then
+ * adding another one that has the same place, we end up with only one entry in
+ * moz_places.
+ */
+
+var bs = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
+         getService(Ci.nsINavBookmarksService);
+var hs = Cc["@mozilla.org/browser/nav-history-service;1"].
+         getService(Ci.nsINavHistoryService);
+var db = Cc["@mozilla.org/browser/nav-history-service;1"].
+         getService(Ci.nsPIPlacesDatabase).
+         DBConnection;
+var os = Cc["@mozilla.org/observer-service;1"].
+         getService(Ci.nsIObserverService);
+var prefs = Cc["@mozilla.org/preferences-service;1"].
+            getService(Ci.nsIPrefService).
+            getBranch("places.");
+
+const TEST_URI = "http://test.com/";
+
+const SYNC_INTERVAL = 600; // ten minutes
+const kSyncPrefName = "syncDBTableIntervalInSecs";
+const kSyncFinished = "places-sync-finished";
+
+// Used to update observer itemId
+var bookmarksObserver = {
+  onItemAdded: function(aItemId, aNewParent, aNewIndex) {
+    observer.itemIds.push(aItemId);
+  }
+}
+bs.addObserver(bookmarksObserver, false);
+
+var observer = {
+  itemIds: [],
+  _placeId: -1,
+  _runCount: 0,
+  observe: function(aSubject, aTopic, aData) {
+    if (aTopic == kSyncFinished) {
+      if (++this._runCount == 1) {
+        let itemId = this.itemIds[this._runCount - 1];
+        // item id must be valid
+        do_check_neq(itemId, null);
+        // Ensure tables have been synced
+        new_test_bookmark_uri_event(itemId, TEST_URI, true);
+
+        // Get the place_id
+        let stmt = db.createStatement(
+          "SELECT fk " +
+          "FROM moz_bookmarks " +
+          "WHERE id = ?"
+        );
+        stmt.bindInt64Parameter(0, itemId);
+        do_check_true(stmt.executeStep());
+        this._placeId = stmt.getInt64(0);
+        stmt.finalize();
+        stmt = null;
+        // place id must be valid
+        do_check_true(this._placeId > 0);
+      }
+      else if (this._runCount == 2) {
+        let itemId = this.itemIds[this._runCount - 1];
+        // item id must be valid
+        do_check_neq(itemId, null);
+        // Ensure it was added
+        new_test_bookmark_uri_event(itemId, TEST_URI, true);
+
+        // Check to make sure we have the same place_id
+        stmt = db.createStatement(
+          "SELECT * " +
+          "FROM moz_bookmarks " +
+          "WHERE id = ?1 " +
+          "AND fk = ?2"
+        );
+        stmt.bindInt64Parameter(0, itemId);
+        stmt.bindInt64Parameter(1, this._placeId);
+        do_check_true(stmt.executeStep());
+        stmt.finalize();
+        stmt = null;
+
+        // remove the observer, we don't need to observe sync on quit
+        os.removeObserver(this, kSyncFinished);
+        bs.removeObserver(bookmarksObserver);
+        // test ends here
+        do_test_finished();
+      }
+      else
+        do_throw("Too many places sync calls");
+    }
+  }
+}
+os.addObserver(observer, kSyncFinished, false);
+
+function run_test()
+{
+  // Set the preference for the timer to a really large value, so it won't
+  // run before the test finishes.
+  prefs.setIntPref(kSyncPrefName, SYNC_INTERVAL);
+
+  // Add the first bookmark
+  let id1 = bs.insertBookmark(bs.unfiledBookmarksFolder, uri(TEST_URI),
+                              bs.DEFAULT_INDEX, "test");
+
+  // Now we add another bookmark to a different folder
+  let id2 = bs.insertBookmark(bs.toolbarFolder, uri(TEST_URI),
+                              bs.DEFAULT_INDEX, "test");
+  do_check_neq(id1, id2);
+
+  do_test_pending();
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/sync/test_multiple_visits_around_sync.js
@@ -0,0 +1,152 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 sts=2 expandtab
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * 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.
+ * 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)
+ *  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
+ * decision by deleting the provisions above and replace them with the notice
+ * 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 ***** */
+
+/**
+ * This test ensures that when adding a visit, then syncing, and adding another
+ * visit to the same url creates two visits and that we only end up with one
+ * entry in moz_places.
+ */
+
+var hs = Cc["@mozilla.org/browser/nav-history-service;1"].
+         getService(Ci.nsINavHistoryService);
+var db = Cc["@mozilla.org/browser/nav-history-service;1"].
+         getService(Ci.nsPIPlacesDatabase).
+         DBConnection;
+var prefs = Cc["@mozilla.org/preferences-service;1"].
+            getService(Ci.nsIPrefService).
+            getBranch("places.");
+var os = Cc["@mozilla.org/observer-service;1"].
+         getService(Ci.nsIObserverService);
+
+const TEST_URI = "http://test.com/";
+
+const kSyncPrefName = "syncDBTableIntervalInSecs";
+const SYNC_INTERVAL = 1;
+const kSyncFinished = "places-sync-finished";
+
+var observer = {
+  visitId: -1,
+  _runCount: 0,
+  _placeId: -1,
+  _lastVisitId: -1,
+  observe: function(aSubject, aTopic, aData) {
+    if (aTopic == kSyncFinished && this.visitId != -1) {
+      // Check the visit
+      new_test_visit_uri_event(this.visitId, TEST_URI, true);
+
+      // sanity check: visitId set by history observer should be the same we
+      // have added
+      do_check_eq(this.visitId, visitIds[this._runCount]);
+
+      if (++this._runCount == 1) {
+        // Get the place_id and pass it on
+        let stmt = db.createStatement(
+          "SELECT place_id " +
+          "FROM moz_historyvisits " +
+          "WHERE id = ?1"
+        );
+        stmt.bindInt64Parameter(0, this.visitId);
+        do_check_true(stmt.executeStep());
+        this._placeId = stmt.getInt64(0);
+        this._lastVisitId = this.visitId;
+        stmt.finalize();
+        stmt = null;
+
+        // clear cached value before continue test
+        this.visitId = -1;
+        continue_test();
+      }
+      else if (this._runCount == 2) {
+        do_check_neq(this.visitId, this._lastVisitId);
+        // Get the place_id and check for equality
+        let stmt = db.createStatement(
+          "SELECT place_id " +
+          "FROM moz_historyvisits " +
+          "WHERE id = ?1"
+        );
+        stmt.bindInt64Parameter(0, this.visitId);
+        do_check_true(stmt.executeStep());
+        do_check_eq(this._placeId, stmt.getInt64(0));
+        stmt.finalize();
+        stmt = null;
+
+        // remove the observers
+        os.removeObserver(this, kSyncFinished);
+        hs.removeObserver(historyObserver, false);
+        do_test_finished();
+      }
+      else
+        do_throw("bad runCount!");
+    }
+  }
+}
+os.addObserver(observer, kSyncFinished, false);
+
+// Used to update observer visitId
+var historyObserver = {
+  onVisit: function(aURI, aVisitId, aTime, aSessionId, aReferringId,
+                    aTransitionType, aAdded) {
+    do_check_true(aVisitId > 0);
+    observer.visitId = aVisitId;
+  }
+}
+hs.addObserver(historyObserver, false);
+
+// used for sanity checks
+var visitIds = [];
+
+function run_test()
+{
+  // First set the preference for the timer to a small value
+  prefs.setIntPref(kSyncPrefName, SYNC_INTERVAL);
+
+  // Now add the first visit
+  visitIds[0] = hs.addVisit(uri(TEST_URI), Date.now() * 1000, null,
+                            hs.TRANSITION_TYPED, false, 0);
+
+  do_test_pending();
+}
+
+function continue_test()
+{
+  // Now we add another visit
+  visitIds[1] = hs.addVisit(uri(TEST_URI), Date.now() * 1000, null,
+                            hs.TRANSITION_TYPED, false, 0);
+}
--- a/toolkit/components/places/tests/unit/test_248970.js
+++ b/toolkit/components/places/tests/unit/test_248970.js
@@ -40,16 +40,18 @@
 // https://wiki.mozilla.org/Firefox3.1/PrivateBrowsing/TestPlan#History
 // http://developer.mozilla.org/en/Using_the_Places_history_service
 
 var histsvc = Cc["@mozilla.org/browser/nav-history-service;1"].
               getService(Ci.nsINavHistoryService);
 var bhist = histsvc.QueryInterface(Ci.nsIBrowserHistory);
 var bmsvc = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
             getService(Ci.nsINavBookmarksService);
+var ios = Cc["@mozilla.org/network/io-service;1"].
+          getService(Components.interfaces.nsIIOService);
 
 /**
  * Function prohibits an attempt to pop up a confirmation
  * dialog box when entering Private Browsing mode.
  *
  * @returns a reference to the Private Browsing service
  */
 var _PBSvc = null;
@@ -242,23 +244,32 @@ function is_bookmark_A_altered(){
   return (node.accessCount!=0);
 }
 
 function run_test() {
   // Fetch the private browsing service
   var pb = get_PBSvc();
 
   if (pb) { // Private Browsing might not be available
+    // need to catch places sync notifications
+    var os = Cc["@mozilla.org/observer-service;1"].
+             getService(Ci.nsIObserverService);
+    const kSyncFinished = "places-sync-finished";
     do_test_pending();
 
-    Services.prefs.setBoolPref("browser.privatebrowsing.keep_current_session", true);
+    var prefBranch = Cc["@mozilla.org/preferences-service;1"].
+                     getService(Ci.nsIPrefBranch);
+    prefBranch.setBoolPref("browser.privatebrowsing.keep_current_session", true);
 
-    var bookmark_A_URI = NetUtil.newURI("http://google.com/");
-    var bookmark_B_URI = NetUtil.newURI("http://bugzilla.mozilla.org/");
-    var onBookmarkAAdded = function() {
+    var bookmark_A_URI = ios.newURI("http://google.com/", null, null);
+    var bookmark_B_URI = ios.newURI("http://bugzilla.mozilla.org/", null, null);
+    var onBookmarkAAdded = {
+      observe: function (aSubject, aTopic, aData) {
+        os.removeObserver(this, kSyncFinished);
+
         check_placesItem_Count();
 
         // Bookmark-A should be bookmarked, data should be retrievable
         do_check_true(bmsvc.isBookmarked(bookmark_A_URI));
         do_check_eq("google", bmsvc.getKeywordForURI(bookmark_A_URI));
 
         // Enter Private Browsing Mode
         pb.privateBrowsingEnabled = true;
@@ -282,21 +293,26 @@ function run_test() {
         // and 1 history entry, Bookmark-A.
         // Private browsing blocked the entry of the new history entries
         check_placesItem_Count();
 
         // Check if Bookmark-A is still accessible
         do_check_true(bmsvc.isBookmarked(bookmark_A_URI));
         do_check_eq("google",bmsvc.getKeywordForURI(bookmark_A_URI));
 
+        os.addObserver(onBookmarkBAdded, kSyncFinished, false);
+
         // Create Bookmark-B
         myBookmarks[1] = create_bookmark(bookmark_B_URI,"title 2", "bugzilla");
-        onBookmarkBAdded();
+      }
     };
-    var onBookmarkBAdded = function() {
+    var onBookmarkBAdded = {
+      observe: function (aSubject, aTopic, aData) {
+        os.removeObserver(this, kSyncFinished);
+
         // A check on the history count should be same as before, 7 history entries with
         // now 2 bookmark items (A) and bookmark (B), so we set num_places_entries to 9
         num_places_entries = 10; // Bookmark-B successfully added but not the history entries.
         check_placesItem_Count();
 
         // Exit Private Browsing Mode
         pb.privateBrowsingEnabled = false;
 
@@ -309,33 +325,34 @@ function run_test() {
         do_check_eq("google",bmsvc.getKeywordForURI(bookmark_A_URI));
 
         // Check that the original set of history items are still accessible via isVisited
         for each(var visited_uri in visited_URIs) {
           do_check_true(uri_in_db(uri(visited_uri)));
           do_check_true(bhist.isVisited(uri(visited_uri)));
         }
 
-        Services.prefs.clearUserPref("browser.privatebrowsing.keep_current_session");
+        prefBranch.clearUserPref("browser.privatebrowsing.keep_current_session");
         do_test_finished();
+      }
     };
 
     // History database should be empty
     do_check_false(histsvc.hasHistoryEntries);
 
     // Create a handful of history items with various visit types
     fill_history_visitedURI();
 
     // History database should have entries
     do_check_true(histsvc.hasHistoryEntries);
 
+    os.addObserver(onBookmarkAAdded, kSyncFinished, false);
+
     // Create Bookmark-A
     myBookmarks[0] = create_bookmark(bookmark_A_URI,"title 1", "google");
 
     // History items should be retrievable by query
     for each(var visited_uri in visited_URIs) {
       do_check_true(bhist.isVisited(uri(visited_uri)));
       do_check_true(uri_in_db(uri(visited_uri)));
     }
-
-    onBookmarkAAdded();
   }
 }
--- a/toolkit/components/places/tests/unit/test_412132.js
+++ b/toolkit/components/places/tests/unit/test_412132.js
@@ -287,32 +287,32 @@ function createLivemark(lmItemURI)
 }
 
 function getFrecency(url)
 {
   var sql;
   var stmt;
   var frecency;
 
-  sql = "SELECT frecency FROM moz_places WHERE url = ?1";
+  sql = "SELECT frecency FROM moz_places_view WHERE url = ?1";
   stmt = dbConn.createStatement(sql);
   stmt.bindUTF8StringParameter(0, url);
   do_check_true(stmt.executeStep());
   frecency = stmt.getInt32(0);
   print("frecency=" + frecency);
   stmt.finalize();
 
   return frecency;
 }
 
 function prepTest(testName, callback)
 {
   print("Test: " + testName);
   waitForClearHistory(function() {
-    dbConn.executeSimpleSQL("DELETE FROM moz_places");
+    dbConn.executeSimpleSQL("DELETE FROM moz_places_view");
     dbConn.executeSimpleSQL("DELETE FROM moz_bookmarks WHERE id > " +
                             defaultBookmarksMaxId);
     callback();
     runNextTest();
   });
 }
 
 function visit(uri)
--- a/toolkit/components/places/tests/unit/test_454977.js
+++ b/toolkit/components/places/tests/unit/test_454977.js
@@ -52,17 +52,17 @@ function add_visit(aURI, aVisitDate, aVi
   do_check_true(visitId > 0);
   // Increase visit_count if applicable
   if (aVisitType != 0 &&
       aVisitType != hs.TRANSITION_EMBED &&
       aVisitType != hs.TRANSITION_FRAMED_LINK &&
       aVisitType != hs.TRANSITION_DOWNLOAD)
     visit_count ++;
   // Get the place id
-  var sql = "SELECT place_id FROM moz_historyvisits WHERE id = ?1";
+  var sql = "SELECT place_id FROM moz_historyvisits_view WHERE id = ?1";
   var stmt = mDBConn.createStatement(sql);
   stmt.bindInt64Parameter(0, visitId);
   do_check_true(stmt.executeStep());
   var placeId = stmt.getInt64(0);
   stmt.finalize();
   do_check_true(placeId > 0);
   return placeId;
 }
--- a/toolkit/components/places/tests/unit/test_doSetAndLoadFaviconForPage_failures.js
+++ b/toolkit/components/places/tests/unit/test_doSetAndLoadFaviconForPage_failures.js
@@ -133,18 +133,18 @@ let historyObserver = {
   onClearHistory: function() {},
   onDeleteVisits: function() {},
 
   onPageChanged: function historyObserver_onPageChanged(pageURI, what, value) {
     if (what != Ci.nsINavHistoryObserver.ATTRIBUTE_FAVICON)
       return;
 
     // dump tables, useful if the test fails.
-    //dump_table("moz_places");
-    //dump_table("moz_favicons");
+    dump_table("moz_places_temp");
+    dump_table("moz_favicons");
 
     // Ensure we have been called by the last test.
     do_check_true(pageURI.equals(uri("http://test4.bar/")));
 
     // Ensure there is only one entry in favicons table.
     let stmt = DBConn().createStatement(
       "SELECT url FROM moz_favicons"
     );
--- a/toolkit/components/places/tests/unit/test_history_removeAllPages.js
+++ b/toolkit/components/places/tests/unit/test_history_removeAllPages.js
@@ -46,18 +46,22 @@ let bs = Cc["@mozilla.org/browser/nav-bo
          getService(Ci.nsINavBookmarksService);
 let as = Cc["@mozilla.org/browser/annotation-service;1"].
          getService(Ci.nsIAnnotationService);
 let os = Cc["@mozilla.org/observer-service;1"].
          getService(Ci.nsIObserverService);
 let lms = Cc["@mozilla.org/browser/livemark-service;2"].
           getService(Ci.nsILivemarkService);
 
+const kSyncFinished = "places-sync-finished";
 const kExpirationFinished = "places-expiration-finished";
 
+// Number of expected sync notifications, we expect one per bookmark.
+const EXPECTED_SYNCS = 4;
+
 function add_fake_livemark() {
   let lmId = lms.createLivemarkFolderOnly(bs.toolbarFolder,
                                           "Livemark",
                                           uri("http://www.mozilla.org/"),
                                           uri("http://www.mozilla.org/test.xml"),
                                           bs.DEFAULT_INDEX);
   // add a visited child
   bs.insertBookmark(lmId, uri("http://visited.livemark.com/"),
@@ -82,110 +86,161 @@ let observer = {
   },
   onDeleteURI: function(aURI) {
   },
 
   onClearHistory: function() {
     // check browserHistory returns no entries
     do_check_eq(0, bh.count);
 
-    // Check that frecency for not cleared items (bookmarks) has been converted
-    // to -MAX(visit_count, 1), so we will be able to recalculate frecency
-    // starting from most frecent bookmarks. 
-    stmt = mDBConn.createStatement(
-      "SELECT h.id FROM moz_places h WHERE h.frecency > 0 " +
-        "AND EXISTS (SELECT id FROM moz_bookmarks WHERE fk = h.id) LIMIT 1");
-    do_check_false(stmt.executeStep());
-    stmt.finalize();
-
-    stmt = mDBConn.createStatement(
-      "SELECT h.id FROM moz_places h WHERE h.frecency = -2 " +
-        "AND EXISTS (SELECT id FROM moz_bookmarks WHERE fk = h.id) LIMIT 1");
-    do_check_true(stmt.executeStep());
-    stmt.finalize();
-
     let expirationObserver = {
       observe: function (aSubject, aTopic, aData) {
         os.removeObserver(this, kExpirationFinished, false);
+ 
+        // Check that frecency for not cleared items (bookmarks) has been converted
+        // to -MAX(visit_count, 1), so we will be able to recalculate frecency
+        // starting from most frecent bookmarks. 
+        // Memory table has been updated, disk table has not
+        stmt = mDBConn.createStatement(
+          "SELECT id FROM moz_places_temp WHERE frecency > 0 LIMIT 1");
+        do_check_false(stmt.executeStep());
+        stmt.finalize();
+
+        stmt = mDBConn.createStatement(
+          "SELECT h.id FROM moz_places_temp h WHERE h.frecency = -2 " +
+            "AND EXISTS (SELECT id FROM moz_bookmarks WHERE fk = h.id) LIMIT 1");
+        do_check_true(stmt.executeStep());
+        stmt.finalize();
 
         // Check that all visit_counts have been brought to 0
         stmt = mDBConn.createStatement(
-          "SELECT id FROM moz_places WHERE visit_count <> 0 LIMIT 1");
+          "SELECT id FROM moz_places_temp WHERE visit_count <> 0 LIMIT 1");
         do_check_false(stmt.executeStep());
         stmt.finalize();
 
         // Check that history tables are empty
         stmt = mDBConn.createStatement(
+          "SELECT * FROM (SELECT id FROM moz_historyvisits_temp LIMIT 1) " +
+          "UNION ALL " +
           "SELECT * FROM (SELECT id FROM moz_historyvisits LIMIT 1)");
         do_check_false(stmt.executeStep());
         stmt.finalize();
 
-        // Check that all moz_places entries except bookmarks and place: have been removed
-        stmt = mDBConn.createStatement(
-          "SELECT h.id FROM moz_places h WHERE SUBSTR(h.url, 1, 6) <> 'place:' "+
-            "AND NOT EXISTS (SELECT id FROM moz_bookmarks WHERE fk = h.id) LIMIT 1");
-        do_check_false(stmt.executeStep());
-        stmt.finalize();
-
-        // Check that we only have favicons for retained places
-        stmt = mDBConn.createStatement(
-          "SELECT f.id FROM moz_favicons f WHERE NOT EXISTS " +
-            "(SELECT id FROM moz_places WHERE favicon_id = f.id) LIMIT 1");
-        do_check_false(stmt.executeStep());
-        stmt.finalize();
-
-        // Check that we only have annotations for retained places
-        stmt = mDBConn.createStatement(
-          "SELECT a.id FROM moz_annos a WHERE NOT EXISTS " +
-            "(SELECT id FROM moz_places WHERE id = a.place_id) LIMIT 1");
-        do_check_false(stmt.executeStep());
-        stmt.finalize();
-
-        // Check that we only have inputhistory for retained places
-        stmt = mDBConn.createStatement(
-          "SELECT i.place_id FROM moz_inputhistory i WHERE NOT EXISTS " +
-            "(SELECT id FROM moz_places WHERE id = i.place_id) LIMIT 1");
-        do_check_false(stmt.executeStep());
-        stmt.finalize();
-
-        // Check that place:uris have frecency 0
-        stmt = mDBConn.createStatement(
-          "SELECT h.id FROM moz_places h " +
-          "WHERE SUBSTR(h.url, 1, 6) = 'place:' AND h.frecency <> 0 LIMIT 1");
-        do_check_false(stmt.executeStep());
-        stmt.finalize();
-
-        // Check that livemarks children don't have frecency <> 0
-        stmt = mDBConn.createStatement(
-          "SELECT h.id FROM moz_places h " +
-          "JOIN moz_bookmarks b ON h.id = b.fk " +
-          "JOIN moz_bookmarks bp ON bp.id = b.parent " +
-          "JOIN moz_items_annos t ON t.item_id = bp.id " +
-          "JOIN moz_anno_attributes n ON t.anno_attribute_id = n.id " +
-          "WHERE n.name = 'livemark/feedURI' AND h.frecency <> 0 LIMIT 1");
-        do_check_false(stmt.executeStep());
-        stmt.finalize();
-
-        do_test_finished();
+        // force a sync and check again disk tables, insertBookmark will do that
+        bs.insertBookmark(bs.unfiledBookmarksFolder, uri("place:folder=4"),
+                          bs.DEFAULT_INDEX, "shortcut");
       }
     }
     os.addObserver(expirationObserver, kExpirationFinished, false);
   },
 
   onPageChanged: function(aURI, aWhat, aValue) {
   },
   onDeleteVisits: function() {
   },
 
-  QueryInterface: XPCOMUtils.generateQI([
-    Ci.nsINavHistoryObserver
-  ]);
+  QueryInterface: function(iid) {
+    if (iid.equals(Ci.nsINavHistoryObserver) ||
+        iid.equals(Ci.nsISupports)) {
+      return this;
+    }
+    throw Cr.NS_ERROR_NO_INTERFACE;
+  }
 }
 hs.addObserver(observer, false);
 
+let syncObserver = {
+  _runCount: 0,
+  observe: function (aSubject, aTopic, aData) {
+    if (++this._runCount < EXPECTED_SYNCS)
+      return;
+    if (this._runCount == EXPECTED_SYNCS) {
+      bh.removeAllPages();
+      return;
+    }
+    os.removeObserver(this, kSyncFinished, false);
+
+    // Sanity: check that places temp table is empty
+    stmt = mDBConn.createStatement(
+      "SELECT id FROM moz_places_temp LIMIT 1");
+    do_check_false(stmt.executeStep());
+    stmt.finalize();
+
+    // Check that frecency for not cleared items (bookmarks) has been converted
+    // to -MAX(visit_count, 1), so we will be able to recalculate frecency
+    // starting from most frecent bookmarks.
+    stmt = mDBConn.createStatement(
+      "SELECT id FROM moz_places WHERE frecency > 0 LIMIT 1");
+    do_check_false(stmt.executeStep());
+    stmt.finalize();
+
+    stmt = mDBConn.createStatement(
+      "SELECT h.id FROM moz_places h WHERE h.frecency = -2 " +
+        "AND EXISTS (SELECT id FROM moz_bookmarks WHERE fk = h.id) LIMIT 1");
+    do_check_true(stmt.executeStep());
+    stmt.finalize();
+
+    // Check that all visit_counts have been brought to 0
+    stmt = mDBConn.createStatement(
+      "SELECT id FROM moz_places WHERE visit_count <> 0 LIMIT 1");
+    do_check_false(stmt.executeStep());
+    stmt.finalize();
+
+    // Check that all moz_places entries except bookmarks and place: have been removed
+    stmt = mDBConn.createStatement(
+      "SELECT h.id FROM moz_places h WHERE SUBSTR(h.url, 1, 6) <> 'place:' "+
+        "AND NOT EXISTS (SELECT id FROM moz_bookmarks WHERE fk = h.id) LIMIT 1");
+    do_check_false(stmt.executeStep());
+    stmt.finalize();
+
+    // Check that we only have favicons for retained places
+    stmt = mDBConn.createStatement(
+      "SELECT f.id FROM moz_favicons f WHERE NOT EXISTS " +
+        "(SELECT id FROM moz_places WHERE favicon_id = f.id) LIMIT 1");
+    do_check_false(stmt.executeStep());
+    stmt.finalize();
+
+    // Check that we only have annotations for retained places
+    stmt = mDBConn.createStatement(
+      "SELECT a.id FROM moz_annos a WHERE NOT EXISTS " +
+        "(SELECT id FROM moz_places WHERE id = a.place_id) LIMIT 1");
+    do_check_false(stmt.executeStep());
+    stmt.finalize();
+
+    // Check that we only have inputhistory for retained places
+    stmt = mDBConn.createStatement(
+      "SELECT i.place_id FROM moz_inputhistory i WHERE NOT EXISTS " +
+        "(SELECT id FROM moz_places WHERE id = i.place_id) LIMIT 1");
+    do_check_false(stmt.executeStep());
+    stmt.finalize();
+
+    // Check that place:uris have frecency 0
+    stmt = mDBConn.createStatement(
+      "SELECT h.id FROM moz_places h " +
+      "WHERE SUBSTR(h.url, 1, 6) = 'place:' AND h.frecency <> 0 LIMIT 1");
+    do_check_false(stmt.executeStep());
+    stmt.finalize();
+
+    // Check that livemarks children don't have frecency <> 0
+    stmt = mDBConn.createStatement(
+      "SELECT h.id FROM moz_places h " +
+      "JOIN moz_bookmarks b ON h.id = b.fk " +
+      "JOIN moz_bookmarks bp ON bp.id = b.parent " +
+      "JOIN moz_items_annos t ON t.item_id = bp.id " +
+      "JOIN moz_anno_attributes n ON t.anno_attribute_id = n.id " +
+      "WHERE n.name = 'livemark/feedURI' AND h.frecency <> 0 LIMIT 1");
+    do_check_false(stmt.executeStep());
+    stmt.finalize();
+
+    do_test_finished();
+  }
+}
+os.addObserver(syncObserver, kSyncFinished, false);
+
+// main
 function run_test() {
   // Add a livemark with a visited and an unvisited child
   add_fake_livemark();
 
   // Add a bunch of visits
   hs.addVisit(uri("http://typed.mozilla.org"), Date.now(), null,
               hs.TRANSITION_TYPED, false, 0);
 
@@ -209,28 +264,18 @@ function run_test() {
   // Add an expire never annotation
   // Actually expire never annotations are removed as soon as a page is removed
   // from the database, so this should act as a normal visit.
   as.setPageAnnotation(uri("http://download.mozilla.org"), "never", "never", 0,
                        as.EXPIRE_NEVER);
 
   // Add a bookmark
   // Bookmarked page should have history cleared and frecency = -old_visit_count
+  // This will also finally sync temp tables to disk
   bs.insertBookmark(bs.unfiledBookmarksFolder, uri("http://typed.mozilla.org"),
                     bs.DEFAULT_INDEX, "bookmark");
 
+  // this visit is not synced to disk
   hs.addVisit(uri("http://typed.mozilla.org"), Date.now(), null,
               hs.TRANSITION_BOOKMARK, false, 0);
 
-  // Since we are checking frecency, we have to disable some stuff that could
-  // change values during test run.  On idle-daily frecencies are updated.
-  Services.obs.removeObserver(hs, "idle-daily");
-
-  // executeSoon allows us to fire the places-init-complete notification before
-  // the removeAllPages call.  Since we are creating a new database, on that
-  // notification we will act on initial frecency, and that could disturb
-  // this test's frecency checks.
-  do_execute_soon(function () {
-    bh.removeAllPages();
-  });
-
   do_test_pending();
 }
--- a/toolkit/components/places/tests/unit/test_migrateFrecency.js
+++ b/toolkit/components/places/tests/unit/test_migrateFrecency.js
@@ -67,17 +67,17 @@ function run_test() {
       switch (topic) {
         case NS_PLACES_INIT_COMPLETE_TOPIC:
           _("Clean up after ourselves: remove observer and mork file");
           os.removeObserver(observer, NS_PLACES_INIT_COMPLETE_TOPIC);
           deleteMork();
 
           _("Now that places has migrated, check that it calculated frecencies");
           var stmt = places.DBConnection.createStatement(
-              "SELECT COUNT(*) FROM moz_places WHERE frecency < 0");
+              "SELECT COUNT(*) FROM moz_places_view WHERE frecency < 0");
           stmt.executeAsync({
               handleResult: function(results) {
                 _("Should always get a result from COUNT(*)");
                 let row = results.getNextRow();
                 do_check_true(!!row);
 
                 _("We should have no negative frecencies after migrate");
                 do_check_eq(row.getResultByIndex(0), 0);
--- a/toolkit/components/places/tests/unit/test_removeVisitsByTimeframe.js
+++ b/toolkit/components/places/tests/unit/test_removeVisitsByTimeframe.js
@@ -385,34 +385,34 @@ function deleteAllHistoryAndBookmarks() 
 /**
  * Returns the frecency of a URI.
  *
  * @param  aURI
  *         the URI of a place
  * @return the frecency of aURI
  */
 function getFrecencyForURI(aURI) {
-  let sql = "SELECT frecency FROM moz_places WHERE url = :url";
+  let sql = "SELECT frecency FROM moz_places_view WHERE url = :url";
   let stmt = dbConn.createStatement(sql);
   stmt.params.url = aURI.spec;
   do_check_true(stmt.executeStep());
   let frecency = stmt.getInt32(0);
   stmt.finalize();
 
   return frecency;
 }
 
 /**
  * Returns true if the URI exists in moz_places and false otherwise.
  *
  * @param  aURI
  *         the URI of a place
  */
 function uriExistsInMozPlaces(aURI) {
-  let sql = "SELECT id FROM moz_places WHERE url = :url";
+  let sql = "SELECT id FROM moz_places_view WHERE url = :url";
   let stmt = dbConn.createStatement(sql);
   stmt.params.url = aURI.spec;
   var exists = stmt.executeStep();
   stmt.finalize();
 
   return exists;
 }
 
--- a/toolkit/components/places/tests/unit/test_update_frecency_after_delete.js
+++ b/toolkit/components/places/tests/unit/test_update_frecency_after_delete.js
@@ -340,17 +340,17 @@ function createLivemark(aLmChildItemURI)
 /**
  * Returns the frecency of a Place.
  *
  * @param  aURL
  *         the URL of a Place
  * @return the frecency of aURL
  */
 function getFrecency(aURL) {
-  let sql = "SELECT frecency FROM moz_places WHERE url = :url";
+  let sql = "SELECT frecency FROM moz_places_view WHERE url = :url";
   let stmt = dbConn.createStatement(sql);
   stmt.params.url = aURL;
   do_check_true(stmt.executeStep());
   let frecency = stmt.getInt32(0);
   print("frecency=" + frecency);
   stmt.finalize();
 
   return frecency;