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

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

Powered by Google App Engine
This is Rietveld 408576698