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

Unified Diff: chrome/browser/resources/local_ntp/local_ntp.js

Issue 13375003: Fixing iframe jank in the local omnibox popup. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: virtual 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « chrome/browser/resources/local_ntp/local_ntp.html ('k') | chrome/browser/resources/omnibox_result.html » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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);
})();
« no previous file with comments | « chrome/browser/resources/local_ntp/local_ntp.html ('k') | chrome/browser/resources/omnibox_result.html » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698