From 1bef7eb888fd87584894b86570c24150d2adef3f Mon Sep 17 00:00:00 2001 From: Edward Lee Date: Thu, 17 Jul 2008 04:48:40 -0700 Subject: [PATCH] Bug 392143 - show keywords as url bar autocomplete choices. r=dietrich, r=gavin --- .../themes/gnomestripe/browser/browser.css | 7 ++ browser/themes/pinstripe/browser/browser.css | 7 ++ browser/themes/winstripe/browser/browser.css | 8 ++ toolkit/components/places/src/nsNavHistory.h | 5 ++ .../places/src/nsNavHistoryAutoComplete.cpp | 84 ++++++++++++++++-- .../tests/autocomplete/test_keyword_search.js | 88 +++++++++++++++++++ toolkit/content/widgets/autocomplete.xml | 20 +++++ 7 files changed, 212 insertions(+), 7 deletions(-) create mode 100644 toolkit/components/places/tests/autocomplete/test_keyword_search.js diff --git a/browser/themes/gnomestripe/browser/browser.css b/browser/themes/gnomestripe/browser/browser.css index 861b8229e563..05813cf28cbb 100644 --- a/browser/themes/gnomestripe/browser/browser.css +++ b/browser/themes/gnomestripe/browser/browser.css @@ -1039,6 +1039,13 @@ toolbar[iconsize="small"] #paste-button[disabled="true"] { height: 16px; } +.ac-result-type-keyword, +.autocomplete-treebody::-moz-tree-image(keyword, treecolAutoCompleteImage) { + list-style-image: url(chrome://browser/skin/Search-glass.png); + width: 16px; + height: 16px; +} + .ac-result-type-tag, .autocomplete-treebody::-moz-tree-image(tag, treecolAutoCompleteImage) { list-style-image: url("chrome://browser/skin/places/tag.png"); diff --git a/browser/themes/pinstripe/browser/browser.css b/browser/themes/pinstripe/browser/browser.css index 716c3aa673d0..8e94d40918c5 100755 --- a/browser/themes/pinstripe/browser/browser.css +++ b/browser/themes/pinstripe/browser/browser.css @@ -993,6 +993,13 @@ statusbarpanel#statusbar-display { height: 16px; } +.ac-result-type-keyword, +.autocomplete-treebody::-moz-tree-image(keyword, treecolAutoCompleteImage) { + list-style-image: url(chrome://global/skin/icons/search-textbox.png); + width: 16px; + height: 16px; +} + richlistitem[selected="true"][current="true"] > hbox > .ac-result-type-bookmark, .autocomplete-treebody::-moz-tree-image(selected, current, bookmark, treecolAutoCompleteImage) { list-style-image: url("chrome://browser/skin/places/star-icons.png"); diff --git a/browser/themes/winstripe/browser/browser.css b/browser/themes/winstripe/browser/browser.css index 6e4e8d953980..a29a463a9c7d 100644 --- a/browser/themes/winstripe/browser/browser.css +++ b/browser/themes/winstripe/browser/browser.css @@ -1184,6 +1184,14 @@ statusbarpanel#statusbar-display { height: 16px; } +.ac-result-type-keyword, +.autocomplete-treebody::-moz-tree-image(keyword, treecolAutoCompleteImage) { + list-style-image: url(chrome://browser/skin/Search-glass.png); + -moz-image-region: rect(0px 32px 16px 16px); + width: 16px; + height: 16px; +} + .ac-result-type-tag, .autocomplete-treebody::-moz-tree-image(tag, treecolAutoCompleteImage) { list-style-image: url("chrome://browser/skin/places/tag.png"); diff --git a/toolkit/components/places/src/nsNavHistory.h b/toolkit/components/places/src/nsNavHistory.h index 04e933ad601f..45351a20a587 100644 --- a/toolkit/components/places/src/nsNavHistory.h +++ b/toolkit/components/places/src/nsNavHistory.h @@ -645,6 +645,7 @@ protected: nsCOMPtr mDBAutoCompleteQuery; // kAutoCompleteIndex_* results nsCOMPtr mDBPreviousQuery; // kAutoCompleteIndex_* results nsCOMPtr mDBAdaptiveQuery; // kAutoCompleteIndex_* results + nsCOMPtr mDBKeywordQuery; // kAutoCompleteIndex_* results nsCOMPtr mDBFeedbackIncrease; /** @@ -667,6 +668,8 @@ protected: PRInt32 mAutoCompleteSearchTimeout; nsCOMPtr mAutoCompleteTimer; + // Original search string for case-sensitive usage + nsString mOrigSearchString; // Search string and tokens for case-insensitive matching nsString mCurrentSearchString; nsStringArray mCurrentSearchTokens; @@ -693,12 +696,14 @@ protected: nsresult AutoCompleteFullHistorySearch(PRBool* aHasMoreResults); nsresult AutoCompletePreviousSearch(); nsresult AutoCompleteAdaptiveSearch(); + nsresult AutoCompleteKeywordSearch(); /** * Query type passed to AutoCompleteProcessSearch to determine what style to * use and if results should be filtered */ enum QueryType { + QUERY_KEYWORD, QUERY_ADAPTIVE, QUERY_FULL }; diff --git a/toolkit/components/places/src/nsNavHistoryAutoComplete.cpp b/toolkit/components/places/src/nsNavHistoryAutoComplete.cpp index 60662407c8c9..ccc39274ef6e 100644 --- a/toolkit/components/places/src/nsNavHistoryAutoComplete.cpp +++ b/toolkit/components/places/src/nsNavHistoryAutoComplete.cpp @@ -286,6 +286,24 @@ nsNavHistory::CreateAutoCompleteQueries() rv = mDBConn->CreateStatement(sql, getter_AddRefs(mDBAdaptiveQuery)); NS_ENSURE_SUCCESS(rv, rv); + sql = NS_LITERAL_CSTRING( + "SELECT REPLACE(s.url, '%s', ?2) search_url, h.title, IFNULL(f.url, " + "(SELECT f.url " + "FROM moz_places r " + "JOIN moz_favicons f ON f.id = r.favicon_id " + "WHERE r.rev_host = s.rev_host " + "ORDER BY r.frecency DESC LIMIT 1)), " + "b.parent, b.title, NULL " + "FROM moz_keywords k " + "JOIN moz_bookmarks b ON b.keyword_id = k.id " + "JOIN moz_places s ON s.id = b.fk " + "LEFT OUTER JOIN moz_places h ON h.url = search_url " + "LEFT OUTER JOIN moz_favicons f ON f.id = h.favicon_id " + "WHERE LOWER(k.keyword) = LOWER(?1) " + "ORDER BY h.frecency DESC"); + rv = mDBConn->CreateStatement(sql, getter_AddRefs(mDBKeywordQuery)); + NS_ENSURE_SUCCESS(rv, rv); + sql = NS_LITERAL_CSTRING( // Leverage the PRIMARY KEY (place_id, input) to insert/update entries "INSERT OR REPLACE INTO moz_inputhistory " @@ -340,6 +358,12 @@ nsNavHistory::PerformAutoComplete() nsresult rv; // Only do some extra searches on the first chunk if (!mCurrentChunkOffset) { + // Only show keywords if there's a search + if (mCurrentSearchTokens.Count()) { + rv = AutoCompleteKeywordSearch(); + NS_ENSURE_SUCCESS(rv, rv); + } + // Get adaptive results first rv = AutoCompleteAdaptiveSearch(); NS_ENSURE_SUCCESS(rv, rv); @@ -444,10 +468,12 @@ nsNavHistory::StartSearch(const nsAString & aSearchString, PRUint32 prevMatchCount = mCurrentResultURLs.Count(); nsAutoString prevSearchString(mCurrentSearchString); + // Keep a copy of the original search for case-sensitive usage + mOrigSearchString = aSearchString; + // Remove whitespace, see bug #392141 for details + mOrigSearchString.Trim(" \r\n\t\b"); // Copy the input search string for case-insensitive search - ToLowerCase(aSearchString, mCurrentSearchString); - // remove whitespace, see bug #392141 for details - mCurrentSearchString.Trim(" \r\n\t\b"); + ToLowerCase(mOrigSearchString, mCurrentSearchString); mCurrentListener = aListener; @@ -608,6 +634,34 @@ nsNavHistory::AddSearchToken(nsAutoString &aToken) mCurrentSearchTokens.AppendString(aToken); } +nsresult +nsNavHistory::AutoCompleteKeywordSearch() +{ + mozStorageStatementScoper scope(mDBKeywordQuery); + + // Get the keyword parameters to replace the %s in the keyword search + nsCAutoString params; + PRInt32 paramPos = mOrigSearchString.FindChar(' ') + 1; + // Make sure to escape them as if they were the query in a url, so " " become + // "+"; "+" become "%2B"; non-ascii escaped + NS_Escape(NS_ConvertUTF16toUTF8(Substring(mOrigSearchString, paramPos)), + params, url_XPAlphas); + + // The first search term might be a keyword + const nsAString &keyword = Substring(mOrigSearchString, 0, + paramPos ? paramPos - 1 : mOrigSearchString.Length()); + nsresult rv = mDBKeywordQuery->BindStringParameter(0, keyword); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mDBKeywordQuery->BindUTF8StringParameter(1, params); + NS_ENSURE_SUCCESS(rv, rv); + + rv = AutoCompleteProcessSearch(mDBKeywordQuery, QUERY_KEYWORD); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + nsresult nsNavHistory::AutoCompleteAdaptiveSearch() { @@ -734,6 +788,17 @@ nsNavHistory::AutoCompleteProcessSearch(mozIStorageStatement* aQuery, nsString style; switch (aType) { + case QUERY_KEYWORD: { + // If we don't have a title, then we must have a keyword, so let the + // UI know it's a keyword; otherwise, we found an exact page match, + // so just show the page like a regular result + if (entryTitle.IsEmpty()) + style = NS_LITERAL_STRING("keyword"); + else + title = entryTitle; + + break; + } case QUERY_FULL: { // If we get any results, there's potentially another chunk to proces if (aHasMoreResults) @@ -776,10 +841,15 @@ nsNavHistory::AutoCompleteProcessSearch(mozIStorageStatement* aQuery, // Tags have a special style to show a tag icon; otherwise, style the // bookmarks that aren't feed items and feed URIs as bookmark - style = showTags ? NS_LITERAL_STRING("tag") : (parentId && - !mLivemarkFeedItemIds.Get(parentId, &dummy)) || - mLivemarkFeedURIs.Get(escapedEntryURL, &dummy) ? - NS_LITERAL_STRING("bookmark") : NS_LITERAL_STRING("favicon"); + if (style.IsEmpty()) { + if (showTags) + style = NS_LITERAL_STRING("tag"); + else if ((parentId && !mLivemarkFeedItemIds.Get(parentId, &dummy)) || + mLivemarkFeedURIs.Get(escapedEntryURL, &dummy)) + style = NS_LITERAL_STRING("bookmark"); + else + style = NS_LITERAL_STRING("favicon"); + } // Get the URI for the favicon nsCAutoString faviconSpec; diff --git a/toolkit/components/places/tests/autocomplete/test_keyword_search.js b/toolkit/components/places/tests/autocomplete/test_keyword_search.js new file mode 100644 index 000000000000..b9031e4bf7d3 --- /dev/null +++ b/toolkit/components/places/tests/autocomplete/test_keyword_search.js @@ -0,0 +1,88 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Places Test Code. + * + * The Initial Developer of the Original Code is + * Edward Lee . + * Portions created by the Initial Developer are Copyright (C) 2008 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/** + * Test for bug 392143 that puts keyword results into the autocomplete. Makes + * sure that multiple parameter queries get spaces converted to +, + converted + * to %2B, non-ascii become escaped, and pages in history that match the + * keyword uses the page's title. + */ + +// Details for the keyword bookmark +let keyBase = "http://abc/?search="; +let keyKey = "key"; + +let unescaped = "ユニコード"; +let pageInHistory = "ThisPageIsInHistory"; + +// Define some shared uris and titles (each page needs its own uri) +let kURIs = [ + keyBase + "%s", + keyBase + "term", + keyBase + "multi+word", + keyBase + "blocking%2B", + keyBase + unescaped, + keyBase + pageInHistory, +]; +let kTitles = [ + "Generic page title", + "Keyword title", +]; + +// Add the keyword bookmark +addPageBook(0, 0, 1, [], keyKey); +// Add in the "fake pages" for keyword searches +gPages[1] = [1,1]; +gPages[2] = [2,1]; +gPages[3] = [3,1]; +gPages[4] = [4,1]; +// Add a page into history +addPageBook(5, 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 = [ + ["0: Plain keyword query", + keyKey + " term", [1]], + ["1: Multi-word keyword query", + keyKey + " multi word", [2]], + ["2: Keyword query with +", + keyKey + " blocking+", [3]], + ["3: Unescaped term in query", + keyKey + " " + unescaped, [4]], + ["4: Keyword that happens to match a page", + keyKey + " " + pageInHistory, [5]], +]; diff --git a/toolkit/content/widgets/autocomplete.xml b/toolkit/content/widgets/autocomplete.xml index e4447a4007d5..095c5d71f93e 100644 --- a/toolkit/content/widgets/autocomplete.xml +++ b/toolkit/content/widgets/autocomplete.xml @@ -1152,6 +1152,7 @@ @@ -1385,6 +1386,8 @@ if (type == "tag") { // Configure the extra box for tags display this._extraBox.hidden = false; + this._extraBox.childNodes[0].hidden = false; + this._extraBox.childNodes[1].hidden = true; this._extraBox.flex = 1; this._extraBox.pack = "end"; @@ -1400,6 +1403,23 @@ // Treat tagged matches as bookmarks for the star type = "bookmark"; + } else if (type == "keyword") { + // Configure the extra box for keyword display + this._extraBox.hidden = false; + this._extraBox.childNodes[0].hidden = true; + this._extraBox.childNodes[1].hidden = false; + this._extraBox.flex = 0; + this._extraBox.pack = "start"; + + // Put the parameters next to the title if we have any + let search = this.getAttribute("text"); + let params = search.substr(search.indexOf(' ') + 1); + + // Emphasize the keyword parameters + this._setUpDescription(this._extra, params); + + // Don't emphasize keyword searches in the title or url + this.setAttribute("text", ""); } else { // Hide the title's extra box if we don't need extra stuff this._extraBox.hidden = true;