Chromium Code Reviews| Index: chrome/browser/resources/local_omnibox_popup/local_omnibox_popup.js |
| diff --git a/chrome/browser/resources/local_omnibox_popup/local_omnibox_popup.js b/chrome/browser/resources/local_omnibox_popup/local_omnibox_popup.js |
| index c86515e86f200762e5c27d05268ac0d9d1b4b99c..84d3cfdce4e4bc9438667cfaee7725d0be055436 100644 |
| --- a/chrome/browser/resources/local_omnibox_popup/local_omnibox_popup.js |
| +++ b/chrome/browser/resources/local_omnibox_popup/local_omnibox_popup.js |
| @@ -7,10 +7,6 @@ |
| * features are not supported on the server. |
| */ |
| -// ========================================================== |
| -// Enums. |
| -// ========================================================== |
| - |
| /** |
| * Possible behaviors for navigateContentWindow. |
| * @enum {number} |
| @@ -27,10 +23,6 @@ var WindowOpenDisposition = { |
| */ |
| var MIDDLE_MOUSE_BUTTON = 1; |
| -// ============================================================================= |
| -// Util functions |
| -// ============================================================================= |
| - |
| /** |
| * The maximum number of suggestions to show. |
| * @type {number} |
| @@ -47,112 +39,99 @@ var MAX_SUGGESTIONS_TO_SHOW = 5; |
| var INLINE_SUGGESTION_THRESHOLD = 1200; |
| /** |
| - * Suggestion provider type corresponding to a verbatim URL suggestion. |
| - * @type {string} |
| + * The color code for a query. |
| + * @type {number} |
| * @const |
| */ |
| -var VERBATIM_URL_TYPE = 'url-what-you-typed'; |
| +var QUERY_COLOR = 0x000000; |
| /** |
| - * Suggestion provider type corresponding to a verbatim search suggestion. |
| + * The color code for a display URL. |
| + * @type {number} |
| + * @const |
| + */ |
| +var URL_COLOR = 0x009933; |
| + |
| +/** |
| + * The color code for a suggestion title. |
| + * @type {number} |
| + * @const |
| + */ |
| +var TITLE_COLOR = 0x666666; |
| + |
| +/** |
| + * A top position which is off-screen. |
| * @type {string} |
| * @const |
| */ |
| -var VERBATIM_SEARCH_TYPE = 'search-what-you-typed'; |
| +var OFF_SCREEN = '-1000px'; |
| /** |
| - * The omnibox input value during the last onnativesuggestions event. |
| + * The expected origin of a suggestion iframe. |
| * @type {string} |
| + * @const |
| */ |
| -var lastInputValue = ''; |
| +var SUGGESTION_ORIGIN = 'chrome-search://suggestion'; |
| /** |
| - * The ordered restricted ids of the currently displayed suggestions. Since the |
| - * suggestions contain the user's personal data (browser history) the searchBox |
| - * API embeds the content of the suggestion in a shadow dom, and assigns a |
| - * random restricted id to each suggestion which is accessible to the JS. |
| - * @type {Array.<number>} |
| + * Suggestion provider type corresponding to a verbatim URL suggestion. |
| + * @type {string} |
| + * @const |
| */ |
| +var VERBATIM_URL_TYPE = 'url-what-you-typed'; |
| -var restrictedIds = []; |
| +/** |
| + * Suggestion provider type corresponding to a verbatim search suggestion. |
| + * @type {string} |
| + * @const |
| + */ |
| +var VERBATIM_SEARCH_TYPE = 'search-what-you-typed'; |
| /** |
| - * The index of the currently selected suggestion or -1 if none are selected. |
| + * "Up" arrow keycode. |
| * @type {number} |
| + * @const |
| */ |
| -var selectedIndex = -1; |
| +var KEY_UP_ARROW = 38; |
| /** |
| - * Shortcut for document.getElementById. |
| - * @param {string} id of the element. |
| - * @return {HTMLElement} with the id. |
| + * "Down" arrow keycode. |
| + * @type {number} |
| + * @const |
| */ |
| -function $(id) { |
| - return document.getElementById(id); |
| -} |
| +var KEY_DOWN_ARROW = 40; |
| /** |
| - * Displays a suggestion. |
| - * @param {Object} suggestion The suggestion to render. |
| - * @param {HTMLElement} box The html element to add the suggestion to. |
| - * @param {boolean} select True to select the selection. |
| + * The displayed suggestions. |
| + * @type {SuggestionsBox} |
| */ |
| -function addSuggestionToBox(suggestion, box, select) { |
| - var suggestionDiv = document.createElement('div'); |
| - suggestionDiv.classList.add('suggestion'); |
| - suggestionDiv.classList.toggle('selected', select); |
| - suggestionDiv.classList.toggle('search', suggestion.is_search); |
| - |
| - if (suggestion.destination_url) { // iframes. |
| - var suggestionIframe = document.createElement('iframe'); |
| - suggestionIframe.className = 'contents'; |
| - suggestionIframe.src = suggestion.destination_url; |
| - suggestionIframe.id = suggestion.rid; |
| - suggestionDiv.appendChild(suggestionIframe); |
| - } else { |
| - var contentsContainer = document.createElement('div'); |
| - var contents = suggestion.combinedNode; |
| - contents.classList.add('contents'); |
| - contentsContainer.appendChild(contents); |
| - suggestionDiv.appendChild(contentsContainer); |
| - suggestionDiv.onclick = function(event) { |
| - handleSuggestionClick(suggestion.rid, event.button); |
| - }; |
| - } |
| - |
| - restrictedIds.push(suggestion.rid); |
| - box.appendChild(suggestionDiv); |
| -} |
| +var active; |
| /** |
| - * Renders the input suggestions. |
| - * @param {Array} nativeSuggestions An array of native suggestions to render. |
| + * The suggestions being rendered. |
| + * @type {SuggestionsBox} |
| */ |
| -function renderSuggestions(nativeSuggestions) { |
| - var box = document.createElement('div'); |
| - box.id = 'suggestionsBox'; |
| - $('suggestions-box-container').appendChild(box); |
| +var pending; |
| - for (var i = 0, length = nativeSuggestions.length; |
| - i < Math.min(MAX_SUGGESTIONS_TO_SHOW, length); ++i) { |
| - addSuggestionToBox(nativeSuggestions[i], box, i == selectedIndex); |
| - } |
| -} |
| +/** |
| + * A pool of iframes to display suggestions. |
| + * @type {IframePool} |
| + */ |
| +var iframePool; |
| /** |
| - * Clears the suggestions being displayed. |
| + * A serial number for the next suggestions rendered. |
| + * @type {number} |
| */ |
| -function clearSuggestions() { |
| - $('suggestions-box-container').innerHTML = ''; |
| - restrictedIds = []; |
| - selectedIndex = -1; |
| -} |
| +var nextRequestId = 0; |
| /** |
| - * @return {number} The height of the dropdown. |
| + * Shortcut for document.querySelector. |
| + * @param {string} selector A selector to query the desired element. |
| + * @return {HTMLElement} matching selector. |
| */ |
| -function getDropdownHeight() { |
| - return $('suggestions-box-container').offsetHeight; |
| +function $$(selector) { |
| + return document.querySelector(selector); |
| } |
| /** |
| @@ -162,53 +141,477 @@ function getDropdownHeight() { |
| */ |
| function shouldSelectSuggestion(suggestion, inVerbatimMode) { |
| var isVerbatimUrl = suggestion.type == VERBATIM_URL_TYPE; |
| - var inlinableSuggestion = suggestion.type != VERBATIM_SEARCH_TYPE && |
| + var inlineableSuggestion = suggestion.type != VERBATIM_SEARCH_TYPE && |
| suggestion.rankingData.relevance > INLINE_SUGGESTION_THRESHOLD; |
| // Verbatim URLs should always be selected. Otherwise, select suggestions |
| // with a high enough score unless we are in verbatim mode (e.g. backspacing |
| // away). |
| - return isVerbatimUrl || (!inVerbatimMode && inlinableSuggestion); |
| + return isVerbatimUrl || (!inVerbatimMode && inlineableSuggestion); |
| } |
| /** |
| - * Updates selectedIndex, bounding it between -1 and the total number of |
| - * of suggestions - 1 (looping as necessary), and selects the corresponding |
| - * suggestion. |
| - * @param {boolean} increment True to increment the selected suggestion, false |
| - * to decrement. |
| + * Extract the desired navigation behavior from a click button. |
| + * @param {number} button The Event#button property of a click event. |
| + * @return {WindowOpenDisposition} The desired behavior for |
| + * navigateContentWindow. |
| */ |
| -function updateSelectedSuggestion(increment) { |
| - var numSuggestions = restrictedIds.length; |
| - if (!numSuggestions) |
| - return; |
| +function getDispositionFromClickButton(button) { |
| + if (button == MIDDLE_MOUSE_BUTTON) |
| + return WindowOpenDisposition.NEW_BACKGROUND_TAB; |
| + return WindowOpenDisposition.CURRENT_TAB; |
| +} |
| + |
| + |
| +/** |
| + * Manages a pool of chrome-search suggestion result iframes. |
| + * @constructor |
| + */ |
| +function IframePool() { |
| + /** |
| + * HTML iframe elements. |
| + * @type {Array.<Element>} |
| + * @private |
| + */ |
| + this.iframes_ = []; |
| +} |
| + |
| +/** |
| + * Initializes the pool with blank result template iframes, positioned off |
| + * screen. |
| + */ |
| +IframePool.prototype.init = function() { |
| + for (var i = 0; i < 2 * MAX_SUGGESTIONS_TO_SHOW; i++) { |
| + var iframe = document.createElement('iframe'); |
| + iframe.className = 'contents'; |
| + iframe.id = 'suggestion-text-' + i; |
| + iframe.src = 'chrome-search://suggestion/result.html'; |
| + iframe.style.top = OFF_SCREEN; |
| + iframe.addEventListener('mouseover', function(e) { |
| + if (active) |
| + active.hover(e.currentTarget.id); |
| + }, false); |
| + iframe.addEventListener('mouseout', function(e) { |
| + if (active) |
| + active.unhover(e.currentTarget.id); |
| + }, false); |
| + document.body.appendChild(iframe); |
| + this.iframes_.push(iframe); |
| + } |
| +}; |
| + |
| +/** |
| + * Reserves a free suggestion iframe from the pool. |
| + * @return {Element} An iframe suitable for holding a suggestion. |
| + */ |
| +IframePool.prototype.reserve = function() { |
| + return this.iframes_.pop(); |
| +}; |
| + |
| +/** |
| + * Releases a suggestion iframe back into the pool. |
| + * @param {Element} iframe The iframe to return to the pool. |
| + */ |
| +IframePool.prototype.release = function(iframe) { |
| + this.iframes_.push(iframe); |
| + iframe.style.top = OFF_SCREEN; |
| +}; |
| + |
| + |
| +/** |
| + * An individual suggestion. |
| + * @param {!Object} data Autocomplete fields for this suggestion. |
| + * @constructor |
| + */ |
| +function Suggestion(data) { |
| + /** |
| + * Autocomplete fields for this suggestion. |
| + * @type {!Object} |
| + * @private |
| + */ |
| + this.data_ = data; |
| +} |
| + |
| +/** |
| + * Releases the iframe reserved for this suggestion. |
| + */ |
| +Suggestion.prototype.destroy = function() { |
| + if (this.iframe_) |
| + iframePool.release(this.iframe_); |
| +}; |
| + |
| +/** |
| + * Creates and appends the placeholder div for this suggestion to box. |
| + * @param {Element} box A suggestions box. |
| + * @param {boolean} selected True if the suggestion should be drawn as selected |
| + * and false otherwise. |
| + */ |
| +Suggestion.prototype.appendToBox = function(box, selected) { |
| + var div = document.createElement('div'); |
| + div.classList.add('suggestion'); |
| + div.classList.toggle('selected', selected); |
| + div.classList.toggle('search', this.data_.is_search); |
| + box.appendChild(div); |
| + this.div_ = div; |
| +}; |
| + |
| +/** |
| + * Sets the iframe element for this suggestion. |
| + * @param {Element} iframe The iframe. |
| + */ |
| +Suggestion.prototype.setIframe = function(iframe) { |
| + this.iframe_ = iframe; |
| +}; |
| + |
| +/** |
| + * @return {number} The restricted id associated with this suggestion. |
| + */ |
| +Suggestion.prototype.getRestrictedId = function() { |
| + return this.data_.rid; |
| +}; |
| + |
| +/** |
| + * Repositions the suggestion iframe to align with its expected dropdown |
| + * position. |
| + * @param {boolean} isRtl True if rendering right-to-left and false if not. |
| + * @param {number} startMargin Leading space before suggestion. |
| + * @param {number} totalMargin Total non-content space on suggestion line. |
| + */ |
| +Suggestion.prototype.reposition = function(isRtl, startMargin, totalMargin) { |
| + // Add in the expected parent offset and the top margin. |
| + this.iframe_.style.top = this.div_.offsetTop + 4 + 'px'; |
| + // 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
|
| + this.iframe_.style[isRtl ? 'right' : 'left'] = |
| + parseInt(startMargin, 10) + 'px'; |
| + this.iframe_.style.width = '-webkit-calc(100% - ' + |
| + (parseInt(totalMargin, 10) + 26) + 'px)'; |
| +}; |
| + |
| +/** |
| + * Updates the suggestion selection state. |
| + * @param {boolean} selected True if drawn selected or false if not. |
| + */ |
| +Suggestion.prototype.select = function(selected) { |
| + this.div_.classList.toggle('selected', selected); |
| +}; |
| + |
| +/** |
| + * Updates the suggestion hover state. |
| + * @param {boolean} hovered True if drawn hovered or false if not. |
| + */ |
| +Suggestion.prototype.hover = function(hovered) { |
| + this.div_.classList.toggle('hovered', hovered); |
| +}; |
| + |
| +/** |
| + * @param {Window} iframeWindow The content window of an iframe. |
| + * @return {boolean} True if this suggestion's iframe has the specified window |
| + * and false if not. |
| + */ |
| +Suggestion.prototype.hasIframeWindow = function(iframeWindow) { |
| + return this.iframe_.contentWindow == iframeWindow; |
| +}; |
| + |
| +/** |
| + * @param {string} id An element id. |
| + * @return {boolean} True if this suggestion's iframe has the specified id and |
| + * false if not. |
| + */ |
| +Suggestion.prototype.hasIframeId = function(id) { |
| + return this.iframe_.id == id; |
| +}; |
| + |
| + |
| +/** |
| + * Displays a suggestions box. |
| + * @param {string} inputValue The user text that prompted these suggestions. |
| + * @param {!Array.<!Object>} suggestionData Suggestion data to display. |
| + * @param {number} selectedIndex The index of the suggestion selected. |
| + * @constructor |
| + */ |
| +function SuggestionsBox(inputValue, suggestionData, selectedIndex) { |
| + /** |
| + * The user text that prompted these suggestions. |
| + * @type {string} |
| + * @private |
| + */ |
| + this.inputValue_ = inputValue; |
| + |
| + /** |
| + * The index of the suggestion currently selected, whether by default or |
| + * because the user arrowed down to it. |
| + * @type {number} |
| + * @private |
| + */ |
| + this.selectedIndex_ = selectedIndex; |
| + |
| + /** |
| + * The index of the suggestion currently under the mouse pointer. |
| + * @type {number} |
| + * @private |
| + */ |
| + this.hoveredIndex_ = -1; |
| + |
| + /** |
| + * A stamp to distinguish this suggestions box from others. |
| + * @type {number} |
| + * @private |
| + */ |
| + this.requestId_ = nextRequestId++; |
| + |
| + /** |
| + * The ordered suggestions this box is displaying. |
| + * @type {Array.<Suggestion>} |
| + * @private |
| + */ |
| + this.suggestions_ = []; |
| + for (var i = 0; i < suggestionData.length; i++) { |
| + this.suggestions_.push(new Suggestion(suggestionData[i])); |
| + } |
| + |
| + /** |
| + * An embedded search API handle. |
| + * @type {Object} |
| + * @private |
| + */ |
| + this.apiHandle_ = getApiObjectHandle(); |
| + |
| + /** |
| + * The container for this suggestions box. div.pending-container if inactive |
| + * and div.active-container if active. |
| + * @type {Element} |
| + * @private |
| + */ |
| + this.container_ = $$('.pending-container'); |
| +} |
| + |
| +/** |
| + * Releases suggestion iframes and ignores any load done message for the |
| + * current suggestions. |
| + */ |
| +SuggestionsBox.prototype.destroy = function() { |
| + for (var i = 0, suggestion; suggestion = this.suggestions_[i]; i++) { |
| + suggestion.destroy(); |
| + } |
| + this.responseId = -1; |
| +}; |
| + |
| +/** |
| + * Starts rendering new suggestions. |
| + */ |
| +SuggestionsBox.prototype.loadSuggestions = function() { |
| + // Create a placeholder DOM in the invisible container. |
| + this.container_.innerHTML = ''; |
| + var box = document.createElement('div'); |
| + box.className = 'suggestions-box'; |
| + this.container_.appendChild(box); |
| + var iframesToLoad = {}; |
| + for (var i = 0, suggestion; suggestion = this.suggestions_[i]; i++) { |
| + suggestion.appendToBox(box, i == this.selectedIndex_); |
| + var iframe = iframePool.reserve(); |
| + suggestion.setIframe(iframe); |
| + iframesToLoad[iframe.id] = suggestion.getRestrictedId(); |
| + } |
| + // Ask the loader iframe to populate the iframes just reserved. |
| + var loadRequest = { |
| + 'load': iframesToLoad, |
| + 'requestId': this.requestId_, |
| + 'style': { |
| + 'queryColor': QUERY_COLOR, |
| + 'urlColor': URL_COLOR, |
| + 'titleColor': TITLE_COLOR |
| + } |
| + }; |
| + $$('#suggestion-loader').contentWindow.postMessage(loadRequest, |
| + SUGGESTION_ORIGIN); |
| +}; |
| + |
| +/** |
| + * @param {number} responseId The id of a request that just finished rendering. |
| + * @return {boolean} Whether the request is for the suggestions in this box. |
| + */ |
| +SuggestionsBox.prototype.isResponseCurrent = function(responseId) { |
| + return responseId == this.requestId_; |
| +}; |
| - var oldSelection = $('suggestionsBox').querySelector('.selected'); |
| +/** |
| + * Moves suggestion iframes into position. |
| + */ |
| +SuggestionsBox.prototype.repositionSuggestions = function() { |
| + // Note: This may be called before margins are ready. In that case, |
| + // suggestion iframes will initially be too large and then size down |
| + // onresize. |
| + var startMargin = this.apiHandle_.startMargin; |
| + var totalMargin = window.innerWidth - this.apiHandle_.width; |
| + var isRtl = this.apiHandle_.isRtl; |
| + for (var i = 0, suggestion; suggestion = this.suggestions_[i]; i++) { |
| + suggestion.reposition(isRtl, startMargin, totalMargin); |
| + } |
| +}; |
| + |
| +/** |
| + * Selects the suggestion before the current selection. |
| + */ |
| +SuggestionsBox.prototype.selectPrevious = function() { |
| + this.changeSelection_(this.selectedIndex_ - 1); |
| +}; |
| + |
| +/** |
| + * Selects the suggestion after the current selection. |
| + */ |
| +SuggestionsBox.prototype.selectNext = function() { |
| + this.changeSelection_(this.selectedIndex_ + 1); |
| +}; |
| + |
| +/** |
| + * Changes the current selected suggestion index. |
| + * @param {number} index The new selection to suggest. |
| + * @private |
| + */ |
| +SuggestionsBox.prototype.changeSelection_ = function(index) { |
| + if (index < -1) |
| + index = -1; |
| + var numSuggestions = this.suggestions_.length; |
| + if (index > numSuggestions - 1) |
| + index = numSuggestions - 1; |
| + this.selectedIndex_ = index; |
| + |
| + this.redrawSelection_(); |
| + this.redrawHover_(); |
| +}; |
| + |
| +/** |
| + * Redraws the selected suggestion. |
| + * @private |
| + */ |
| +SuggestionsBox.prototype.redrawSelection_ = function() { |
| + var oldSelection = this.container_.querySelector('.selected'); |
| if (oldSelection) |
| oldSelection.classList.remove('selected'); |
| + if (this.selectedIndex_ == -1) { |
| + this.apiHandle_.setValue(this.inputValue_); |
| + } else { |
| + this.suggestions_[this.selectedIndex_].select(true); |
| + this.apiHandle_.setRestrictedValue( |
| + this.suggestions_[this.selectedIndex_].getRestrictedId()); |
| + } |
| +}; |
| - if (increment) |
| - selectedIndex = ++selectedIndex > numSuggestions - 1 ? -1 : selectedIndex; |
| - else |
| - selectedIndex = --selectedIndex < -1 ? numSuggestions - 1 : selectedIndex; |
| - var apiHandle = getApiObjectHandle(); |
| - if (selectedIndex == -1) { |
| - apiHandle.setValue(lastInputValue); |
| +/** |
| + * Returns the restricted id of the iframe clicked. |
| + * @param {!Window} iframeWindow The window of the iframe that was clicked. |
| + * @return {?number} The restricted id clicked or null if none. |
| + */ |
| +SuggestionsBox.prototype.getClickTarget = function(iframeWindow) { |
| + for (var i = 0, suggestion; suggestion = this.suggestions_[i]; ++i) { |
| + if (suggestion.hasIframeWindow(iframeWindow)) |
| + return suggestion.getRestrictedId(); |
| + } |
| + return null; |
| +}; |
| + |
| +/** |
| + * Called when the user hovers on the specified iframe. |
| + * @param {string} iframeId The id of the iframe hovered. |
| + */ |
| +SuggestionsBox.prototype.hover = function(iframeId) { |
| + this.hoveredIndex_ = -1; |
| + for (var i = 0, suggestion; suggestion = this.suggestions_[i]; ++i) { |
| + if (suggestion.hasIframeId(iframeId)) { |
| + this.hoveredIndex_ = i; |
| + break; |
| + } |
| + } |
| + this.redrawHover_(); |
| +}; |
| + |
| +/** |
| + * Called when the user unhovers the specified iframe. |
| + * @param {string} iframeId The id of the iframe hovered. |
| + */ |
| +SuggestionsBox.prototype.unhover = function(iframeId) { |
| + if (this.suggestions_[this.hoveredIndex_] && |
| + this.suggestions_[this.hoveredIndex_].hasIframeId(iframeId)) { |
| + this.clearHover(); |
| + } |
| +}; |
| + |
| +/** |
| + * Clears the current hover. |
| + */ |
| +SuggestionsBox.prototype.clearHover = function() { |
| + this.hoveredIndex_ = -1; |
| + this.redrawHover_(); |
| +}; |
| + |
| +/** |
| + * Redraws the mouse hover background. |
| + * @private |
| + */ |
| +SuggestionsBox.prototype.redrawHover_ = function() { |
| + var oldHover = this.container_.querySelector('.hovered'); |
| + if (oldHover) |
| + oldHover.classList.remove('hovered'); |
| + if (this.hoveredIndex_ != -1 && this.hoveredIndex_ != this.selectedIndex_) |
| + this.suggestions_[this.hoveredIndex_].hover(true); |
| +}; |
| + |
| +/** |
| + * Marks the suggestions container as active. |
| + */ |
| +SuggestionsBox.prototype.activate = function() { |
| + this.container_.className = 'active-container'; |
| +}; |
| + |
| +/** |
| + * Marks the suggestions container as inactive. |
| + */ |
| +SuggestionsBox.prototype.deactivate = function() { |
| + this.container_.className = 'pending-container'; |
| + this.container_.innerHTML = ''; |
| +}; |
| + |
| +/** |
| + * @return {number} The height of the suggestions container. |
| + */ |
| +SuggestionsBox.prototype.getHeight = function() { |
| + return this.container_.offsetHeight; |
| +}; |
| + |
| +/** |
| + * Clears the currently active suggestions and shows pending suggestions. |
| + */ |
| +function makePendingSuggestionsActive() { |
| + if (active) { |
| + active.deactivate(); |
| + active.destroy(); |
| } else { |
| - var newSelection = $('suggestionsBox').querySelector( |
| - '.suggestion:nth-of-type(' + (selectedIndex + 1) + ')'); |
| - newSelection.classList.add('selected'); |
| - apiHandle.setRestrictedValue(restrictedIds[selectedIndex]); |
| + // Initially there will be no active suggestions, but we still want to use |
| + // div.active-container to load the next suggestions. |
| + $$('.active-container').className = 'pending-container'; |
| } |
| + pending.activate(); |
| + active = pending; |
| + pending = null; |
| + active.repositionSuggestions(); |
| + getApiObjectHandle().showOverlay(active.getHeight()); |
| } |
| -// ============================================================================= |
| -// Handlers / API stuff |
| -// ============================================================================= |
| +/** |
| + * Hides the active suggestions box. |
| + */ |
| +function hideActiveSuggestions() { |
| + getApiObjectHandle().showOverlay(0); |
| + if (active) { |
| + $$('.active-container').innerHTML = ''; |
| + active.destroy(); |
| + } |
| + active = null; |
| +} |
| /** |
| * @return {Object} the handle to the searchBox API. |
| */ |
| - function getApiObjectHandle() { |
| +function getApiObjectHandle() { |
| if (window.cideb) |
| return window.cideb; |
| if (window.navigator && window.navigator.embeddedSearch && |
| @@ -224,22 +627,28 @@ function updateSelectedSuggestion(increment) { |
| * Updates suggestions in response to a onchange or onnativesuggestions call. |
| */ |
| function updateSuggestions() { |
| + if (pending) { |
| + pending.destroy(); |
| + pending = null; |
| + } |
| var apiHandle = getApiObjectHandle(); |
| - lastInputValue = apiHandle.value; |
| - |
| - clearSuggestions(); |
| - var nativeSuggestions = apiHandle.nativeSuggestions; |
| - if (nativeSuggestions.length) { |
| - nativeSuggestions.sort(function(a, b) { |
| + var inputValue = apiHandle.value; |
| + var suggestions = apiHandle.nativeSuggestions; |
| + if (!inputValue || !suggestions.length) { |
| + hideActiveSuggestions(); |
| + return; |
| + } |
| + if (suggestions.length) { |
| + suggestions.sort(function(a, b) { |
| return b.rankingData.relevance - a.rankingData.relevance; |
| }); |
| - if (shouldSelectSuggestion(nativeSuggestions[0], apiHandle.verbatim)) |
| + var selectedIndex = -1; |
| + if (shouldSelectSuggestion(suggestions[0], apiHandle.verbatim)) |
| selectedIndex = 0; |
| - renderSuggestions(nativeSuggestions); |
| + pending = new SuggestionsBox(inputValue, |
| + suggestions.slice(0, MAX_SUGGESTIONS_TO_SHOW), selectedIndex); |
| + pending.loadSuggestions(); |
| } |
| - |
| - var height = getDropdownHeight(); |
| - apiHandle.showOverlay(height); |
| } |
| /** |
| @@ -262,91 +671,77 @@ function appendSuggestionStyles() { |
| (window.innerWidth - apiHandle.width - startMargin) + 'px;' + |
| ' font: ' + apiHandle.fontSize + 'px "' + apiHandle.font + '";' + |
| '}'; |
| - document.querySelector('head').appendChild(style); |
| + $$('head').appendChild(style); |
| + if (active) |
| + active.repositionSuggestions(); |
| window.removeEventListener('resize', appendSuggestionStyles); |
| } |
| /** |
| - * Extract the desired navigation behavior from a click button. |
| - * @param {number} button The Event#button property of a click event. |
| - * @return {WindowOpenDisposition} The desired behavior for |
| - * navigateContentWindow. |
| - */ |
| -function getDispositionFromClickButton(button) { |
| - if (button == MIDDLE_MOUSE_BUTTON) |
| - return WindowOpenDisposition.NEW_BACKGROUND_TAB; |
| - return WindowOpenDisposition.CURRENT_TAB; |
| -} |
| - |
| -/** |
| - * Handles suggestion clicks. |
| - * @param {number} restrictedId The restricted id of the suggestion being |
| - * clicked. |
| - * @param {number} button The Event#button property of a click event. |
| - * |
| - */ |
| -function handleSuggestionClick(restrictedId, button) { |
| - clearSuggestions(); |
| - getApiObjectHandle().navigateContentWindow( |
| - restrictedId, getDispositionFromClickButton(button)); |
| -} |
| - |
| -/** |
| - * chrome.searchBox.onkeypress implementation. |
| + * Makes keys navigate through suggestions. |
| * @param {Object} e The key being pressed. |
| */ |
| function handleKeyPress(e) { |
| + if (!active) |
| + return; |
| switch (e.keyCode) { |
| - case 38: // Up arrow |
| - updateSelectedSuggestion(false); |
| + case KEY_UP_ARROW: |
| + active.selectPrevious(); |
| break; |
| - case 40: // Down arrow |
| - updateSelectedSuggestion(true); |
| + case KEY_DOWN_ARROW: |
| + active.selectNext(); |
| break; |
| } |
| } |
| /** |
| - * Handles the postMessage calls from the result iframes. |
| - * @param {Object} message The message containing details of clicks the iframes. |
| + * Handles postMessage calls from suggestion iframes. |
| + * @param {Object} message A notification that all iframes are done loading or |
| + * that an iframe was clicked. |
| */ |
| function handleMessage(message) { |
| - if (message.origin != 'null' || !message.data || |
| - message.data.eventType != 'click') { |
| + if (message.origin != SUGGESTION_ORIGIN) |
| return; |
| - } |
| - var iframes = document.getElementsByClassName('contents'); |
| - for (var i = 0; i < iframes.length; ++i) { |
| - if (iframes[i].contentWindow == message.source) { |
| - handleSuggestionClick(parseInt(iframes[i].id, 10), |
| - message.data.button); |
| - break; |
| + if ('loaded' in message.data) { |
| + if (pending && pending.isResponseCurrent(message.data.loaded)) { |
| + makePendingSuggestionsActive(); |
| + } |
| + } else if ('click' in message.data) { |
| + if (active) { |
| + var restrictedId = active.getClickTarget(message.source); |
| + if (restrictedId != null) { |
| + hideActiveSuggestions(); |
| + getApiObjectHandle().navigateContentWindow(restrictedId, |
| + getDispositionFromClickButton(message.data.click)); |
| + } |
| } |
| } |
| } |
| /** |
| - * chrome.searchBox.embeddedSearch.onsubmit implementation. |
| - */ |
| -function onSubmit() { |
| -} |
| - |
| -/** |
| - * Sets up the searchBox API. |
| + * Sets up the embedded search API and creates suggestion iframes. |
| */ |
| -function setUpApi() { |
| +function init() { |
| + iframePool = new IframePool(); |
| + iframePool.init(); |
| var apiHandle = getApiObjectHandle(); |
| apiHandle.onnativesuggestions = updateSuggestions; |
| apiHandle.onchange = updateSuggestions; |
| apiHandle.onkeypress = handleKeyPress; |
| - apiHandle.onsubmit = onSubmit; |
| - $('suggestions-box-container').dir = apiHandle.rtl ? 'rtl' : 'ltr'; |
| + // Instant checks for this handler to be bound. |
| + apiHandle.onsubmit = function() {}; |
| + $$('.active-container').dir = apiHandle.rtl ? 'rtl' : 'ltr'; |
| + $$('.pending-container').dir = apiHandle.rtl ? 'rtl' : 'ltr'; |
| // Delay adding these styles until the window width is available. |
| window.addEventListener('resize', appendSuggestionStyles); |
| if (apiHandle.nativeSuggestions.length) |
| - handleNativeSuggestions(); |
| + updateSuggestions(); |
| } |
| -document.addEventListener('DOMContentLoaded', setUpApi); |
| +document.addEventListener('DOMContentLoaded', init); |
| window.addEventListener('message', handleMessage, false); |
| +window.addEventListener('blur', function() { |
| + if (active) |
| + active.clearHover(); |
| +}, false); |