1 // Copyright 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
9 if (!chrome.embeddedSearch) {
10 chrome.embeddedSearch = new function() {
12 // =========================================================================
14 // =========================================================================
15 native function GetFont();
16 // DEPRECATED. TODO(sreeram): Remove once google.com no longer uses this.
17 native function NavigateContentWindow();
19 function escapeHTML(text) {
20 return text.replace(/[<>&"']/g, function(match) {
22 case '<': return '<';
23 case '>': return '>';
24 case '&': return '&';
25 case '"': return '"';
26 case "'": return ''';
33 // Returns the |restrictedText| wrapped in a ShadowDOM.
34 function SafeWrap(restrictedText, width, height, opt_fontSize,
36 var node = document.createElement('div');
37 var nodeShadow = safeObjects.createShadowRoot.apply(node);
38 nodeShadow.applyAuthorStyles = true;
39 nodeShadow.innerHTML =
41 'width: ' + width + 'px !important;' +
42 'height: ' + height + 'px !important;' +
43 'font-family: \'' + GetFont() + '\', \'Arial\' !important;' +
45 'font-size: ' + opt_fontSize + 'px !important;' : '') +
46 'overflow: hidden !important;' +
47 'text-overflow: ellipsis !important;' +
48 'white-space: nowrap !important"' +
49 (opt_direction ? ' dir="' + opt_direction + '"' : '') +
53 safeObjects.defineProperty(node, 'webkitShadowRoot', { value: null });
57 chrome.embeddedSearchOnWindowReady = function() {
58 // |embeddedSearchOnWindowReady| is used for initializing window context
59 // and should be called only once per context.
60 safeObjects.createShadowRoot = Element.prototype.webkitCreateShadowRoot;
61 safeObjects.defineProperty = Object.defineProperty;
62 delete window.chrome.embeddedSearchOnWindowReady;
65 // =========================================================================
67 // =========================================================================
68 // DEPRECATED. TODO(sreeram): Remove once google.com no longer uses this.
69 this.navigateContentWindow = function(destination, disposition) {
70 return NavigateContentWindow(destination, disposition);
73 this.searchBox = new function() {
75 // =======================================================================
77 // =======================================================================
78 var MAX_CLIENT_SUGGESTIONS_TO_DEDUPE = 6;
79 var MAX_ALLOWED_DEDUPE_ATTEMPTS = 5;
81 var HTTP_REGEX = /^https?:\/\//;
83 var WWW_REGEX = /^www\./;
85 // =======================================================================
87 // =======================================================================
88 native function GetQuery();
89 native function GetVerbatim();
90 native function GetSelectionStart();
91 native function GetSelectionEnd();
92 native function GetStartMargin();
93 native function GetRightToLeft();
94 native function GetAutocompleteResults();
95 native function GetDisplayInstantResults();
96 native function GetFontSize();
97 native function IsKeyCaptureEnabled();
98 native function SetSuggestions();
99 native function SetQuerySuggestion();
100 native function SetQuerySuggestionFromAutocompleteResult();
101 native function SetQuery();
102 native function SetQueryFromAutocompleteResult();
103 native function ShowOverlay();
104 native function FocusOmnibox();
105 native function StartCapturingKeyStrokes();
106 native function StopCapturingKeyStrokes();
107 native function NavigateSearchBox();
109 function SafeWrapSuggestion(restrictedText) {
110 return SafeWrap(restrictedText, window.innerWidth - 155, 22);
113 // Wraps the AutocompleteResult query and URL into ShadowDOM nodes so that
114 // the JS cannot access them and deletes the raw values.
115 function GetAutocompleteResultsWrapper() {
116 var autocompleteResults = DedupeAutocompleteResults(
117 GetAutocompleteResults());
118 var userInput = GetQuery();
119 for (var i = 0, result; result = autocompleteResults[i]; ++i) {
120 var title = escapeHTML(result.contents);
121 var url = escapeHTML(CleanUrl(result.destination_url, userInput));
122 var combinedHtml = '<span class=chrome_url>' + url + '</span>';
123 // TODO(dcblack): Rename these titleElement, urlElement, and
124 // combinedElement for optimal correctness.
126 result.titleNode = SafeWrapSuggestion(title);
127 combinedHtml += '<span class=chrome_separator> – </span>' +
128 '<span class=chrome_title>' + title + '</span>';
130 result.urlNode = SafeWrapSuggestion(url);
131 result.combinedNode = SafeWrapSuggestion(combinedHtml);
132 delete result.contents;
133 delete result.destination_url;
135 return autocompleteResults;
138 // TODO(dcblack): Do this in C++ instead of JS.
139 function CleanUrl(url, userInput) {
140 if (url.indexOf(userInput) == 0) {
143 url = url.replace(HTTP_REGEX, '');
144 if (url.indexOf(userInput) == 0) {
147 return url.replace(WWW_REGEX, '');
150 // TODO(dcblack): Do this in C++ instead of JS.
151 function CanonicalizeUrl(url) {
152 return url.replace(HTTP_REGEX, '').replace(WWW_REGEX, '');
155 // Removes duplicates from AutocompleteResults.
156 // TODO(dcblack): Do this in C++ instead of JS.
157 function DedupeAutocompleteResults(autocompleteResults) {
158 var urlToResultMap = {};
159 for (var i = 0, result; result = autocompleteResults[i]; ++i) {
160 var url = CanonicalizeUrl(result.destination_url);
161 if (url in urlToResultMap) {
162 var oldRelevance = urlToResultMap[url].rankingData.relevance;
163 var newRelevance = result.rankingData.relevance;
164 if (newRelevance > oldRelevance) {
165 urlToResultMap[url] = result;
168 urlToResultMap[url] = result;
171 var dedupedResults = [];
172 for (url in urlToResultMap) {
173 dedupedResults.push(urlToResultMap[url]);
175 return dedupedResults;
178 var lastPrefixQueriedForDuplicates = '';
179 var numDedupeAttempts = 0;
181 function DedupeClientSuggestions(clientSuggestions) {
182 var userInput = GetQuery();
183 if (userInput == lastPrefixQueriedForDuplicates) {
184 numDedupeAttempts += 1;
185 if (numDedupeAttempts > MAX_ALLOWED_DEDUPE_ATTEMPTS) {
186 // Request blocked for privacy reasons.
187 // TODO(dcblack): This check is insufficient. We should have a
188 // check such that it's only callable once per onnativesuggestions,
189 // not once per prefix. Also, there is a timing problem where if
190 // the user types quickly then the client will (correctly) attempt
191 // to render stale results, and end up calling dedupe multiple times
192 // when getValue shows the same prefix. A better solution would be
193 // to have the client send up rid ranges to dedupe against and have
194 // the binary keep around all the old suggestions ever given to this
195 // overlay. I suspect such an approach would clean up this code
200 lastPrefixQueriedForDuplicates = userInput;
201 numDedupeAttempts = 1;
204 var autocompleteResults = DedupeAutocompleteResults(
205 GetAutocompleteResults());
207 for (var i = 0, result; result = autocompleteResults[i]; ++i) {
208 var nativeUrl = CanonicalizeUrl(result.destination_url);
209 nativeUrls[nativeUrl] = result.rid;
211 for (var i = 0; clientSuggestions[i] &&
212 i < MAX_CLIENT_SUGGESTIONS_TO_DEDUPE; ++i) {
213 var result = clientSuggestions[i];
215 var clientUrl = CanonicalizeUrl(result.url);
216 if (clientUrl in nativeUrls) {
217 result.duplicateOf = nativeUrls[clientUrl];
224 // =======================================================================
225 // Exported functions
226 // =======================================================================
227 this.__defineGetter__('value', GetQuery);
228 this.__defineGetter__('verbatim', GetVerbatim);
229 this.__defineGetter__('selectionStart', GetSelectionStart);
230 this.__defineGetter__('selectionEnd', GetSelectionEnd);
231 this.__defineGetter__('startMargin', GetStartMargin);
232 this.__defineGetter__('rtl', GetRightToLeft);
233 this.__defineGetter__('nativeSuggestions', GetAutocompleteResultsWrapper);
234 this.__defineGetter__('isKeyCaptureEnabled', IsKeyCaptureEnabled);
235 this.__defineGetter__('displayInstantResults', GetDisplayInstantResults);
236 this.__defineGetter__('font', GetFont);
237 this.__defineGetter__('fontSize', GetFontSize);
239 this.setSuggestions = function(text) {
240 SetSuggestions(text);
242 this.setAutocompleteText = function(text, behavior) {
243 SetQuerySuggestion(text, behavior);
245 this.setRestrictedAutocompleteText = function(autocompleteResultId) {
246 SetQuerySuggestionFromAutocompleteResult(autocompleteResultId);
248 this.setValue = function(text, type) {
249 SetQuery(text, type);
251 this.setRestrictedValue = function(autocompleteResultId) {
252 SetQueryFromAutocompleteResult(autocompleteResultId);
254 // TODO(jered): Remove the deprecated "reason" argument.
255 this.showOverlay = function(reason, height) {
256 ShowOverlay(reason, height);
258 // TODO(jered): Remove this when GWS knows about showOverlay().
259 this.show = this.showOverlay;
260 this.markDuplicateSuggestions = function(clientSuggestions) {
261 return DedupeClientSuggestions(clientSuggestions);
263 this.focus = function() {
266 this.startCapturingKeyStrokes = function() {
267 StartCapturingKeyStrokes();
269 this.stopCapturingKeyStrokes = function() {
270 StopCapturingKeyStrokes();
272 this.navigateContentWindow = function(destination, disposition) {
273 NavigateSearchBox(destination, disposition);
275 this.onchange = null;
276 this.onsubmit = null;
277 this.oncancel = null;
278 this.onresize = null;
279 this.onkeypress = null;
280 this.onkeycapturechange = null;
281 this.oncontextchange = null;
282 this.onmarginchange = null;
283 this.onnativesuggestions = null;
285 // DEPRECATED. These methods are from the legacy searchbox API.
286 // TODO(jered): Delete these.
287 native function GetX();
288 native function GetY();
289 native function GetWidth();
290 native function GetHeight();
291 this.__defineGetter__('x', GetX);
292 this.__defineGetter__('y', GetY);
293 this.__defineGetter__('width', GetWidth);
294 this.__defineGetter__('height', GetHeight);
297 this.newTabPage = new function() {
299 // =======================================================================
301 // =======================================================================
302 native function GetMostVisitedItems();
303 native function GetThemeBackgroundInfo();
304 native function DeleteMostVisitedItem();
305 native function UndoAllMostVisitedDeletions();
306 native function UndoMostVisitedDeletion();
307 native function NavigateNewTabPage();
309 function SafeWrapMostVisited(restrictedText, width, opt_direction) {
310 return SafeWrap(restrictedText, width, 18, 11, opt_direction);
313 function GetMostVisitedItemsWrapper() {
314 var mostVisitedItems = GetMostVisitedItems();
315 for (var i = 0, item; item = mostVisitedItems[i]; ++i) {
316 var title = escapeHTML(item.title);
317 var domain = escapeHTML(item.domain);
318 item.titleElement = SafeWrapMostVisited(title, 140, item.direction);
319 item.domainElement = SafeWrapMostVisited(domain, 123);
322 delete item.direction;
324 return mostVisitedItems;
327 // =======================================================================
328 // Exported functions
329 // =======================================================================
330 this.__defineGetter__('mostVisited', GetMostVisitedItemsWrapper);
331 this.__defineGetter__('themeBackgroundInfo', GetThemeBackgroundInfo);
333 this.deleteMostVisitedItem = function(restrictedId) {
334 DeleteMostVisitedItem(restrictedId);
336 this.undoAllMostVisitedDeletions = function() {
337 UndoAllMostVisitedDeletions();
339 this.undoMostVisitedDeletion = function(restrictedId) {
340 UndoMostVisitedDeletion(restrictedId);
342 this.navigateContentWindow = function(destination, disposition) {
343 NavigateNewTabPage(destination, disposition);
346 this.onmostvisitedchange = null;
347 this.onthemechange = null;
350 // Export legacy searchbox API.
351 // TODO: Remove this when Instant Extended is fully launched.
352 chrome.searchBox = this.searchBox;