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

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

Powered by Google App Engine
This is Rietveld 408576698