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

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: Rebase. 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;
samarth 2013/04/05 22:30:25 If we are using closure style, then add this membe
Jered 2013/04/06 02:45:16 Not using closure style.
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';
samarth 2013/04/05 22:30:25 Can you add named constants for the 4 and 26 below
Jered 2013/04/06 02:45:16 Done.
282 // Call parseInt to enforce that startMargin and totalMargin are really
283 // numbers since we're interpolating CSS.
284 this.iframe_.style[isRtl ? 'right' : 'left'] =
285 parseInt(startMargin, 10) + 'px';
Dan Beam 2013/04/06 00:18:12 what happens when this is NaN?
Jered 2013/04/06 02:45:16 Changed to set this style only if finite.
286 this.iframe_.style.width = '-webkit-calc(100% - ' +
287 (parseInt(totalMargin, 10) + 26) + 'px)';
288 };
289
290 /**
291 * Updates the suggestion selection state.
292 * @param {boolean} selected True if drawn selected or false if not.
293 */
294 Suggestion.prototype.select = function(selected) {
295 this.div_.classList.toggle('selected', selected);
296 };
297
298 /**
299 * Updates the suggestion hover state.
300 * @param {boolean} hovered True if drawn hovered or false if not.
301 */
302 Suggestion.prototype.hover = function(hovered) {
303 this.div_.classList.toggle('hovered', hovered);
304 };
305
306 /**
307 * @param {Window} iframeWindow The content window of an iframe.
308 * @return {boolean} True if this suggestion's iframe has the specified window
309 * and false if not.
310 */
311 Suggestion.prototype.hasIframeWindow = function(iframeWindow) {
312 return this.iframe_.contentWindow == iframeWindow;
313 };
314
315 /**
316 * @param {string} id An element id.
317 * @return {boolean} True if this suggestion's iframe has the specified id and
318 * false if not.
319 */
320 Suggestion.prototype.hasIframeId = function(id) {
321 return this.iframe_.id == id;
322 };
323
324
325 /**
326 * Displays a suggestions box.
327 * @param {string} inputValue The user text that prompted these suggestions.
328 * @param {!Array.<!Object>} suggestionData Suggestion data to display.
329 * @param {number} selectedIndex The index of the suggestion selected.
330 * @constructor
331 */
332 function SuggestionsBox(inputValue, suggestionData, selectedIndex) {
333 /**
334 * The user text that prompted these suggestions.
335 * @type {string}
336 * @private
337 */
338 this.inputValue_ = inputValue;
339
340 /**
341 * The index of the suggestion currently selected, whether by default or
342 * because the user arrowed down to it.
343 * @type {number}
344 * @private
345 */
346 this.selectedIndex_ = selectedIndex;
347
348 /**
349 * The index of the suggestion currently under the mouse pointer.
350 * @type {number}
351 * @private
352 */
353 this.hoveredIndex_ = -1;
354
355 /**
356 * A stamp to distinguish this suggestions box from others.
357 * @type {number}
358 * @private
359 */
360 this.requestId_ = nextRequestId++;
361
362 /**
363 * The ordered suggestions this box is displaying.
364 * @type {Array.<Suggestion>}
365 * @private
366 */
367 this.suggestions_ = [];
368 for (var i = 0; i < suggestionData.length; i++) {
369 this.suggestions_.push(new Suggestion(suggestionData[i]));
370 }
371
372 /**
373 * An embedded search API handle.
374 * @type {Object}
375 * @private
376 */
377 this.apiHandle_ = getApiObjectHandle();
378
379 /**
380 * The container for this suggestions box. div.pending-container if inactive
381 * and div.active-container if active.
382 * @type {Element}
383 * @private
384 */
385 this.container_ = $$('.pending-container');
386 }
387
388 /**
389 * Releases suggestion iframes and ignores any load done message for the
390 * current suggestions.
391 */
392 SuggestionsBox.prototype.destroy = function() {
393 for (var i = 0, suggestion; suggestion = this.suggestions_[i]; i++) {
394 suggestion.destroy();
395 }
Dan Beam 2013/04/06 00:18:12 doesn't this leave you with a bunch of destroyed s
Jered 2013/04/06 02:45:16 Done.
396 this.responseId = -1;
397 };
398
399 /**
400 * Starts rendering new suggestions.
401 */
402 SuggestionsBox.prototype.loadSuggestions = function() {
403 // Create a placeholder DOM in the invisible container.
404 this.container_.innerHTML = '';
405 var box = document.createElement('div');
406 box.className = 'suggestions-box';
407 this.container_.appendChild(box);
408 var iframesToLoad = {};
409 for (var i = 0, suggestion; suggestion = this.suggestions_[i]; i++) {
410 suggestion.appendToBox(box, i == this.selectedIndex_);
411 var iframe = iframePool.reserve();
412 suggestion.setIframe(iframe);
413 iframesToLoad[iframe.id] = suggestion.getRestrictedId();
414 }
415 // Ask the loader iframe to populate the iframes just reserved.
416 var loadRequest = {
417 'load': iframesToLoad,
418 'requestId': this.requestId_,
419 'style': {
420 'queryColor': QUERY_COLOR,
421 'urlColor': URL_COLOR,
422 'titleColor': TITLE_COLOR
423 }
424 };
425 $$('#suggestion-loader').contentWindow.postMessage(loadRequest,
426 SUGGESTION_ORIGIN);
427 };
428
429 /**
430 * @param {number} responseId The id of a request that just finished rendering.
431 * @return {boolean} Whether the request is for the suggestions in this box.
432 */
433 SuggestionsBox.prototype.isResponseCurrent = function(responseId) {
434 return responseId == this.requestId_;
435 };
436
437 /**
438 * Moves suggestion iframes into position.
439 */
440 SuggestionsBox.prototype.repositionSuggestions = function() {
441 // Note: This may be called before margins are ready. In that case,
442 // suggestion iframes will initially be too large and then size down
443 // onresize.
444 var startMargin = this.apiHandle_.startMargin;
445 var totalMargin = window.innerWidth - this.apiHandle_.width;
446 var isRtl = this.apiHandle_.isRtl;
447 for (var i = 0, suggestion; suggestion = this.suggestions_[i]; i++) {
448 suggestion.reposition(isRtl, startMargin, totalMargin);
449 }
450 };
451
452 /**
453 * Selects the suggestion before the current selection.
454 */
455 SuggestionsBox.prototype.selectPrevious = function() {
456 this.changeSelection_(this.selectedIndex_ - 1);
457 };
458
459 /**
460 * Selects the suggestion after the current selection.
461 */
462 SuggestionsBox.prototype.selectNext = function() {
463 this.changeSelection_(this.selectedIndex_ + 1);
464 };
465
466 /**
467 * Changes the current selected suggestion index.
468 * @param {number} index The new selection to suggest.
469 * @private
470 */
471 SuggestionsBox.prototype.changeSelection_ = function(index) {
472 if (index < -1)
473 index = -1;
Dan Beam 2013/04/06 00:18:12 index = Math.min(numSuggestions - 1, Math.max(-1,
Jered 2013/04/06 02:45:16 Done.
474 var numSuggestions = this.suggestions_.length;
475 if (index > numSuggestions - 1)
476 index = numSuggestions - 1;
477 this.selectedIndex_ = index;
478
479 this.redrawSelection_();
480 this.redrawHover_();
481 };
482
483 /**
484 * Redraws the selected suggestion.
485 * @private
486 */
487 SuggestionsBox.prototype.redrawSelection_ = function() {
488 var oldSelection = this.container_.querySelector('.selected');
samarth 2013/04/05 22:30:25 Optional: you could have looked up the existing se
Jered 2013/04/06 02:45:16 I like this way because I can tell for sure there'
186 if (oldSelection) 489 if (oldSelection)
187 oldSelection.classList.remove('selected'); 490 oldSelection.classList.remove('selected');
188 491 if (this.selectedIndex_ == -1) {
189 if (increment) 492 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 { 493 } else {
197 var newSelection = $('suggestionsBox').querySelector( 494 this.suggestions_[this.selectedIndex_].select(true);
198 '.suggestion:nth-of-type(' + (selectedIndex + 1) + ')'); 495 this.apiHandle_.setRestrictedValue(
199 newSelection.classList.add('selected'); 496 this.suggestions_[this.selectedIndex_].getRestrictedId());
200 apiHandle.setRestrictedValue(restrictedIds[selectedIndex]); 497 }
201 } 498 };
202 } 499
203 500 /**
204 // ============================================================================= 501 * Returns the restricted id of the iframe clicked.
205 // Handlers / API stuff 502 * @param {!Window} iframeWindow The window of the iframe that was clicked.
206 // ============================================================================= 503 * @return {?number} The restricted id clicked or null if none.
504 */
505 SuggestionsBox.prototype.getClickTarget = function(iframeWindow) {
506 for (var i = 0, suggestion; suggestion = this.suggestions_[i]; ++i) {
507 if (suggestion.hasIframeWindow(iframeWindow))
508 return suggestion.getRestrictedId();
509 }
510 return null;
511 };
512
513 /**
514 * Called when the user hovers on the specified iframe.
515 * @param {string} iframeId The id of the iframe hovered.
516 */
517 SuggestionsBox.prototype.hover = function(iframeId) {
518 this.hoveredIndex_ = -1;
519 for (var i = 0, suggestion; suggestion = this.suggestions_[i]; ++i) {
520 if (suggestion.hasIframeId(iframeId)) {
521 this.hoveredIndex_ = i;
522 break;
523 }
524 }
525 this.redrawHover_();
526 };
527
528 /**
529 * Called when the user unhovers the specified iframe.
530 * @param {string} iframeId The id of the iframe hovered.
531 */
532 SuggestionsBox.prototype.unhover = function(iframeId) {
533 if (this.suggestions_[this.hoveredIndex_] &&
534 this.suggestions_[this.hoveredIndex_].hasIframeId(iframeId)) {
535 this.clearHover();
536 }
537 };
538
539 /**
540 * Clears the current hover.
541 */
542 SuggestionsBox.prototype.clearHover = function() {
543 this.hoveredIndex_ = -1;
544 this.redrawHover_();
545 };
546
547 /**
548 * Redraws the mouse hover background.
549 * @private
550 */
551 SuggestionsBox.prototype.redrawHover_ = function() {
552 var oldHover = this.container_.querySelector('.hovered');
553 if (oldHover)
554 oldHover.classList.remove('hovered');
samarth 2013/04/05 22:30:25 Again, it's weird that you have a .hover() method
Jered 2013/04/06 02:45:16 See above. It's mostly paranoia from past uis that
555 if (this.hoveredIndex_ != -1 && this.hoveredIndex_ != this.selectedIndex_)
556 this.suggestions_[this.hoveredIndex_].hover(true);
557 };
558
559 /**
560 * Marks the suggestions container as active.
561 */
562 SuggestionsBox.prototype.activate = function() {
563 this.container_.className = 'active-container';
564 };
565
566 /**
567 * Marks the suggestions container as inactive.
568 */
569 SuggestionsBox.prototype.deactivate = function() {
570 this.container_.className = 'pending-container';
571 this.container_.innerHTML = '';
572 };
573
574 /**
575 * @return {number} The height of the suggestions container.
576 */
577 SuggestionsBox.prototype.getHeight = function() {
578 return this.container_.offsetHeight;
579 };
580
581 /**
582 * Clears the currently active suggestions and shows pending suggestions.
583 */
584 function makePendingSuggestionsActive() {
585 if (active) {
samarth 2013/04/05 22:30:25 Yay, I can understand this function now :)
Jered 2013/04/06 02:45:16 Awesome!
586 active.deactivate();
587 active.destroy();
588 } else {
589 // Initially there will be no active suggestions, but we still want to use
590 // div.active-container to load the next suggestions.
591 $$('.active-container').className = 'pending-container';
592 }
593 pending.activate();
594 active = pending;
595 pending = null;
596 active.repositionSuggestions();
597 getApiObjectHandle().showOverlay(active.getHeight());
598 }
599
600 /**
601 * Hides the active suggestions box.
602 */
603 function hideActiveSuggestions() {
604 getApiObjectHandle().showOverlay(0);
605 if (active) {
606 $$('.active-container').innerHTML = '';
607 active.destroy();
608 }
609 active = null;
610 }
207 611
208 /** 612 /**
209 * @return {Object} the handle to the searchBox API. 613 * @return {Object} the handle to the searchBox API.
210 */ 614 */
211 function getApiObjectHandle() { 615 function getApiObjectHandle() {
212 if (window.cideb) 616 if (window.cideb)
213 return window.cideb; 617 return window.cideb;
214 if (window.navigator && window.navigator.embeddedSearch && 618 if (window.navigator && window.navigator.embeddedSearch &&
215 window.navigator.embeddedSearch.searchBox) 619 window.navigator.embeddedSearch.searchBox)
216 return window.navigator.embeddedSearch.searchBox; 620 return window.navigator.embeddedSearch.searchBox;
217 if (window.chrome && window.chrome.embeddedSearch && 621 if (window.chrome && window.chrome.embeddedSearch &&
218 window.chrome.embeddedSearch.searchBox) 622 window.chrome.embeddedSearch.searchBox)
219 return window.chrome.embeddedSearch.searchBox; 623 return window.chrome.embeddedSearch.searchBox;
220 return null; 624 return null;
221 } 625 }
222 626
223 /** 627 /**
224 * Updates suggestions in response to a onchange or onnativesuggestions call. 628 * Updates suggestions in response to a onchange or onnativesuggestions call.
225 */ 629 */
226 function updateSuggestions() { 630 function updateSuggestions() {
631 if (pending) {
632 pending.destroy();
633 pending = null;
634 }
227 var apiHandle = getApiObjectHandle(); 635 var apiHandle = getApiObjectHandle();
228 lastInputValue = apiHandle.value; 636 var inputValue = apiHandle.value;
229 637 var suggestions = apiHandle.nativeSuggestions;
230 clearSuggestions(); 638 if (!inputValue || !suggestions.length) {
231 var nativeSuggestions = apiHandle.nativeSuggestions; 639 hideActiveSuggestions();
232 if (nativeSuggestions.length) { 640 return;
233 nativeSuggestions.sort(function(a, b) { 641 }
642 if (suggestions.length) {
643 suggestions.sort(function(a, b) {
234 return b.rankingData.relevance - a.rankingData.relevance; 644 return b.rankingData.relevance - a.rankingData.relevance;
235 }); 645 });
236 if (shouldSelectSuggestion(nativeSuggestions[0], apiHandle.verbatim)) 646 var selectedIndex = -1;
647 if (shouldSelectSuggestion(suggestions[0], apiHandle.verbatim))
samarth 2013/04/05 22:30:25 Optional: you can make this a method on the Sugges
Jered 2013/04/06 02:45:16 Alas, this is suggestionData, not a Suggestion obj
237 selectedIndex = 0; 648 selectedIndex = 0;
238 renderSuggestions(nativeSuggestions); 649 pending = new SuggestionsBox(inputValue,
650 suggestions.slice(0, MAX_SUGGESTIONS_TO_SHOW), selectedIndex);
651 pending.loadSuggestions();
239 } 652 }
240
241 var height = getDropdownHeight();
242 apiHandle.showOverlay(height);
243 } 653 }
244 654
245 /** 655 /**
246 * Appends a style node for suggestion properties that depend on apiHandle. 656 * Appends a style node for suggestion properties that depend on apiHandle.
247 */ 657 */
248 function appendSuggestionStyles() { 658 function appendSuggestionStyles() {
249 var apiHandle = getApiObjectHandle(); 659 var apiHandle = getApiObjectHandle();
250 var isRtl = apiHandle.rtl; 660 var isRtl = apiHandle.rtl;
251 var startMargin = apiHandle.startMargin; 661 var startMargin = apiHandle.startMargin;
252 var style = document.createElement('style'); 662 var style = document.createElement('style');
253 style.type = 'text/css'; 663 style.type = 'text/css';
254 style.id = 'suggestionStyle'; 664 style.id = 'suggestionStyle';
255 style.textContent = 665 style.textContent =
256 '.suggestion, ' + 666 '.suggestion, ' +
257 '.suggestion.search {' + 667 '.suggestion.search {' +
258 ' background-position: ' + 668 ' background-position: ' +
259 (isRtl ? '-webkit-calc(100% - 5px)' : '5px') + ' 4px;' + 669 (isRtl ? '-webkit-calc(100% - 5px)' : '5px') + ' 4px;' +
260 ' -webkit-margin-start: ' + startMargin + 'px;' + 670 ' -webkit-margin-start: ' + startMargin + 'px;' +
261 ' -webkit-margin-end: ' + 671 ' -webkit-margin-end: ' +
262 (window.innerWidth - apiHandle.width - startMargin) + 'px;' + 672 (window.innerWidth - apiHandle.width - startMargin) + 'px;' +
263 ' font: ' + apiHandle.fontSize + 'px "' + apiHandle.font + '";' + 673 ' font: ' + apiHandle.fontSize + 'px "' + apiHandle.font + '";' +
264 '}'; 674 '}';
265 document.querySelector('head').appendChild(style); 675 $$('head').appendChild(style);
676 if (active)
677 active.repositionSuggestions();
266 window.removeEventListener('resize', appendSuggestionStyles); 678 window.removeEventListener('resize', appendSuggestionStyles);
267 } 679 }
268 680
269 /** 681 /**
270 * Extract the desired navigation behavior from a click button. 682 * 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. 683 * @param {Object} e The key being pressed.
297 */ 684 */
298 function handleKeyPress(e) { 685 function handleKeyPress(e) {
686 if (!active)
687 return;
Dan Beam 2013/04/06 00:18:12 nit: \n
Jered 2013/04/06 02:45:16 Done.
299 switch (e.keyCode) { 688 switch (e.keyCode) {
300 case 38: // Up arrow 689 case KEY_UP_ARROW:
301 updateSelectedSuggestion(false); 690 active.selectPrevious();
302 break; 691 break;
303 case 40: // Down arrow 692 case KEY_DOWN_ARROW:
304 updateSelectedSuggestion(true); 693 active.selectNext();
305 break; 694 break;
306 } 695 }
307 } 696 }
308 697
309 /** 698 /**
310 * Handles the postMessage calls from the result iframes. 699 * Handles postMessage calls from suggestion iframes.
311 * @param {Object} message The message containing details of clicks the iframes. 700 * @param {Object} message A notification that all iframes are done loading or
701 * that an iframe was clicked.
312 */ 702 */
313 function handleMessage(message) { 703 function handleMessage(message) {
314 if (message.origin != 'null' || !message.data || 704 if (message.origin != SUGGESTION_ORIGIN)
315 message.data.eventType != 'click') {
316 return; 705 return;
317 }
318 706
319 var iframes = document.getElementsByClassName('contents'); 707 if ('loaded' in message.data) {
320 for (var i = 0; i < iframes.length; ++i) { 708 if (pending && pending.isResponseCurrent(message.data.loaded)) {
321 if (iframes[i].contentWindow == message.source) { 709 makePendingSuggestionsActive();
322 handleSuggestionClick(parseInt(iframes[i].id, 10), 710 }
323 message.data.button); 711 } else if ('click' in message.data) {
324 break; 712 if (active) {
713 var restrictedId = active.getClickTarget(message.source);
714 if (restrictedId != null) {
715 hideActiveSuggestions();
716 getApiObjectHandle().navigateContentWindow(restrictedId,
717 getDispositionFromClickButton(message.data.click));
718 }
325 } 719 }
326 } 720 }
327 } 721 }
328 722
329 /** 723 /**
330 * chrome.searchBox.embeddedSearch.onsubmit implementation. 724 * Sets up the embedded search API and creates suggestion iframes.
331 */ 725 */
332 function onSubmit() { 726 function init() {
333 } 727 iframePool = new IframePool();
334 728 iframePool.init();
335 /**
336 * Sets up the searchBox API.
337 */
338 function setUpApi() {
339 var apiHandle = getApiObjectHandle(); 729 var apiHandle = getApiObjectHandle();
340 apiHandle.onnativesuggestions = updateSuggestions; 730 apiHandle.onnativesuggestions = updateSuggestions;
341 apiHandle.onchange = updateSuggestions; 731 apiHandle.onchange = updateSuggestions;
342 apiHandle.onkeypress = handleKeyPress; 732 apiHandle.onkeypress = handleKeyPress;
343 apiHandle.onsubmit = onSubmit; 733 // Instant checks for this handler to be bound.
344 $('suggestions-box-container').dir = apiHandle.rtl ? 'rtl' : 'ltr'; 734 apiHandle.onsubmit = function() {};
735 $$('.active-container').dir = apiHandle.rtl ? 'rtl' : 'ltr';
736 $$('.pending-container').dir = apiHandle.rtl ? 'rtl' : 'ltr';
345 // Delay adding these styles until the window width is available. 737 // Delay adding these styles until the window width is available.
346 window.addEventListener('resize', appendSuggestionStyles); 738 window.addEventListener('resize', appendSuggestionStyles);
347 if (apiHandle.nativeSuggestions.length) 739 if (apiHandle.nativeSuggestions.length)
348 handleNativeSuggestions(); 740 updateSuggestions();
349 } 741 }
350 742
351 document.addEventListener('DOMContentLoaded', setUpApi); 743 document.addEventListener('DOMContentLoaded', init);
352 window.addEventListener('message', handleMessage, false); 744 window.addEventListener('message', handleMessage, false);
745 window.addEventListener('blur', function() {
746 if (active)
747 active.clearHover();
748 }, false);
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698