searchbox_api.js revision 7d4cd473f85ac64c3747c96c277f9e506a0d2246
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.
4
5var chrome;
6if (!chrome)
7  chrome = {};
8
9if (!chrome.embeddedSearch) {
10  chrome.embeddedSearch = new function() {
11
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();
18
19    // =========================================================================
20    //                           Exported functions
21    // =========================================================================
22    // DEPRECATED. TODO(sreeram): Remove once google.com no longer uses this.
23    this.navigateContentWindow = function(destination, disposition) {
24      return NavigateContentWindow(destination, disposition);
25    };
26
27    this.searchBox = new function() {
28
29      // =======================================================================
30      //                                  Constants
31      // =======================================================================
32      var MAX_CLIENT_SUGGESTIONS_TO_DEDUPE = 6;
33      var MAX_ALLOWED_DEDUPE_ATTEMPTS = 5;
34
35      var HTTP_REGEX = /^https?:\/\//;
36
37      var WWW_REGEX = /^www\./;
38
39      // =======================================================================
40      //                            Private functions
41      // =======================================================================
42      native function GetQuery();
43      native function GetVerbatim();
44      native function GetSelectionStart();
45      native function GetSelectionEnd();
46      native function GetStartMargin();
47      native function GetRightToLeft();
48      native function GetAutocompleteResults();
49      native function GetDisplayInstantResults();
50      native function GetFontSize();
51      native function IsFocused();
52      native function IsKeyCaptureEnabled();
53      native function SetQuery();
54      native function SetQueryFromAutocompleteResult();
55      native function SetSuggestion();
56      native function SetSuggestionFromAutocompleteResult();
57      native function SetSuggestions();
58      native function ShowOverlay();
59      native function FocusOmnibox();
60      native function StartCapturingKeyStrokes();
61      native function StopCapturingKeyStrokes();
62      native function NavigateSearchBox();
63      native function ShowBars();
64      native function HideBars();
65      native function GetSuggestionData();
66      native function GetMostVisitedItemData();
67
68      function GetAutocompleteResultsWrapper() {
69        var autocompleteResults = DedupeAutocompleteResults(
70            GetAutocompleteResults());
71        for (var i = 0, result; result = autocompleteResults[i]; ++i) {
72          result.destination_url = null;
73          result.contents = null;
74          result.description = null;
75        }
76        return autocompleteResults;
77      }
78
79      // TODO(dcblack): Do this in C++ instead of JS.
80      function CanonicalizeUrl(url) {
81        return url.replace(HTTP_REGEX, '').replace(WWW_REGEX, '');
82      }
83
84      // Removes duplicates from AutocompleteResults.
85      // TODO(dcblack): Do this in C++ instead of JS.
86      function DedupeAutocompleteResults(autocompleteResults) {
87        var urlToResultMap = {};
88        for (var i = 0, result; result = autocompleteResults[i]; ++i) {
89          var url = CanonicalizeUrl(result.destination_url);
90          if (url in urlToResultMap) {
91            var oldRelevance = urlToResultMap[url].rankingData.relevance;
92            var newRelevance = result.rankingData.relevance;
93            if (newRelevance > oldRelevance) {
94              urlToResultMap[url] = result;
95            }
96          } else {
97            urlToResultMap[url] = result;
98          }
99        }
100        var dedupedResults = [];
101        for (url in urlToResultMap) {
102          dedupedResults.push(urlToResultMap[url]);
103        }
104        return dedupedResults;
105      }
106
107      var lastPrefixQueriedForDuplicates = '';
108      var numDedupeAttempts = 0;
109
110      function DedupeClientSuggestions(clientSuggestions) {
111        var userInput = GetQuery();
112        if (userInput == lastPrefixQueriedForDuplicates) {
113          numDedupeAttempts += 1;
114          if (numDedupeAttempts > MAX_ALLOWED_DEDUPE_ATTEMPTS) {
115            // Request blocked for privacy reasons.
116            // TODO(dcblack): This check is insufficient.  We should have a
117            // check such that it's only callable once per onnativesuggestions,
118            // not once per prefix.  Also, there is a timing problem where if
119            // the user types quickly then the client will (correctly) attempt
120            // to render stale results, and end up calling dedupe multiple times
121            // when getValue shows the same prefix.  A better solution would be
122            // to have the client send up rid ranges to dedupe against and have
123            // the binary keep around all the old suggestions ever given to this
124            // overlay.  I suspect such an approach would clean up this code
125            // quite a bit.
126            return false;
127          }
128        } else {
129          lastPrefixQueriedForDuplicates = userInput;
130          numDedupeAttempts = 1;
131        }
132
133        var autocompleteResults = DedupeAutocompleteResults(
134            GetAutocompleteResults());
135        var nativeUrls = {};
136        for (var i = 0, result; result = autocompleteResults[i]; ++i) {
137          var nativeUrl = CanonicalizeUrl(result.destination_url);
138          nativeUrls[nativeUrl] = result.rid;
139        }
140        for (var i = 0; clientSuggestions[i] &&
141             i < MAX_CLIENT_SUGGESTIONS_TO_DEDUPE; ++i) {
142          var result = clientSuggestions[i];
143          if (result.url) {
144            var clientUrl = CanonicalizeUrl(result.url);
145            if (clientUrl in nativeUrls) {
146              result.duplicateOf = nativeUrls[clientUrl];
147            }
148          }
149        }
150        return true;
151      }
152
153      // =======================================================================
154      //                           Exported functions
155      // =======================================================================
156      this.__defineGetter__('value', GetQuery);
157      this.__defineGetter__('verbatim', GetVerbatim);
158      this.__defineGetter__('selectionStart', GetSelectionStart);
159      this.__defineGetter__('selectionEnd', GetSelectionEnd);
160      this.__defineGetter__('startMargin', GetStartMargin);
161      this.__defineGetter__('rtl', GetRightToLeft);
162      this.__defineGetter__('nativeSuggestions', GetAutocompleteResultsWrapper);
163      this.__defineGetter__('isFocused', IsFocused);
164      this.__defineGetter__('isKeyCaptureEnabled', IsKeyCaptureEnabled);
165      this.__defineGetter__('displayInstantResults', GetDisplayInstantResults);
166      this.__defineGetter__('font', GetFont);
167      this.__defineGetter__('fontSize', GetFontSize);
168
169      // This method is restricted to chrome-search://suggestion pages by
170      // checking the invoking context's origin in searchbox_extension.cc.
171      this.getSuggestionData = function(restrictedId) {
172        return GetSuggestionData(restrictedId);
173      };
174
175      // This method is restricted to chrome-search://most-visited pages by
176      // checking the invoking context's origin in searchbox_extension.cc.
177      this.getMostVisitedItemData = function(restrictedId) {
178        return GetMostVisitedItemData(restrictedId);
179      };
180
181      this.setSuggestions = function(text) {
182        SetSuggestions(text);
183      };
184      this.setAutocompleteText = function(text, behavior) {
185        SetSuggestion(text, behavior);
186      };
187      this.setRestrictedAutocompleteText = function(autocompleteResultId) {
188        SetSuggestionFromAutocompleteResult(autocompleteResultId);
189      };
190      this.setValue = function(text, type) {
191        SetQuery(text, type);
192      };
193      // Must access nativeSuggestions before calling setRestrictedValue.
194      this.setRestrictedValue = function(autocompleteResultId) {
195        SetQueryFromAutocompleteResult(autocompleteResultId);
196      };
197      this.showOverlay = function(height) {
198        ShowOverlay(height);
199      };
200      this.markDuplicateSuggestions = function(clientSuggestions) {
201        return DedupeClientSuggestions(clientSuggestions);
202      };
203      this.focus = function() {
204        FocusOmnibox();
205      };
206      this.startCapturingKeyStrokes = function() {
207        StartCapturingKeyStrokes();
208      };
209      this.stopCapturingKeyStrokes = function() {
210        StopCapturingKeyStrokes();
211      };
212      this.navigateContentWindow = function(destination, disposition) {
213        NavigateSearchBox(destination, disposition);
214      };
215      this.showBars = function() {
216        ShowBars();
217      };
218      this.hideBars = function() {
219        HideBars();
220      };
221      this.onchange = null;
222      this.onsubmit = null;
223      this.oncancel = null;
224      this.onresize = null;
225      this.onkeypress = null;
226      this.onkeycapturechange = null;
227      this.onmarginchange = null;
228      this.onnativesuggestions = null;
229      this.onbarshidden = null;
230      this.onfocuschange = null;
231      this.ontogglevoicesearch = null;
232
233      // DEPRECATED. These methods are from the legacy searchbox API.
234      // TODO(jered): Delete these.
235      native function GetX();
236      native function GetY();
237      native function GetWidth();
238      native function GetHeight();
239      this.__defineGetter__('x', GetX);
240      this.__defineGetter__('y', GetY);
241      this.__defineGetter__('width', GetWidth);
242      this.__defineGetter__('height', GetHeight);
243    };
244
245    this.newTabPage = new function() {
246
247      // =======================================================================
248      //                            Private functions
249      // =======================================================================
250      native function GetMostVisitedItems();
251      native function GetThemeBackgroundInfo();
252      native function DeleteMostVisitedItem();
253      native function UndoAllMostVisitedDeletions();
254      native function UndoMostVisitedDeletion();
255      native function NavigateNewTabPage();
256      native function IsInputInProgress();
257
258      function GetMostVisitedItemsWrapper() {
259        var mostVisitedItems = GetMostVisitedItems();
260        for (var i = 0, item; item = mostVisitedItems[i]; ++i) {
261          // These properties are private data and should not be returned to
262          // the page. They are only accessible via getMostVisitedItemData().
263          item.url = null;
264          item.title = null;
265          item.domain = null;
266          item.direction = null;
267        }
268        return mostVisitedItems;
269      }
270
271      // =======================================================================
272      //                           Exported functions
273      // =======================================================================
274      this.__defineGetter__('mostVisited', GetMostVisitedItemsWrapper);
275      this.__defineGetter__('themeBackgroundInfo', GetThemeBackgroundInfo);
276      this.__defineGetter__('isInputInProgress', IsInputInProgress);
277
278      this.deleteMostVisitedItem = function(restrictedId) {
279        DeleteMostVisitedItem(restrictedId);
280      };
281      this.undoAllMostVisitedDeletions = function() {
282        UndoAllMostVisitedDeletions();
283      };
284      this.undoMostVisitedDeletion = function(restrictedId) {
285        UndoMostVisitedDeletion(restrictedId);
286      };
287      this.navigateContentWindow = function(destination, disposition) {
288        NavigateNewTabPage(destination, disposition);
289      }
290
291      this.onmostvisitedchange = null;
292      this.onthemechange = null;
293      this.oninputstart = null;
294      this.oninputcancel = null;
295    };
296
297    // Export legacy searchbox API.
298    // TODO: Remove this when Instant Extended is fully launched.
299    chrome.searchBox = this.searchBox;
300  };
301}
302