Index: chrome/renderer/resources/extensions/searchbox_api.js |
diff --git a/chrome/renderer/resources/extensions/searchbox_api.js b/chrome/renderer/resources/extensions/searchbox_api.js |
index e7fc4e0f8c24e540ded492d17dbd170e6c95d86f..b3854dd0b929d3253eb573333fa3b30cf23d474b 100644 |
--- a/chrome/renderer/resources/extensions/searchbox_api.js |
+++ b/chrome/renderer/resources/extensions/searchbox_api.js |
@@ -7,7 +7,20 @@ if (!chrome) |
chrome = {}; |
if (!chrome.searchBox) { |
chrome.searchBox = new function() { |
- native function GetValue(); |
+ // ========================================================================= |
+ // Constants |
+ // ========================================================================= |
+ |
+ var MAX_CLIENT_SUGGESTIONS_TO_DEDUPE = 6; |
+ |
+ var HTTP_REGEX = /^https?:\/\//; |
+ |
+ var WWW_REGEX = /^www\./; |
+ |
+ // ========================================================================= |
+ // Private functions |
+ // ========================================================================= |
+ native function GetQuery(); |
native function GetVerbatim(); |
native function GetSelectionStart(); |
native function GetSelectionEnd(); |
@@ -15,8 +28,126 @@ if (!chrome.searchBox) { |
native function GetY(); |
native function GetWidth(); |
native function GetHeight(); |
+ native function GetAutocompleteResults(); |
+ native function GetKeyCode(); |
native function SetSuggestions(); |
- this.__defineGetter__('value', GetValue); |
+ native function SetQuerySuggestion(); |
+ native function SetQuerySuggestionFromAutocompleteResult(); |
+ native function SetQuery(); |
+ native function SetQueryFromAutocompleteResult(); |
+ native function SetPreviewHeight(); |
+ |
+ // Returns the |restrictedText| wrapped in a ShadowDOM. |
+ function SafeWrap(restrictedText) { |
+ var node = document.createElement('div'); |
+ var nodeShadow = new WebKitShadowRoot(node); |
+ nodeShadow.applyAuthorStyles = true; |
+ nodeShadow.innerHTML = |
+ '<div style="width:700px!important;' + |
+ ' height:22px!important;' + |
+ ' font-family:\'Chrome Droid Sans\',\'Arial\'!important;' + |
+ ' overflow:hidden!important;' + |
+ ' text-overflow:ellipsis!important">' + |
+ ' <nobr>' + restrictedText + '</nobr>' + |
+ '</div>'; |
+ return node; |
+ } |
+ |
+ // Wraps the AutocompleteResult query and URL into ShadowDOM nodes so that |
+ // the JS cannot access them and deletes the raw values. |
+ function GetAutocompleteResultsWrapper() { |
+ var autocompleteResults = DedupeAutcompleteResults( |
+ GetAutocompleteResults()); |
+ var userInput = GetQuery(); |
+ for (var i = 0; i < autocompleteResults.length; ++i) { |
+ var result = autocompleteResults[i]; |
+ var title = result.contents; |
+ var url = CleanUrl(result.destination_url, userInput); |
+ var combinedHtml = '<span class=chrome_url>' + url + '</span>'; |
+ if (title) { |
+ result.titleNode = SafeWrap(title); |
+ combinedHtml += '<span class=chrome_separator> – </span>' + |
+ '<span class=chrome_title>' + title + '</span>'; |
+ } |
+ result.urlNode = SafeWrap(url); |
+ result.combinedNode = SafeWrap(combinedHtml); |
+ delete result.contents; |
+ delete result.destination_url; |
+ } |
+ return autocompleteResults; |
+ } |
+ |
+ function CleanUrl(url, userInput) { |
+ if (url.indexOf(userInput) == 0) { |
+ return url; |
+ } |
+ url = url.replace(HTTP_REGEX, ''); |
+ if (url.indexOf(userInput) == 0) { |
+ return url; |
+ } |
+ return url.replace(WWW_REGEX, ''); |
+ } |
+ |
+ function CanonicalizeUrl(url) { |
+ return url.replace(HTTP_REGEX, '').replace(WWW_REGEX, ''); |
+ } |
+ |
+ // Removes duplicates from AutocompleteResults. |
+ // TODO(dcblack): Do this in C++ instead of JS. |
+ function DedupeAutcompleteResults(autocompleteResults) { |
+ var urlToResultMap = {}; |
+ for (var i = 0; i < autocompleteResults.length; ++i) { |
+ var result = autocompleteResults[i]; |
+ var url = CanonicalizeUrl(result.destination_url); |
+ if (url in urlToResultMap) { |
+ var oldRelevance = urlToResultMap[url].rankingData.relevance; |
+ var newRelevance = result.rankingData.relevance; |
+ if (newRelevance > oldRelevance) { |
+ urlToResultMap[url] = result; |
+ } |
+ } else { |
+ urlToResultMap[url] = result; |
+ } |
+ } |
+ var dedupedResults = []; |
+ for (url in urlToResultMap) { |
+ dedupedResults.push(urlToResultMap[url]); |
+ } |
+ return dedupedResults; |
+ } |
+ |
+ var lastPrefixQueriedForDuplicates = ''; |
+ |
+ function DedupeClientSuggestions(clientSuggestions) { |
+ var userInput = GetQuery(); |
+ if (userInput == lastPrefixQueriedForDuplicates) { |
+ // Request blocked for privacy reasons. |
+ // TODO(dcblack): This check is insufficient. We should have a check |
+ // such that it's only callable once per onnativesuggestions, not |
+ // once per prefix. |
+ return false; |
+ } |
+ lastPrefixQueriedForDuplicates = userInput; |
+ var autocompleteResults = GetAutocompleteResults(); |
+ var nativeUrls = {}; |
+ for (var i = 0; i < autocompleteResults.length; ++i) { |
+ var nativeUrl = CanonicalizeUrl(autocompleteResults[i].destination_url); |
+ nativeUrls[nativeUrl] = autocompleteResults[i].rid; |
+ } |
+ for (var i = 0; i < clientSuggestions.length && |
+ i < MAX_CLIENT_SUGGESTIONS_TO_DEDUPE; ++i) { |
+ var clientUrl = CanonicalizeUrl(clientSuggestions[i].url); |
+ if (clientUrl in nativeUrls) { |
+ clientSuggestions[i].duplicateOf = nativeUrls[clientUrl]; |
+ } |
+ } |
+ return true; |
+ } |
+ |
+ // ========================================================================= |
+ // Exported functions |
+ // ========================================================================= |
+ this.__defineGetter__('value', GetQuery); |
this.__defineGetter__('verbatim', GetVerbatim); |
this.__defineGetter__('selectionStart', GetSelectionStart); |
this.__defineGetter__('selectionEnd', GetSelectionEnd); |
@@ -24,12 +155,34 @@ if (!chrome.searchBox) { |
this.__defineGetter__('y', GetY); |
this.__defineGetter__('width', GetWidth); |
this.__defineGetter__('height', GetHeight); |
+ this.__defineGetter__('nativeSuggestions', GetAutocompleteResultsWrapper); |
+ this.__defineGetter__('keyCode', GetKeyCode); |
this.setSuggestions = function(text) { |
SetSuggestions(text); |
}; |
+ this.setAutocompleteText = function(text, behavior) { |
+ SetQuerySuggestion(text, behavior); |
+ }; |
+ this.setRestrictedAutocompleteText = function(resultId, behavior) { |
+ SetQuerySuggestionFromAutocompleteResult(resultId, behavior); |
+ }; |
+ this.setValue = function(text, type) { |
+ SetQuery(text, type); |
+ }; |
+ this.setRestrictedValue = function(resultId, behavior) { |
+ SetQueryFromAutocompleteResult(resultId, behavior); |
+ }; |
+ this.setNonNativeDropdownHeight = function(height) { |
+ SetPreviewHeight(height); |
+ }; |
+ this.markDuplicateSuggestions = function(clientSuggestions) { |
+ return DedupeClientSuggestions(clientSuggestions); |
+ }; |
this.onchange = null; |
this.onsubmit = null; |
this.oncancel = null; |
this.onresize = null; |
+ this.onautocompleteresults = null; |
+ this.onkeypress = null; |
}; |
} |