Most visited thumbnails and favicons need id-based urls
[chromium-blink-merge.git] / chrome / renderer / resources / extensions / searchbox_api.js
blob24b2bc142cb9320382c8276949655c9a25b22921
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.
5 var chrome;
6 if (!chrome)
7   chrome = {};
9 if (!chrome.embeddedSearch) {
10   chrome.embeddedSearch = new function() {
12     // =========================================================================
13     //                            Private functions
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) {
21         switch (match) {
22           case '<': return '&lt;';
23           case '>': return '&gt;';
24           case '&': return '&amp;';
25           case '"': return '&quot;';
26           case "'": return '&apos;';
27         }
28       });
29     }
31     var safeObjects = {};
33     // Returns the |restrictedText| wrapped in a ShadowDOM.
34     function SafeWrap(restrictedText, width, height, opt_fontSize,
35         opt_direction) {
36       var node = document.createElement('div');
37       var nodeShadow = safeObjects.createShadowRoot.apply(node);
38       nodeShadow.applyAuthorStyles = true;
39       nodeShadow.innerHTML =
40           '<div style="' +
41               'width: ' + width + 'px !important;' +
42               'height: ' + height + 'px !important;' +
43               'font-family: \'' + GetFont() + '\', \'Arial\' !important;' +
44               (opt_fontSize ?
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 + '"' : '') +
50               '>' +
51             restrictedText +
52           '</div>';
53       safeObjects.defineProperty(node, 'webkitShadowRoot', { value: null });
54       return node;
55     }
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;
63     };
65     // =========================================================================
66     //                           Exported functions
67     // =========================================================================
68     // DEPRECATED. TODO(sreeram): Remove once google.com no longer uses this.
69     this.navigateContentWindow = function(destination, disposition) {
70       return NavigateContentWindow(destination, disposition);
71     };
73     this.searchBox = new function() {
75       // =======================================================================
76       //                                  Constants
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       // =======================================================================
86       //                            Private functions
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);
111       }
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.
125           if (title) {
126             result.titleNode = SafeWrapSuggestion(title);
127             combinedHtml += '<span class=chrome_separator> &ndash; </span>' +
128                 '<span class=chrome_title>' + title + '</span>';
129           }
130           result.urlNode = SafeWrapSuggestion(url);
131           result.combinedNode = SafeWrapSuggestion(combinedHtml);
132           delete result.contents;
133           delete result.destination_url;
134         }
135         return autocompleteResults;
136       }
138       // TODO(dcblack): Do this in C++ instead of JS.
139       function CleanUrl(url, userInput) {
140         if (url.indexOf(userInput) == 0) {
141           return url;
142         }
143         url = url.replace(HTTP_REGEX, '');
144         if (url.indexOf(userInput) == 0) {
145           return url;
146         }
147         return url.replace(WWW_REGEX, '');
148       }
150       // TODO(dcblack): Do this in C++ instead of JS.
151       function CanonicalizeUrl(url) {
152         return url.replace(HTTP_REGEX, '').replace(WWW_REGEX, '');
153       }
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;
166             }
167           } else {
168             urlToResultMap[url] = result;
169           }
170         }
171         var dedupedResults = [];
172         for (url in urlToResultMap) {
173           dedupedResults.push(urlToResultMap[url]);
174         }
175         return dedupedResults;
176       }
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
196             // quite a bit.
197             return false;
198           }
199         } else {
200           lastPrefixQueriedForDuplicates = userInput;
201           numDedupeAttempts = 1;
202         }
204         var autocompleteResults = DedupeAutocompleteResults(
205             GetAutocompleteResults());
206         var nativeUrls = {};
207         for (var i = 0, result; result = autocompleteResults[i]; ++i) {
208           var nativeUrl = CanonicalizeUrl(result.destination_url);
209           nativeUrls[nativeUrl] = result.rid;
210         }
211         for (var i = 0; clientSuggestions[i] &&
212              i < MAX_CLIENT_SUGGESTIONS_TO_DEDUPE; ++i) {
213           var result = clientSuggestions[i];
214           if (result.url) {
215             var clientUrl = CanonicalizeUrl(result.url);
216             if (clientUrl in nativeUrls) {
217               result.duplicateOf = nativeUrls[clientUrl];
218             }
219           }
220         }
221         return true;
222       }
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);
241       };
242       this.setAutocompleteText = function(text, behavior) {
243         SetQuerySuggestion(text, behavior);
244       };
245       this.setRestrictedAutocompleteText = function(autocompleteResultId) {
246         SetQuerySuggestionFromAutocompleteResult(autocompleteResultId);
247       };
248       this.setValue = function(text, type) {
249         SetQuery(text, type);
250       };
251       this.setRestrictedValue = function(autocompleteResultId) {
252         SetQueryFromAutocompleteResult(autocompleteResultId);
253       };
254       // TODO(jered): Remove the deprecated "reason" argument.
255       this.showOverlay = function(reason, height) {
256         ShowOverlay(reason, height);
257       };
258       // TODO(jered): Remove this when GWS knows about showOverlay().
259       this.show = this.showOverlay;
260       this.markDuplicateSuggestions = function(clientSuggestions) {
261         return DedupeClientSuggestions(clientSuggestions);
262       };
263       this.focus = function() {
264         FocusOmnibox();
265       };
266       this.startCapturingKeyStrokes = function() {
267         StartCapturingKeyStrokes();
268       };
269       this.stopCapturingKeyStrokes = function() {
270         StopCapturingKeyStrokes();
271       };
272       this.navigateContentWindow = function(destination, disposition) {
273         NavigateSearchBox(destination, disposition);
274       }
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);
295     };
297     this.newTabPage = new function() {
299       // =======================================================================
300       //                            Private functions
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);
311       }
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);
320           delete item.title;
321           delete item.domain;
322           delete item.direction;
323         }
324         return mostVisitedItems;
325       }
327       // =======================================================================
328       //                           Exported functions
329       // =======================================================================
330       this.__defineGetter__('mostVisited', GetMostVisitedItemsWrapper);
331       this.__defineGetter__('themeBackgroundInfo', GetThemeBackgroundInfo);
333       this.deleteMostVisitedItem = function(restrictedId) {
334         DeleteMostVisitedItem(restrictedId);
335       };
336       this.undoAllMostVisitedDeletions = function() {
337         UndoAllMostVisitedDeletions();
338       };
339       this.undoMostVisitedDeletion = function(restrictedId) {
340         UndoMostVisitedDeletion(restrictedId);
341       };
342       this.navigateContentWindow = function(destination, disposition) {
343         NavigateNewTabPage(destination, disposition);
344       }
346       this.onmostvisitedchange = null;
347       this.onthemechange = null;
348     };
350     // Export legacy searchbox API.
351     // TODO: Remove this when Instant Extended is fully launched.
352     chrome.searchBox = this.searchBox;
353   };