Bug 566489 - Enable inline autocomplete again, but make it smarter.
authorMichael Ventnor<ventnor.bugzilla@gmail.com>
Thu, 19 Jan 2012 12:31:24 +0100
changeset 84888 953bde82b7a7d2ea4408b1792791612ac8434214
parent 84887 3898e1c52fa332cfb80c449bb4e2be48229da8d1
child 84889 2d06b0b645b57c9bd56cc7cc68d97998eebcbf38
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)
bugs566489
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 566489 - Enable inline autocomplete again, but make it smarter. Original patch by Michael Ventnor, further improved by David Dahl <ddahl@mozilla.com>. r=mak
browser/app/profile/firefox.js
browser/base/content/browser.xul
browser/base/content/openLocation.xul
toolkit/components/autocomplete/nsAutoCompleteController.cpp
toolkit/components/places/nsPlacesAutoComplete.js
toolkit/components/places/nsPlacesAutoComplete.manifest
toolkit/content/widgets/autocomplete.xml
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -276,30 +276,30 @@ pref("browser.urlbar.clickSelectsAll", f
 #else
 pref("browser.urlbar.clickSelectsAll", true);
 #endif
 #ifdef UNIX_BUT_NOT_MAC
 pref("browser.urlbar.doubleClickSelectsAll", true);
 #else
 pref("browser.urlbar.doubleClickSelectsAll", false);
 #endif
-pref("browser.urlbar.autoFill", false);
+pref("browser.urlbar.autoFill", true);
 // 0: Match anywhere (e.g., middle of words)
 // 1: Match on word boundaries and then try matching anywhere
 // 2: Match only on word boundaries (e.g., after / or .)
 // 3: Match at the beginning of the url or title
 pref("browser.urlbar.matchBehavior", 1);
 pref("browser.urlbar.filter.javascript", true);
 
 // the maximum number of results to show in autocomplete when doing richResults
 pref("browser.urlbar.maxRichResults", 12);
 // The amount of time (ms) to wait after the user has stopped typing
 // before starting to perform autocomplete.  50 is the default set in
 // autocomplete.xml.
-pref("browser.urlbar.delay", 50);
+pref("browser.urlbar.delay", 0);
 
 // The special characters below can be typed into the urlbar to either restrict
 // the search to visited history, bookmarked, tagged pages; or force a match on
 // just the title text or url.
 pref("browser.urlbar.restrict.history", "^");
 pref("browser.urlbar.restrict.bookmark", "*");
 pref("browser.urlbar.restrict.tag", "+");
 pref("browser.urlbar.restrict.openpage", "%");
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -497,17 +497,17 @@
         </dummyobservertarget>
       </toolbaritem>
 
       <toolbaritem id="urlbar-container" align="center" flex="400" persist="width" combined="true"
                    title="&locationItem.title;" class="chromeclass-location" removable="true">
         <textbox id="urlbar" flex="1"
                  placeholder="&urlbar.placeholder;"
                  type="autocomplete"
-                 autocompletesearch="history"
+                 autocompletesearch="urlinline history"
                  autocompletesearchparam="enable-actions"
                  autocompletepopup="PopupAutoCompleteRichResult"
                  completeselectedindex="true"
                  tabscrolling="true"
                  showcommentcolumn="true"
                  showimagecolumn="true"
                  enablehistory="true"
                  maxrows="6"
--- a/browser/base/content/openLocation.xul
+++ b/browser/base/content/openLocation.xul
@@ -64,17 +64,17 @@
     <separator orient="vertical" class="thin"/>
     <vbox flex="1">
       <description>&enter.label;</description>
       <separator class="thin"/>
 
       <hbox align="center">
         <textbox id="dialog.input" flex="1" type="autocomplete"
                   completeselectedindex="true"
-                  autocompletesearch="history"
+                  autocompletesearch="urlinline history"
                   enablehistory="true"
                   class="uri-element"
                   oninput="doEnabling();"/>
         <button label="&chooseFile.label;" oncommand="onChooseFile();"/>
       </hbox>
       <hbox align="center">
         <label value="&openWhere.label;"/>
         <menulist id="openWhereList">
--- a/toolkit/components/autocomplete/nsAutoCompleteController.cpp
+++ b/toolkit/components/autocomplete/nsAutoCompleteController.cpp
@@ -1093,16 +1093,22 @@ nsAutoCompleteController::StartSearchTim
   // If we don't check for this, we won't be able to cancel the original timer
   // and may crash when it fires (bug 236659).
   if (mTimer || !mInput)
     return NS_OK;
 
   PRUint32 timeout;
   mInput->GetTimeout(&timeout);
 
+  if (timeout == 0) {
+    // The consumer wants to execute the search synchronously
+    StartSearch();
+    return NS_OK;
+  }
+
   nsresult rv;
   mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
   if (NS_FAILED(rv))
       return rv;
   rv = mTimer->InitWithCallback(this, timeout, nsITimer::TYPE_ONE_SHOT);
   if (NS_FAILED(rv))
       mTimer = nsnull;
 
--- a/toolkit/components/places/nsPlacesAutoComplete.js
+++ b/toolkit/components/places/nsPlacesAutoComplete.js
@@ -89,23 +89,33 @@ const kQueryIndexPlaceId = 8;
 const kQueryIndexQueryType = 9;
 const kQueryIndexOpenPageCount = 10;
 
 // AutoComplete query type constants.  Describes the various types of queries
 // that we can process.
 const kQueryTypeKeyword = 0;
 const kQueryTypeFiltered = 1;
 
+// Autocomplete minimum time before query is executed
+const kAsyncQueriesWaitTime = 50;
+
 // 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.";
+const kBrowserUrlbarAutofillPref = "browser.urlbar.autoFill";
 
+////////////////////////////////////////////////////////////////////////////////
+//// Globals
+
+XPCOMUtils.defineLazyServiceGetter(this, "gTextURIService",
+                                   "@mozilla.org/intl/texttosuburi;1",
+                                   "nsITextToSubURI");
 
 ////////////////////////////////////////////////////////////////////////////////
 //// Helpers
 
 /**
  * Initializes our temporary table on a given database.
  *
  * @param aDatabase
@@ -134,16 +144,77 @@ function initTempTable(aDatabase)
   +   "DELETE FROM moz_openpages_temp "
   +   "WHERE url = NEW.url; "
   + "END "
   );
   stmt.executeAsync();
   stmt.finalize();
 }
 
+/**
+ * Used to unescape encoded URI strings, and drop information that we do not
+ * care about for searching.
+ *
+ * @param aURIString
+ *        The text to unescape and modify.
+ * @return the modified uri.
+ */
+function fixupSearchText(aURIString)
+{
+  let uri = aURIString;
+
+  if (uri.indexOf("http://") == 0) {
+    uri = uri.slice(7);
+  }
+  else if (uri.indexOf("https://") == 0) {
+    uri = uri.slice(8);
+  }
+  else if (uri.indexOf("ftp://") == 0) {
+    uri = uri.slice(6);
+  }
+
+  if (uri.indexOf("www.") == 0) {
+    uri = uri.slice(4);
+  }
+
+  return gTextURIService.unEscapeURIForUI("UTF-8", uri);
+}
+
+/**
+ * safePrefGetter get the pref with typo safety.
+ * This will return the default value provided if no pref is set.
+ *
+ * @param aPrefBranch
+ *        The nsIPrefBranch2 containing the required preference
+ * @param aName
+ *        A preference name
+ * @param aDefault
+ *        The preference's default value
+ * @return the preference value or provided default
+ */
+
+function safePrefGetter(aPrefBranch, aName, aDefault) {
+  let types = {
+    boolean: "Bool",
+    number: "Int",
+    string: "Char"
+  };
+  let type = types[typeof(aDefault)];
+  if (!type) {
+    throw "Unknown type!";
+  }
+  // If the pref isn't set, we want to use the default.
+  try {
+    return aPrefBranch["get" + type + "Pref"](aName);
+  }
+  catch (e) {
+    return aDefault;
+  }
+}
+
 
 ////////////////////////////////////////////////////////////////////////////////
 //// AutoCompleteStatementCallbackWrapper class
 
 /**
  * Wraps a callback and ensures that handleCompletion is not dispatched if the
  * query is no longer tracked.
  *
@@ -173,18 +244,19 @@ AutoCompleteStatementCallbackWrapper.pro
     this._callback.handleError.apply(this._callback, arguments);
   },
 
   handleCompletion: function ACSCW_handleCompletion(aReason)
   {
     // Only dispatch handleCompletion if we are not done searching and are a
     // pending search.
     let callback = this._callback;
-    if (!callback.isSearchComplete() && callback.isPendingSearch(this._handle))
+    if (!callback.isSearchComplete() && callback.isPendingSearch(this._handle)) {
       callback.handleCompletion.apply(callback, arguments);
+    }
   },
 
   //////////////////////////////////////////////////////////////////////////////
   //// AutoCompleteStatementCallbackWrapper
 
   /**
    * Executes the specified query asynchronously.  This object will notify
    * this._callback if we should notify (logic explained in handleCompletion).
@@ -270,20 +342,16 @@ function nsPlacesAutoComplete()
 
     return db;
   });
 
   XPCOMUtils.defineLazyServiceGetter(this, "_bh",
                                      "@mozilla.org/browser/global-history;2",
                                      "nsIBrowserHistory");
 
-  XPCOMUtils.defineLazyServiceGetter(this, "_textURIService",
-                                     "@mozilla.org/intl/texttosuburi;1",
-                                     "nsITextToSubURI");
-
   XPCOMUtils.defineLazyServiceGetter(this, "_bs",
                                      "@mozilla.org/browser/nav-bookmarks-service;1",
                                      "nsINavBookmarksService");
 
   XPCOMUtils.defineLazyServiceGetter(this, "_ioService",
                                      "@mozilla.org/network/io-service;1",
                                      "nsIIOService");
 
@@ -435,94 +503,57 @@ function nsPlacesAutoComplete()
 
 nsPlacesAutoComplete.prototype = {
   //////////////////////////////////////////////////////////////////////////////
   //// nsIAutoCompleteSearch
 
   startSearch: function PAC_startSearch(aSearchString, aSearchParam,
                                         aPreviousResult, aListener)
   {
-    // If a previous query is running and the controller has not taken care
-    // of stopping it, kill it.
-    if ("_pendingQuery" in this)
-      this.stopSearch();
-
-    // Note: We don't use aPreviousResult to make sure ordering of results are
-    //       consistent.  See bug 412730 for more details.
-
-    // We want to store the original string with no leading or trailing
-    // whitespace for case sensitive searches.
-    this._originalSearchString = aSearchString.trim();
-
-    this._currentSearchString =
-      this._fixupSearchText(this._originalSearchString.toLowerCase());
-
-    var searchParamParts = aSearchParam.split(" ");
-    this._enableActions = searchParamParts.indexOf("enable-actions") != -1;
-
-    this._listener = aListener;
-    let result = Cc["@mozilla.org/autocomplete/simple-result;1"].
-                 createInstance(Ci.nsIAutoCompleteSimpleResult);
-    result.setSearchString(aSearchString);
-    result.setListener(this);
-    this._result = result;
+    // Stop the search in case the controller has not taken care of it.
+    this.stopSearch();
 
-    // If we are not enabled, we need to return now.
-    if (!this._enabled) {
-      this._finishSearch(true);
-      return;
-    }
-
-    // Reset our search behavior to the default.
-    if (this._currentSearchString)
-      this._behavior = this._defaultBehavior;
-    else
-      this._behavior = this._emptySearchDefaultBehavior;
-
-    // For any given search, we run up to four queries:
-    // 1) keywords (this._keywordQuery)
-    // 2) adaptive learning (this._adaptiveQuery)
-    // 3) open pages not supported by history (this._openPagesQuery)
-    // 4) query from this._getSearch
-    // (1) only gets ran if we get any filtered tokens from this._getSearch,
-    // since if there are no tokens, there is nothing to match, so there is no
-    // reason to run the query).
-    let {query, tokens} =
-      this._getSearch(this._getUnfilteredSearchTokens(this._currentSearchString));
-    let queries = tokens.length ?
-      [this._getBoundKeywordQuery(tokens), this._getBoundAdaptiveQuery(), this._getBoundOpenPagesQuery(tokens), query] :
-      [this._getBoundAdaptiveQuery(), this._getBoundOpenPagesQuery(tokens), query];
-
-    // Start executing our queries.
-    this._telemetryStartTime = Date.now();
-    this._executeQueries(queries);
-
-    // Set up our persistent state for the duration of the search.
-    this._searchTokens = tokens;
-    this._usedPlaces = {};
+    // Unlike urlInlineComplete, we don't want this search to start
+    // synchronously. Wait kAsyncQueriesWaitTime before launching the query.
+    this._startTimer = Cc["@mozilla.org/timer;1"]
+                       .createInstance(Ci.nsITimer);
+    let timerCallback = (function() {
+      this._doStartSearch(aSearchString, aSearchParam,
+                          aPreviousResult, aListener);
+    }).bind(this);
+    this._startTimer.initWithCallback(timerCallback,
+                                      kAsyncQueriesWaitTime,
+                                      Ci.nsITimer.TYPE_ONE_SHOT);
   },
 
   stopSearch: function PAC_stopSearch()
   {
     // We need to cancel our searches so we do not get any [more] results.
     // However, it's possible we haven't actually started any searches, so this
     // method may throw because this._pendingQuery may be undefined.
-    if (this._pendingQuery)
+    if (this._pendingQuery) {
       this._stopActiveQuery();
+    }
 
     this._finishSearch(false);
+
+    if (this._startTimer) {
+      this._startTimer.cancel();
+      delete this._startTimer;
+    }
   },
 
   //////////////////////////////////////////////////////////////////////////////
   //// nsIAutoCompleteSimpleResultListener
 
   onValueRemoved: function PAC_onValueRemoved(aResult, aURISpec, aRemoveFromDB)
   {
-    if (aRemoveFromDB)
+    if (aRemoveFromDB) {
       this._bh.removePage(this._ioService.newURI(aURISpec, null, null));
+    }
   },
 
   //////////////////////////////////////////////////////////////////////////////
   //// mozIPlacesAutoComplete
 
   // If the connection has not yet been started, use this local cache.  This
   // prevents autocomplete from initing the database till the first search.
   _openPagesCache: [],
@@ -570,31 +601,33 @@ nsPlacesAutoComplete.prototype = {
         // And finish our search.
         this._finishSearch(true);
         return;
       }
 
     }
 
     // Notify about results if we've gotten them.
-    if (haveMatches)
+    if (haveMatches) {
       this._notifyResults(true);
+    }
   },
 
   handleError: function PAC_handleError(aError)
   {
     Components.utils.reportError("Places AutoComplete: An async statement encountered an " +
                                  "error: " + aError.result + ", '" + aError.message + "'");
   },
 
   handleCompletion: function PAC_handleCompletion(aReason)
   {
     // If we have already finished our search, we should bail out early.
-    if (this.isSearchComplete())
+    if (this.isSearchComplete()) {
       return;
+    }
 
     // If we do not have enough results, and our match type is
     // MATCH_BOUNDARY_ANYWHERE, search again with MATCH_ANYWHERE to get more
     // results.
     if (this._matchBehavior == MATCH_BOUNDARY_ANYWHERE &&
         this._result.matchCount < this._maxRichResults && !this._secondPass) {
       this._secondPass = true;
       let queries = [
@@ -651,39 +684,76 @@ nsPlacesAutoComplete.prototype = {
   },
 
   //////////////////////////////////////////////////////////////////////////////
   //// nsPlacesAutoComplete
 
   get _databaseInitialized()
     Object.getOwnPropertyDescriptor(this, "_db").value !== undefined,
 
-  /**
-   * Used to unescape encoded URI strings, and drop information that we do not
-   * care about for searching.
-   *
-   * @param aURIString
-   *        The text to unescape and modify.
-   * @return the modified uri.
-   */
-  _fixupSearchText: function PAC_fixupSearchText(aURIString)
+  _doStartSearch: function PAC_doStartSearch(aSearchString, aSearchParam,
+                                             aPreviousResult, aListener)
   {
-    let uri = aURIString;
+    this._startTimer.cancel();
+    delete this._startTimer;
+
+    // Note: We don't use aPreviousResult to make sure ordering of results are
+    //       consistent.  See bug 412730 for more details.
+
+    // We want to store the original string with no leading or trailing
+    // whitespace for case sensitive searches.
+    this._originalSearchString = aSearchString.trim();
+
+    this._currentSearchString =
+      fixupSearchText(this._originalSearchString.toLowerCase());
+
+    let searchParamParts = aSearchParam.split(" ");
+    this._enableActions = searchParamParts.indexOf("enable-actions") != -1;
+
+    this._listener = aListener;
+    let result = Cc["@mozilla.org/autocomplete/simple-result;1"].
+                 createInstance(Ci.nsIAutoCompleteSimpleResult);
+    result.setSearchString(aSearchString);
+    result.setListener(this);
+    this._result = result;
 
-    if (uri.indexOf("http://") == 0)
-      uri = uri.slice(7);
-    else if (uri.indexOf("https://") == 0)
-      uri = uri.slice(8);
-    else if (uri.indexOf("ftp://") == 0)
-      uri = uri.slice(6);
+    // If we are not enabled, we need to return now.
+    if (!this._enabled) {
+      this._finishSearch(true);
+      return;
+    }
 
-    if (uri.indexOf("www.") == 0)
-      uri = uri.slice(4);
+    // Reset our search behavior to the default.
+    if (this._currentSearchString) {
+      this._behavior = this._defaultBehavior;
+    }
+    else {
+      this._behavior = this._emptySearchDefaultBehavior;
+    }
+    // For any given search, we run up to four queries:
+    // 1) keywords (this._keywordQuery)
+    // 2) adaptive learning (this._adaptiveQuery)
+    // 3) open pages not supported by history (this._openPagesQuery)
+    // 4) query from this._getSearch
+    // (1) only gets ran if we get any filtered tokens from this._getSearch,
+    // since if there are no tokens, there is nothing to match, so there is no
+    // reason to run the query).
+    let {query, tokens} =
+      this._getSearch(this._getUnfilteredSearchTokens(this._currentSearchString));
+    let queries = tokens.length ?
+      [this._getBoundKeywordQuery(tokens), this._getBoundAdaptiveQuery(), this._getBoundOpenPagesQuery(tokens), query] :
+      [this._getBoundAdaptiveQuery(), this._getBoundOpenPagesQuery(tokens), query];
 
-    return this._textURIService.unEscapeURIForUI("UTF-8", uri);
+    // Start executing our queries.
+    this._telemetryStartTime = Date.now();
+    this._executeQueries(queries);
+
+    // Set up our persistent state for the duration of the search.
+    this._searchTokens = tokens;
+    this._usedPlaces = {};
   },
 
   /**
    * Generates the tokens used in searching from a given string.
    *
    * @param aSearchString
    *        The string to generate tokens from.
    * @return an array of tokens.
@@ -701,18 +771,19 @@ nsPlacesAutoComplete.prototype = {
    *
    * @param aNotify
    *        Indicates if we should notify the AutoComplete listener about our
    *        results or not.
    */
   _finishSearch: function PAC_finishSearch(aNotify)
   {
     // Notify about results if we are supposed to.
-    if (aNotify)
+    if (aNotify) {
       this._notifyResults(false);
+    }
 
     // Clear our state
     delete this._originalSearchString;
     delete this._currentSearchString;
     delete this._searchTokens;
     delete this._listener;
     delete this._result;
     delete this._usedPlaces;
@@ -752,20 +823,20 @@ nsPlacesAutoComplete.prototype = {
    *
    * @param aSearchOngoing
    *        Indicates if the search is ongoing or not.
    */
   _notifyResults: function PAC_notifyResults(aSearchOngoing)
   {
     let result = this._result;
     let resultCode = result.matchCount ? "RESULT_SUCCESS" : "RESULT_NOMATCH";
-    if (aSearchOngoing)
+    if (aSearchOngoing) {
       resultCode += "_ONGOING";
+    }
     result.setSearchResult(Ci.nsIAutoCompleteResult[resultCode]);
-    result.setDefaultIndex(result.matchCount ? 0 : -1);
     this._listener.onSearchResult(this, result);
     if (this._telemetryStartTime) {
       let elapsed = Date.now() - this._telemetryStartTime;
       if (elapsed > 50) {
         try {
           Services.telemetry
                   .getHistogramById("PLACES_AUTOCOMPLETE_1ST_RESULT_TIME_MS")
                   .add(elapsed);
@@ -782,60 +853,47 @@ nsPlacesAutoComplete.prototype = {
    *
    * @param [optional] aRegisterObserver
    *        Indicates if the preference observer should be added or not.  The
    *        default value is false.
    */
   _loadPrefs: function PAC_loadPrefs(aRegisterObserver)
   {
     let self = this;
-    function safeGetter(aName, aDefault) {
-      let types = {
-        boolean: "Bool",
-        number: "Int",
-        string: "Char"
-      };
-      let type = types[typeof(aDefault)];
-      if (!type)
-        throw "Unknown type!";
 
-      // If the pref isn't set, we want to use the default.
-      try {
-        return self._prefs["get" + type + "Pref"](aName);
-      }
-      catch (e) {
-        return aDefault;
-      }
-    }
-
-    this._enabled = safeGetter("autocomplete.enabled", true);
-    this._matchBehavior = safeGetter("matchBehavior", MATCH_BOUNDARY_ANYWHERE);
-    this._filterJavaScript = safeGetter("filter.javascript", true);
-    this._maxRichResults = safeGetter("maxRichResults", 25);
-    this._restrictHistoryToken = safeGetter("restrict.history", "^");
-    this._restrictBookmarkToken = safeGetter("restrict.bookmark", "*");
-    this._restrictTypedToken = safeGetter("restrict.typed", "~");
-    this._restrictTagToken = safeGetter("restrict.tag", "+");
-    this._restrictOpenPageToken = safeGetter("restrict.openpage", "%");
-    this._matchTitleToken = safeGetter("match.title", "#");
-    this._matchURLToken = safeGetter("match.url", "@");
-    this._defaultBehavior = safeGetter("default.behavior", 0);
+    this._enabled = safePrefGetter(this._prefs, "autocomplete.enabled", true);
+    this._matchBehavior = safePrefGetter(this._prefs,
+                                         "matchBehavior",
+                                         MATCH_BOUNDARY_ANYWHERE);
+    this._filterJavaScript = safePrefGetter(this._prefs, "filter.javascript", true);
+    this._maxRichResults = safePrefGetter(this._prefs, "maxRichResults", 25);
+    this._restrictHistoryToken = safePrefGetter(this._prefs,
+                                                "restrict.history", "^");
+    this._restrictBookmarkToken = safePrefGetter(this._prefs,
+                                                 "restrict.bookmark", "*");
+    this._restrictTypedToken = safePrefGetter(this._prefs, "restrict.typed", "~");
+    this._restrictTagToken = safePrefGetter(this._prefs, "restrict.tag", "+");
+    this._restrictOpenPageToken = safePrefGetter(this._prefs,
+                                                 "restrict.openpage", "%");
+    this._matchTitleToken = safePrefGetter(this._prefs, "match.title", "#");
+    this._matchURLToken = safePrefGetter(this._prefs, "match.url", "@");
+    this._defaultBehavior = safePrefGetter(this._prefs, "default.behavior", 0);
     // Further restrictions to apply for "empty searches" (i.e. searches for "").
     this._emptySearchDefaultBehavior =
       this._defaultBehavior |
-      safeGetter("default.behavior.emptyRestriction",
-                 Ci.mozIPlacesAutoComplete.BEHAVIOR_HISTORY |
-                 Ci.mozIPlacesAutoComplete.BEHAVIOR_TYPED);
+      safePrefGetter(this._prefs, "default.behavior.emptyRestriction",
+                     Ci.mozIPlacesAutoComplete.BEHAVIOR_HISTORY |
+                     Ci.mozIPlacesAutoComplete.BEHAVIOR_TYPED);
 
     // Validate matchBehavior; default to MATCH_BOUNDARY_ANYWHERE.
     if (this._matchBehavior != MATCH_ANYWHERE &&
         this._matchBehavior != MATCH_BOUNDARY &&
-        this._matchBehavior != MATCH_BEGINNING)
+        this._matchBehavior != MATCH_BEGINNING) {
       this._matchBehavior = MATCH_BOUNDARY_ANYWHERE;
-
+    }
     // register observer
     if (aRegisterObserver) {
       let pb = this._prefs.QueryInterface(Ci.nsIPrefBranch2);
       pb.addObserver("", this, false);
     }
   },
 
   /**
@@ -860,18 +918,19 @@ nsPlacesAutoComplete.prototype = {
           break;
         case this._restrictBookmarkToken:
           this._setBehavior("bookmark");
           break;
         case this._restrictTagToken:
           this._setBehavior("tag");
           break;
         case this._restrictOpenPageToken:
-          if (!this._enableActions)
+          if (!this._enableActions) {
             continue;
+          }
           this._setBehavior("openpage");
           break;
         case this._matchTitleToken:
           this._setBehavior("title");
           break;
         case this._matchURLToken:
           this._setBehavior("url");
           break;
@@ -884,18 +943,19 @@ nsPlacesAutoComplete.prototype = {
       };
 
       aTokens.splice(i, 1);
     }
 
     // Set the right JavaScript behavior based on our preference.  Note that the
     // preference is whether or not we should filter JavaScript, and the
     // behavior is if we should search it or not.
-    if (!this._filterJavaScript)
+    if (!this._filterJavaScript) {
       this._setBehavior("javascript");
+    }
 
     return {
       query: this._getBoundSearchQuery(this._matchBehavior, aTokens),
       tokens: aTokens
     };
   },
 
   /**
@@ -973,19 +1033,19 @@ nsPlacesAutoComplete.prototype = {
    */
   _getBoundKeywordQuery: function PAC_getBoundKeywordQuery(aTokens)
   {
     // The keyword is the first word in the search string, with the parameters
     // following it.
     let searchString = this._originalSearchString;
     let queryString = "";
     let queryIndex = searchString.indexOf(" ");
-    if (queryIndex != -1)
+    if (queryIndex != -1) {
       queryString = searchString.substring(queryIndex + 1);
-
+    }
     // We need to escape the parameters as if they were the query in a URL
     queryString = encodeURIComponent(queryString).replace("%20", "+", "g");
 
     // The first word could be a keyword, so that's what we'll search.
     let keyword = aTokens[0];
 
     let query = this._keywordQuery;
     let (params = query.params) {
@@ -1000,18 +1060,19 @@ nsPlacesAutoComplete.prototype = {
   /**
    * Obtains the adaptive query with the properly bound parameters.
    *
    * @return the bound adaptive query.
    */
   _getBoundAdaptiveQuery: function PAC_getBoundAdaptiveQuery(aMatchBehavior)
   {
     // If we were not given a match behavior, use the stored match behavior.
-    if (arguments.length == 0)
+    if (arguments.length == 0) {
       aMatchBehavior = this._matchBehavior;
+    }
 
     let query = this._adaptiveQuery;
     let (params = query.params) {
       params.parent = this._bs.tagsFolder;
       params.search_string = this._currentSearchString;
       params.query_type = kQueryTypeFiltered;
       params.matchBehavior = aMatchBehavior;
       params.searchBehavior = this._behavior;
@@ -1057,50 +1118,55 @@ nsPlacesAutoComplete.prototype = {
     let title = entryBookmarkTitle || entryTitle;
 
     let style;
     if (aRow.getResultByIndex(kQueryIndexQueryType) == kQueryTypeKeyword) {
       // If we do not have a title, then we must have a keyword, so let the UI
       // know it is a keyword.  Otherwise, we found an exact page match, so just
       // show the page like a regular result.  Because the page title is likely
       // going to be more specific than the bookmark title (keyword title).
-      if (!entryTitle)
+      if (!entryTitle) {
         style = "keyword";
-      else
+      }
+      else {
         title = entryTitle;
+      }
     }
 
     // We will always prefer to show tags if we have them.
     let showTags = !!entryTags;
 
     // However, we'll act as if a page is not bookmarked or tagged if the user
     // only wants only history and not bookmarks or tags.
     if (this._hasBehavior("history") &&
         !(this._hasBehavior("bookmark") || this._hasBehavior("tag"))) {
       showTags = false;
       style = "favicon";
     }
 
     // If we have tags and should show them, we need to add them to the title.
-    if (showTags)
+    if (showTags) {
       title += kTitleTagsSeparator + entryTags;
-
+    }
     // We have to determine the right style to display.  Tags show the tag icon,
     // bookmarks get the bookmark icon, and keywords get the keyword icon.  If
     // the result does not fall into any of those, it just gets the favicon.
     if (!style) {
       // It is possible that we already have a style set (from a keyword
       // search or because of the user's preferences), so only set it if we
       // haven't already done so.
-      if (showTags)
+      if (showTags) {
         style = "tag";
-      else if (entryBookmarked)
+      }
+      else if (entryBookmarked) {
         style = "bookmark";
-      else
+      }
+      else {
         style = "favicon";
+      }
     }
 
     this._addToResults(entryId, url, title, entryFavicon, action + style);
     return true;
   },
 
   /**
    * Checks to see if the given place has already been added to the results.
@@ -1221,13 +1287,318 @@ nsPlacesAutoComplete.prototype = {
   classID: Components.ID("d0272978-beab-4adc-a3d4-04b76acfa4e7"),
 
   QueryInterface: XPCOMUtils.generateQI([
     Ci.nsIAutoCompleteSearch,
     Ci.nsIAutoCompleteSimpleResultListener,
     Ci.mozIPlacesAutoComplete,
     Ci.mozIStorageStatementCallback,
     Ci.nsIObserver,
+    Ci.nsISupportsWeakReference,
   ])
 };
 
-let components = [nsPlacesAutoComplete];
+////////////////////////////////////////////////////////////////////////////////
+//// urlInlineComplete class
+
+function urlInlineComplete()
+{
+  this._loadPrefs(true);
+  // register observers
+  Services.obs.addObserver(this, kTopicShutdown, false);
+}
+
+urlInlineComplete.prototype = {
+
+/////////////////////////////////////////////////////////////////////////////////
+//// Database and query getters
+
+  __db: null,
+
+  get _db()
+  {
+    if (!this.__db && this._autofill) {
+      this.__db = Cc["@mozilla.org/browser/nav-history-service;1"].
+        getService(Ci.nsPIPlacesDatabase).
+        DBConnection.
+        clone(true);
+    }
+    return this.__db;
+  },
+
+  __syncQuery: null,
+
+  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(
+        "SELECT host || '/' "
+        + "FROM moz_hosts "
+        + "WHERE host BETWEEN :search_string AND :search_string || X'FFFF' "
+        + "ORDER BY frecency DESC "
+        + "LIMIT 1"
+      );
+    }
+    return this.__syncQuery;
+  },
+
+  __asyncQuery: null,
+
+  get _asyncQuery()
+  {
+    if (!this.__asyncQuery) {
+      this.__asyncQuery = this._db.createAsyncStatement(
+        "SELECT h.url "
+        + "FROM moz_places h "
+        + "WHERE h.frecency <> 0 "
+        +   "AND AUTOCOMPLETE_MATCH(:searchString, h.url, "
+        +                          "h.title, '', "
+        +                          "h.visit_count, h.typed, 0, 0, "
+        +                          ":matchBehavior, :searchBehavior) "
+        + "ORDER BY h.frecency DESC, h.id DESC "
+        + "LIMIT 1"
+      );
+    }
+    return this.__asyncQuery;
+  },
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// nsIAutoCompleteSearch
+
+  startSearch: function UIC_startSearch(aSearchString, aSearchParam,
+                                        aPreviousResult, aListener)
+  {
+    // Stop the search in case the controller has not taken care of it.
+    if (this._pendingQuery) {
+      this.stopSearch();
+    }
+
+    // We want to store the original string with no leading or trailing
+    // whitespace for case sensitive searches.
+    this._originalSearchString = aSearchString;
+    this._currentSearchString =
+      fixupSearchText(this._originalSearchString.toLowerCase());
+
+    let result = Cc["@mozilla.org/autocomplete/simple-result;1"].
+                 createInstance(Ci.nsIAutoCompleteSimpleResult);
+    result.setSearchString(aSearchString);
+    result.setTypeAheadResult(true);
+
+    this._result = result;
+    this._listener = aListener;
+
+    if (this._currentSearchString.length == 0 || !this._db) {
+      this._finishSearch();
+      return;
+    }
+
+    // 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;
+      try {
+        hasDomainResult = query.executeStep();
+        if (hasDomainResult) {
+          domain = query.getString(0);
+        }
+      } finally {
+        query.reset();
+      }
+
+      if (hasDomainResult) {
+        // We got a match for a domain, we can add it immediately.
+        let appendResult = domain.slice(this._currentSearchString.length);
+        result.appendMatch(aSearchString + appendResult, "");
+
+        this._finishSearch();
+        return;
+      }
+    }
+
+    // We did not get a result from the synchronous domain search.
+    // We now do an asynchronous search through places, and complete
+    // up to the next URL separator.
+
+    // First, check if this is necessary.
+    // We don't need to search if we have no "/" separator, or if it's at
+    // the end of the search text.
+    if (lastSlashIndex == -1 ||
+        lastSlashIndex == this._currentSearchString.length - 1) {
+      this._finishSearch();
+      return;
+    }
+
+    // Within the standard autocomplete query, we only search the beginning
+    // of URLs for 1 result.
+    let query = this._asyncQuery;
+    let (params = query.params) {
+      params.matchBehavior = MATCH_BEGINNING;
+      params.searchBehavior = Ci.mozIPlacesAutoComplete["BEHAVIOR_URL"];
+      params.searchString = this._currentSearchString;
+    }
+
+    // Execute the async query
+    let wrapper = new AutoCompleteStatementCallbackWrapper(this, this._db);
+    this._pendingQuery = wrapper.executeAsync([query]);
+  },
+
+  stopSearch: function UIC_stopSearch()
+  {
+    delete this._originalSearchString;
+    delete this._currentSearchString;
+    delete this._result;
+    delete this._listener;
+
+    if (this._pendingQuery) {
+      this._pendingQuery.cancel();
+      delete this._pendingQuery;
+    }
+  },
+
+  /**
+   * Loads the preferences that we care about.
+   *
+   * @param [optional] aRegisterObserver
+   *        Indicates if the preference observer should be added or not.  The
+   *        default value is false.
+   */
+  _loadPrefs: function UIC_loadPrefs(aRegisterObserver)
+  {
+    this._autofill = safePrefGetter(Services.prefs,
+                                    kBrowserUrlbarAutofillPref,
+                                    true);
+    if (aRegisterObserver) {
+      Services.prefs.addObserver(kBrowserUrlbarAutofillPref, this, true);
+    }
+  },
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// mozIStorageStatementCallback
+
+  handleResult: function UIC_handleResult(aResultSet)
+  {
+    let row = aResultSet.getNextRow();
+    let url = fixupSearchText(row.getResultByIndex(0));
+
+    // We must complete the URL up to the next separator (which is /, ? or #).
+    let appendText = url.slice(this._currentSearchString.length);
+    let separatorIndex = appendText.search(/[\/\?\#]/);
+    if (separatorIndex != -1) {
+      if (appendText[separatorIndex] == "/") {
+        separatorIndex++; // Include the "/" separator
+      }
+      appendText = appendText.slice(0, separatorIndex);
+    }
+
+    // Add the result
+    this._result.appendMatch(this._originalSearchString + appendText, "");
+
+    // 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 " +
+                                 "error: " + aError.result + ", '" + aError.message + "'");
+  },
+
+  handleCompletion: function UIC_handleCompletion(aReason)
+  {
+    this._finishSearch();
+  },
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// nsIObserver
+
+  observe: function PAC_observe(aSubject, aTopic, aData)
+  {
+    if (aTopic == kTopicShutdown) {
+      Services.obs.removeObserver(this, kTopicShutdown);
+      this._closeDatabase();
+    }
+    else if (aTopic == kPrefChanged) {
+      this._loadPrefs();
+      if (!this._autofill) {
+        this.stopSearch();
+        this._closeDatabase();
+      }
+    }
+  },
+
+  /**
+   *
+   * Finalize and close the database safely
+   *
+   **/
+  _closeDatabase: function UIC_closeDatabase()
+  {
+    // Finalize the statements that we have used.
+    let stmts = [
+      "__syncQuery",
+      "__asyncQuery",
+    ];
+    for (let i = 0; i < stmts.length; i++) {
+      // We do not want to create any query we haven't already created, so
+      // see if it is a getter first.
+      if (this[stmts[i]]) {
+        this[stmts[i]].finalize();
+        this[stmts[i]] = null;
+      }
+    }
+    if (this.__db) {
+      this._db.asyncClose();
+      this.__db = null;
+    }
+  },
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// urlInlineComplete
+
+  _finishSearch: function UIC_finishSearch()
+  {
+    // Notify the result object
+    let result = this._result;
+
+    if (result.matchCount) {
+      result.setDefaultIndex(0);
+      result.setSearchResult(Ci.nsIAutoCompleteResult["RESULT_SUCCESS"]);
+    } else {
+      result.setDefaultIndex(-1);
+      result.setSearchResult(Ci.nsIAutoCompleteResult["RESULT_NOMATCH"]);
+    }
+
+    this._listener.onSearchResult(this, result);
+    this.stopSearch();
+  },
+
+  isSearchComplete: function UIC_isSearchComplete()
+  {
+    return this._pendingQuery == null;
+  },
+
+  isPendingSearch: function UIC_isPendingSearch(aHandle)
+  {
+    return this._pendingQuery == aHandle;
+  },
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// nsISupports
+
+  classID: Components.ID("c88fae2d-25cf-4338-a1f4-64a320ea7440"),
+
+  QueryInterface: XPCOMUtils.generateQI([
+    Ci.nsIAutoCompleteSearch,
+    Ci.mozIStorageStatementCallback,
+    Ci.nsIObserver,
+    Ci.nsISupportsWeakReference,
+  ])
+};
+
+let components = [nsPlacesAutoComplete, urlInlineComplete];
 const NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
--- a/toolkit/components/places/nsPlacesAutoComplete.manifest
+++ b/toolkit/components/places/nsPlacesAutoComplete.manifest
@@ -1,2 +1,6 @@
 component {d0272978-beab-4adc-a3d4-04b76acfa4e7} nsPlacesAutoComplete.js
 contract @mozilla.org/autocomplete/search;1?name=history {d0272978-beab-4adc-a3d4-04b76acfa4e7}
+
+component {c88fae2d-25cf-4338-a1f4-64a320ea7440} nsPlacesAutoComplete.js
+contract @mozilla.org/autocomplete/search;1?name=urlinline {c88fae2d-25cf-4338-a1f4-64a320ea7440}
+
--- a/toolkit/content/widgets/autocomplete.xml
+++ b/toolkit/content/widgets/autocomplete.xml
@@ -161,17 +161,17 @@
                 onget="return this.getAttribute('showcommentcolumn') == 'true';"/>
 
       <property name="showImageColumn"
                 onset="this.setAttribute('showimagecolumn', val); return val;"
                 onget="return this.getAttribute('showimagecolumn') == 'true';"/>
 
       <property name="timeout"
                 onset="this.setAttribute('timeout', val); return val;"
-                onget="return parseInt(this.getAttribute('timeout')) || 50;"/>
+                onget="var t = parseInt(this.getAttribute('timeout')); return isNaN(t) ? 50 : t;"/>
 
       <property name="searchParam"
                 onget="return this.getAttribute('autocompletesearchparam') || '';"
                 onset="this.setAttribute('autocompletesearchparam', val); return val;"/>
 
       <property name="searchCount" readonly="true"
                 onget="this.initSearchNames(); return this.mSearchNames.length;"/>