Bug 699751 - Change schema for new inline autocomplete.
authorMichael Ventnor<ventnor.bugzilla@gmail.com>
Thu, 19 Jan 2012 12:31:23 +0100
changeset 84887 3898e1c52fa332cfb80c449bb4e2be48229da8d1
parent 84886 f23efef7f6160a1620ee5d3af2bc2596f31079a7
child 84888 953bde82b7a7d2ea4408b1792791612ac8434214
push id21881
push usermbrubeck@mozilla.com
push dateThu, 19 Jan 2012 18:41:36 +0000
treeherdermozilla-central@e5e66f40c35b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs699751
milestone12.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 699751 - Change schema for new inline autocomplete. Original patch by Michael Ventnor, further improved by David Dahl <ddahl@mozilla.com>. r=mak
toolkit/components/places/Database.cpp
toolkit/components/places/Database.h
toolkit/components/places/SQLFunctions.cpp
toolkit/components/places/SQLFunctions.h
toolkit/components/places/nsPlacesIndexes.h
toolkit/components/places/nsPlacesTables.h
toolkit/components/places/nsPlacesTriggers.h
toolkit/components/places/tests/head_common.js
toolkit/components/places/tests/migration/test_current_from_v10.js
toolkit/components/places/tests/unit/test_hosts_triggers.js
toolkit/components/places/tests/unit/xpcshell.ini
--- a/toolkit/components/places/Database.cpp
+++ b/toolkit/components/places/Database.cpp
@@ -732,16 +732,23 @@ Database::InitSchema(bool* aDatabaseMigr
 
       if (currentSchemaVersion < 16) {
         rv = MigrateV16Up();
         NS_ENSURE_SUCCESS(rv, rv);
       }
 
       // Firefox 11 uses schema version 16.
 
+      if (currentSchemaVersion < 17) {
+        rv = MigrateV17Up();
+        NS_ENSURE_SUCCESS(rv, rv);
+      }
+
+      // Firefox 12 uses schema version 17.
+
       // Schema Upgrades must add migration code here.
 
       rv = UpdateBookmarkRootTitles();
       // We don't want a broken localization to cause us to think
       // the database is corrupt and needs to be replaced.
       MOZ_ASSERT(NS_SUCCEEDED(rv));
     }
   }
@@ -775,16 +782,22 @@ Database::InitSchema(bool* aDatabaseMigr
     NS_ENSURE_SUCCESS(rv, rv);
     rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_HISTORYVISITS_VISITDATE);
     NS_ENSURE_SUCCESS(rv, rv);
 
     // moz_inputhistory.
     rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_INPUTHISTORY);
     NS_ENSURE_SUCCESS(rv, rv);
 
+    // moz_hosts.
+    rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_HOSTS);
+    NS_ENSURE_SUCCESS(rv, rv);
+    rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_HOSTS_FRECENCYHOST);
+    NS_ENSURE_SUCCESS(rv, rv);
+
     // moz_bookmarks.
     rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_BOOKMARKS);
     NS_ENSURE_SUCCESS(rv, rv);
     rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_PLACETYPE);
     NS_ENSURE_SUCCESS(rv, rv);
     rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_PARENTPOSITION);
     NS_ENSURE_SUCCESS(rv, rv);
     rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_PLACELASTMODIFIED);
@@ -922,30 +935,40 @@ Database::InitFunctions()
   nsresult rv = GetUnreversedHostFunction::create(mMainConn);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = MatchAutoCompleteFunction::create(mMainConn);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = CalculateFrecencyFunction::create(mMainConn);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = GenerateGUIDFunction::create(mMainConn);
   NS_ENSURE_SUCCESS(rv, rv);
+  rv = FixupURLFunction::create(mMainConn);
+  NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 nsresult
 Database::InitTempTriggers()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   nsresult rv = mMainConn->ExecuteSimpleSQL(CREATE_HISTORYVISITS_AFTERINSERT_TRIGGER);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = mMainConn->ExecuteSimpleSQL(CREATE_HISTORYVISITS_AFTERDELETE_TRIGGER);
   NS_ENSURE_SUCCESS(rv, rv);
 
+  // Add the triggers that update the moz_hosts table as necessary.
+  rv = mMainConn->ExecuteSimpleSQL(CREATE_PLACES_AFTERINSERT_TRIGGER);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = mMainConn->ExecuteSimpleSQL(CREATE_PLACES_AFTERDELETE_TRIGGER);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = mMainConn->ExecuteSimpleSQL(CREATE_PLACES_AFTERUPDATE_TRIGGER);
+  NS_ENSURE_SUCCESS(rv, rv);
+
   return NS_OK;
 }
 
 nsresult
 Database::UpdateBookmarkRootTitles()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
@@ -1622,16 +1645,66 @@ Database::MigrateV16Up()
     "SET guid = GENERATE_GUID() "
     "WHERE guid ISNULL "
   ));
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
+nsresult
+Database::MigrateV17Up()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  bool tableExists = false;
+
+  nsresult rv = mMainConn->TableExists(NS_LITERAL_CSTRING("moz_hosts"), &tableExists);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (!tableExists) {
+    // For anyone who used in-development versions of this autocomplete,
+    // drop the old tables and its indexes.
+    rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+      "DROP INDEX IF EXISTS moz_hostnames_frecencyindex"
+    ));
+    NS_ENSURE_SUCCESS(rv, rv);
+    rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+      "DROP TABLE IF EXISTS moz_hostnames"
+    ));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    // Add the moz_hosts table so we can get hostnames for URL autocomplete.
+    rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_HOSTS);
+    NS_ENSURE_SUCCESS(rv, rv);
+    rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_HOSTS_FRECENCYHOST);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  // Fill the moz_hosts table with all the domains in moz_places.
+  nsCOMPtr<mozIStorageAsyncStatement> fillHostsStmt;
+  rv = mMainConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
+    "INSERT OR IGNORE INTO moz_hosts (host, frecency) "
+        "SELECT fixup_url(get_unreversed_host(h.rev_host)) AS host, "
+               "(SELECT MAX(frecency) FROM moz_places "
+                "WHERE rev_host = h.rev_host OR rev_host = h.rev_host || 'www.'"
+               ") AS frecency "
+        "FROM moz_places h "
+        "WHERE LENGTH(h.rev_host) > 1 "
+        "GROUP BY h.rev_host"
+  ), getter_AddRefs(fillHostsStmt));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<mozIStoragePendingStatement> ps;
+  rv = fillHostsStmt->ExecuteAsync(nsnull, getter_AddRefs(ps));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
 void
 Database::Shutdown()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!mShuttingDown);
 
   mMainThreadStatements.FinalizeStatements();
   mMainThreadAsyncStatements.FinalizeStatements();
--- a/toolkit/components/places/Database.h
+++ b/toolkit/components/places/Database.h
@@ -42,17 +42,17 @@
 #include "nsWeakReference.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsIObserver.h"
 #include "mozilla/storage.h"
 #include "mozilla/storage/StatementCache.h"
 
 // This is the schema version. Update it at any schema change and add a
 // corresponding migrateVxx method below.
-#define DATABASE_SCHEMA_VERSION 16
+#define DATABASE_SCHEMA_VERSION 17
 
 // Fired after Places inited.
 #define TOPIC_PLACES_INIT_COMPLETE "places-init-complete"
 // Fired when initialization fails due to a locked database.
 #define TOPIC_DATABASE_LOCKED "places-database-locked"
 // This topic is received when the profile is about to be lost.  Places does
 // initial shutdown work and notifies TOPIC_PLACES_SHUTDOWN to all listeners.
 // Any shutdown work that requires the Places APIs should happen here.
@@ -294,16 +294,17 @@ protected:
   nsresult MigrateV8Up();
   nsresult MigrateV9Up();
   nsresult MigrateV10Up();
   nsresult MigrateV11Up();
   nsresult MigrateV13Up();
   nsresult MigrateV14Up();
   nsresult MigrateV15Up();
   nsresult MigrateV16Up();
+  nsresult MigrateV17Up();
 
   nsresult UpdateBookmarkRootTitles();
   nsresult CheckAndUpdateGUIDs();
 
 private:
   ~Database();
 
   /**
--- a/toolkit/components/places/SQLFunctions.cpp
+++ b/toolkit/components/places/SQLFunctions.cpp
@@ -731,10 +731,61 @@ namespace places {
     }
     else {
       result->SetAsAString(EmptyString());
     }
     NS_ADDREF(*_result = result);
     return NS_OK;
   }
 
+////////////////////////////////////////////////////////////////////////////////
+//// Fixup URL Function
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// FixupURLFunction
+
+  /* static */
+  nsresult
+  FixupURLFunction::create(mozIStorageConnection *aDBConn)
+  {
+    nsRefPtr<FixupURLFunction> function = new FixupURLFunction();
+    nsresult rv = aDBConn->CreateFunction(
+      NS_LITERAL_CSTRING("fixup_url"), 1, function
+    );
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    return NS_OK;
+  }
+
+  NS_IMPL_THREADSAFE_ISUPPORTS1(
+    FixupURLFunction,
+    mozIStorageFunction
+  )
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// mozIStorageFunction
+
+  NS_IMETHODIMP
+  FixupURLFunction::OnFunctionCall(mozIStorageValueArray *aArguments,
+                                   nsIVariant **_result)
+  {
+    // Must have non-null function arguments.
+    MOZ_ASSERT(aArguments);
+
+    nsAutoString src;
+    aArguments->GetString(0, src);
+
+    nsCOMPtr<nsIWritableVariant> result =
+      do_CreateInstance("@mozilla.org/variant;1");
+    NS_ENSURE_STATE(result);
+
+    // Remove common URL hostname prefixes
+    if (StringBeginsWith(src, NS_LITERAL_STRING("www."))) {
+      src.Cut(0, 4);
+    }
+
+    result->SetAsAString(src);
+    NS_ADDREF(*_result = result);
+    return NS_OK;
+  }
+
 } // namespace places
 } // namespace mozilla
--- a/toolkit/components/places/SQLFunctions.h
+++ b/toolkit/components/places/SQLFunctions.h
@@ -265,12 +265,40 @@ public:
    * Registers the function with the specified database connection.
    *
    * @param aDBConn
    *        The database connection to register with.
    */
   static nsresult create(mozIStorageConnection *aDBConn);
 };
 
+
+////////////////////////////////////////////////////////////////////////////////
+//// Fixup URL Function
+
+/**
+ * Make a given URL more suitable for searches, by removing common prefixes
+ * such as "www."
+ *
+ * @param url
+ *        A URL.
+ * @return
+ *        The same URL, with redundant parts removed.
+ */
+class FixupURLFunction : public mozIStorageFunction
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_MOZISTORAGEFUNCTION
+
+  /**
+   * Registers the function with the specified database connection.
+   *
+   * @param aDBConn
+   *        The database connection to register with.
+   */
+  static nsresult create(mozIStorageConnection *aDBConn);
+};
+
 } // namespace places
 } // namespace storage
 
 #endif // mozilla_places_SQLFunctions_h_
--- a/toolkit/components/places/nsPlacesIndexes.h
+++ b/toolkit/components/places/nsPlacesIndexes.h
@@ -132,16 +132,25 @@
  */
 
 #define CREATE_IDX_MOZ_ANNOS_PLACEATTRIBUTE \
   CREATE_PLACES_IDX( \
     "placeattributeindex", "moz_annos", "place_id, anno_attribute_id", "UNIQUE" \
   )
 
 /**
+ * moz_hosts
+ */
+
+#define CREATE_IDX_MOZ_HOSTS_FRECENCYHOST \
+  CREATE_PLACES_IDX( \
+    "frecencyhostindex", "moz_hosts", "frecency, host", "" \
+  )
+
+/**
  * moz_items_annos
  */
 
 #define CREATE_IDX_MOZ_ITEMSANNOS_PLACEATTRIBUTE \
   CREATE_PLACES_IDX( \
     "itemattributeindex", "moz_items_annos", "item_id, anno_attribute_id", "UNIQUE" \
   )
 
--- a/toolkit/components/places/nsPlacesTables.h
+++ b/toolkit/components/places/nsPlacesTables.h
@@ -157,16 +157,24 @@
 
 #define CREATE_MOZ_KEYWORDS NS_LITERAL_CSTRING( \
   "CREATE TABLE moz_keywords (" \
     "  id INTEGER PRIMARY KEY AUTOINCREMENT" \
     ", keyword TEXT UNIQUE" \
   ")" \
 )
 
+#define CREATE_MOZ_HOSTS NS_LITERAL_CSTRING( \
+  "CREATE TABLE moz_hosts (" \
+    "  id INTEGER PRIMARY KEY" \
+    ", host TEXT NOT NULL UNIQUE" \
+    ", frecency INTEGER" \
+  ")" \
+)
+
 // Note: this should be kept up-to-date with the definition in
 //       nsPlacesAutoComplete.js.
 #define CREATE_MOZ_OPENPAGES_TEMP NS_LITERAL_CSTRING( \
   "CREATE TEMP TABLE moz_openpages_temp (" \
     "  url TEXT PRIMARY KEY" \
     ", open_count INTEGER" \
   ")" \
 )
--- a/toolkit/components/places/nsPlacesTriggers.h
+++ b/toolkit/components/places/nsPlacesTriggers.h
@@ -75,16 +75,54 @@
       "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;" \
   "END" \
 )
 
 /**
+ * These triggers update the hostnames table whenever moz_places changes.
+ */
+#define CREATE_PLACES_AFTERINSERT_TRIGGER NS_LITERAL_CSTRING( \
+  "CREATE TEMP TRIGGER moz_places_afterinsert_trigger " \
+  "AFTER INSERT ON moz_places FOR EACH ROW " \
+  "WHEN LENGTH(NEW.rev_host) > 1 " \
+  "BEGIN " \
+    "INSERT OR REPLACE INTO moz_hosts (id, host, frecency) " \
+    "VALUES (" \
+      "(SELECT id FROM moz_hosts WHERE host = fixup_url(get_unreversed_host(NEW.rev_host))), " \
+      "fixup_url(get_unreversed_host(NEW.rev_host)), " \
+      "MAX((SELECT frecency FROM moz_hosts WHERE host = fixup_url(get_unreversed_host(NEW.rev_host))), NEW.frecency) " \
+    "); " \
+  "END" \
+)
+
+#define CREATE_PLACES_AFTERDELETE_TRIGGER NS_LITERAL_CSTRING( \
+  "CREATE TEMP TRIGGER moz_places_afterdelete_trigger " \
+  "AFTER DELETE ON moz_places FOR EACH ROW " \
+  "BEGIN " \
+    "DELETE FROM moz_hosts " \
+    "WHERE host = fixup_url(get_unreversed_host(OLD.rev_host)) " \
+      "AND NOT EXISTS(SELECT 1 FROM moz_places WHERE rev_host = OLD.rev_host); " \
+  "END" \
+)
+
+#define CREATE_PLACES_AFTERUPDATE_TRIGGER NS_LITERAL_CSTRING( \
+  "CREATE TEMP TRIGGER moz_places_afterupdate_frecency_trigger " \
+  "AFTER UPDATE OF frecency ON moz_places FOR EACH ROW " \
+  "WHEN NEW.frecency >= 0 " \
+  "BEGIN " \
+    "UPDATE moz_hosts " \
+    "SET frecency = (SELECT MAX(frecency) FROM moz_places WHERE rev_host = NEW.rev_host OR rev_host = NEW.rev_host || 'www.') " \
+    "WHERE host = fixup_url(get_unreversed_host(NEW.rev_host)); " \
+  "END" \
+)
+
+/**
  * This trigger removes a row from moz_openpages_temp when open_count reaches 0.
  *
  * @note this should be kept up-to-date with the definition in
  *       nsPlacesAutoComplete.js
  */
 #define CREATE_REMOVEOPENPAGE_CLEANUP_TRIGGER NS_LITERAL_CSTRING( \
   "CREATE TEMPORARY TRIGGER moz_openpages_temp_afterupdate_trigger " \
   "AFTER UPDATE OF open_count ON moz_openpages_temp FOR EACH ROW " \
--- a/toolkit/components/places/tests/head_common.js
+++ b/toolkit/components/places/tests/head_common.js
@@ -30,17 +30,17 @@
  * 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 CURRENT_SCHEMA_VERSION = 16;
+const CURRENT_SCHEMA_VERSION = 17;
 
 const NS_APP_USER_PROFILE_50_DIR = "ProfD";
 const NS_APP_PROFILE_DIR_STARTUP = "ProfDS";
 const NS_APP_BOOKMARKS_50_FILE = "BMarks";
 
 // Shortcuts to transitions type.
 const TRANSITION_LINK = Ci.nsINavHistoryService.TRANSITION_LINK;
 const TRANSITION_TYPED = Ci.nsINavHistoryService.TRANSITION_TYPED;
--- a/toolkit/components/places/tests/migration/test_current_from_v10.js
+++ b/toolkit/components/places/tests/migration/test_current_from_v10.js
@@ -273,16 +273,47 @@ function test_place_guid_annotation_remo
   stmt.params.attr_name = kGuidAnnotationName;
   do_check_true(stmt.executeStep());
   do_check_eq(stmt.getInt32(0), 0);
   stmt.finalize();
 
   run_next_test();
 }
 
+function test_moz_hosts()
+{
+  // This will throw if the column does not exist
+  let stmt = DBConn().createStatement(
+    "SELECT host, frecency "
+  + "FROM moz_hosts "
+  );
+  stmt.finalize();
+
+  // check the number of entries in moz_hosts equals the number of
+  // unique rev_host in moz_places
+  var query = "SELECT ("
+              + "SELECT COUNT(host) "
+              + "FROM moz_hosts), ("
+              + "SELECT COUNT(DISTINCT rev_host) "
+              + "FROM moz_places "
+              + "WHERE LENGTH(rev_host) > 1)";
+
+  stmt = DBConn().createStatement(query);
+  try {
+    stmt.executeStep();
+    let mozPlacesCount = stmt.getInt32(0);
+    let mozHostsCount = stmt.getInt32(1);
+    do_check_eq(mozPlacesCount, mozHostsCount);
+  }
+  finally {
+    stmt.finalize();
+    run_next_test();
+  }
+}
+
 function test_final_state()
 {
   // We open a new database mostly so that we can check that the settings were
   // actually saved.
   let dbFile = gProfD.clone();
   dbFile.append(kDBName);
   let db = Services.storage.openUnsharedDatabase(dbFile);
 
@@ -292,16 +323,18 @@ function test_final_state()
     do_check_eq(stmt.getString(0).toLowerCase(), "wal");
     stmt.finalize();
   }
 
   do_check_true(db.indexExists("moz_bookmarks_guid_uniqueindex"));
   do_check_true(db.indexExists("moz_places_guid_uniqueindex"));
   do_check_true(db.indexExists("moz_favicons_guid_uniqueindex"));
 
+  do_check_true(db.indexExists("moz_hosts_frecencyhostindex"));
+
   do_check_eq(db.schemaVersion, CURRENT_SCHEMA_VERSION);
 
   db.close();
   run_next_test();
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 //// Test Runner
@@ -311,16 +344,17 @@ function test_final_state()
   test_moz_bookmarks_guid_exists,
   test_bookmark_guids_non_null,
   test_bookmark_guid_annotation_imported,
   test_bookmark_guid_annotation_removed,
   test_moz_places_guid_exists,
   test_place_guids_non_null,
   test_place_guid_annotation_imported,
   test_place_guid_annotation_removed,
+  test_moz_hosts,
   test_final_state,
 ].forEach(add_test);
 
 function run_test()
 {
   setPlacesDatabase("places_v10.sqlite");
   run_next_test();
 }
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/unit/test_hosts_triggers.js
@@ -0,0 +1,167 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * This file tests the validity of various triggers that add remove hosts from moz_hosts
+ */
+
+// add some visits and remove them, add a bookmark,
+// change its uri, then remove it, and
+// for each change check that moz_hosts has correctly been updated.
+
+function isHostInMozPlaces(aURI)
+{
+  let stmt = DBConn().createStatement(
+    "SELECT url "
+    + "FROM moz_places "
+    + "WHERE url = :host"
+  );
+  let result = false;
+  stmt.params.host = aURI.spec;
+  while(stmt.executeStep()) {
+    if (stmt.row.url == aURI.spec) {
+      result = true;
+      break;
+    }
+  }
+  stmt.finalize();
+  return result;
+}
+
+function isHostInMozHosts(aURI)
+{
+  let stmt = DBConn().createStatement(
+    "SELECT host "
+    + "FROM moz_hosts "
+    + "WHERE host = :host"
+  );
+  let result = false;
+  stmt.params.host = aURI.host;
+  while(stmt.executeStep()) {
+    if (stmt.row.host == aURI.host) {
+      result = true;
+      break;
+    }
+  }
+  stmt.finalize();
+  return result;
+}
+
+let urls = [{uri: NetUtil.newURI("http://visit1.mozilla.org"),
+             expected: "visit1.mozilla.org",
+            },
+            {uri: NetUtil.newURI("http://visit2.mozilla.org"),
+             expected: "visit2.mozilla.org",
+            },
+            {uri: NetUtil.newURI("http://www.foo.mozilla.org"),
+             expected: "foo.mozilla.org",
+            },
+           ];
+
+function VisitInfo(aTransitionType, aVisitTime)
+{
+  this.transitionType =
+    aTransitionType === undefined ? TRANSITION_LINK : aTransitionType;
+  this.visitDate = aVisitTime || Date.now() * 1000;
+}
+
+const NEW_URL = "http://different.mozilla.org/";
+
+function test_moz_hosts_update()
+{
+  let places = [];
+  urls.forEach(function(url) {
+    let place = {
+                  uri: url.uri,
+                  title: "test for " + url.url,
+                  visits: [
+                    new VisitInfo(),
+                  ],
+    };
+    places.push(place);
+  });
+
+  XPCOMUtils.defineLazyServiceGetter(this, "gHistory",
+                                     "@mozilla.org/browser/history;1",
+                                     "mozIAsyncHistory");
+
+  gHistory.updatePlaces(places, {
+    handleResult: function () {
+    },
+    handleError: function () {
+      do_throw("gHistory.updatePlaces() failed");
+    },
+    handleCompletion: function () {
+      do_check_true(isHostInMozHosts(urls[0].uri));
+      do_check_true(isHostInMozHosts(urls[1].uri));
+      // strip the WWW from the url before testing...
+      do_check_true(isHostInMozHosts(NetUtil.newURI("http://foo.mozilla.org")));
+      run_next_test();
+    }
+  });
+}
+
+function test_remove_places()
+{
+  for (let idx in urls) {
+    PlacesUtils.history.removePage(urls[idx].uri);
+  }
+
+  waitForClearHistory(function (){
+    for (let idx in urls) {
+      do_check_false(isHostInMozHosts(urls[idx].uri));
+    }
+    run_next_test();
+  });
+}
+
+function test_bookmark_changes()
+{
+  let testUri = NetUtil.newURI("http://test.mozilla.org");
+
+  let itemId = PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
+                                                     testUri,
+                                                     PlacesUtils.bookmarks.DEFAULT_INDEX,
+                                                     "bookmark title");
+
+  do_check_true(isHostInMozPlaces(testUri));
+
+  // Change the hostname
+  PlacesUtils.bookmarks.changeBookmarkURI(itemId, NetUtil.newURI(NEW_URL));
+
+  waitForClearHistory(function (){
+    let newUri = NetUtil.newURI(NEW_URL);
+    do_check_true(isHostInMozPlaces(newUri));
+    do_check_true(isHostInMozHosts(newUri));
+    do_check_false(isHostInMozHosts(NetUtil.newURI("http://test.mozilla.org")));
+    run_next_test();
+  });
+}
+
+function test_bookmark_removal()
+{
+  let itemId = PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.unfiledBookmarksFolderId,
+                                                    PlacesUtils.bookmarks.DEFAULT_INDEX);
+  let newUri = NetUtil.newURI(NEW_URL);
+  PlacesUtils.bookmarks.removeItem(itemId);
+  waitForClearHistory(function (){
+    do_check_false(isHostInMozHosts(newUri));
+    run_next_test();
+  });
+
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// Test Runner
+
+[
+  test_moz_hosts_update,
+  test_remove_places,
+  test_bookmark_changes,
+  test_bookmark_removal,
+].forEach(add_test);
+
+function run_test()
+{
+  run_next_test();
+}
--- a/toolkit/components/places/tests/unit/xpcshell.ini
+++ b/toolkit/components/places/tests/unit/xpcshell.ini
@@ -84,16 +84,17 @@ skip-if = os == "android"
 [test_history_autocomplete_tags.js]
 [test_history_catobs.js]
 [test_history_notifications.js]
 [test_history_observer.js]
 [test_history_removeAllPages.js]
 # Bug 676989: test hangs consistently on Android
 skip-if = os == "android"
 [test_history_sidebar.js]
+[test_hosts_triggers.js]
 [test_isURIVisited.js]
 [test_isvisited.js]
 [test_lastModified.js]
 [test_livemarkService_getLivemarkIdForFeedURI.js]
 [test_markpageas.js]
 [test_moz-anno_favicon_mime_type.js]
 [test_multi_queries.js]
 # Bug 676989: test fails consistently on Android