| Index: chrome/browser/resources/local_ntp/local_ntp.js
|
| diff --git a/chrome/browser/resources/local_ntp/local_ntp.js b/chrome/browser/resources/local_ntp/local_ntp.js
|
| index 8610518c190a9de19e7a1d190da4795d251c590b..f44c9836c9d7168323309a3239b197d25dfe7e69 100644
|
| --- a/chrome/browser/resources/local_ntp/local_ntp.js
|
| +++ b/chrome/browser/resources/local_ntp/local_ntp.js
|
| @@ -3,6 +3,7 @@
|
| // found in the LICENSE file.
|
|
|
| (function() {
|
| +<include src="../../../../ui/webui/resources/js/assert.js">
|
|
|
| /**
|
| * True if this a Google page and not some other search provider. Used to
|
| @@ -22,6 +23,7 @@ var isGooglePage = location.href.indexOf('isGoogle') != -1;
|
| * @const
|
| */
|
| var CLASSES = {
|
| + ACTIVE_SUGGESTIONS_CONTAINER: 'active-suggestions-container',
|
| BLACKLIST: 'mv-blacklist', // triggers tile blacklist animation
|
| BLACKLIST_BUTTON: 'mv-x',
|
| CUSTOM_THEME: 'custom-theme',
|
| @@ -35,8 +37,14 @@ var CLASSES = {
|
| HIDE_BLACKLIST_BUTTON: 'mv-x-hide', // hides blacklist button during animation
|
| HIDE_NOTIFICATION: 'mv-notice-hide',
|
| HIDE_TILE: 'mv-tile-hide', // hides tiles on small browser width
|
| + HOVERED: 'hovered',
|
| + PENDING_SUGGESTIONS_CONTAINER: 'pending-suggestions-container',
|
| PAGE: 'mv-page', // page tiles
|
| + SEARCH: 'search',
|
| SELECTED: 'selected', // a selected suggestion (if any)
|
| + SUGGESTION: 'suggestion',
|
| + SUGGESTION_CONTENTS: 'suggestion-contents',
|
| + SUGGESTIONS_BOX: 'suggestions-box',
|
| THUMBNAIL: 'mv-thumb',
|
| TILE: 'mv-tile',
|
| TITLE: 'mv-title'
|
| @@ -57,9 +65,9 @@ var IDS = {
|
| NOTIFICATION_MESSAGE: 'mv-msg',
|
| NTP_CONTENTS: 'ntp-contents',
|
| RESTORE_ALL_LINK: 'mv-restore',
|
| - SUGGESTIONS_BOX: 'suggestions-box',
|
| - SUGGESTIONS_CONTAINER: 'suggestions-box-container',
|
| + SUGGESTION_LOADER: 'suggestion-loader',
|
| SUGGESTION_STYLE: 'suggestion-style',
|
| + SUGGESTION_TEXT_PREFIX: 'suggestion-text-',
|
| TILES: 'mv-tiles',
|
| TOP_MARGIN: 'mv-top-margin',
|
| UNDO_LINK: 'mv-undo'
|
| @@ -625,6 +633,41 @@ var MAX_SUGGESTIONS_TO_SHOW = 5;
|
| var INLINE_SUGGESTION_THRESHOLD = 1200;
|
|
|
| /**
|
| + * The color code for a query.
|
| + * @type {number}
|
| + * @const
|
| + */
|
| +var SUGGESTION_QUERY_COLOR = 0x000000;
|
| +
|
| +/**
|
| + * The color code for a suggestion display URL.
|
| + * @type {number}
|
| + * @const
|
| + */
|
| +var SUGGESTION_URL_COLOR = 0x009933;
|
| +
|
| +/**
|
| + * The color code for a suggestion title.
|
| + * @type {number}
|
| + * @const
|
| + */
|
| +var SUGGESTION_TITLE_COLOR = 0x666666;
|
| +
|
| +/**
|
| + * A top position which is off-screen.
|
| + * @type {string}
|
| + * @const
|
| + */
|
| +var OFF_SCREEN = '-1000px';
|
| +
|
| +/**
|
| + * The expected origin of a suggestion iframe.
|
| + * @type {string}
|
| + * @const
|
| + */
|
| +var SUGGESTION_ORIGIN = 'chrome-search://suggestion';
|
| +
|
| +/**
|
| * Suggestion provider type corresponding to a verbatim URL suggestion.
|
| * @type {string}
|
| * @const
|
| @@ -639,152 +682,541 @@ var VERBATIM_URL_TYPE = 'url-what-you-typed';
|
| var VERBATIM_SEARCH_TYPE = 'search-what-you-typed';
|
|
|
| /**
|
| - * The omnibox input value during the last onnativesuggestions event.
|
| - * @type {string}
|
| + * "Up" arrow keycode.
|
| + * @type {number}
|
| + * @const
|
| */
|
| -var lastInputValue = '';
|
| +var KEY_UP_ARROW = 38;
|
|
|
| /**
|
| - * 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>}
|
| + * "Down" arrow keycode.
|
| + * @type {number}
|
| + * @const
|
| */
|
| -
|
| -var restrictedIds = [];
|
| +var KEY_DOWN_ARROW = 40;
|
|
|
| /**
|
| - * The index of the currently selected suggestion or -1 if none are selected.
|
| + * Pixels of padding inside a suggestion div for displaying its icon.
|
| * @type {number}
|
| + * @const
|
| */
|
| -var selectedIndex = -1;
|
| +var SUGGESTION_ICON_PADDING = 26;
|
|
|
| /**
|
| - * The browser embeddedSearch.searchBox object.
|
| - * @type {Object}
|
| + * Pixels by which iframes should be moved down relative to their wrapping
|
| + * suggestion div.
|
| */
|
| -var searchboxApiHandle;
|
| +var SUGGESTION_TOP_OFFSET = 4;
|
|
|
| /**
|
| - * 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(CLASSES.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 activeBox;
|
|
|
| /**
|
| - * Renders the input suggestions.
|
| - * @param {!Array} nativeSuggestions An array of native suggestions to render.
|
| + * The suggestions being rendered.
|
| + * @type {SuggestionsBox}
|
| */
|
| -function renderSuggestions(nativeSuggestions) {
|
| - for (var i = 0, length = nativeSuggestions.length;
|
| - i < Math.min(MAX_SUGGESTIONS_TO_SHOW, length); ++i) {
|
| - // Don't add the search-what-you-typed suggestion if it's the top match.
|
| - if (i > 0 || nativeSuggestions[i].type != VERBATIM_SEARCH_TYPE) {
|
| - var box = $(IDS.SUGGESTIONS_BOX);
|
| - if (!box) {
|
| - box = document.createElement('div');
|
| - box.id = IDS.SUGGESTIONS_BOX;
|
| - $(IDS.SUGGESTIONS_CONTAINER).appendChild(box);
|
| - }
|
| - addSuggestionToBox(nativeSuggestions[i], box, i == selectedIndex);
|
| - }
|
| - }
|
| -}
|
| +var pendingBox;
|
|
|
| /**
|
| - * Clears the suggestions being displayed.
|
| + * A pool of iframes to display suggestions.
|
| + * @type {IframePool}
|
| */
|
| -function clearSuggestions() {
|
| - $(IDS.SUGGESTIONS_CONTAINER).innerHTML = '';
|
| - restrictedIds = [];
|
| - selectedIndex = -1;
|
| -}
|
| +var iframePool;
|
|
|
| /**
|
| - * @return {number} The height of the dropdown.
|
| + * A serial number for the next suggestions rendered.
|
| + * @type {number}
|
| */
|
| -function getDropdownHeight() {
|
| - return $(IDS.SUGGESTIONS_CONTAINER).offsetHeight;
|
| -}
|
| +var nextRequestId = 0;
|
|
|
| /**
|
| - * @param {!Object} suggestion A suggestion.
|
| + * @param {Object} suggestion A suggestion.
|
| * @param {boolean} inVerbatimMode Are we in verbatim mode?
|
| * @return {boolean} True if the suggestion should be selected.
|
| */
|
| 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;
|
| +}
|
|
|
| - var oldSelection = $(IDS.SUGGESTIONS_BOX).querySelector('.selected');
|
| - if (oldSelection)
|
| - oldSelection.classList.remove(CLASSES.SELECTED);
|
| - if (increment) {
|
| - if (selectedIndex > numSuggestions)
|
| - selectedIndex = -1;
|
| - else
|
| - ++selectedIndex;
|
| - } else {
|
| - if (selectedIndex < 0)
|
| - selectedIndex = numSuggestions - 1;
|
| - else
|
| - --selectedIndex;
|
| +
|
| +/**
|
| + * Manages a pool of chrome-search suggestion result iframes.
|
| + * @constructor
|
| + */
|
| +function IframePool() {
|
| +}
|
| +
|
| +IframePool.prototype = {
|
| + /**
|
| + * HTML iframe elements.
|
| + * @type {Array.<Element>}
|
| + * @private
|
| + */
|
| + iframes_: [],
|
| +
|
| + /**
|
| + * Initializes the pool with blank result template iframes, positioned off
|
| + * screen.
|
| + */
|
| + init: function() {
|
| + for (var i = 0; i < 2 * MAX_SUGGESTIONS_TO_SHOW; ++i) {
|
| + var iframe = document.createElement('iframe');
|
| + iframe.className = CLASSES.SUGGESTION_CONTENTS;
|
| + iframe.id = IDS.SUGGESTION_TEXT_PREFIX + i;
|
| + iframe.src = 'chrome-search://suggestion/result.html';
|
| + iframe.style.top = OFF_SCREEN;
|
| + iframe.addEventListener('mouseover', function(e) {
|
| + if (activeBox)
|
| + activeBox.hover(e.currentTarget.id);
|
| + }, false);
|
| + iframe.addEventListener('mouseout', function(e) {
|
| + if (activeBox)
|
| + activeBox.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.
|
| + */
|
| + reserve: function() {
|
| + return this.iframes_.pop();
|
| + },
|
| +
|
| + /**
|
| + * Releases a suggestion iframe back into the pool.
|
| + * @param {Element} iframe The iframe to return to the pool.
|
| + */
|
| + 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) {
|
| + assert(data);
|
| + /**
|
| + * Autocomplete fields for this suggestion.
|
| + * @type {!Object}
|
| + * @private
|
| + */
|
| + this.data_ = data;
|
| +}
|
| +
|
| +Suggestion.prototype = {
|
| + /**
|
| + * Releases the iframe reserved for this suggestion.
|
| + */
|
| + 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.
|
| + */
|
| + appendToBox: function(box, selected) {
|
| + var div = document.createElement('div');
|
| + div.classList.add(CLASSES.SUGGESTION);
|
| + div.classList.toggle(CLASSES.SELECTED, selected);
|
| + div.classList.toggle(CLASSES.SEARCH, this.data_.is_search);
|
| + box.appendChild(div);
|
| + this.div_ = div;
|
| + },
|
| +
|
| + /**
|
| + * 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.
|
| + */
|
| + reposition: function(isRtl, startMargin, totalMargin) {
|
| + // Add in the expected parent offset and the top margin.
|
| + this.iframe_.style.top = this.div_.offsetTop + SUGGESTION_TOP_OFFSET + 'px';
|
| + // Call parseInt to enforce that startMargin and totalMargin are really
|
| + // numbers since we're interpolating CSS.
|
| + startMargin = parseInt(startMargin, 10);
|
| + totalMargin = parseInt(totalMargin, 10);
|
| + if (isFinite(startMargin) && isFinite(totalMargin)) {
|
| + this.iframe_.style[isRtl ? 'right' : 'left'] = startMargin + 'px';
|
| + this.iframe_.style.width = '-webkit-calc(100% - ' +
|
| + (totalMargin + SUGGESTION_ICON_PADDING) + 'px)';
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Updates the suggestion selection state.
|
| + * @param {boolean} selected True if drawn selected or false if not.
|
| + */
|
| + select: function(selected) {
|
| + this.div_.classList.toggle(CLASSES.SELECTED, selected);
|
| + },
|
| +
|
| + /**
|
| + * Updates the suggestion hover state.
|
| + * @param {boolean} hovered True if drawn hovered or false if not.
|
| + */
|
| + hover: function(hovered) {
|
| + this.div_.classList.toggle(CLASSES.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.
|
| + */
|
| + 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.
|
| + */
|
| + hasIframeId: function(id) {
|
| + return this.iframe_.id == id;
|
| + },
|
| +
|
| + /**
|
| + * The iframe element for this suggestion.
|
| + * @type {Element}
|
| + */
|
| + set iframe(iframe) {
|
| + this.iframe_ = iframe;
|
| + },
|
| +
|
| + /**
|
| + * The restricted id associated with this suggestion.
|
| + * @type {number}
|
| + */
|
| + get restrictedId() {
|
| + return this.data_.rid;
|
| + },
|
| +};
|
| +
|
| +
|
| +/**
|
| + * 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]));
|
| }
|
|
|
| - if (selectedIndex == -1) {
|
| - searchboxApiHandle.setValue(lastInputValue);
|
| + /**
|
| + * The container for this suggestions box. div.pending-suggestion-container
|
| + * if inactive and div.active-suggestion-container if active.
|
| + * @type {Element}
|
| + * @private
|
| + */
|
| + this.container_ = $qs('.' + CLASSES.PENDING_SUGGESTIONS_CONTAINER);
|
| + assert(this.container_);
|
| +}
|
| +
|
| +SuggestionsBox.prototype = {
|
| + /**
|
| + * Releases suggestion iframes and ignores any load done message for the
|
| + * current suggestions.
|
| + */
|
| + destroy: function() {
|
| + while (this.suggestions_.length > 0) {
|
| + this.suggestions_.pop().destroy();
|
| + }
|
| + this.responseId = -1;
|
| + },
|
| +
|
| + /**
|
| + * Starts rendering new suggestions.
|
| + */
|
| + loadSuggestions: function() {
|
| + // Create a placeholder DOM in the invisible container.
|
| + this.container_.innerHTML = '';
|
| +
|
| + var box = document.createElement('div');
|
| + box.className = CLASSES.SUGGESTIONS_BOX;
|
| + this.container_.appendChild(box);
|
| +
|
| + var iframesToLoad = {};
|
| + for (var i = 0; i < this.suggestions_.length; ++i) {
|
| + var suggestion = this.suggestions_[i];
|
| + suggestion.appendToBox(box, i == this.selectedIndex_);
|
| + var iframe = iframePool.reserve();
|
| + suggestion.iframe = iframe;
|
| + iframesToLoad[iframe.id] = suggestion.restrictedId;
|
| + }
|
| +
|
| + // Ask the loader iframe to populate the iframes just reserved.
|
| + var loadRequest = {
|
| + load: iframesToLoad,
|
| + requestId: this.requestId_,
|
| + style: {
|
| + queryColor: SUGGESTION_QUERY_COLOR,
|
| + urlColor: SUGGESTION_URL_COLOR,
|
| + titleColor: SUGGESTION_TITLE_COLOR
|
| + }
|
| + };
|
| + $(IDS.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.
|
| + */
|
| + isResponseCurrent: function(responseId) {
|
| + return responseId == this.requestId_;
|
| + },
|
| +
|
| + /**
|
| + * Moves suggestion iframes into position.
|
| + */
|
| + 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 = searchboxApiHandle.startMargin;
|
| + var totalMargin = window.innerWidth - searchboxApiHandle.width;
|
| + var isRtl = searchboxApiHandle.isRtl;
|
| + for (var i = 0; i < this.suggestions_.length; ++i) {
|
| + this.suggestions_[i].reposition(isRtl, startMargin, totalMargin);
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Selects the suggestion before the current selection.
|
| + */
|
| + selectPrevious: function() {
|
| + this.changeSelection_(this.selectedIndex_ - 1);
|
| + },
|
| +
|
| + /**
|
| + * Selects the suggestion after the current selection.
|
| + */
|
| + selectNext: function() {
|
| + this.changeSelection_(this.selectedIndex_ + 1);
|
| + },
|
| +
|
| + /**
|
| + * Changes the current selected suggestion index.
|
| + * @param {number} index The new selection to suggest.
|
| + * @private
|
| + */
|
| + changeSelection_: function(index) {
|
| + var numSuggestions = this.suggestions_.length;
|
| + this.selectedIndex_ = Math.min(numSuggestions - 1, Math.max(-1, index));
|
| +
|
| + this.redrawSelection_();
|
| + this.redrawHover_();
|
| + },
|
| +
|
| + /**
|
| + * Redraws the selected suggestion.
|
| + * @private
|
| + */
|
| + redrawSelection_: function() {
|
| + var oldSelection = this.container_.querySelector('.' + CLASSES.SELECTED);
|
| + if (oldSelection)
|
| + oldSelection.classList.remove(CLASSES.SELECTED);
|
| + if (this.selectedIndex_ == -1) {
|
| + searchboxApiHandle.setValue(this.inputValue_);
|
| + } else {
|
| + this.suggestions_[this.selectedIndex_].select(true);
|
| + searchboxApiHandle.setRestrictedValue(
|
| + this.suggestions_[this.selectedIndex_].restrictedId);
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * @param {!Window} iframeWindow The window of the iframe that was clicked.
|
| + * @return {?number} The restricted ID of the iframe that was clicked, or
|
| + * null if there was none.
|
| + */
|
| + getClickTarget: function(iframeWindow) {
|
| + for (var i = 0; i < this.suggestions_.length; ++i) {
|
| + if (this.suggestions_[i].hasIframeWindow(iframeWindow))
|
| + return this.suggestions_[i].restrictedId;
|
| + }
|
| + return null;
|
| + },
|
| +
|
| + /**
|
| + * Called when the user hovers on the specified iframe to update hoveredIndex_
|
| + * and draw a hover background.
|
| + * @param {string} iframeId The id of the iframe hovered.
|
| + */
|
| + hover: function(iframeId) {
|
| + this.hoveredIndex_ = -1;
|
| + for (var i = 0; i < this.suggestions_.length; ++i) {
|
| + if (this.suggestions_[i].hasIframeId(iframeId)) {
|
| + this.hoveredIndex_ = i;
|
| + break;
|
| + }
|
| + }
|
| + this.redrawHover_();
|
| + },
|
| +
|
| + /**
|
| + * Called when the user unhovers the specified iframe to clear the current
|
| + * hover.
|
| + * @param {string} iframeId The id of the iframe hovered.
|
| + */
|
| + unhover: function(iframeId) {
|
| + if (this.suggestions_[this.hoveredIndex_] &&
|
| + this.suggestions_[this.hoveredIndex_].hasIframeId(iframeId)) {
|
| + this.clearHover();
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Clears the current hover.
|
| + */
|
| + clearHover: function() {
|
| + this.hoveredIndex_ = -1;
|
| + this.redrawHover_();
|
| + },
|
| +
|
| + /**
|
| + * Redraws the mouse hover background.
|
| + * @private
|
| + */
|
| + redrawHover_: function() {
|
| + var oldHover = this.container_.querySelector('.' + CLASSES.HOVERED);
|
| + if (oldHover)
|
| + oldHover.classList.remove(CLASSES.HOVERED);
|
| + if (this.hoveredIndex_ != -1 && this.hoveredIndex_ != this.selectedIndex_)
|
| + this.suggestions_[this.hoveredIndex_].hover(true);
|
| + },
|
| +
|
| + /**
|
| + * Marks the suggestions container as active.
|
| + */
|
| + activate: function() {
|
| + this.container_.className = CLASSES.ACTIVE_SUGGESTIONS_CONTAINER;
|
| + },
|
| +
|
| + /**
|
| + * Marks the suggestions container as inactive.
|
| + */
|
| + deactivate: function() {
|
| + this.container_.className = CLASSES.PENDING_SUGGESTIONS_CONTAINER;
|
| + this.container_.innerHTML = '';
|
| + },
|
| +
|
| + /**
|
| + * The height of the suggestions container.
|
| + * @type {number}
|
| + */
|
| + get height() {
|
| + return this.container_.offsetHeight;
|
| + },
|
| +};
|
| +
|
| +
|
| +/**
|
| + * Clears the currently active suggestions and shows pending suggestions.
|
| + */
|
| +function makePendingSuggestionsActive() {
|
| + if (activeBox) {
|
| + activeBox.deactivate();
|
| + activeBox.destroy();
|
| } else {
|
| - var newSelection = $(IDS.SUGGESTIONS_BOX).querySelector(
|
| - '.suggestion:nth-of-type(' + (selectedIndex + 1) + ')');
|
| - newSelection.classList.add(CLASSES.SELECTED);
|
| - searchboxApiHandle.setRestrictedValue(restrictedIds[selectedIndex]);
|
| + // Initially there will be no active suggestions, but we still want to use
|
| + // div.active-container to load the next suggestions.
|
| + $qs('.' + CLASSES.ACTIVE_SUGGESTIONS_CONTAINER).className =
|
| + CLASSES.PENDING_SUGGESTIONS_CONTAINER;
|
| + }
|
| + pendingBox.activate();
|
| + activeBox = pendingBox;
|
| + pendingBox = null;
|
| + activeBox.repositionSuggestions();
|
| + searchboxApiHandle.showOverlay(activeBox.height);
|
| +}
|
| +
|
| +/**
|
| + * Hides the active suggestions box.
|
| + */
|
| +function hideActiveSuggestions() {
|
| + searchboxApiHandle.showOverlay(0);
|
| + if (activeBox) {
|
| + $qs('.' + CLASSES.ACTIVE_SUGGESTIONS_CONTAINER).innerHTML = '';
|
| + activeBox.destroy();
|
| }
|
| + activeBox = null;
|
| }
|
|
|
| /**
|
| @@ -792,35 +1224,29 @@ function updateSelectedSuggestion(increment) {
|
| */
|
| function updateSuggestions() {
|
| appendSuggestionStyles();
|
| - lastInputValue = searchboxApiHandle.value;
|
| -
|
| - // Hide the NTP if input has made it into the omnibox.
|
| - var showNTP = lastInputValue == '';
|
| - updateNtpVisibility(showNTP);
|
| -
|
| - clearSuggestions();
|
| - if (showNTP) {
|
| - searchboxApiHandle.showBars();
|
| + if (pendingBox)
|
| + pendingBox.destroy();
|
| + pendingBox = null;
|
| + var suggestions = searchboxApiHandle.nativeSuggestions;
|
| + if (suggestions.length) {
|
| + suggestions.sort(function(a, b) {
|
| + return b.rankingData.relevance - a.rankingData.relevance;
|
| + });
|
| + var selectedIndex = -1;
|
| + if (shouldSelectSuggestion(suggestions[0], searchboxApiHandle.verbatim))
|
| + selectedIndex = 0;
|
| + // Don't display a search-what-you-typed suggestion if it's the top match.
|
| + if (suggestions[0].type == VERBATIM_SEARCH_TYPE)
|
| + suggestions.shift();
|
| + }
|
| + var inputValue = searchboxApiHandle.value;
|
| + if (!!inputValue && suggestions.length) {
|
| + pendingBox = new SuggestionsBox(inputValue,
|
| + suggestions.slice(0, MAX_SUGGESTIONS_TO_SHOW), selectedIndex);
|
| + pendingBox.loadSuggestions();
|
| } else {
|
| - var nativeSuggestions = searchboxApiHandle.nativeSuggestions;
|
| - if (nativeSuggestions.length) {
|
| - nativeSuggestions.sort(function(a, b) {
|
| - return b.rankingData.relevance - a.rankingData.relevance;
|
| - });
|
| - if (shouldSelectSuggestion(
|
| - nativeSuggestions[0], searchboxApiHandle.verbatim)) {
|
| - selectedIndex = 0;
|
| - }
|
| -
|
| - renderSuggestions(nativeSuggestions);
|
| - searchboxApiHandle.hideBars();
|
| - } else {
|
| - searchboxApiHandle.showBars();
|
| - }
|
| + hideActiveSuggestions();
|
| }
|
| -
|
| - var height = getDropdownHeight();
|
| - searchboxApiHandle.showOverlay(height);
|
| }
|
|
|
| /**
|
| @@ -850,61 +1276,43 @@ function 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();
|
| - searchboxApiHandle.navigateContentWindow(
|
| - restrictedId, getDispositionFromClickButton(button));
|
| -}
|
| -
|
| -/**
|
| - * chrome.searchBox.onkeypress implementation.
|
| - * @param {!Event} e The key being pressed.
|
| + * Makes keys navigate through suggestions.
|
| + * @param {Object} e The key being pressed.
|
| */
|
| function handleKeyPress(e) {
|
| + if (!activeBox)
|
| + return;
|
| +
|
| switch (e.keyCode) {
|
| - case 38: // Up arrow.
|
| - updateSelectedSuggestion(false);
|
| + case KEY_UP_ARROW:
|
| + activeBox.selectPrevious();
|
| break;
|
| - case 40: // Down arrow.
|
| - updateSelectedSuggestion(true);
|
| + case KEY_DOWN_ARROW:
|
| + activeBox.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 (pendingBox && pendingBox.isResponseCurrent(message.data.loaded))
|
| + makePendingSuggestionsActive();
|
| + } else if ('click' in message.data) {
|
| + if (activeBox) {
|
| + var restrictedId = activeBox.getClickTarget(message.source);
|
| + if (restrictedId != null) {
|
| + hideActiveSuggestions();
|
| + searchboxApiHandle.navigateContentWindow(restrictedId,
|
| + getDispositionFromClickButton(message.data.click));
|
| + }
|
| }
|
| }
|
| }
|
| @@ -923,6 +1331,15 @@ function $(id) {
|
| }
|
|
|
| /**
|
| + * Shortcut for document.querySelector.
|
| + * @param {string} selector A selector to query the desired element.
|
| + * @return {HTMLElement} The first element to match |selector| or null.
|
| + */
|
| +function $qs(selector) {
|
| + return document.querySelector(selector);
|
| +}
|
| +
|
| +/**
|
| * Utility function which creates an element with an optional classname and
|
| * appends it to the specified parent.
|
| * @param {Element} parent The parent to append the new element.
|
| @@ -975,6 +1392,8 @@ function getEmbeddedSearchApiHandle() {
|
| * Google-provided page.
|
| */
|
| function init() {
|
| + iframePool = new IframePool();
|
| + iframePool.init();
|
| topMarginElement = $(IDS.TOP_MARGIN);
|
| tilesContainer = $(IDS.TILES);
|
| notification = $(IDS.NOTIFICATION);
|
| @@ -1031,12 +1450,14 @@ function init() {
|
| var value = searchboxApiHandle.value;
|
| if (!value) {
|
| // Interpret onsubmit with an empty query as an ESC key press.
|
| - clearSuggestions();
|
| + hideActiveSuggestions();
|
| updateNtpVisibility(true);
|
| }
|
| };
|
| -
|
| - $(IDS.SUGGESTIONS_CONTAINER).dir = searchboxApiHandle.rtl ? 'rtl' : 'ltr';
|
| + $qs('.' + CLASSES.ACTIVE_SUGGESTIONS_CONTAINER).dir =
|
| + searchboxApiHandle.rtl ? 'rtl' : 'ltr';
|
| + $qs('.' + CLASSES.PENDING_SUGGESTIONS_CONTAINER).dir =
|
| + searchboxApiHandle.rtl ? 'rtl' : 'ltr';
|
|
|
| if (!document.webkitHidden)
|
| window.addEventListener('resize', addDelayedTransitions);
|
| @@ -1078,4 +1499,8 @@ function addDelayedTransitions() {
|
|
|
| document.addEventListener('DOMContentLoaded', init);
|
| window.addEventListener('message', handleMessage, false);
|
| +window.addEventListener('blur', function() {
|
| + if (activeBox)
|
| + activeBox.clearHover();
|
| +}, false);
|
| })();
|
|
|