| OLD | NEW |
| (Empty) |
| 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 | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 /** | |
| 6 * @fileoverview Support for omnibox behavior in offline mode or when API | |
| 7 * features are not supported on the server. | |
| 8 */ | |
| 9 | |
| 10 // ========================================================== | |
| 11 // Enums. | |
| 12 // ========================================================== | |
| 13 | |
| 14 /** | |
| 15 * Possible behaviors for navigateContentWindow. | |
| 16 * @enum {number} | |
| 17 */ | |
| 18 var WindowOpenDisposition = { | |
| 19 CURRENT_TAB: 1, | |
| 20 NEW_BACKGROUND_TAB: 2 | |
| 21 }; | |
| 22 | |
| 23 /** | |
| 24 * The JavaScript button event value for a middle click. | |
| 25 * @type {number} | |
| 26 * @const | |
| 27 */ | |
| 28 var MIDDLE_MOUSE_BUTTON = 1; | |
| 29 | |
| 30 // ============================================================================= | |
| 31 // Util functions | |
| 32 // ============================================================================= | |
| 33 | |
| 34 /** | |
| 35 * The maximum number of suggestions to show. | |
| 36 * @type {number} | |
| 37 * @const | |
| 38 */ | |
| 39 var MAX_SUGGESTIONS_TO_SHOW = 5; | |
| 40 | |
| 41 /** | |
| 42 * Assume any native suggestion with a score higher than this value has been | |
| 43 * inlined by the browser. | |
| 44 * @type {number} | |
| 45 * @const | |
| 46 */ | |
| 47 var INLINE_SUGGESTION_THRESHOLD = 1200; | |
| 48 | |
| 49 /** | |
| 50 * Suggestion provider type corresponding to a verbatim URL suggestion. | |
| 51 * @type {string} | |
| 52 * @const | |
| 53 */ | |
| 54 var VERBATIM_URL_TYPE = 'url-what-you-typed'; | |
| 55 | |
| 56 /** | |
| 57 * Suggestion provider type corresponding to a verbatim search suggestion. | |
| 58 * @type {string} | |
| 59 * @const | |
| 60 */ | |
| 61 var VERBATIM_SEARCH_TYPE = 'search-what-you-typed'; | |
| 62 | |
| 63 /** | |
| 64 * The omnibox input value during the last onnativesuggestions event. | |
| 65 * @type {string} | |
| 66 */ | |
| 67 var lastInputValue = ''; | |
| 68 | |
| 69 /** | |
| 70 * The ordered restricted ids of the currently displayed suggestions. Since the | |
| 71 * suggestions contain the user's personal data (browser history) the searchBox | |
| 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 */ | |
| 76 | |
| 77 var restrictedIds = []; | |
| 78 | |
| 79 /** | |
| 80 * The index of the currently selected suggestion or -1 if none are selected. | |
| 81 * @type {number} | |
| 82 */ | |
| 83 var selectedIndex = -1; | |
| 84 | |
| 85 /** | |
| 86 * Shortcut for document.getElementById. | |
| 87 * @param {string} id of the element. | |
| 88 * @return {HTMLElement} with the id. | |
| 89 */ | |
| 90 function $(id) { | |
| 91 return document.getElementById(id); | |
| 92 } | |
| 93 | |
| 94 /** | |
| 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 // Don't add the search-what-you-typed suggestion if it's the top match. | |
| 139 if (i > 0 || nativeSuggestions[i].type != VERBATIM_SEARCH_TYPE) | |
| 140 addSuggestionToBox(nativeSuggestions[i], box, i == selectedIndex); | |
| 141 } | |
| 142 } | |
| 143 | |
| 144 /** | |
| 145 * Clears the suggestions being displayed. | |
| 146 */ | |
| 147 function clearSuggestions() { | |
| 148 $('suggestions-box-container').innerHTML = ''; | |
| 149 restrictedIds = []; | |
| 150 selectedIndex = -1; | |
| 151 } | |
| 152 | |
| 153 /** | |
| 154 * @return {number} The height of the dropdown. | |
| 155 */ | |
| 156 function getDropdownHeight() { | |
| 157 return $('suggestions-box-container').offsetHeight; | |
| 158 } | |
| 159 | |
| 160 /** | |
| 161 * @param {Object} suggestion A suggestion. | |
| 162 * @param {boolean} inVerbatimMode Are we in verbatim mode? | |
| 163 * @return {boolean} True if the suggestion should be selected. | |
| 164 */ | |
| 165 function shouldSelectSuggestion(suggestion, inVerbatimMode) { | |
| 166 var isVerbatimUrl = suggestion.type == VERBATIM_URL_TYPE; | |
| 167 var inlinableSuggestion = suggestion.type != VERBATIM_SEARCH_TYPE && | |
| 168 suggestion.rankingData.relevance > INLINE_SUGGESTION_THRESHOLD; | |
| 169 // Verbatim URLs should always be selected. Otherwise, select suggestions | |
| 170 // with a high enough score unless we are in verbatim mode (e.g. backspacing | |
| 171 // away). | |
| 172 return isVerbatimUrl || (!inVerbatimMode && inlinableSuggestion); | |
| 173 } | |
| 174 | |
| 175 /** | |
| 176 * Updates selectedIndex, bounding it between -1 and the total number of | |
| 177 * of suggestions - 1 (looping as necessary), and selects the corresponding | |
| 178 * suggestion. | |
| 179 * @param {boolean} increment True to increment the selected suggestion, false | |
| 180 * to decrement. | |
| 181 */ | |
| 182 function updateSelectedSuggestion(increment) { | |
| 183 var numSuggestions = restrictedIds.length; | |
| 184 if (!numSuggestions) | |
| 185 return; | |
| 186 | |
| 187 var oldSelection = $('suggestionsBox').querySelector('.selected'); | |
| 188 if (oldSelection) | |
| 189 oldSelection.classList.remove('selected'); | |
| 190 | |
| 191 if (increment) | |
| 192 selectedIndex = ++selectedIndex > numSuggestions - 1 ? -1 : selectedIndex; | |
| 193 else | |
| 194 selectedIndex = --selectedIndex < -1 ? numSuggestions - 1 : selectedIndex; | |
| 195 var apiHandle = getApiObjectHandle(); | |
| 196 if (selectedIndex == -1) { | |
| 197 apiHandle.setValue(lastInputValue); | |
| 198 } else { | |
| 199 var newSelection = $('suggestionsBox').querySelector( | |
| 200 '.suggestion:nth-of-type(' + (selectedIndex + 1) + ')'); | |
| 201 newSelection.classList.add('selected'); | |
| 202 apiHandle.setRestrictedValue(restrictedIds[selectedIndex]); | |
| 203 } | |
| 204 } | |
| 205 | |
| 206 // ============================================================================= | |
| 207 // Handlers / API stuff | |
| 208 // ============================================================================= | |
| 209 | |
| 210 /** | |
| 211 * @return {Object} the handle to the searchBox API. | |
| 212 */ | |
| 213 function getApiObjectHandle() { | |
| 214 if (window.cideb) | |
| 215 return window.cideb; | |
| 216 if (window.navigator && window.navigator.embeddedSearch && | |
| 217 window.navigator.embeddedSearch.searchBox) | |
| 218 return window.navigator.embeddedSearch.searchBox; | |
| 219 if (window.chrome && window.chrome.embeddedSearch && | |
| 220 window.chrome.embeddedSearch.searchBox) | |
| 221 return window.chrome.embeddedSearch.searchBox; | |
| 222 return null; | |
| 223 } | |
| 224 | |
| 225 /** | |
| 226 * Updates suggestions in response to a onchange or onnativesuggestions call. | |
| 227 */ | |
| 228 function updateSuggestions() { | |
| 229 var apiHandle = getApiObjectHandle(); | |
| 230 lastInputValue = apiHandle.value; | |
| 231 | |
| 232 clearSuggestions(); | |
| 233 var nativeSuggestions = apiHandle.nativeSuggestions; | |
| 234 if (nativeSuggestions.length) { | |
| 235 nativeSuggestions.sort(function(a, b) { | |
| 236 return b.rankingData.relevance - a.rankingData.relevance; | |
| 237 }); | |
| 238 if (shouldSelectSuggestion(nativeSuggestions[0], apiHandle.verbatim)) | |
| 239 selectedIndex = 0; | |
| 240 renderSuggestions(nativeSuggestions); | |
| 241 } | |
| 242 | |
| 243 var height = getDropdownHeight(); | |
| 244 apiHandle.showOverlay(height); | |
| 245 } | |
| 246 | |
| 247 /** | |
| 248 * Appends a style node for suggestion properties that depend on apiHandle. | |
| 249 */ | |
| 250 function appendSuggestionStyles() { | |
| 251 var apiHandle = getApiObjectHandle(); | |
| 252 var isRtl = apiHandle.rtl; | |
| 253 var startMargin = apiHandle.startMargin; | |
| 254 var style = document.createElement('style'); | |
| 255 style.type = 'text/css'; | |
| 256 style.id = 'suggestionStyle'; | |
| 257 style.textContent = | |
| 258 '.suggestion, ' + | |
| 259 '.suggestion.search {' + | |
| 260 ' background-position: ' + | |
| 261 (isRtl ? '-webkit-calc(100% - 5px)' : '5px') + ' 4px;' + | |
| 262 ' -webkit-margin-start: ' + startMargin + 'px;' + | |
| 263 ' -webkit-margin-end: ' + | |
| 264 (window.innerWidth - apiHandle.width - startMargin) + 'px;' + | |
| 265 ' font: ' + apiHandle.fontSize + 'px "' + apiHandle.font + '";' + | |
| 266 '}'; | |
| 267 document.querySelector('head').appendChild(style); | |
| 268 window.removeEventListener('resize', appendSuggestionStyles); | |
| 269 } | |
| 270 | |
| 271 /** | |
| 272 * Extract the desired navigation behavior from a click button. | |
| 273 * @param {number} button The Event#button property of a click event. | |
| 274 * @return {WindowOpenDisposition} The desired behavior for | |
| 275 * navigateContentWindow. | |
| 276 */ | |
| 277 function getDispositionFromClickButton(button) { | |
| 278 if (button == MIDDLE_MOUSE_BUTTON) | |
| 279 return WindowOpenDisposition.NEW_BACKGROUND_TAB; | |
| 280 return WindowOpenDisposition.CURRENT_TAB; | |
| 281 } | |
| 282 | |
| 283 /** | |
| 284 * Handles suggestion clicks. | |
| 285 * @param {number} restrictedId The restricted id of the suggestion being | |
| 286 * clicked. | |
| 287 * @param {number} button The Event#button property of a click event. | |
| 288 * | |
| 289 */ | |
| 290 function handleSuggestionClick(restrictedId, button) { | |
| 291 clearSuggestions(); | |
| 292 getApiObjectHandle().navigateContentWindow( | |
| 293 restrictedId, getDispositionFromClickButton(button)); | |
| 294 } | |
| 295 | |
| 296 /** | |
| 297 * chrome.searchBox.onkeypress implementation. | |
| 298 * @param {Object} e The key being pressed. | |
| 299 */ | |
| 300 function handleKeyPress(e) { | |
| 301 switch (e.keyCode) { | |
| 302 case 38: // Up arrow | |
| 303 updateSelectedSuggestion(false); | |
| 304 break; | |
| 305 case 40: // Down arrow | |
| 306 updateSelectedSuggestion(true); | |
| 307 break; | |
| 308 } | |
| 309 } | |
| 310 | |
| 311 /** | |
| 312 * Handles the postMessage calls from the result iframes. | |
| 313 * @param {Object} message The message containing details of clicks the iframes. | |
| 314 */ | |
| 315 function handleMessage(message) { | |
| 316 if (message.origin != 'null' || !message.data || | |
| 317 message.data.eventType != 'click') { | |
| 318 return; | |
| 319 } | |
| 320 | |
| 321 var iframes = document.getElementsByClassName('contents'); | |
| 322 for (var i = 0; i < iframes.length; ++i) { | |
| 323 if (iframes[i].contentWindow == message.source) { | |
| 324 handleSuggestionClick(parseInt(iframes[i].id, 10), | |
| 325 message.data.button); | |
| 326 break; | |
| 327 } | |
| 328 } | |
| 329 } | |
| 330 | |
| 331 /** | |
| 332 * chrome.searchBox.embeddedSearch.onsubmit implementation. | |
| 333 */ | |
| 334 function onSubmit() { | |
| 335 } | |
| 336 | |
| 337 /** | |
| 338 * Sets up the searchBox API. | |
| 339 */ | |
| 340 function setUpApi() { | |
| 341 var apiHandle = getApiObjectHandle(); | |
| 342 apiHandle.onnativesuggestions = updateSuggestions; | |
| 343 apiHandle.onchange = updateSuggestions; | |
| 344 apiHandle.onkeypress = handleKeyPress; | |
| 345 apiHandle.onsubmit = onSubmit; | |
| 346 $('suggestions-box-container').dir = apiHandle.rtl ? 'rtl' : 'ltr'; | |
| 347 // Delay adding these styles until the window width is available. | |
| 348 window.addEventListener('resize', appendSuggestionStyles); | |
| 349 if (apiHandle.nativeSuggestions.length) | |
| 350 handleNativeSuggestions(); | |
| 351 } | |
| 352 | |
| 353 document.addEventListener('DOMContentLoaded', setUpApi); | |
| 354 window.addEventListener('message', handleMessage, false); | |
| OLD | NEW |