Bug 429531 - Location bar should show non-word-boundary matches below word-boundary matches. r=dietrich, ui-r=beltzner, b-ff3=beltzner, a1.9=beltzner
authoredward.lee@engineering.uiuc.edu
Wed, 23 Apr 2008 21:20:05 -0700
changeset 14653 355b8f5bdc370810dbc5c98ee9b4b08678e68f1c
parent 14652 55271d850c5616ca9344059e47cbefd48820f88f
child 14654 2600d76cf1e724ae68763be32ed324e953e78573
push id14
push userbsmedberg@mozilla.com
push dateTue, 29 Apr 2008 14:30:10 +0000
treeherdermozilla-central@78e482f2d4be [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdietrich, beltzner
bugs429531
milestone1.9pre
Bug 429531 - Location bar should show non-word-boundary matches below word-boundary matches. r=dietrich, ui-r=beltzner, b-ff3=beltzner, a1.9=beltzner
browser/app/profile/firefox.js
toolkit/components/places/src/nsNavHistory.cpp
toolkit/components/places/src/nsNavHistory.h
toolkit/components/places/src/nsNavHistoryAutoComplete.cpp
toolkit/components/places/tests/autocomplete/test_word_boundary_search.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -208,17 +208,20 @@ pref("browser.urlbar.clickSelectsAll", t
 #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.matchOnlyTyped", false);
-pref("browser.urlbar.matchOnWordBoundary", 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 .)
+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);
 // Size of "chunks" affects the number of places to process between each search
 // timeout (ms). Too big and the UI will be unresponsive; too small and we'll
 // be waiting on the timeout too often without many results.
 pref("browser.urlbar.search.chunkSize", 1000);
--- a/toolkit/components/places/src/nsNavHistory.cpp
+++ b/toolkit/components/places/src/nsNavHistory.cpp
@@ -108,17 +108,17 @@
 #define RECENT_EVENT_QUEUE_MAX_LENGTH 128
 
 // preference ID strings
 #define PREF_BRANCH_BASE                        "browser."
 #define PREF_BROWSER_HISTORY_EXPIRE_DAYS_MIN    "history_expire_days_min"
 #define PREF_BROWSER_HISTORY_EXPIRE_DAYS_MAX    "history_expire_days"
 #define PREF_BROWSER_HISTORY_EXPIRE_SITES       "history_expire_sites"
 #define PREF_AUTOCOMPLETE_ONLY_TYPED            "urlbar.matchOnlyTyped"
-#define PREF_AUTOCOMPLETE_ON_WORD_BOUNDARY      "urlbar.matchOnWordBoundary"
+#define PREF_AUTOCOMPLETE_MATCH_BEHAVIOR        "urlbar.matchBehavior"
 #define PREF_AUTOCOMPLETE_FILTER_JAVASCRIPT     "urlbar.filter.javascript"
 #define PREF_AUTOCOMPLETE_ENABLED               "urlbar.autocomplete.enabled"
 #define PREF_AUTOCOMPLETE_MAX_RICH_RESULTS      "urlbar.maxRichResults"
 #define PREF_AUTOCOMPLETE_SEARCH_CHUNK_SIZE     "urlbar.search.chunkSize"
 #define PREF_AUTOCOMPLETE_SEARCH_TIMEOUT        "urlbar.search.timeout"
 #define PREF_DB_CACHE_PERCENTAGE                "history_cache_percentage"
 #define PREF_FRECENCY_NUM_VISITS                "places.frecency.numVisits"
 #define PREF_FRECENCY_CALC_ON_IDLE              "places.frecency.numCalcOnIdle"
@@ -324,17 +324,17 @@ nsNavHistory::GetSingleton()
 // nsNavHistory::nsNavHistory
 
 nsNavHistory::nsNavHistory() : mBatchLevel(0),
                                mBatchHasTransaction(PR_FALSE),
                                mNowValid(PR_FALSE),
                                mExpireNowTimer(nsnull),
                                mExpire(this),
                                mAutoCompleteOnlyTyped(PR_FALSE),
-                               mAutoCompleteOnWordBoundary(PR_TRUE),
+                               mAutoCompleteMatchBehavior(MATCH_BOUNDARY_ANYWHERE),
                                mAutoCompleteMaxResults(25),
                                mAutoCompleteSearchChunkSize(100),
                                mAutoCompleteSearchTimeout(100),
                                mPreviousChunkOffset(-1),
                                mAutoCompleteFinishedSearch(PR_FALSE),
                                mExpireDaysMin(0),
                                mExpireDaysMax(0),
                                mExpireSites(0),
@@ -469,17 +469,17 @@ nsNavHistory::Init()
 
   nsCOMPtr<nsIObserverService> observerService =
     do_GetService("@mozilla.org/observer-service;1", &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIPrefBranch2> pbi = do_QueryInterface(mPrefBranch);
   if (pbi) {
     pbi->AddObserver(PREF_AUTOCOMPLETE_ONLY_TYPED, this, PR_FALSE);
-    pbi->AddObserver(PREF_AUTOCOMPLETE_ON_WORD_BOUNDARY, this, PR_FALSE);
+    pbi->AddObserver(PREF_AUTOCOMPLETE_MATCH_BEHAVIOR, this, PR_FALSE);
     pbi->AddObserver(PREF_AUTOCOMPLETE_FILTER_JAVASCRIPT, this, PR_FALSE);
     pbi->AddObserver(PREF_AUTOCOMPLETE_MAX_RICH_RESULTS, this, PR_FALSE);
     pbi->AddObserver(PREF_AUTOCOMPLETE_SEARCH_CHUNK_SIZE, this, PR_FALSE);
     pbi->AddObserver(PREF_AUTOCOMPLETE_SEARCH_TIMEOUT, this, PR_FALSE);
     pbi->AddObserver(PREF_BROWSER_HISTORY_EXPIRE_DAYS_MAX, this, PR_FALSE);
     pbi->AddObserver(PREF_BROWSER_HISTORY_EXPIRE_DAYS_MIN, this, PR_FALSE);
     pbi->AddObserver(PREF_BROWSER_HISTORY_EXPIRE_SITES, this, PR_FALSE);
   }
@@ -1871,18 +1871,32 @@ nsNavHistory::LoadPrefs(PRBool aInitiali
   if (NS_FAILED(mPrefBranch->GetIntPref(PREF_BROWSER_HISTORY_EXPIRE_SITES,
                                         &mExpireSites)))
     mExpireSites = EXPIRATION_CAP_SITES;
   
 #ifdef MOZ_XUL
   PRBool oldCompleteOnlyTyped = mAutoCompleteOnlyTyped;
   mPrefBranch->GetBoolPref(PREF_AUTOCOMPLETE_ONLY_TYPED,
                            &mAutoCompleteOnlyTyped);
-  mPrefBranch->GetBoolPref(PREF_AUTOCOMPLETE_ON_WORD_BOUNDARY,
-                           &mAutoCompleteOnWordBoundary);
+
+  PRInt32 matchBehavior;
+  mPrefBranch->GetIntPref(PREF_AUTOCOMPLETE_MATCH_BEHAVIOR,
+                          &matchBehavior);
+  switch (matchBehavior) {
+    case 0:
+      mAutoCompleteMatchBehavior = MATCH_ANYWHERE;
+      break;
+    case 2:
+      mAutoCompleteMatchBehavior = MATCH_BOUNDARY;
+      break;
+    default:
+      mAutoCompleteMatchBehavior = MATCH_BOUNDARY_ANYWHERE;
+      break;
+  }
+
   mPrefBranch->GetBoolPref(PREF_AUTOCOMPLETE_FILTER_JAVASCRIPT,
                            &mAutoCompleteFilterJavascript);
   mPrefBranch->GetIntPref(PREF_AUTOCOMPLETE_MAX_RICH_RESULTS,
                           &mAutoCompleteMaxResults);
   mPrefBranch->GetIntPref(PREF_AUTOCOMPLETE_SEARCH_CHUNK_SIZE,
                           &mAutoCompleteSearchChunkSize);
   mPrefBranch->GetIntPref(PREF_AUTOCOMPLETE_SEARCH_TIMEOUT,
                           &mAutoCompleteSearchTimeout);
--- a/toolkit/components/places/src/nsNavHistory.h
+++ b/toolkit/components/places/src/nsNavHistory.h
@@ -658,20 +658,30 @@ protected:
   static const PRInt32 kAutoCompleteIndex_ParentId;
   static const PRInt32 kAutoCompleteIndex_BookmarkTitle;
   static const PRInt32 kAutoCompleteIndex_Tags;
   nsCOMPtr<mozIStorageStatement> mDBAutoCompleteQuery; //  kAutoCompleteIndex_* results
   nsCOMPtr<mozIStorageStatement> mDBPreviousQuery; //  kAutoCompleteIndex_* results
   nsCOMPtr<mozIStorageStatement> mDBAdaptiveQuery; //  kAutoCompleteIndex_* results
   nsCOMPtr<mozIStorageStatement> mDBFeedbackIncrease;
 
+  /**
+   * AutoComplete word matching behavior to determine if words should match on
+   * word boundaries or not or both.
+   */
+  enum MatchType {
+    MATCH_ANYWHERE,
+    MATCH_BOUNDARY_ANYWHERE,
+    MATCH_BOUNDARY
+  };
+
   nsresult InitAutoComplete();
   nsresult CreateAutoCompleteQueries();
   PRBool mAutoCompleteOnlyTyped;
-  PRBool mAutoCompleteOnWordBoundary;
+  MatchType mAutoCompleteMatchBehavior;
   PRBool mAutoCompleteFilterJavascript;
   PRInt32 mAutoCompleteMaxResults;
   PRInt32 mAutoCompleteSearchChunkSize;
   PRInt32 mAutoCompleteSearchTimeout;
   nsCOMPtr<nsITimer> mAutoCompleteTimer;
 
   // Search string and tokens for case-insensitive matching
   nsString mCurrentSearchString;
@@ -682,16 +692,18 @@ protected:
   nsresult AutoCompleteFeedback(PRInt32 aIndex,
                                 nsIAutoCompleteController *aController);
 
 #ifdef MOZ_XUL
   nsCOMPtr<nsIAutoCompleteObserver> mCurrentListener;
   nsCOMPtr<nsIAutoCompleteSimpleResult> mCurrentResult;
 #endif
 
+  MatchType mCurrentMatchType;
+  MatchType mPreviousMatchType;
   nsDataHashtable<nsStringHashKey, PRBool> mCurrentResultURLs;
   PRInt32 mCurrentChunkOffset;
   PRInt32 mPreviousChunkOffset;
 
   nsDataHashtable<nsTrimInt64HashKey, PRBool> mLivemarkFeedItemIds;
   nsDataHashtable<nsStringHashKey, PRBool> mLivemarkFeedURIs;
 
   nsresult AutoCompleteFullHistorySearch(PRBool* aHasMoreResults);
--- a/toolkit/components/places/src/nsNavHistoryAutoComplete.cpp
+++ b/toolkit/components/places/src/nsNavHistoryAutoComplete.cpp
@@ -355,22 +355,32 @@ nsNavHistory::PerformAutoComplete()
       mCurrentChunkOffset = mPreviousChunkOffset - mAutoCompleteSearchChunkSize;
   } else {
     rv = AutoCompleteFullHistorySearch(&moreChunksToSearch);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   // If we ran out of pages to search, set offset to -1, so we can tell the
   // difference between completing and stopping because we have enough results
-  if (!moreChunksToSearch)
-    mCurrentChunkOffset = -1;
+  PRBool notEnoughResults = !AutoCompleteHasEnoughResults();
+  if (!moreChunksToSearch) {
+    // But check first to see if we don't have enough results, and we're
+    // matching word boundaries, so try again without the match restriction
+    if (notEnoughResults && mCurrentMatchType == MATCH_BOUNDARY_ANYWHERE) {
+      mCurrentMatchType = MATCH_ANYWHERE;
+      mCurrentChunkOffset = -mAutoCompleteSearchChunkSize;
+      moreChunksToSearch = PR_TRUE;
+    } else {
+      mCurrentChunkOffset = -1;
+    }
+  } else {
+    // We know that we do have more chunks, so make sure we want more results
+    moreChunksToSearch = notEnoughResults;
+  }
 
-  // Only search more chunks if there are more and we need more results
-  moreChunksToSearch &= !AutoCompleteHasEnoughResults();
- 
   // Determine the result of the search
   PRUint32 count;
   mCurrentResult->GetMatchCount(&count); 
 
   if (count > 0) {
     mCurrentResult->SetSearchResult(moreChunksToSearch ?
       nsIAutoCompleteResult::RESULT_SUCCESS_ONGOING :
       nsIAutoCompleteResult::RESULT_SUCCESS);
@@ -397,16 +407,17 @@ nsNavHistory::PerformAutoComplete()
     DoneSearching(PR_TRUE);
   }
   return NS_OK;
 }
 
 void
 nsNavHistory::DoneSearching(PRBool aFinished)
 {
+  mPreviousMatchType = mCurrentMatchType;
   mPreviousChunkOffset = mCurrentChunkOffset;
   mAutoCompleteFinishedSearch = aFinished;
   mCurrentResult = nsnull;
   mCurrentListener = nsnull;
 }
 
 // nsNavHistory::StartSearch
 //
@@ -495,20 +506,25 @@ nsNavHistory::StartSearch(const nsAStrin
       nsStringArray urls;
       (void)mCurrentResultURLs.EnumerateRead(HashedURLsToArray, &urls);
 
       // Bind the parameters right away. We can only use the query once.
       for (PRUint32 i = 0; i < prevMatchCount; i++) {
         rv = mDBPreviousQuery->BindStringParameter(i + 1, *urls[i]);
         NS_ENSURE_SUCCESS(rv, rv);
       }
+
+      // Use the same match behavior as the previous search
+      mCurrentMatchType = mPreviousMatchType;
     }
   } else {
     // Clear out any previous result queries
     mDBPreviousQuery = nsnull;
+    // Default to matching based on the user's preference
+    mCurrentMatchType = mAutoCompleteMatchBehavior;
   }
 
   mAutoCompleteFinishedSearch = PR_FALSE;
   mCurrentChunkOffset = 0;
   mCurrentResultURLs.Clear();
   mCurrentSearchTokens.Clear();
   mLivemarkFeedItemIds.Clear();
   mLivemarkFeedURIs.Clear();
@@ -665,17 +681,17 @@ nsNavHistory::AutoCompleteProcessSearch(
   NS_ENSURE_TRUE(faviconService, NS_ERROR_OUT_OF_MEMORY);
 
   // We want to filter javascript: URIs if the search doesn't start with it
   PRBool filterJavascript = mAutoCompleteFilterJavascript &&
     !StartsWithJS(mCurrentSearchString);
 
   // Determine what type of search to try matching tokens against targets
   PRBool (*tokenMatchesTarget)(const nsAString &, const nsAString &) =
-    mAutoCompleteOnWordBoundary ? FindOnBoundary : FindAnywhere;
+    mCurrentMatchType != MATCH_ANYWHERE ? FindOnBoundary : FindAnywhere;
 
   PRBool hasMore = PR_FALSE;
   // Determine the result of the search
   while (NS_SUCCEEDED(aQuery->ExecuteStep(&hasMore)) && hasMore) {
     nsAutoString escapedEntryURL;
     nsresult rv = aQuery->GetString(kAutoCompleteIndex_URL, escapedEntryURL);
     NS_ENSURE_SUCCESS(rv, rv);
 
--- a/toolkit/components/places/tests/autocomplete/test_word_boundary_search.js
+++ b/toolkit/components/places/tests/autocomplete/test_word_boundary_search.js
@@ -35,16 +35,20 @@
  * ***** END LICENSE BLOCK ***** */
 
 /**
  * Test bug 393678 to make sure matches against the url, title, tags are only
  * made on word boundaries instead of in the middle of words.
  *
  * Make sure we don't try matching one after a CamelCase because the upper-case
  * isn't really a word boundary. (bug 429498)
+ *
+ * Bug 429531 provides switching between "must match on word boundary" and "can
+ * match," so leverage "must match" pref for checking word boundary logic and
+ * make sure "can match" matches anywhere.
  */
 
 let katakana = ["\u30a8", "\u30c9"]; // E, Do
 let ideograph = ["\u4efb", "\u5929", "\u5802"]; // Nin Ten Do
 
 // Define some shared uris and titles (each page needs its own uri)
 let kURIs = [
   "http://matchme/",
@@ -83,18 +87,20 @@ addPageBook(7, 4);
 // Ideograph
 addPageBook(8, 5);
 // CamelCase
 addPageBook(9, 0);
 
 // Provide for each test: description; search terms; array of gPages indices of
 // pages that should match; optional function to be run before the test
 let gTests = [
+  // Tests after this one will match only on word boundaries
   ["0: Match 'match' at the beginning or after / or on a CamelCase",
-   "match", [0,2,4,9]],
+   "match", [0,2,4,9],
+   function() setBehavior(2)],
   ["1: Match 'dont' at the beginning or after /",
    "dont", [1,3,5]],
   ["2: Match '2' after the slash and after a word (in tags too)",
    "2", [2,3,4,5]],
   ["3: Match 't' at the beginning or after /",
    "t", [0,1,2,3,4,5,9]],
   ["4: Match 'word' after many consecutive word boundaries",
    "word", [6]],
@@ -114,9 +120,18 @@ let gTests = [
    ideograph[1], [8]],
   ["11: Ideographs are treated as words so 'do' is yet another",
    ideograph[2], [8]],
 
   ["12: Extra negative assert that we don't match in the middle",
    "ch", []],
   ["13: Don't match one character after a camel-case word boundary (bug 429498)",
    "atch", []],
+
+  // Tests after this one will match against word boundaries and anywhere
+  ["14: Match on word boundaries as well as anywhere (bug 429531)",
+   "tch", [0,1,2,3,4,5,9],
+   function() setBehavior(1)],
 ];
+
+function setBehavior(aType) {
+  prefs.setIntPref("browser.urlbar.matchBehavior", aType);
+}