Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(927)

Side by Side Diff: chrome/browser/resources/local_omnibox_popup/local_omnibox_popup.js

Issue 13375003: Fixing iframe jank in the local omnibox popup. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Include missing file. Created 7 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright 2012 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 /** 5 /**
6 * @fileoverview Support for omnibox behavior in offline mode or when API 6 * @fileoverview Support for omnibox behavior in offline mode or when API
7 * features are not supported on the server. 7 * features are not supported on the server.
8 */ 8 */
9 9
10 // ==========================================================
11 // Enums.
12 // ==========================================================
13
14 /** 10 /**
15 * Possible behaviors for navigateContentWindow. 11 * Possible behaviors for navigateContentWindow.
16 * @enum {number} 12 * @enum {number}
17 */ 13 */
18 var WindowOpenDisposition = { 14 var WindowOpenDisposition = {
19 CURRENT_TAB: 1, 15 CURRENT_TAB: 1,
20 NEW_BACKGROUND_TAB: 2 16 NEW_BACKGROUND_TAB: 2
21 }; 17 };
22 18
23 /** 19 /**
24 * The JavaScript button event value for a middle click. 20 * The JavaScript button event value for a middle click.
25 * @type {number} 21 * @type {number}
26 * @const 22 * @const
27 */ 23 */
28 var MIDDLE_MOUSE_BUTTON = 1; 24 var MIDDLE_MOUSE_BUTTON = 1;
29 25
30 // =============================================================================
31 // Util functions
32 // =============================================================================
33
34 /** 26 /**
35 * The maximum number of suggestions to show. 27 * The maximum number of suggestions to show.
36 * @type {number} 28 * @type {number}
37 * @const 29 * @const
38 */ 30 */
39 var MAX_SUGGESTIONS_TO_SHOW = 5; 31 var MAX_SUGGESTIONS_TO_SHOW = 5;
40 32
41 /** 33 /**
42 * Assume any native suggestion with a score higher than this value has been 34 * Assume any native suggestion with a score higher than this value has been
43 * inlined by the browser. 35 * inlined by the browser.
44 * @type {number} 36 * @type {number}
45 * @const 37 * @const
46 */ 38 */
47 var INLINE_SUGGESTION_THRESHOLD = 1200; 39 var INLINE_SUGGESTION_THRESHOLD = 1200;
48 40
49 /** 41 /**
42 * The color code for a display URL.
43 * @type {string}
44 * @const
45 */
46 var URL_COLOR = '#093';
47
48 /**
49 * The color code for a suggestion title.
50 * @type {string}
51 * @const
52 */
53 var TITLE_COLOR = '#666';
54
55 /**
56 * A top position which is off-screen.
57 * @type {string}
58 * @const
59 */
60 var OFF_SCREEN = '-1000px';
61
62 /**
63 * The expected origin of a suggestion iframe.
64 * @type {string}
65 * @const
66 */
67 var SUGGESTION_ORIGIN = 'chrome-search://suggestion';
68
69 /**
50 * Suggestion provider type corresponding to a verbatim URL suggestion. 70 * Suggestion provider type corresponding to a verbatim URL suggestion.
51 * @type {string} 71 * @type {string}
52 * @const 72 * @const
53 */ 73 */
54 var VERBATIM_URL_TYPE = 'url-what-you-typed'; 74 var VERBATIM_URL_TYPE = 'url-what-you-typed';
55 75
56 /** 76 /**
57 * Suggestion provider type corresponding to a verbatim search suggestion. 77 * Suggestion provider type corresponding to a verbatim search suggestion.
58 * @type {string} 78 * @type {string}
59 * @const 79 * @const
60 */ 80 */
61 var VERBATIM_SEARCH_TYPE = 'search-what-you-typed'; 81 var VERBATIM_SEARCH_TYPE = 'search-what-you-typed';
62 82
63 /** 83 /**
64 * The omnibox input value during the last onnativesuggestions event. 84 * The displayed suggestions.
65 * @type {string} 85 * @type {SuggestionsBox}
66 */ 86 */
67 var lastInputValue = ''; 87 var activeSuggestionsBox;
68 88
69 /** 89 /**
70 * The ordered restricted ids of the currently displayed suggestions. Since the 90 * The suggestions being rendered.
71 * suggestions contain the user's personal data (browser history) the searchBox 91 * @type {SuggestionsBox}
72 * API embeds the content of the suggestion in a shadow dom, and assigns a
73 * random restricted id to each suggestion which is accessible to the JS.
74 * @type {Array.<number>}
75 */ 92 */
76 93 var pendingSuggestionsBox;
77 var restrictedIds = [];
78 94
79 /** 95 /**
80 * The index of the currently selected suggestion or -1 if none are selected. 96 * A pool of iframes to display suggestions.
97 * @type {IframePool}
98 */
99 var iframePool;
100
101 /**
102 * A serial number for the next suggestions rendered.
81 * @type {number} 103 * @type {number}
82 */ 104 */
83 var selectedIndex = -1; 105 var nextRequestId = 0;
84 106
85 /** 107 /**
86 * Shortcut for document.getElementById. 108 * Shortcut for document.querySelector.
87 * @param {string} id of the element. 109 * @param {string} selector A selector to query the desired element.
88 * @return {HTMLElement} with the id. 110 * @return {HTMLElement} matching selector.
89 */ 111 */
90 function $(id) { 112 function $(selector) {
91 return document.getElementById(id); 113 return document.querySelector(selector);
92 } 114 }
93 115
94 /** 116 /**
95 * Displays a suggestion.
96 * @param {Object} suggestion The suggestion to render.
97 * @param {HTMLElement} box The html element to add the suggestion to.
98 * @param {boolean} select True to select the selection.
99 */
100 function addSuggestionToBox(suggestion, box, select) {
101 var suggestionDiv = document.createElement('div');
102 suggestionDiv.classList.add('suggestion');
103 suggestionDiv.classList.toggle('selected', select);
104 suggestionDiv.classList.toggle('search', suggestion.is_search);
105
106 var suggestionIframe = document.createElement('iframe');
107 suggestionIframe.className = 'contents';
108 suggestionIframe.src = suggestion.destination_url;
109 suggestionIframe.id = suggestion.rid;
110 suggestionDiv.appendChild(suggestionIframe);
111
112 restrictedIds.push(suggestion.rid);
113 box.appendChild(suggestionDiv);
114 }
115
116 /**
117 * Renders the input suggestions.
118 * @param {Array} nativeSuggestions An array of native suggestions to render.
119 */
120 function renderSuggestions(nativeSuggestions) {
121 var box = document.createElement('div');
122 box.id = 'suggestionsBox';
123 $('suggestions-box-container').appendChild(box);
124
125 for (var i = 0, length = nativeSuggestions.length;
126 i < Math.min(MAX_SUGGESTIONS_TO_SHOW, length); ++i) {
127 addSuggestionToBox(nativeSuggestions[i], box, i == selectedIndex);
128 }
129 }
130
131 /**
132 * Clears the suggestions being displayed.
133 */
134 function clearSuggestions() {
135 $('suggestions-box-container').innerHTML = '';
136 restrictedIds = [];
137 selectedIndex = -1;
138 }
139
140 /**
141 * @return {number} The height of the dropdown.
142 */
143 function getDropdownHeight() {
144 return $('suggestions-box-container').offsetHeight;
145 }
146
147 /**
148 * @param {Object} suggestion A suggestion. 117 * @param {Object} suggestion A suggestion.
149 * @param {boolean} inVerbatimMode Are we in verbatim mode? 118 * @param {boolean} inVerbatimMode Are we in verbatim mode?
150 * @return {boolean} True if the suggestion should be selected. 119 * @return {boolean} True if the suggestion should be selected.
151 */ 120 */
152 function shouldSelectSuggestion(suggestion, inVerbatimMode) { 121 function shouldSelectSuggestion(suggestion, inVerbatimMode) {
153 var isVerbatimUrl = suggestion.type == VERBATIM_URL_TYPE; 122 var isVerbatimUrl = suggestion.type == VERBATIM_URL_TYPE;
154 var inlinableSuggestion = suggestion.type != VERBATIM_SEARCH_TYPE && 123 var inlinableSuggestion = suggestion.type != VERBATIM_SEARCH_TYPE &&
155 suggestion.rankingData.relevance > INLINE_SUGGESTION_THRESHOLD; 124 suggestion.rankingData.relevance > INLINE_SUGGESTION_THRESHOLD;
156 // Verbatim URLs should always be selected. Otherwise, select suggestions 125 // Verbatim URLs should always be selected. Otherwise, select suggestions
157 // with a high enough score unless we are in verbatim mode (e.g. backspacing 126 // with a high enough score unless we are in verbatim mode (e.g. backspacing
158 // away). 127 // away).
159 return isVerbatimUrl || (!inVerbatimMode && inlinableSuggestion); 128 return isVerbatimUrl || (!inVerbatimMode && inlinableSuggestion);
160 } 129 }
161 130
162 /** 131 /**
163 * Updates selectedIndex, bounding it between -1 and the total number of 132 * Extract the desired navigation behavior from a click button.
164 * of suggestions - 1 (looping as necessary), and selects the corresponding 133 * @param {number} button The Event#button property of a click event.
165 * suggestion. 134 * @return {WindowOpenDisposition} The desired behavior for
166 * @param {boolean} increment True to increment the selected suggestion, false 135 * navigateContentWindow.
167 * to decrement. 136 */
168 */ 137 function getDispositionFromClickButton(button) {
169 function updateSelectedSuggestion(increment) { 138 if (button == MIDDLE_MOUSE_BUTTON)
170 var numSuggestions = restrictedIds.length; 139 return WindowOpenDisposition.NEW_BACKGROUND_TAB;
171 if (!numSuggestions) 140 return WindowOpenDisposition.CURRENT_TAB;
172 return; 141 }
173 142
174 var oldSelection = $('suggestionsBox').querySelector('.selected'); 143 /**
samarth 2013/04/02 23:26:58 Not sure what style Chrome JS prefers, but I would
Jered 2013/04/03 18:49:33 Yeah, ideally we'd have a compiler of some sort...
144 * Manages a pool of chrome-search iframes.
145 * @constructor
146 */
147 function IframePool() {
148 this.iframes_ = [];
149 }
150
151 /**
152 * Initializes the pool with blank result template iframes, positioned off
153 * screen.
154 */
155 IframePool.prototype.init = function() {
156 for (var i = 0; i < 2 * MAX_SUGGESTIONS_TO_SHOW; i++) {
157 var iframe = document.createElement('iframe');
158 iframe.className = 'contents';
159 iframe.id = 'suggestion-text-' + i;
160 iframe.src = 'chrome-search://suggestion/result.html';
161 iframe.style.top = OFF_SCREEN;
162 iframe.addEventListener('mouseover', hover(iframe.id), false);
samarth 2013/04/02 23:26:58 It's not clear at all that hover() returns a funct
Jered 2013/04/03 18:49:33 Done.
163 iframe.addEventListener('mouseout', unhover(iframe.id), false);
164 document.body.appendChild(iframe);
165 this.iframes_[i] = iframe;
166 }
167 };
168
169 /**
170 * Retrieves a free suggestion iframe from the pool.
171 * @return {Element} An iframe suitable for holding a suggestion.
172 */
173 IframePool.prototype.get = function() {
174 return this.iframes_.pop();
175 };
176
177 /**
178 * Releases a suggestion iframe back into the pool.
179 * @param {Element} iframe The iframe to return to the pool.
180 */
181 IframePool.prototype.release = function(iframe) {
182 this.iframes_.push(iframe);
183 iframe.style.top = OFF_SCREEN;
184 };
185
186 /**
187 * Displays a suggestions box.
188 * @param {string} inputValue The user text which prompted these suggestions.
189 * @param {Array.<Object>} suggestions Suggestions to display.
190 * @param {number} selectedIndex The index of the suggestion selected.
191 * @constructor
192 */
193 function SuggestionsBox(inputValue, suggestions, selectedIndex) {
194 this.inputValue_ = inputValue;
195 this.suggestions_ = suggestions;
196 this.selectedIndex_ = selectedIndex;
197
198 /**
199 * The index of the suggestion currently under the mouse pointer.
200 * @type {number}
201 * @private
202 */
203 this.hoveredIndex_ = -1;
204
205 /**
206 * A stamp to distinguish this suggestions box from others.
207 * @type {number}
208 * @private
209 */
210 this.requestId_ = nextRequestId++;
211
212 /**
213 * The ordered iframes showing suggestions in this suggestions box.
214 * @type {Array.<Element>}
215 * @private
216 */
217 this.iframes_ = [];
218
219 /**
220 * The ordered restricted ids for suggestions in this suggestions box.
221 * @type {Array.<number>}
222 * @private
223 */
224 this.restrictedIds_ = [];
225
226 /**
227 * An embedded search API handle.
228 * @type {Object}
229 * @private
230 */
231 this.apiHandle_ = getApiObjectHandle();
232
233 /**
234 * The CSS class of the container for these suggestions. Initially pending,
235 * then active once shown.
236 * @type {string}
237 */
238 this.containerClass = 'pending-container';
239 }
240
241 /**
242 * Starts rendering new suggestions.
243 */
244 SuggestionsBox.prototype.load = function() {
245 // Create a placeholder DOM in the invisible container.
246 $('.' + this.containerClass).innerHTML = '';
samarth 2013/04/02 23:26:58 This file uses a mix of raw DOM operations (see If
Jered 2013/04/03 18:49:33 Leaving as is based on our offline conversation, s
247 var box = document.createElement('div');
248 box.className = 'suggestionsBox';
249 $('.' + this.containerClass).appendChild(box);
250 var framesToLoad = {};
251 for (var i = 0, suggestion; suggestion = this.suggestions_[i]; i++) {
252 var div = document.createElement('div');
253 div.classList.add('suggestion');
254 div.classList.toggle('selected', i == this.selectedIndex_);
255 div.classList.toggle('search', suggestion.is_search);
256 box.appendChild(div);
257 // Reserve an iframe for each suggestion.
258 this.iframes_[i] = iframePool.get();
259 this.restrictedIds_[i] = suggestion.rid;
260 framesToLoad[this.iframes_[i].id] = suggestion.rid;
261 }
262 // Ask the loader iframe to populate the iframes just reserved.
263 var loadRequest = {
264 'load': framesToLoad,
265 'requestId': this.requestId_,
266 'style': {
267 'urlColor': URL_COLOR,
268 'titleColor': TITLE_COLOR
269 }
270 };
271 $('#suggestion-loader').contentWindow.postMessage(loadRequest,
272 SUGGESTION_ORIGIN);
273 };
274
275 /**
276 * Releases suggestion iframes and ignores a load done message for the current
277 * suggestions.
278 */
279 SuggestionsBox.prototype.releaseIframes = function() {
280 for (var i = 0; i < this.iframes_.length; i++) {
281 iframePool.release(this.iframes_[i]);
282 }
283 this.responseId = -1;
284 };
285
286 /**
287 * Returns whether the given request should be displayed.
288 * @param {number} requestId The number of the request that finished rendering.
289 * @return {boolean} True if should display, false if not.
290 */
291 SuggestionsBox.prototype.shouldShow = function(requestId) {
292 return requestId == this.requestId_;
293 };
294
295 /**
296 * Moves iframes into position.
297 */
298 SuggestionsBox.prototype.showIframes = function() {
299 var divs = document.querySelectorAll('.' + this.containerClass +
300 ' .suggestion');
301 // Note: This may be called before margins are ready. In that case,
302 // suggestion iframes will initially be too large and then size down
303 // onresize.
304 var startMargin = this.apiHandle_.startMargin;
305 var totalMargin = window.innerWidth - this.apiHandle_.width;
306 for (var i = 0; i < divs.length && i < this.iframes_.length; i++) {
307 // Add in the expected parent offset and the top margin.
308 this.iframes_[i].style.top = (divs[i].offsetTop + 4) + 'px';
309 this.iframes_[i].style[this.apiHandle_.isRtl ? 'right' : 'left'] =
310 startMargin + 'px';
311 this.iframes_[i].style.width = '-webkit-calc(100% - ' +
312 (totalMargin + 26) + 'px)';
313 }
314 };
315
316 /**
317 * Selects the suggestion before the current selection.
318 */
319 SuggestionsBox.prototype.selectPrevious = function() {
320 var numSuggestions = this.suggestions_.length;
321 this.selectedIndex_--;
322 if (this.selectedIndex_ < -1)
323 this.selectedIndex_ = -1;
324 this.redrawSelection_();
325 this.redrawHover_();
326 };
327
328 /**
329 * Selects the suggestion after the current selection.
330 */
331 SuggestionsBox.prototype.selectNext = function() {
332 var numSuggestions = this.suggestions_.length;
333 this.selectedIndex_++;
334 if (this.selectedIndex_ > numSuggestions - 1)
335 this.selectedIndex_ = numSuggestions - 1;
336 this.redrawSelection_();
337 this.redrawHover_();
338 };
339
340 /**
341 * Redraws the selected suggestion.
342 * @private
343 */
344 SuggestionsBox.prototype.redrawSelection_ = function() {
345 var oldSelection = $('.' + this.containerClass + ' .selected');
175 if (oldSelection) 346 if (oldSelection)
176 oldSelection.classList.remove('selected'); 347 oldSelection.classList.remove('selected');
177 348 if (this.selectedIndex_ == -1) {
178 if (increment) 349 this.apiHandle_.setValue(this.inputValue_);
179 selectedIndex = ++selectedIndex > numSuggestions - 1 ? -1 : selectedIndex;
180 else
181 selectedIndex = --selectedIndex < -1 ? numSuggestions - 1 : selectedIndex;
182 var apiHandle = getApiObjectHandle();
183 if (selectedIndex == -1) {
184 apiHandle.setValue(lastInputValue);
185 } else { 350 } else {
186 var newSelection = $('suggestionsBox').querySelector( 351 var newSelection = $('.' + this.containerClass +
187 '.suggestion:nth-of-type(' + (selectedIndex + 1) + ')'); 352 ' .suggestion:nth-of-type(' + (this.selectedIndex_ + 1) + ')');
188 newSelection.classList.add('selected'); 353 newSelection.classList.add('selected');
189 apiHandle.setRestrictedValue(restrictedIds[selectedIndex]); 354 this.apiHandle_.setRestrictedValue(
190 } 355 this.suggestions_[this.selectedIndex_].rid);
191 } 356 }
192 357 };
193 // ============================================================================= 358
194 // Handlers / API stuff 359 /**
195 // ============================================================================= 360 * Returns the restricted id of the iframe clicked.
361 * @param {Window} iframeWindow The window of the iframe that was clicked.
362 * @return {number?} The restricted id clicked or null if none.
363 */
364 SuggestionsBox.prototype.getClickTarget = function(iframeWindow) {
365 for (var i = 0; i < this.iframes_.length; ++i)
366 if (this.iframes_[i].contentWindow == iframeWindow)
367 return this.restrictedIds_[i];
368 return null;
369 };
370
371 /**
372 * Called when the user hovers on the specified iframe.
373 * @param {string} iframeId The id of the iframe hovered.
374 */
375 SuggestionsBox.prototype.hover = function(iframeId) {
376 this.hoveredIndex_ = -1;
377 for (var i = 0; i < this.iframes_.length; ++i) {
378 if (this.iframes_[i].id == iframeId) {
379 this.hoveredIndex_ = i;
380 break;
381 }
382 }
383 this.redrawHover_();
384 };
385
386 /**
387 * Called when the user unhovers the specified iframe.
388 * @param {string} iframeId The id of the iframe hovered.
389 */
390 SuggestionsBox.prototype.unhover = function(iframeId) {
391 for (var i = 0; i < this.iframes_.length; ++i) {
392 if (this.iframes_[i].id == iframeId && this.hoveredIndex_ == i) {
393 this.hoveredIndex_ = -1;
394 break;
395 }
396 }
397 this.redrawHover_();
398 };
399
400 /**
401 * Clears the current hover.
402 */
403 SuggestionsBox.prototype.clearHover = function() {
404 this.hoveredIndex_ = -1;
405 this.redrawHover_();
406 };
407
408 /**
409 * Redraws the mouse hover background.
410 * @private
411 */
412 SuggestionsBox.prototype.redrawHover_ = function() {
413 if (this.hoveredIndex_ == -1) {
414 var divs = document.querySelectorAll('.' + this.containerClass +
415 ' .suggestion');
416 for (var i = 0; i < divs.length; i++)
417 divs[i].classList.remove('hovered');
418 } else if (this.hoveredIndex_ != this.selectedIndex_) {
419 var newHover = $('.' + this.containerClass +
420 ' .suggestion:nth-of-type(' + (this.hoveredIndex_ + 1) + ')');
samarth 2013/04/02 23:26:58 A lot of code like this could be cleaned up if the
Jered 2013/04/03 18:49:33 Done. I still found it cleaner to have separate re
421 newHover.classList.add('hovered');
422 }
423 };
424
425 /**
426 * Clears the currently active suggestions and shows pending suggestions.
427 */
428 function makePendingSuggestionsActive() {
429 if (activeSuggestionsBox)
430 activeSuggestionsBox.releaseIframes();
431 activeSuggestionsBox = pendingSuggestionsBox;
432 pendingSuggestionsBox = null;
433 var oldActiveContainer = $('.active-container');
434 $('.pending-container').className = 'active-container';
435 oldActiveContainer.className = 'pending-container';
436 activeSuggestionsBox.containerClass = 'active-container';
samarth 2013/04/02 23:26:58 This is a little weird. The suggestions box is al
Jered 2013/04/03 18:49:33 Done, sort of. I'm not sure this is less confusing
437 activeSuggestionsBox.showIframes();
438 var height = $('.active-container').offsetHeight;
439 getApiObjectHandle().showOverlay(height);
440 }
441
442 /**
443 * Hides the active suggestions box.
444 */
445 function hideActiveSuggestions() {
446 getApiObjectHandle().showOverlay(0);
447 $('.active-container').innerHTML = '';
448 if (activeSuggestionsBox)
449 activeSuggestionsBox.releaseIframes();
450 activeSuggestionsBox = null;
451 }
196 452
197 /** 453 /**
198 * @return {Object} the handle to the searchBox API. 454 * @return {Object} the handle to the searchBox API.
199 */ 455 */
200 function getApiObjectHandle() { 456 function getApiObjectHandle() {
201 if (window.cideb) 457 if (window.cideb)
202 return window.cideb; 458 return window.cideb;
203 if (window.navigator && window.navigator.embeddedSearch && 459 if (window.navigator && window.navigator.embeddedSearch &&
204 window.navigator.embeddedSearch.searchBox) 460 window.navigator.embeddedSearch.searchBox)
205 return window.navigator.embeddedSearch.searchBox; 461 return window.navigator.embeddedSearch.searchBox;
206 if (window.chrome && window.chrome.embeddedSearch && 462 if (window.chrome && window.chrome.embeddedSearch &&
207 window.chrome.embeddedSearch.searchBox) 463 window.chrome.embeddedSearch.searchBox)
208 return window.chrome.embeddedSearch.searchBox; 464 return window.chrome.embeddedSearch.searchBox;
209 return null; 465 return null;
210 } 466 }
211 467
212 /** 468 /**
213 * Updates suggestions in response to a onchange or onnativesuggestions call. 469 * Updates suggestions in response to a onchange or onnativesuggestions call.
214 */ 470 */
215 function updateSuggestions() { 471 function updateSuggestions() {
472 if (pendingSuggestionsBox) {
473 pendingSuggestionsBox.releaseIframes();
474 pendingSuggestionsBox = null;
475 }
216 var apiHandle = getApiObjectHandle(); 476 var apiHandle = getApiObjectHandle();
217 lastInputValue = apiHandle.value; 477 var inputValue = apiHandle.value;
218 478 var suggestions = apiHandle.nativeSuggestions;
219 clearSuggestions(); 479 if (!inputValue || !suggestions.length) {
220 var nativeSuggestions = apiHandle.nativeSuggestions; 480 hideActiveSuggestions();
221 if (nativeSuggestions.length) { 481 return;
222 nativeSuggestions.sort(function(a, b) { 482 }
483 if (suggestions.length) {
484 suggestions.sort(function(a, b) {
223 return b.rankingData.relevance - a.rankingData.relevance; 485 return b.rankingData.relevance - a.rankingData.relevance;
224 }); 486 });
225 if (shouldSelectSuggestion(nativeSuggestions[0], apiHandle.verbatim)) 487 var selectedIndex = -1;
488 if (shouldSelectSuggestion(suggestions[0], apiHandle.verbatim))
226 selectedIndex = 0; 489 selectedIndex = 0;
227 renderSuggestions(nativeSuggestions); 490 pendingSuggestionsBox = new SuggestionsBox(inputValue,
491 suggestions.slice(0, MAX_SUGGESTIONS_TO_SHOW), selectedIndex);
492 pendingSuggestionsBox.load();
228 } 493 }
229
230 var height = getDropdownHeight();
231 apiHandle.showOverlay(height);
232 } 494 }
233 495
234 /** 496 /**
235 * Appends a style node for suggestion properties that depend on apiHandle. 497 * Appends a style node for suggestion properties that depend on apiHandle.
236 */ 498 */
237 function appendSuggestionStyles() { 499 function appendSuggestionStyles() {
238 var apiHandle = getApiObjectHandle(); 500 var apiHandle = getApiObjectHandle();
239 var isRtl = apiHandle.rtl; 501 var isRtl = apiHandle.rtl;
240 var startMargin = apiHandle.startMargin; 502 var startMargin = apiHandle.startMargin;
241 var style = document.createElement('style'); 503 var style = document.createElement('style');
242 style.type = 'text/css'; 504 style.type = 'text/css';
243 style.id = 'suggestionStyle'; 505 style.id = 'suggestionStyle';
244 style.textContent = 506 style.textContent =
245 '.suggestion, ' + 507 '.suggestion, ' +
246 '.suggestion.search {' + 508 '.suggestion.search {' +
247 ' background-position: ' + 509 ' background-position: ' +
248 (isRtl ? '-webkit-calc(100% - 5px)' : '5px') + ' 4px;' + 510 (isRtl ? '-webkit-calc(100% - 5px)' : '5px') + ' 4px;' +
249 ' -webkit-margin-start: ' + startMargin + 'px;' + 511 ' -webkit-margin-start: ' + startMargin + 'px;' +
250 ' -webkit-margin-end: ' + 512 ' -webkit-margin-end: ' +
251 (window.innerWidth - apiHandle.width - startMargin) + 'px;' + 513 (window.innerWidth - apiHandle.width - startMargin) + 'px;' +
252 ' font: ' + apiHandle.fontSize + 'px "' + apiHandle.font + '";' + 514 ' font: ' + apiHandle.fontSize + 'px "' + apiHandle.font + '";' +
253 '}'; 515 '}';
254 document.querySelector('head').appendChild(style); 516 $('head').appendChild(style);
517 if (activeSuggestionsBox)
518 activeSuggestionsBox.showIframes();
255 window.removeEventListener('resize', appendSuggestionStyles); 519 window.removeEventListener('resize', appendSuggestionStyles);
256 } 520 }
257 521
258 /** 522 /**
259 * Extract the desired navigation behavior from a click button. 523 * Makes keys navigate through suggestions.
260 * @param {number} button The Event#button property of a click event.
261 * @return {WindowOpenDisposition} The desired behavior for
262 * navigateContentWindow.
263 */
264 function getDispositionFromClickButton(button) {
265 if (button == MIDDLE_MOUSE_BUTTON)
266 return WindowOpenDisposition.NEW_BACKGROUND_TAB;
267 return WindowOpenDisposition.CURRENT_TAB;
268 }
269
270 /**
271 * Handles suggestion clicks.
272 * @param {number} restrictedId The restricted id of the suggestion being
273 * clicked.
274 * @param {number} button The Event#button property of a click event.
275 *
276 */
277 function handleSuggestionClick(restrictedId, button) {
278 clearSuggestions();
279 getApiObjectHandle().navigateContentWindow(
280 restrictedId, getDispositionFromClickButton(button));
281 }
282
283 /**
284 * chrome.searchBox.onkeypress implementation.
285 * @param {Object} e The key being pressed. 524 * @param {Object} e The key being pressed.
286 */ 525 */
287 function handleKeyPress(e) { 526 function handleKeyPress(e) {
288 switch (e.keyCode) { 527 if (activeSuggestionsBox) {
289 case 38: // Up arrow 528 switch (e.keyCode) {
290 updateSelectedSuggestion(false); 529 case 38: // Up arrow
291 break; 530 activeSuggestionsBox.selectPrevious();
292 case 40: // Down arrow 531 break;
293 updateSelectedSuggestion(true); 532 case 40: // Down arrow
294 break; 533 activeSuggestionsBox.selectNext();
295 } 534 break;
296 }
297
298 /**
299 * Handles the postMessage calls from the result iframes.
300 * @param {Object} message The message containing details of clicks the iframes.
301 */
302 function handleMessage(message) {
303 if (message.origin != 'null' || !message.data ||
304 message.data.eventType != 'click') {
305 return;
306 }
307
308 var iframes = document.getElementsByClassName('contents');
309 for (var i = 0; i < iframes.length; ++i) {
310 if (iframes[i].contentWindow == message.source) {
311 handleSuggestionClick(parseInt(iframes[i].id, 10),
312 message.data.button);
313 break;
314 } 535 }
315 } 536 }
316 } 537 }
317 538
318 /** 539 /**
319 * chrome.searchBox.embeddedSearch.onsubmit implementation. 540 * Gets a function to call when an iframe is hovered.
541 * @param {string} id The iframe id.
542 * @return {function()} A function to call when an iframe is hovered.
320 */ 543 */
321 function onSubmit() { 544 function hover(id) {
545 return function() {
546 if (activeSuggestionsBox)
547 activeSuggestionsBox.hover(id);
548 };
322 } 549 }
323 550
324 /** 551 /**
325 * Sets up the searchBox API. 552 * Gets a function to call when an iframe is unhovered.
553 * @param {string} id The iframe id.
554 * @return {function()} A function to call when an iframe is unhovered.
326 */ 555 */
327 function setUpApi() { 556 function unhover(id) {
557 return function() {
558 if (activeSuggestionsBox)
559 activeSuggestionsBox.unhover(id);
560 }
561 }
562
563 /**
564 * Handles postMessage calls from suggestion iframes.
565 * @param {Object} message A notification that all iframes are done loading or
566 * that an iframe was clicked.
567 */
568 function handleMessage(message) {
569 if (message.origin != SUGGESTION_ORIGIN)
570 return;
571
572 if ('loaded' in message.data) {
573 if (pendingSuggestionsBox &&
574 pendingSuggestionsBox.shouldShow(message.data.loaded)) {
575 makePendingSuggestionsActive();
576 }
577 } else if ('click' in message.data) {
578 if (activeSuggestionsBox) {
579 var targetId = activeSuggestionsBox.getClickTarget(message.source);
580 if (targetId != null) {
581 hideActiveSuggestions();
582 getApiObjectHandle().navigateContentWindow(targetId,
583 getDispositionFromClickButton(message.data.click));
584 }
585 }
586 }
587 }
588
589 /**
590 * Clears hover when window is blurred.
591 */
592 function clearHover() {
593 if (activeSuggestionsBox)
594 activeSuggestionsBox.clearHover();
595 }
596
597 /**
598 * Sets up the embedded search API and creates suggestion iframes.
599 */
600 function init() {
601 iframePool = new IframePool();
602 iframePool.init();
328 var apiHandle = getApiObjectHandle(); 603 var apiHandle = getApiObjectHandle();
329 apiHandle.onnativesuggestions = updateSuggestions; 604 apiHandle.onnativesuggestions = updateSuggestions;
330 apiHandle.onchange = updateSuggestions; 605 apiHandle.onchange = updateSuggestions;
331 apiHandle.onkeypress = handleKeyPress; 606 apiHandle.onkeypress = handleKeyPress;
332 apiHandle.onsubmit = onSubmit; 607 // Instant checks for this handler to be bound.
333 $('suggestions-box-container').dir = apiHandle.rtl ? 'rtl' : 'ltr'; 608 apiHandle.onsubmit = function() {};
609 $('.active-container').dir = apiHandle.rtl ? 'rtl' : 'ltr';
610 $('.pending-container').dir = apiHandle.rtl ? 'rtl' : 'ltr';
334 // Delay adding these styles until the window width is available. 611 // Delay adding these styles until the window width is available.
335 window.addEventListener('resize', appendSuggestionStyles); 612 window.addEventListener('resize', appendSuggestionStyles);
336 if (apiHandle.nativeSuggestions.length) 613 if (apiHandle.nativeSuggestions.length)
337 handleNativeSuggestions(); 614 updateSuggestions();
338 } 615 }
339 616
340 document.addEventListener('DOMContentLoaded', setUpApi); 617 document.addEventListener('DOMContentLoaded', init);
341 window.addEventListener('message', handleMessage, false); 618 window.addEventListener('message', handleMessage, false);
619 window.addEventListener('blur', clearHover, false);
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698