Bug 720081 - Part 2: inline autocomplete should respect protocol and www prefix
authorMarco Bonardo <mbonardo@mozilla.com>
Fri, 01 Jun 2012 14:38:39 +0200
changeset 95931 99805da33911d8936df780882deac82228394da6
parent 95930 cff98981add846ed1a7e4f441858ee9765f5819c
child 95932 feacec1c819558743ff294ccac61d2f03bb0fb51
push idunknown
push userunknown
push dateunknown
bugs720081
milestone14.0a2
Bug 720081 - Part 2: inline autocomplete should respect protocol and www prefix r=dietrich a=gavin
toolkit/components/places/Database.cpp
toolkit/components/places/Database.h
toolkit/components/places/nsPlacesAutoComplete.js
toolkit/components/places/nsPlacesTables.h
toolkit/components/places/nsPlacesTriggers.h
toolkit/components/places/tests/head_common.js
toolkit/components/places/tests/inline/test_casing.js
toolkit/components/places/tests/inline/test_trimming.js
toolkit/components/places/tests/inline/xpcshell.ini
toolkit/components/places/tests/migration/test_current_from_v10.js
toolkit/components/places/tests/unit/test_hosts_triggers.js
--- a/toolkit/components/places/Database.cpp
+++ b/toolkit/components/places/Database.cpp
@@ -760,17 +760,22 @@ Database::InitSchema(bool* aDatabaseMigr
 
       // Firefox 13 uses schema version 19.
 
       if (currentSchemaVersion < 20) {
         rv = MigrateV20Up();
         NS_ENSURE_SUCCESS(rv, rv);
       }
 
-      // Firefox 14 uses schema version 20.
+      if (currentSchemaVersion < 21) {
+        rv = MigrateV21Up();
+        NS_ENSURE_SUCCESS(rv, rv);
+      }
+
+      // Firefox 14 uses schema version 21.
 
       // 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));
     }
@@ -1881,16 +1886,49 @@ Database::MigrateV20Up()
   );
   NS_ENSURE_SUCCESS(rv, rv);
   rv = deleteOldBookmarkGUIDAnnosStmt->Execute();
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
+nsresult
+Database::MigrateV21Up()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // Add a prefix column to moz_hosts.
+  nsCOMPtr<mozIStorageStatement> stmt;
+  nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+    "SELECT prefix FROM moz_hosts"
+  ), getter_AddRefs(stmt));
+  if (NS_FAILED(rv)) {
+    rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+      "ALTER TABLE moz_hosts ADD COLUMN prefix"
+    ));
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  // Update prefixes.
+  nsCOMPtr<mozIStorageAsyncStatement> updatePrefixesStmt;
+  rv = mMainConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
+    "UPDATE moz_hosts SET prefix = ( "
+      HOSTS_PREFIX_PRIORITY_FRAGMENT
+    ") "
+  ), getter_AddRefs(updatePrefixesStmt));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<mozIStoragePendingStatement> ps;
+  rv = updatePrefixesStmt->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 20
+#define DATABASE_SCHEMA_VERSION 21
 
 // 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.
@@ -298,16 +298,17 @@ protected:
   nsresult MigrateV13Up();
   nsresult MigrateV14Up();
   nsresult MigrateV15Up();
   nsresult MigrateV16Up();
   nsresult MigrateV17Up();
   nsresult MigrateV18Up();
   nsresult MigrateV19Up();
   nsresult MigrateV20Up();
+  nsresult MigrateV21Up();
 
   nsresult UpdateBookmarkRootTitles();
   nsresult CheckAndUpdateGUIDs();
 
 private:
   ~Database();
 
   /**
--- a/toolkit/components/places/nsPlacesAutoComplete.js
+++ b/toolkit/components/places/nsPlacesAutoComplete.js
@@ -1315,17 +1315,17 @@ urlInlineComplete.prototype = {
 
   get _syncQuery()
   {
     if (!this.__syncQuery) {
       // Add a trailing slash at the end of the hostname, since we always
       // want to complete up to and including a URL separator.
       this.__syncQuery = this._db.createStatement(
           "/* do not warn (bug no): could index on (typed,frecency) but not worth it */ "
-        + "SELECT host || '/' "
+        + "SELECT host || '/', prefix || host || '/' "
         + "FROM moz_hosts "
         + "WHERE host BETWEEN :search_string AND :search_string || X'FFFF' "
         + "AND frecency <> 0 "
         + (this._autofillTyped ? "AND typed = 1 " : "")
         + "ORDER BY frecency DESC "
         + "LIMIT 1"
       );
     }
@@ -1406,29 +1406,32 @@ urlInlineComplete.prototype = {
     // Do a synchronous search on the table of domains.
     let query = this._syncQuery;
     query.params.search_string = this._currentSearchString.toLowerCase();
 
     // Domains have no "/" in them.
     let lastSlashIndex = this._currentSearchString.lastIndexOf("/");
     if (lastSlashIndex == -1) {
       var hasDomainResult = false;
-      var domain;
+      var domain, untrimmedDomain;
       try {
         hasDomainResult = query.executeStep();
         if (hasDomainResult) {
           domain = query.getString(0);
+          untrimmedDomain = query.getString(1);
         }
       } finally {
         query.reset();
       }
 
       if (hasDomainResult) {
         // We got a match for a domain, we can add it immediately.
-        result.appendMatch(this._strippedPrefix + domain, "");
+        // TODO (bug 754265): this is a temporary solution introduced while
+        // waiting for a propert dedicated API.
+        result.appendMatch(this._strippedPrefix + domain, untrimmedDomain);
 
         this._finishSearch();
         return;
       }
     }
 
     // We did not get a result from the synchronous domain search.
     // We now do an asynchronous search through places, and complete
@@ -1509,31 +1512,36 @@ urlInlineComplete.prototype = {
   get searchType() Ci.nsIAutoCompleteSearchDescriptor.SEARCH_TYPE_IMMEDIATE,
 
   //////////////////////////////////////////////////////////////////////////////
   //// mozIStorageStatementCallback
 
   handleResult: function UIC_handleResult(aResultSet)
   {
     let row = aResultSet.getNextRow();
-    let url = fixupSearchText(row.getResultByIndex(0));
+    let value = row.getResultByIndex(0);
+    let url = fixupSearchText(value);
+
+    let prefix = value.slice(0, value.length - url.length);
 
     // We must complete the URL up to the next separator (which is /, ? or #).
     let separatorIndex = url.slice(this._currentSearchString.length)
                             .search(/[\/\?\#]/);
     if (separatorIndex != -1) {
       separatorIndex += this._currentSearchString.length;
       if (url[separatorIndex] == "/") {
         separatorIndex++; // Include the "/" separator
       }
       url = url.slice(0, separatorIndex);
     }
 
-    // Add the result
-    this._result.appendMatch(this._strippedPrefix + url, "");
+    // Add the result.
+    // TODO (bug 754265): this is a temporary solution introduced while
+    // waiting for a propert dedicated API.
+    this._result.appendMatch(this._strippedPrefix + url, prefix + url);
 
     // handleCompletion() will cause the result listener to be called, and
     // will display the result in the UI.
   },
 
   handleError: function UIC_handleError(aError)
   {
     Components.utils.reportError("URL Inline Complete: An async statement encountered an " +
--- a/toolkit/components/places/nsPlacesTables.h
+++ b/toolkit/components/places/nsPlacesTables.h
@@ -163,16 +163,17 @@
 )
 
 #define CREATE_MOZ_HOSTS NS_LITERAL_CSTRING( \
   "CREATE TABLE moz_hosts (" \
     "  id INTEGER PRIMARY KEY" \
     ", host TEXT NOT NULL UNIQUE" \
     ", frecency INTEGER" \
     ", typed INTEGER NOT NULL DEFAULT 0" \
+    ", prefix TEXT" \
   ")" \
 )
 
 // 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" \
--- a/toolkit/components/places/nsPlacesTriggers.h
+++ b/toolkit/components/places/nsPlacesTriggers.h
@@ -75,44 +75,82 @@
       "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" \
 )
 
 /**
+ * Select the best prefix for a host, based on existing pages registered for it.
+ * Prefixes have a priority, from the top to the bottom, so that secure pages
+ * have higher priority, and more generically "www." prefixed hosts come before
+ * unprefixed ones.
+ * Each condition just checks if a page exists for a specific prefixed host,
+ * and if so returns the relative prefix.
+ */
+#define HOSTS_PREFIX_PRIORITY_FRAGMENT \
+  "SELECT CASE " \
+    "WHEN EXISTS( " \
+      "SELECT 1 FROM moz_places WHERE url BETWEEN 'https://www.' || host || '/' " \
+                                             "AND 'https://www.' || host || '/' || X'FFFF' " \
+    ") THEN 'https://www.' " \
+    "WHEN EXISTS( " \
+      "SELECT 1 FROM moz_places WHERE url BETWEEN 'https://' || host || '/' " \
+                                             "AND 'https://' || host || '/' || X'FFFF' " \
+    ") THEN 'https://' " \
+    "WHEN EXISTS( " \
+      "SELECT 1 FROM moz_places WHERE url BETWEEN 'ftp://' || host || '/' " \
+                                             "AND 'ftp://' || host || '/' || X'FFFF' " \
+    ") THEN 'ftp://' " \
+    "WHEN EXISTS( " \
+      "SELECT 1 FROM moz_places WHERE url BETWEEN 'http://www.' || host || '/' " \
+                                             "AND 'http://www.' || host || '/' || X'FFFF' " \
+    ") THEN 'www.' " \
+  "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, typed) " \
+    "INSERT OR REPLACE INTO moz_hosts (id, host, frecency, typed, prefix) " \
     "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(IFNULL((SELECT frecency FROM moz_hosts WHERE host = fixup_url(get_unreversed_host(NEW.rev_host))), -1), NEW.frecency), " \
-      "MAX(IFNULL((SELECT typed FROM moz_hosts WHERE host = fixup_url(get_unreversed_host(NEW.rev_host))), 0), NEW.typed) " \
+      "MAX(IFNULL((SELECT typed FROM moz_hosts WHERE host = fixup_url(get_unreversed_host(NEW.rev_host))), 0), NEW.typed), " \
+      "(" HOSTS_PREFIX_PRIORITY_FRAGMENT \
+       "FROM ( " \
+          "SELECT fixup_url(get_unreversed_host(NEW.rev_host)) AS host " \
+        ") AS match " \
+      ") " \
     "); " \
   "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 = get_unreversed_host(host || '.') || '.' " \
              "OR rev_host = get_unreversed_host(host || '.') || '.www.' " \
       "); " \
+    "UPDATE moz_hosts " \
+    "SET prefix = (" \
+      HOSTS_PREFIX_PRIORITY_FRAGMENT \
+    ") " \
+    "WHERE host = fixup_url(get_unreversed_host(OLD.rev_host)); " \
   "END" \
 )
 
 // For performance reasons the host frecency is updated only when the page
 // frecency changes by a meaningful percentage.  This is because the frecency
 // decay algorithm requires to update all the frecencies at once, causing a
 // too high overhead, while leaving the ordering unchanged.
 #define CREATE_PLACES_AFTERUPDATE_FRECENCY_TRIGGER NS_LITERAL_CSTRING( \
--- 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 = 20;
+const CURRENT_SCHEMA_VERSION = 21;
 
 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/inline/test_casing.js
+++ b/toolkit/components/places/tests/inline/test_casing.js
@@ -18,17 +18,17 @@ add_autocomplete_test([
   function () {
     addBookmark({ url: "http://mozilla.org/test/" });
   }
 ]);
 
 add_autocomplete_test([
   "Searching for cased entry 3",
   "mozilla.org/T",
-  { autoFilled: "mozilla.org/Test/", completed: "mozilla.org/Test/" },
+  { autoFilled: "mozilla.org/Test/", completed: "http://mozilla.org/Test/" },
   function () {
     addBookmark({ url: "http://mozilla.org/Test/" });
   }
 ]);
 
 add_autocomplete_test([
   "Searching for cased entry 4",
   "mOzilla.org/t",
@@ -36,17 +36,17 @@ add_autocomplete_test([
   function () {
     addBookmark({ url: "http://mozilla.org/Test/" });
   },
 ]);
 
 add_autocomplete_test([
   "Searching for cased entry 5",
   "mOzilla.org/T",
-  { autoFilled: "mOzilla.org/Test/", completed: "mozilla.org/Test/" },
+  { autoFilled: "mOzilla.org/Test/", completed: "http://mozilla.org/Test/" },
   function () {
     addBookmark({ url: "http://mozilla.org/Test/" });
   },
 ]);
 
 add_autocomplete_test([
   "Searching for untrimmed cased entry",
   "http://mOz",
@@ -54,17 +54,17 @@ add_autocomplete_test([
   function () {
     addBookmark({ url: "http://mozilla.org/Test/" });
   },
 ]);
 
 add_autocomplete_test([
   "Searching for untrimmed cased entry with www",
   "http://www.mOz",
-  { autoFilled: "http://www.mOzilla.org/", completed: "http://www.mozilla.org/" },
+  { autoFilled: "http://www.mOzilla.org/", completed: "www.mozilla.org/" },
   function () {
     addBookmark({ url: "http://www.mozilla.org/Test/" });
   },
 ]);
 
 add_autocomplete_test([
   "Searching for untrimmed cased entry with path",
   "http://mOzilla.org/t",
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/inline/test_trimming.js
@@ -0,0 +1,134 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+add_autocomplete_test([
+  "Searching for untrimmed https://www entry",
+  "mo",
+  { autoFilled: "mozilla.org/", completed: "https://www.mozilla.org/" },
+  function () {
+    addBookmark({ url: "https://www.mozilla.org/test/" });
+  },
+]);
+
+add_autocomplete_test([
+  "Searching for untrimmed https://www entry with path",
+  "mozilla.org/t",
+  { autoFilled: "mozilla.org/test/", completed: "https://www.mozilla.org/test/" },
+  function () {
+    addBookmark({ url: "https://www.mozilla.org/test/" });
+  },
+]);
+
+add_autocomplete_test([
+  "Searching for untrimmed https:// entry",
+  "mo",
+  { autoFilled: "mozilla.org/", completed: "https://mozilla.org/" },
+  function () {
+    addBookmark({ url: "https://mozilla.org/test/" });
+  },
+]);
+
+add_autocomplete_test([
+  "Searching for untrimmed https:// entry with path",
+  "mozilla.org/t",
+  { autoFilled: "mozilla.org/test/", completed: "https://mozilla.org/test/" },
+  function () {
+    addBookmark({ url: "https://mozilla.org/test/" });
+  },
+]);
+
+add_autocomplete_test([
+  "Searching for untrimmed http://www entry",
+  "mo",
+  { autoFilled: "mozilla.org/", completed: "www.mozilla.org/" },
+  function () {
+    addBookmark({ url: "http://www.mozilla.org/test/" });
+  },
+]);
+
+add_autocomplete_test([
+  "Searching for untrimmed http://www entry with path",
+  "mozilla.org/t",
+  { autoFilled: "mozilla.org/test/", completed: "http://www.mozilla.org/test/" },
+  function () {
+    addBookmark({ url: "http://www.mozilla.org/test/" });
+  },
+]);
+
+add_autocomplete_test([
+  "Searching for untrimmed ftp:// entry",
+  "mo",
+  { autoFilled: "mozilla.org/", completed: "ftp://mozilla.org/" },
+  function () {
+    addBookmark({ url: "ftp://mozilla.org/test/" });
+  },
+]);
+
+add_autocomplete_test([
+  "Searching for untrimmed ftp:// entry with path",
+  "mozilla.org/t",
+  { autoFilled: "mozilla.org/test/", completed: "ftp://mozilla.org/test/" },
+  function () {
+    addBookmark({ url: "ftp://mozilla.org/test/" });
+  },
+]);
+
+add_autocomplete_test([
+  "Ensuring correct priority 1",
+  "mo",
+  { autoFilled: "mozilla.org/", completed: "https://www.mozilla.org/" },
+  function () {
+    addBookmark({ url: "https://www.mozilla.org/test/" });
+    addBookmark({ url: "https://mozilla.org/test/" });
+    addBookmark({ url: "ftp://mozilla.org/test/" });
+    addBookmark({ url: "http://www.mozilla.org/test/" });
+    addBookmark({ url: "http://mozilla.org/test/" });
+  },
+]);
+
+add_autocomplete_test([
+  "Ensuring correct priority 2",
+  "mo",
+  { autoFilled: "mozilla.org/", completed: "https://mozilla.org/" },
+  function () {
+    addBookmark({ url: "https://mozilla.org/test/" });
+    addBookmark({ url: "ftp://mozilla.org/test/" });
+    addBookmark({ url: "http://www.mozilla.org/test/" });
+    addBookmark({ url: "http://mozilla.org/test/" });
+  },
+]);
+
+add_autocomplete_test([
+  "Ensuring correct priority 3",
+  "mo",
+  { autoFilled: "mozilla.org/", completed: "ftp://mozilla.org/" },
+  function () {
+    addBookmark({ url: "ftp://mozilla.org/test/" });
+    addBookmark({ url: "http://www.mozilla.org/test/" });
+    addBookmark({ url: "http://mozilla.org/test/" });
+  },
+]);
+
+add_autocomplete_test([
+  "Ensuring correct priority 4",
+  "mo",
+  { autoFilled: "mozilla.org/", completed: "www.mozilla.org/" },
+  function () {
+    addBookmark({ url: "http://www.mozilla.org/test/" });
+    addBookmark({ url: "http://mozilla.org/test/" });
+  },
+]);
+
+add_autocomplete_test([
+  "Ensuring longer domain can't match",
+  "mo",
+  { autoFilled: "mozilla.co/", completed: "mozilla.co/" },
+  function () {
+    // The .co should be preferred, but should not get the https from the .com.
+    // The .co domain must be added later to activate the trigger bug.
+    addBookmark({ url: "https://mozilla.com/" });
+    addBookmark({ url: "http://mozilla.co/" });
+    addBookmark({ url: "http://mozilla.co/" });
+  },
+]);
--- a/toolkit/components/places/tests/inline/xpcshell.ini
+++ b/toolkit/components/places/tests/inline/xpcshell.ini
@@ -1,10 +1,11 @@
 [DEFAULT]
 head = head_autocomplete.js
 tail = 
 
 [test_autocomplete_functional.js]
 [test_casing.js]
 [test_do_not_trim.js]
 [test_keywords.js]
+[test_trimming.js]
 [test_typed.js]
 [test_zero_frecency.js]
--- a/toolkit/components/places/tests/migration/test_current_from_v10.js
+++ b/toolkit/components/places/tests/migration/test_current_from_v10.js
@@ -277,17 +277,17 @@ function test_place_guid_annotation_remo
 
   run_next_test();
 }
 
 function test_moz_hosts()
 {
   // This will throw if the column does not exist
   let stmt = DBConn().createStatement(
-    "SELECT host, frecency, typed "
+    "SELECT host, frecency, typed, prefix "
   + "FROM moz_hosts "
   );
   stmt.finalize();
 
   // moz_hosts is populated asynchronously, so query asynchronously to serialize
   // to that.
   // check the number of entries in moz_hosts equals the number of
   // unique rev_host in moz_places
--- a/toolkit/components/places/tests/unit/test_hosts_triggers.js
+++ b/toolkit/components/places/tests/unit/test_hosts_triggers.js
@@ -27,47 +27,47 @@ function isHostInMozPlaces(aURI)
       result = true;
       break;
     }
   }
   stmt.finalize();
   return result;
 }
 
-function isHostInMozHosts(aURI, aTyped)
+function isHostInMozHosts(aURI, aTyped, aPrefix)
 {
   let stmt = DBConn().createStatement(
-    "SELECT host, typed "
+    "SELECT host, typed, prefix "
     + "FROM moz_hosts "
     + "WHERE host = fixup_url(:host) "
     + "AND frecency NOTNULL "
   );
   let result = false;
   stmt.params.host = aURI.host;
   if (stmt.executeStep()) {
-    if (aTyped != null)
-      result = aTyped == stmt.row.typed;
-    else
-      result = true;
+    result = aTyped == stmt.row.typed && aPrefix == stmt.row.prefix;
   }
   stmt.finalize();
   return result;
 }
 
 let urls = [{uri: NetUtil.newURI("http://visit1.mozilla.org"),
              expected: "visit1.mozilla.org",
-             typed: 0
+             typed: 0,
+             prefix: null
             },
             {uri: NetUtil.newURI("http://visit2.mozilla.org"),
              expected: "visit2.mozilla.org",
-             typed: 0
+             typed: 0,
+             prefix: null
             },
             {uri: NetUtil.newURI("http://www.foo.mozilla.org"),
              expected: "foo.mozilla.org",
-             typed: 1
+             typed: 1,
+             prefix: "www."
             },
            ];
 
 function VisitInfo(aTransitionType, aVisitTime)
 {
   this.transitionType =
     aTransitionType === undefined ? TRANSITION_LINK : aTransitionType;
   this.visitDate = aVisitTime || Date.now() * 1000;
@@ -91,33 +91,33 @@ function test_moz_hosts_update()
 
   gHistory.updatePlaces(places, {
     handleResult: function () {
     },
     handleError: function () {
       do_throw("gHistory.updatePlaces() failed");
     },
     handleCompletion: function () {
-      do_check_true(isHostInMozHosts(urls[0].uri, urls[0].typed));
-      do_check_true(isHostInMozHosts(urls[1].uri, urls[1].typed));
-      do_check_true(isHostInMozHosts(urls[2].uri, urls[2].typed));
+      do_check_true(isHostInMozHosts(urls[0].uri, urls[0].typed, urls[0].prefix));
+      do_check_true(isHostInMozHosts(urls[1].uri, urls[1].typed, urls[1].prefix));
+      do_check_true(isHostInMozHosts(urls[2].uri, urls[2].typed, urls[2].prefix));
       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));
+      do_check_false(isHostInMozHosts(urls[idx].uri, urls[idx].typed, urls[idx].prefix));
     }
     run_next_test();
   });
 }
 
 function test_bookmark_changes()
 {
   let testUri = NetUtil.newURI("http://test.mozilla.org");
@@ -130,30 +130,30 @@ function test_bookmark_changes()
   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")));
+    do_check_true(isHostInMozHosts(newUri, false, null));
+    do_check_false(isHostInMozHosts(NetUtil.newURI("http://test.mozilla.org"), false, null));
     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));
+    do_check_false(isHostInMozHosts(newUri, false, null));
     run_next_test();
   });
 }
 
 function test_moz_hosts_typed_update()
 {
   const TEST_URI = NetUtil.newURI("http://typed.mozilla.com");
   let places = [{ uri: TEST_URI
@@ -165,17 +165,17 @@ function test_moz_hosts_typed_update()
 
   gHistory.updatePlaces(places, {
     handleResult: function () {
     },
     handleError: function () {
       do_throw("gHistory.updatePlaces() failed");
     },
     handleCompletion: function () {
-      do_check_true(isHostInMozHosts(TEST_URI, true));
+      do_check_true(isHostInMozHosts(TEST_URI, true, null));
       run_next_test();
     }
   });
 }
 
 function test_moz_hosts_www_remove()
 {
   function test_removal(aURIToRemove, aURIToKeep, aCallback) {
@@ -191,17 +191,18 @@ function test_moz_hosts_www_remove()
     gHistory.updatePlaces(places, {
       handleResult: function () {
       },
       handleError: function () {
         do_throw("gHistory.updatePlaces() failed");
       },
       handleCompletion: function () {
         PlacesUtils.history.removePage(aURIToRemove);
-        do_check_true(isHostInMozHosts(aURIToKeep));
+        let prefix = /www/.test(aURIToKeep.spec) ? "www." : null;
+        do_check_true(isHostInMozHosts(aURIToKeep, false, prefix));
         waitForClearHistory(aCallback);
       }
     });
   }
 
   const TEST_URI = NetUtil.newURI("http://rem.mozilla.com");
   const TEST_WWW_URI = NetUtil.newURI("http://www.rem.mozilla.com");
   test_removal(TEST_URI, TEST_WWW_URI, function() {