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

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: Changing JS style. 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 // ========================================================== 10 (function() {
Dan Beam 2013/04/06 00:18:13 <include src="../path/to/shared/util.js>
Jered 2013/04/06 02:45:16 I get an error about an unrecognized < token. What
Dan Beam 2013/04/06 02:51:47 files should be run through grit to pick up this m
Jered 2013/04/06 16:00:30 Ah, I was missing flattenhtml="true". After readin
11 // Enums.
12 // ==========================================================
13
14 /** 11 /**
15 * Possible behaviors for navigateContentWindow. 12 * Possible behaviors for navigateContentWindow.
16 * @enum {number} 13 * @enum {number}
17 */ 14 */
18 var WindowOpenDisposition = { 15 var WindowOpenDisposition = {
19 CURRENT_TAB: 1, 16 CURRENT_TAB: 1,
20 NEW_BACKGROUND_TAB: 2 17 NEW_BACKGROUND_TAB: 2
21 }; 18 };
22 19
23 /** 20 /**
24 * The JavaScript button event value for a middle click. 21 * The JavaScript button event value for a middle click.
25 * @type {number} 22 * @type {number}
26 * @const 23 * @const
27 */ 24 */
28 var MIDDLE_MOUSE_BUTTON = 1; 25 var MIDDLE_MOUSE_BUTTON = 1;
29 26
30 // =============================================================================
31 // Util functions
32 // =============================================================================
33
34 /** 27 /**
35 * The maximum number of suggestions to show. 28 * The maximum number of suggestions to show.
36 * @type {number} 29 * @type {number}
37 * @const 30 * @const
38 */ 31 */
39 var MAX_SUGGESTIONS_TO_SHOW = 5; 32 var MAX_SUGGESTIONS_TO_SHOW = 5;
40 33
41 /** 34 /**
42 * Assume any native suggestion with a score higher than this value has been 35 * Assume any native suggestion with a score higher than this value has been
43 * inlined by the browser. 36 * inlined by the browser.
44 * @type {number} 37 * @type {number}
45 * @const 38 * @const
46 */ 39 */
47 var INLINE_SUGGESTION_THRESHOLD = 1200; 40 var INLINE_SUGGESTION_THRESHOLD = 1200;
48 41
49 /** 42 /**
43 * The color code for a query.
44 * @type {number}
45 * @const
46 */
47 var QUERY_COLOR = 0x000000;
48
49 /**
50 * The color code for a display URL.
51 * @type {number}
52 * @const
53 */
54 var URL_COLOR = 0x009933;
55
56 /**
57 * The color code for a suggestion title.
58 * @type {number}
59 * @const
60 */
61 var TITLE_COLOR = 0x666666;
62
63 /**
64 * A top position which is off-screen.
65 * @type {string}
66 * @const
67 */
68 var OFF_SCREEN = '-1000px';
69
70 /**
71 * The expected origin of a suggestion iframe.
72 * @type {string}
73 * @const
74 */
75 var SUGGESTION_ORIGIN = 'chrome-search://suggestion';
76
77 /**
50 * Suggestion provider type corresponding to a verbatim URL suggestion. 78 * Suggestion provider type corresponding to a verbatim URL suggestion.
51 * @type {string} 79 * @type {string}
52 * @const 80 * @const
53 */ 81 */
54 var VERBATIM_URL_TYPE = 'url-what-you-typed'; 82 var VERBATIM_URL_TYPE = 'url-what-you-typed';
55 83
56 /** 84 /**
57 * Suggestion provider type corresponding to a verbatim search suggestion. 85 * Suggestion provider type corresponding to a verbatim search suggestion.
58 * @type {string} 86 * @type {string}
59 * @const 87 * @const
60 */ 88 */
61 var VERBATIM_SEARCH_TYPE = 'search-what-you-typed'; 89 var VERBATIM_SEARCH_TYPE = 'search-what-you-typed';
62 90
63 /** 91 /**
64 * The omnibox input value during the last onnativesuggestions event. 92 * "Up" arrow keycode.
65 * @type {string} 93 * @type {number}
94 * @const
66 */ 95 */
67 var lastInputValue = ''; 96 var KEY_UP_ARROW = 38;
68 97
69 /** 98 /**
70 * The ordered restricted ids of the currently displayed suggestions. Since the 99 * "Down" arrow keycode.
71 * suggestions contain the user's personal data (browser history) the searchBox 100 * @type {number}
72 * API embeds the content of the suggestion in a shadow dom, and assigns a 101 * @const
73 * random restricted id to each suggestion which is accessible to the JS.
74 * @type {Array.<number>}
75 */ 102 */
76 103 var KEY_DOWN_ARROW = 40;
77 var restrictedIds = [];
78 104
79 /** 105 /**
80 * The index of the currently selected suggestion or -1 if none are selected. 106 * The displayed suggestions.
107 * @type {SuggestionsBox}
108 */
109 var active;
Dan Beam 2013/04/06 00:18:13 could you rename this to activeBox or something?
Jered 2013/04/06 02:45:16 Done.
110
111 /**
112 * The suggestions being rendered.
113 * @type {SuggestionsBox}
114 */
115 var pending;
116
117 /**
118 * A pool of iframes to display suggestions.
119 * @type {IframePool}
120 */
121 var iframePool;
122
123 /**
124 * A serial number for the next suggestions rendered.
81 * @type {number} 125 * @type {number}
82 */ 126 */
83 var selectedIndex = -1; 127 var nextRequestId = 0;
84 128
85 /** 129 /**
86 * Shortcut for document.getElementById. 130 * Shortcut for document.querySelector.
87 * @param {string} id of the element. 131 * @param {string} selector A selector to query the desired element.
88 * @return {HTMLElement} with the id. 132 * @return {HTMLElement} matching selector.
Dan Beam 2013/04/06 00:18:13 s/matching selector/The first element to match |se
Jered 2013/04/06 02:45:16 Done.
89 */ 133 */
90 function $(id) { 134 function $qs(selector) {
91 return document.getElementById(id); 135 return document.querySelector(selector);
92 } 136 }
93 137
94 /** 138 /**
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 if (suggestion.destination_url) { // iframes.
107 var suggestionIframe = document.createElement('iframe');
108 suggestionIframe.className = 'contents';
109 suggestionIframe.src = suggestion.destination_url;
110 suggestionIframe.id = suggestion.rid;
111 suggestionDiv.appendChild(suggestionIframe);
112 } else {
113 var contentsContainer = document.createElement('div');
114 var contents = suggestion.combinedNode;
115 contents.classList.add('contents');
116 contentsContainer.appendChild(contents);
117 suggestionDiv.appendChild(contentsContainer);
118 suggestionDiv.onclick = function(event) {
119 handleSuggestionClick(suggestion.rid, event.button);
120 };
121 }
122
123 restrictedIds.push(suggestion.rid);
124 box.appendChild(suggestionDiv);
125 }
126
127 /**
128 * Renders the input suggestions.
129 * @param {Array} nativeSuggestions An array of native suggestions to render.
130 */
131 function renderSuggestions(nativeSuggestions) {
132 var box = document.createElement('div');
133 box.id = 'suggestionsBox';
134 $('suggestions-box-container').appendChild(box);
135
136 for (var i = 0, length = nativeSuggestions.length;
137 i < Math.min(MAX_SUGGESTIONS_TO_SHOW, length); ++i) {
138 addSuggestionToBox(nativeSuggestions[i], box, i == selectedIndex);
139 }
140 }
141
142 /**
143 * Clears the suggestions being displayed.
144 */
145 function clearSuggestions() {
146 $('suggestions-box-container').innerHTML = '';
147 restrictedIds = [];
148 selectedIndex = -1;
149 }
150
151 /**
152 * @return {number} The height of the dropdown.
153 */
154 function getDropdownHeight() {
155 return $('suggestions-box-container').offsetHeight;
156 }
157
158 /**
159 * @param {Object} suggestion A suggestion. 139 * @param {Object} suggestion A suggestion.
160 * @param {boolean} inVerbatimMode Are we in verbatim mode? 140 * @param {boolean} inVerbatimMode Are we in verbatim mode?
161 * @return {boolean} True if the suggestion should be selected. 141 * @return {boolean} True if the suggestion should be selected.
162 */ 142 */
163 function shouldSelectSuggestion(suggestion, inVerbatimMode) { 143 function shouldSelectSuggestion(suggestion, inVerbatimMode) {
164 var isVerbatimUrl = suggestion.type == VERBATIM_URL_TYPE; 144 var isVerbatimUrl = suggestion.type == VERBATIM_URL_TYPE;
165 var inlinableSuggestion = suggestion.type != VERBATIM_SEARCH_TYPE && 145 var inlineableSuggestion = suggestion.type != VERBATIM_SEARCH_TYPE &&
166 suggestion.rankingData.relevance > INLINE_SUGGESTION_THRESHOLD; 146 suggestion.rankingData.relevance > INLINE_SUGGESTION_THRESHOLD;
167 // Verbatim URLs should always be selected. Otherwise, select suggestions 147 // Verbatim URLs should always be selected. Otherwise, select suggestions
168 // with a high enough score unless we are in verbatim mode (e.g. backspacing 148 // with a high enough score unless we are in verbatim mode (e.g. backspacing
169 // away). 149 // away).
170 return isVerbatimUrl || (!inVerbatimMode && inlinableSuggestion); 150 return isVerbatimUrl || (!inVerbatimMode && inlineableSuggestion);
171 } 151 }
172 152
173 /** 153 /**
174 * Updates selectedIndex, bounding it between -1 and the total number of 154 * Extract the desired navigation behavior from a click button.
175 * of suggestions - 1 (looping as necessary), and selects the corresponding 155 * @param {number} button The Event#button property of a click event.
176 * suggestion. 156 * @return {WindowOpenDisposition} The desired behavior for
177 * @param {boolean} increment True to increment the selected suggestion, false 157 * navigateContentWindow.
178 * to decrement. 158 */
179 */ 159 function getDispositionFromClickButton(button) {
180 function updateSelectedSuggestion(increment) { 160 if (button == MIDDLE_MOUSE_BUTTON)
181 var numSuggestions = restrictedIds.length; 161 return WindowOpenDisposition.NEW_BACKGROUND_TAB;
182 if (!numSuggestions) 162 return WindowOpenDisposition.CURRENT_TAB;
183 return; 163 }
184 164
185 var oldSelection = $('suggestionsBox').querySelector('.selected'); 165
186 if (oldSelection) 166 /**
187 oldSelection.classList.remove('selected'); 167 * Manages a pool of chrome-search suggestion result iframes.
188 168 * @constructor
189 if (increment) 169 */
190 selectedIndex = ++selectedIndex > numSuggestions - 1 ? -1 : selectedIndex; 170 function IframePool() {
191 else 171 }
192 selectedIndex = --selectedIndex < -1 ? numSuggestions - 1 : selectedIndex; 172
193 var apiHandle = getApiObjectHandle(); 173 IframePool.prototype = {
194 if (selectedIndex == -1) { 174 /**
195 apiHandle.setValue(lastInputValue); 175 * HTML iframe elements.
176 * @type {Array.<Element>}
177 * @private
178 */
179 iframes_: [],
180
181 /**
182 * Initializes the pool with blank result template iframes, positioned off
183 * screen.
184 */
185 init: function() {
186 for (var i = 0; i < 2 * MAX_SUGGESTIONS_TO_SHOW; ++i) {
187 var iframe = document.createElement('iframe');
188 iframe.className = 'contents';
189 iframe.id = 'suggestion-text-' + i;
190 iframe.src = 'chrome-search://suggestion/result.html';
191 iframe.style.top = OFF_SCREEN;
192 iframe.addEventListener('mouseover', function(e) {
193 if (active)
194 active.hover(e.currentTarget.id);
195 }, false);
196 iframe.addEventListener('mouseout', function(e) {
197 if (active)
198 active.unhover(e.currentTarget.id);
199 }, false);
200 document.body.appendChild(iframe);
201 this.iframes_.push(iframe);
202 }
203 },
204
205 /**
206 * Reserves a free suggestion iframe from the pool.
207 * @return {Element} An iframe suitable for holding a suggestion.
208 */
209 reserve: function() {
210 return this.iframes_.pop();
211 },
212
213 /**
214 * Releases a suggestion iframe back into the pool.
215 * @param {Element} iframe The iframe to return to the pool.
216 */
217 release: function(iframe) {
218 this.iframes_.push(iframe);
219 iframe.style.top = OFF_SCREEN;
220 },
221 };
222
223
224 /**
225 * An individual suggestion.
226 * @param {!Object} data Autocomplete fields for this suggestion.
227 * @constructor
228 */
229 function Suggestion(data) {
230 /**
231 * Autocomplete fields for this suggestion.
232 * @type {!Object}
233 * @private
234 */
235 this.data_ = data;
236 }
237
238 Suggestion.prototype = {
239 /**
240 * Releases the iframe reserved for this suggestion.
241 */
242 destroy: function() {
243 if (this.iframe_)
244 iframePool.release(this.iframe_);
245 },
246
247 /**
248 * Creates and appends the placeholder div for this suggestion to box.
249 * @param {Element} box A suggestions box.
250 * @param {boolean} selected True if the suggestion should be drawn as
251 * selected and false otherwise.
252 */
253 appendToBox: function(box, selected) {
254 var div = document.createElement('div');
255 div.classList.add('suggestion');
256 div.classList.toggle('selected', selected);
257 div.classList.toggle('search', this.data_.is_search);
258 box.appendChild(div);
259 this.div_ = div;
260 },
261
262 /**
263 * Repositions the suggestion iframe to align with its expected dropdown
264 * position.
265 * @param {boolean} isRtl True if rendering right-to-left and false if not.
266 * @param {number} startMargin Leading space before suggestion.
267 * @param {number} totalMargin Total non-content space on suggestion line.
268 */
269 reposition: function(isRtl, startMargin, totalMargin) {
270 // Add in the expected parent offset and the top margin.
271 this.iframe_.style.top = this.div_.offsetTop + 4 + 'px';
272 // Call parseInt to enforce that startMargin and totalMargin are really
273 // numbers since we're interpolating CSS.
274 this.iframe_.style[isRtl ? 'right' : 'left'] =
275 parseInt(startMargin, 10) + 'px';
276 this.iframe_.style.width = '-webkit-calc(100% - ' +
277 (parseInt(totalMargin, 10) + 26) + 'px)';
278 },
279
280 /**
281 * Updates the suggestion selection state.
282 * @param {boolean} selected True if drawn selected or false if not.
283 */
284 select: function(selected) {
285 this.div_.classList.toggle('selected', selected);
286 },
287
288 /**
289 * Updates the suggestion hover state.
290 * @param {boolean} hovered True if drawn hovered or false if not.
291 */
292 hover: function(hovered) {
293 this.div_.classList.toggle('hovered', hovered);
294 },
295
296 /**
297 * @param {Window} iframeWindow The content window of an iframe.
298 * @return {boolean} True if this suggestion's iframe has the specified
299 * window and false if not.
300 */
301 hasIframeWindow: function(iframeWindow) {
302 return this.iframe_.contentWindow == iframeWindow;
303 },
304
305 /**
306 * @param {string} id An element id.
307 * @return {boolean} True if this suggestion's iframe has the specified id
308 * and false if not.
309 */
310 hasIframeId: function(id) {
311 return this.iframe_.id == id;
312 },
313
314 /**
315 * The iframe element for this suggestion.
316 * @type {Element}
317 */
318 set iframe(iframe) {
319 this.iframe_ = iframe;
320 },
321
322 /**
323 * The restricted id associated with this suggestion.
324 * @type {number}
325 */
326 get restrictedId() {
327 return this.data_.rid;
328 },
329 };
330
331
332 /**
333 * Displays a suggestions box.
334 * @param {string} inputValue The user text that prompted these suggestions.
335 * @param {!Array.<!Object>} suggestionData Suggestion data to display.
336 * @param {number} selectedIndex The index of the suggestion selected.
337 * @constructor
338 */
339 function SuggestionsBox(inputValue, suggestionData, selectedIndex) {
340 /**
341 * The user text that prompted these suggestions.
342 * @type {string}
343 * @private
344 */
345 this.inputValue_ = inputValue;
346
347 /**
348 * The index of the suggestion currently selected, whether by default or
349 * because the user arrowed down to it.
350 * @type {number}
351 * @private
352 */
353 this.selectedIndex_ = selectedIndex;
354
355 /**
356 * The index of the suggestion currently under the mouse pointer.
357 * @type {number}
358 * @private
359 */
360 this.hoveredIndex_ = -1;
361
362 /**
363 * A stamp to distinguish this suggestions box from others.
364 * @type {number}
365 * @private
366 */
367 this.requestId_ = nextRequestId++;
368
369 /**
370 * The ordered suggestions this box is displaying.
371 * @type {Array.<Suggestion>}
372 * @private
373 */
374 this.suggestions_ = [];
375 for (var i = 0; i < suggestionData.length; ++i) {
376 this.suggestions_.push(new Suggestion(suggestionData[i]));
377 }
378
379 /**
380 * An embedded search API handle.
381 * @type {Object}
382 * @private
383 */
384 this.apiHandle_ = getApiObjectHandle();
385
386 /**
387 * The container for this suggestions box. div.pending-container if inactive
388 * and div.active-container if active.
389 * @type {Element}
390 * @private
391 */
392 this.container_ = $qs('.pending-container');
Dan Beam 2013/04/06 00:18:13 assert(this.container_);
Jered 2013/04/06 02:45:16 See above.
393 }
394
395 SuggestionsBox.prototype = {
396 /**
397 * Releases suggestion iframes and ignores any load done message for the
398 * current suggestions.
399 */
400 destroy: function() {
401 for (var i = 0; i < this.suggestions_.length; ++i) {
402 this.suggestions_[i].destroy();
403 }
Dan Beam 2013/04/06 00:18:13 or alternatively this.suggestions_.length = 0;
Jered 2013/04/06 02:45:16 Did the while loop thing.
404 this.responseId = -1;
405 },
406
407 /**
408 * Starts rendering new suggestions.
409 */
410 loadSuggestions: function() {
411 // Create a placeholder DOM in the invisible container.
412 this.container_.innerHTML = '';
Dan Beam 2013/04/06 00:18:13 nit: \n
Jered 2013/04/06 02:45:16 Done.
413 var box = document.createElement('div');
414 box.className = 'suggestions-box';
415 this.container_.appendChild(box);
Dan Beam 2013/04/06 00:18:13 nit: \n
Jered 2013/04/06 02:45:16 Done.
416 var iframesToLoad = {};
417 for (var i = 0; i < this.suggestions_.length; ++i) {
418 var suggestion = this.suggestions_[i];
419 suggestion.appendToBox(box, i == this.selectedIndex_);
420 var iframe = iframePool.reserve();
421 suggestion.iframe = iframe;
422 iframesToLoad[iframe.id] = suggestion.restrictedId;
423 }
Dan Beam 2013/04/06 00:18:13 nit: \n
Jered 2013/04/06 02:45:16 Done.
424 // Ask the loader iframe to populate the iframes just reserved.
425 var loadRequest = {
426 'load': iframesToLoad,
Dan Beam 2013/04/06 00:18:13 we generally don't quote keys, but I understand wh
Jered 2013/04/06 02:45:16 Done.
427 'requestId': this.requestId_,
428 'style': {
429 'queryColor': QUERY_COLOR,
430 'urlColor': URL_COLOR,
431 'titleColor': TITLE_COLOR
432 }
433 };
434 $qs('#suggestion-loader').contentWindow.postMessage(loadRequest,
435 SUGGESTION_ORIGIN);
436 },
437
438 /**
439 * @param {number} responseId The id of a request that just finished
440 * rendering.
441 * @return {boolean} Whether the request is for the suggestions in this box.
442 */
443 isResponseCurrent: function(responseId) {
444 return responseId == this.requestId_;
445 },
446
447 /**
448 * Moves suggestion iframes into position.
449 */
450 repositionSuggestions: function() {
451 // Note: This may be called before margins are ready. In that case,
452 // suggestion iframes will initially be too large and then size down
453 // onresize.
454 var startMargin = this.apiHandle_.startMargin;
455 var totalMargin = window.innerWidth - this.apiHandle_.width;
456 var isRtl = this.apiHandle_.isRtl;
457 for (var i = 0; i < this.suggestions_.length; ++i) {
458 this.suggestions_[i].reposition(isRtl, startMargin, totalMargin);
459 }
460 },
461
462 /**
463 * Selects the suggestion before the current selection.
464 */
465 selectPrevious: function() {
466 this.changeSelection_(this.selectedIndex_ - 1);
467 },
468
469 /**
470 * Selects the suggestion after the current selection.
471 */
472 selectNext: function() {
473 this.changeSelection_(this.selectedIndex_ + 1);
474 },
475
476 /**
477 * Changes the current selected suggestion index.
478 * @param {number} index The new selection to suggest.
479 * @private
480 */
481 changeSelection_: function(index) {
482 if (index < -1)
483 index = -1;
484 var numSuggestions = this.suggestions_.length;
485 if (index > numSuggestions - 1)
486 index = numSuggestions - 1;
487 this.selectedIndex_ = index;
488
489 this.redrawSelection_();
490 this.redrawHover_();
491 },
492
493 /**
494 * Redraws the selected suggestion.
495 * @private
496 */
497 redrawSelection_: function() {
498 var oldSelection = this.container_.querySelector('.selected');
499 if (oldSelection)
500 oldSelection.classList.remove('selected');
501 if (this.selectedIndex_ == -1) {
502 this.apiHandle_.setValue(this.inputValue_);
503 } else {
504 this.suggestions_[this.selectedIndex_].select(true);
505 this.apiHandle_.setRestrictedValue(
506 this.suggestions_[this.selectedIndex_].restrcitedId);
507 }
508 },
509
510 /**
511 * Returns the restricted id of the iframe clicked.
512 * @param {!Window} iframeWindow The window of the iframe that was clicked.
513 * @return {?number} The restricted id clicked or null if none.
514 */
515 getClickTarget: function(iframeWindow) {
516 for (var i = 0; i < this.suggestions_.length; ++i) {
517 if (this.suggestions_[i].hasIframeWindow(iframeWindow))
518 return this.suggestions_[i].restrictedId;
519 }
520 return null;
521 },
522
523 /**
524 * Called when the user hovers on the specified iframe.
525 * @param {string} iframeId The id of the iframe hovered.
526 */
527 hover: function(iframeId) {
528 this.hoveredIndex_ = -1;
529 for (var i = 0; i < this.suggestions_.length; ++i) {
530 if (this.suggestions_[i].hasIframeId(iframeId)) {
531 this.hoveredIndex_ = i;
532 break;
533 }
534 }
535 this.redrawHover_();
536 },
537
538 /**
539 * Called when the user unhovers the specified iframe.
540 * @param {string} iframeId The id of the iframe hovered.
541 */
542 unhover: function(iframeId) {
543 if (this.suggestions_[this.hoveredIndex_] &&
544 this.suggestions_[this.hoveredIndex_].hasIframeId(iframeId)) {
545 this.clearHover();
546 }
547 },
548
549 /**
550 * Clears the current hover.
551 */
552 clearHover: function() {
553 this.hoveredIndex_ = -1;
554 this.redrawHover_();
555 },
556
557 /**
558 * Redraws the mouse hover background.
559 * @private
560 */
561 redrawHover_: function() {
562 var oldHover = this.container_.querySelector('.hovered');
563 if (oldHover)
564 oldHover.classList.remove('hovered');
565 if (this.hoveredIndex_ != -1 && this.hoveredIndex_ != this.selectedIndex_)
566 this.suggestions_[this.hoveredIndex_].hover(true);
567 },
568
569 /**
570 * Marks the suggestions container as active.
571 */
572 activate: function() {
573 this.container_.className = 'active-container';
574 },
575
576 /**
577 * Marks the suggestions container as inactive.
578 */
579 deactivate: function() {
580 this.container_.className = 'pending-container';
581 this.container_.innerHTML = '';
582 },
583
584 /**
585 * The height of the suggestions container.
586 * @type {number}
587 */
588 get height() {
589 return this.container_.offsetHeight;
590 },
591 };
592
593
594 /**
595 * Clears the currently active suggestions and shows pending suggestions.
596 */
597 function makePendingSuggestionsActive() {
598 if (active) {
599 active.deactivate();
600 active.destroy();
196 } else { 601 } else {
197 var newSelection = $('suggestionsBox').querySelector( 602 // Initially there will be no active suggestions, but we still want to use
198 '.suggestion:nth-of-type(' + (selectedIndex + 1) + ')'); 603 // div.active-container to load the next suggestions.
199 newSelection.classList.add('selected'); 604 $qs('.active-container').className = 'pending-container';
200 apiHandle.setRestrictedValue(restrictedIds[selectedIndex]);
201 } 605 }
202 } 606 pending.activate();
203 607 active = pending;
204 // ============================================================================= 608 pending = null;
205 // Handlers / API stuff 609 active.repositionSuggestions();
206 // ============================================================================= 610 getApiObjectHandle().showOverlay(active.height);
611 }
612
613 /**
614 * Hides the active suggestions box.
615 */
616 function hideActiveSuggestions() {
617 getApiObjectHandle().showOverlay(0);
618 if (active) {
619 $qs('.active-container').innerHTML = '';
620 active.destroy();
621 }
622 active = null;
623 }
207 624
208 /** 625 /**
209 * @return {Object} the handle to the searchBox API. 626 * @return {Object} the handle to the searchBox API.
210 */ 627 */
211 function getApiObjectHandle() { 628 function getApiObjectHandle() {
212 if (window.cideb) 629 if (window.cideb)
213 return window.cideb; 630 return window.cideb;
214 if (window.navigator && window.navigator.embeddedSearch && 631 if (window.navigator && window.navigator.embeddedSearch &&
215 window.navigator.embeddedSearch.searchBox) 632 window.navigator.embeddedSearch.searchBox)
216 return window.navigator.embeddedSearch.searchBox; 633 return window.navigator.embeddedSearch.searchBox;
217 if (window.chrome && window.chrome.embeddedSearch && 634 if (window.chrome && window.chrome.embeddedSearch &&
218 window.chrome.embeddedSearch.searchBox) 635 window.chrome.embeddedSearch.searchBox)
219 return window.chrome.embeddedSearch.searchBox; 636 return window.chrome.embeddedSearch.searchBox;
220 return null; 637 return null;
221 } 638 }
222 639
223 /** 640 /**
224 * Updates suggestions in response to a onchange or onnativesuggestions call. 641 * Updates suggestions in response to a onchange or onnativesuggestions call.
225 */ 642 */
226 function updateSuggestions() { 643 function updateSuggestions() {
644 if (pending) {
Dan Beam 2013/04/06 00:18:13 nit: if (pending) pending.destroy(); pend
Jered 2013/04/06 02:45:16 Done.
645 pending.destroy();
646 pending = null;
647 }
227 var apiHandle = getApiObjectHandle(); 648 var apiHandle = getApiObjectHandle();
228 lastInputValue = apiHandle.value; 649 var inputValue = apiHandle.value;
229 650 var suggestions = apiHandle.nativeSuggestions;
230 clearSuggestions(); 651 if (!inputValue || !suggestions.length) {
231 var nativeSuggestions = apiHandle.nativeSuggestions; 652 hideActiveSuggestions();
232 if (nativeSuggestions.length) { 653 return;
233 nativeSuggestions.sort(function(a, b) { 654 }
655 if (suggestions.length) {
656 suggestions.sort(function(a, b) {
234 return b.rankingData.relevance - a.rankingData.relevance; 657 return b.rankingData.relevance - a.rankingData.relevance;
235 }); 658 });
236 if (shouldSelectSuggestion(nativeSuggestions[0], apiHandle.verbatim)) 659 var selectedIndex = -1;
660 if (shouldSelectSuggestion(suggestions[0], apiHandle.verbatim))
237 selectedIndex = 0; 661 selectedIndex = 0;
238 renderSuggestions(nativeSuggestions); 662 pending = new SuggestionsBox(inputValue,
663 suggestions.slice(0, MAX_SUGGESTIONS_TO_SHOW), selectedIndex);
664 pending.loadSuggestions();
239 } 665 }
240
241 var height = getDropdownHeight();
242 apiHandle.showOverlay(height);
243 } 666 }
244 667
245 /** 668 /**
246 * Appends a style node for suggestion properties that depend on apiHandle. 669 * Appends a style node for suggestion properties that depend on apiHandle.
247 */ 670 */
248 function appendSuggestionStyles() { 671 function appendSuggestionStyles() {
249 var apiHandle = getApiObjectHandle(); 672 var apiHandle = getApiObjectHandle();
250 var isRtl = apiHandle.rtl; 673 var isRtl = apiHandle.rtl;
251 var startMargin = apiHandle.startMargin; 674 var startMargin = apiHandle.startMargin;
252 var style = document.createElement('style'); 675 var style = document.createElement('style');
253 style.type = 'text/css'; 676 style.type = 'text/css';
254 style.id = 'suggestionStyle'; 677 style.id = 'suggestionStyle';
255 style.textContent = 678 style.textContent =
256 '.suggestion, ' + 679 '.suggestion, ' +
257 '.suggestion.search {' + 680 '.suggestion.search {' +
258 ' background-position: ' + 681 ' background-position: ' +
259 (isRtl ? '-webkit-calc(100% - 5px)' : '5px') + ' 4px;' + 682 (isRtl ? '-webkit-calc(100% - 5px)' : '5px') + ' 4px;' +
260 ' -webkit-margin-start: ' + startMargin + 'px;' + 683 ' -webkit-margin-start: ' + startMargin + 'px;' +
261 ' -webkit-margin-end: ' + 684 ' -webkit-margin-end: ' +
262 (window.innerWidth - apiHandle.width - startMargin) + 'px;' + 685 (window.innerWidth - apiHandle.width - startMargin) + 'px;' +
263 ' font: ' + apiHandle.fontSize + 'px "' + apiHandle.font + '";' + 686 ' font: ' + apiHandle.fontSize + 'px "' + apiHandle.font + '";' +
264 '}'; 687 '}';
265 document.querySelector('head').appendChild(style); 688 $qs('head').appendChild(style);
689 if (active)
690 active.repositionSuggestions();
266 window.removeEventListener('resize', appendSuggestionStyles); 691 window.removeEventListener('resize', appendSuggestionStyles);
267 } 692 }
268 693
269 /** 694 /**
270 * Extract the desired navigation behavior from a click button. 695 * Makes keys navigate through suggestions.
271 * @param {number} button The Event#button property of a click event.
272 * @return {WindowOpenDisposition} The desired behavior for
273 * navigateContentWindow.
274 */
275 function getDispositionFromClickButton(button) {
276 if (button == MIDDLE_MOUSE_BUTTON)
277 return WindowOpenDisposition.NEW_BACKGROUND_TAB;
278 return WindowOpenDisposition.CURRENT_TAB;
279 }
280
281 /**
282 * Handles suggestion clicks.
283 * @param {number} restrictedId The restricted id of the suggestion being
284 * clicked.
285 * @param {number} button The Event#button property of a click event.
286 *
287 */
288 function handleSuggestionClick(restrictedId, button) {
289 clearSuggestions();
290 getApiObjectHandle().navigateContentWindow(
291 restrictedId, getDispositionFromClickButton(button));
292 }
293
294 /**
295 * chrome.searchBox.onkeypress implementation.
296 * @param {Object} e The key being pressed. 696 * @param {Object} e The key being pressed.
297 */ 697 */
298 function handleKeyPress(e) { 698 function handleKeyPress(e) {
699 if (!active)
700 return;
299 switch (e.keyCode) { 701 switch (e.keyCode) {
300 case 38: // Up arrow 702 case KEY_UP_ARROW:
301 updateSelectedSuggestion(false); 703 active.selectPrevious();
302 break; 704 break;
303 case 40: // Down arrow 705 case KEY_DOWN_ARROW:
304 updateSelectedSuggestion(true); 706 active.selectNext();
305 break; 707 break;
306 } 708 }
307 } 709 }
308 710
309 /** 711 /**
310 * Handles the postMessage calls from the result iframes. 712 * Handles postMessage calls from suggestion iframes.
311 * @param {Object} message The message containing details of clicks the iframes. 713 * @param {Object} message A notification that all iframes are done loading or
714 * that an iframe was clicked.
312 */ 715 */
313 function handleMessage(message) { 716 function handleMessage(message) {
314 if (message.origin != 'null' || !message.data || 717 if (message.origin != SUGGESTION_ORIGIN)
315 message.data.eventType != 'click') {
316 return; 718 return;
317 }
318 719
319 var iframes = document.getElementsByClassName('contents'); 720 if ('loaded' in message.data) {
320 for (var i = 0; i < iframes.length; ++i) { 721 if (pending && pending.isResponseCurrent(message.data.loaded)) {
321 if (iframes[i].contentWindow == message.source) { 722 makePendingSuggestionsActive();
322 handleSuggestionClick(parseInt(iframes[i].id, 10), 723 }
323 message.data.button); 724 } else if ('click' in message.data) {
324 break; 725 if (active) {
726 var restrictedId = active.getClickTarget(message.source);
727 if (restrictedId != null) {
728 hideActiveSuggestions();
729 getApiObjectHandle().navigateContentWindow(restrictedId,
730 getDispositionFromClickButton(message.data.click));
731 }
325 } 732 }
326 } 733 }
327 } 734 }
328 735
329 /** 736 /**
330 * chrome.searchBox.embeddedSearch.onsubmit implementation. 737 * Sets up the embedded search API and creates suggestion iframes.
331 */ 738 */
332 function onSubmit() { 739 function init() {
333 } 740 iframePool = new IframePool();
334 741 iframePool.init();
335 /**
336 * Sets up the searchBox API.
337 */
338 function setUpApi() {
339 var apiHandle = getApiObjectHandle(); 742 var apiHandle = getApiObjectHandle();
340 apiHandle.onnativesuggestions = updateSuggestions; 743 apiHandle.onnativesuggestions = updateSuggestions;
341 apiHandle.onchange = updateSuggestions; 744 apiHandle.onchange = updateSuggestions;
342 apiHandle.onkeypress = handleKeyPress; 745 apiHandle.onkeypress = handleKeyPress;
343 apiHandle.onsubmit = onSubmit; 746 // Instant checks for this handler to be bound.
344 $('suggestions-box-container').dir = apiHandle.rtl ? 'rtl' : 'ltr'; 747 apiHandle.onsubmit = function() {};
748 $qs('.active-container').dir = apiHandle.rtl ? 'rtl' : 'ltr';
749 $qs('.pending-container').dir = apiHandle.rtl ? 'rtl' : 'ltr';
345 // Delay adding these styles until the window width is available. 750 // Delay adding these styles until the window width is available.
346 window.addEventListener('resize', appendSuggestionStyles); 751 window.addEventListener('resize', appendSuggestionStyles);
347 if (apiHandle.nativeSuggestions.length) 752 if (apiHandle.nativeSuggestions.length)
348 handleNativeSuggestions(); 753 updateSuggestions();
349 } 754 }
350 755
351 document.addEventListener('DOMContentLoaded', setUpApi); 756 document.addEventListener('DOMContentLoaded', init);
352 window.addEventListener('message', handleMessage, false); 757 window.addEventListener('message', handleMessage, false);
758 window.addEventListener('blur', function() {
759 if (active)
760 active.clearHover();
761 }, false);
762 })();
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698