Sat, 03 Jan 2015 20:18:00 +0100
Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.
michael@0 | 1 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 4 | |
michael@0 | 5 | const SEARCH_RESPONSE_SUGGESTION_JSON = "application/x-suggestions+json"; |
michael@0 | 6 | |
michael@0 | 7 | const BROWSER_SUGGEST_PREF = "browser.search.suggest.enabled"; |
michael@0 | 8 | const XPCOM_SHUTDOWN_TOPIC = "xpcom-shutdown"; |
michael@0 | 9 | const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed"; |
michael@0 | 10 | |
michael@0 | 11 | const Cc = Components.classes; |
michael@0 | 12 | const Ci = Components.interfaces; |
michael@0 | 13 | const Cr = Components.results; |
michael@0 | 14 | const Cu = Components.utils; |
michael@0 | 15 | |
michael@0 | 16 | const HTTP_OK = 200; |
michael@0 | 17 | const HTTP_INTERNAL_SERVER_ERROR = 500; |
michael@0 | 18 | const HTTP_BAD_GATEWAY = 502; |
michael@0 | 19 | const HTTP_SERVICE_UNAVAILABLE = 503; |
michael@0 | 20 | |
michael@0 | 21 | Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
michael@0 | 22 | Cu.import("resource://gre/modules/nsFormAutoCompleteResult.jsm"); |
michael@0 | 23 | Cu.import("resource://gre/modules/Services.jsm"); |
michael@0 | 24 | |
michael@0 | 25 | /** |
michael@0 | 26 | * SuggestAutoComplete is a base class that implements nsIAutoCompleteSearch |
michael@0 | 27 | * and can collect results for a given search by using the search URL supplied |
michael@0 | 28 | * by the subclass. We do it this way since the AutoCompleteController in |
michael@0 | 29 | * Mozilla requires a unique XPCOM Service for every search provider, even if |
michael@0 | 30 | * the logic for two providers is identical. |
michael@0 | 31 | * @constructor |
michael@0 | 32 | */ |
michael@0 | 33 | function SuggestAutoComplete() { |
michael@0 | 34 | this._init(); |
michael@0 | 35 | } |
michael@0 | 36 | SuggestAutoComplete.prototype = { |
michael@0 | 37 | |
michael@0 | 38 | _init: function() { |
michael@0 | 39 | this._addObservers(); |
michael@0 | 40 | this._suggestEnabled = Services.prefs.getBoolPref(BROWSER_SUGGEST_PREF); |
michael@0 | 41 | }, |
michael@0 | 42 | |
michael@0 | 43 | get _suggestionLabel() { |
michael@0 | 44 | delete this._suggestionLabel; |
michael@0 | 45 | let bundle = Services.strings.createBundle("chrome://global/locale/search/search.properties"); |
michael@0 | 46 | return this._suggestionLabel = bundle.GetStringFromName("suggestion_label"); |
michael@0 | 47 | }, |
michael@0 | 48 | |
michael@0 | 49 | /** |
michael@0 | 50 | * Search suggestions will be shown if this._suggestEnabled is true. |
michael@0 | 51 | */ |
michael@0 | 52 | _suggestEnabled: null, |
michael@0 | 53 | |
michael@0 | 54 | /************************************************************************* |
michael@0 | 55 | * Server request backoff implementation fields below |
michael@0 | 56 | * These allow us to throttle requests if the server is getting hammered. |
michael@0 | 57 | **************************************************************************/ |
michael@0 | 58 | |
michael@0 | 59 | /** |
michael@0 | 60 | * This is an array that contains the timestamps (in unixtime) of |
michael@0 | 61 | * the last few backoff-triggering errors. |
michael@0 | 62 | */ |
michael@0 | 63 | _serverErrorLog: [], |
michael@0 | 64 | |
michael@0 | 65 | /** |
michael@0 | 66 | * If we receive this number of backoff errors within the amount of time |
michael@0 | 67 | * specified by _serverErrorPeriod, then we initiate backoff. |
michael@0 | 68 | */ |
michael@0 | 69 | _maxErrorsBeforeBackoff: 3, |
michael@0 | 70 | |
michael@0 | 71 | /** |
michael@0 | 72 | * If we receive enough consecutive errors (where "enough" is defined by |
michael@0 | 73 | * _maxErrorsBeforeBackoff above) within this time period, |
michael@0 | 74 | * we trigger the backoff behavior. |
michael@0 | 75 | */ |
michael@0 | 76 | _serverErrorPeriod: 600000, // 10 minutes in milliseconds |
michael@0 | 77 | |
michael@0 | 78 | /** |
michael@0 | 79 | * If we get another backoff error immediately after timeout, we increase the |
michael@0 | 80 | * backoff to (2 x old period) + this value. |
michael@0 | 81 | */ |
michael@0 | 82 | _serverErrorTimeoutIncrement: 600000, // 10 minutes in milliseconds |
michael@0 | 83 | |
michael@0 | 84 | /** |
michael@0 | 85 | * The current amount of time to wait before trying a server request |
michael@0 | 86 | * after receiving a backoff error. |
michael@0 | 87 | */ |
michael@0 | 88 | _serverErrorTimeout: 0, |
michael@0 | 89 | |
michael@0 | 90 | /** |
michael@0 | 91 | * Time (in unixtime) after which we're allowed to try requesting again. |
michael@0 | 92 | */ |
michael@0 | 93 | _nextRequestTime: 0, |
michael@0 | 94 | |
michael@0 | 95 | /** |
michael@0 | 96 | * The last engine we requested against (so that we can tell if the |
michael@0 | 97 | * user switched engines). |
michael@0 | 98 | */ |
michael@0 | 99 | _serverErrorEngine: null, |
michael@0 | 100 | |
michael@0 | 101 | /** |
michael@0 | 102 | * The XMLHttpRequest object. |
michael@0 | 103 | * @private |
michael@0 | 104 | */ |
michael@0 | 105 | _request: null, |
michael@0 | 106 | |
michael@0 | 107 | /** |
michael@0 | 108 | * The object implementing nsIAutoCompleteObserver that we notify when |
michael@0 | 109 | * we have found results |
michael@0 | 110 | * @private |
michael@0 | 111 | */ |
michael@0 | 112 | _listener: null, |
michael@0 | 113 | |
michael@0 | 114 | /** |
michael@0 | 115 | * If this is true, we'll integrate form history results with the |
michael@0 | 116 | * suggest results. |
michael@0 | 117 | */ |
michael@0 | 118 | _includeFormHistory: true, |
michael@0 | 119 | |
michael@0 | 120 | /** |
michael@0 | 121 | * True if a request for remote suggestions was sent. This is used to |
michael@0 | 122 | * differentiate between the "_request is null because the request has |
michael@0 | 123 | * already returned a result" and "_request is null because no request was |
michael@0 | 124 | * sent" cases. |
michael@0 | 125 | */ |
michael@0 | 126 | _sentSuggestRequest: false, |
michael@0 | 127 | |
michael@0 | 128 | /** |
michael@0 | 129 | * This is the callback for the suggest timeout timer. |
michael@0 | 130 | */ |
michael@0 | 131 | notify: function SAC_notify(timer) { |
michael@0 | 132 | // FIXME: bug 387341 |
michael@0 | 133 | // Need to break the cycle between us and the timer. |
michael@0 | 134 | this._formHistoryTimer = null; |
michael@0 | 135 | |
michael@0 | 136 | // If this._listener is null, we've already sent out suggest results, so |
michael@0 | 137 | // nothing left to do here. |
michael@0 | 138 | if (!this._listener) |
michael@0 | 139 | return; |
michael@0 | 140 | |
michael@0 | 141 | // Otherwise, the XMLHTTPRequest for suggest results is taking too long, |
michael@0 | 142 | // so send out the form history results and cancel the request. |
michael@0 | 143 | this._listener.onSearchResult(this, this._formHistoryResult); |
michael@0 | 144 | this._reset(); |
michael@0 | 145 | }, |
michael@0 | 146 | |
michael@0 | 147 | /** |
michael@0 | 148 | * This determines how long (in ms) we should wait before giving up on |
michael@0 | 149 | * the suggestions and just showing local form history results. |
michael@0 | 150 | */ |
michael@0 | 151 | _suggestionTimeout: 500, |
michael@0 | 152 | |
michael@0 | 153 | /** |
michael@0 | 154 | * This is the callback for that the form history service uses to |
michael@0 | 155 | * send us results. |
michael@0 | 156 | */ |
michael@0 | 157 | onSearchResult: function SAC_onSearchResult(search, result) { |
michael@0 | 158 | this._formHistoryResult = result; |
michael@0 | 159 | |
michael@0 | 160 | if (this._request) { |
michael@0 | 161 | // We still have a pending request, wait a bit to give it a chance to |
michael@0 | 162 | // finish. |
michael@0 | 163 | this._formHistoryTimer = Cc["@mozilla.org/timer;1"]. |
michael@0 | 164 | createInstance(Ci.nsITimer); |
michael@0 | 165 | this._formHistoryTimer.initWithCallback(this, this._suggestionTimeout, |
michael@0 | 166 | Ci.nsITimer.TYPE_ONE_SHOT); |
michael@0 | 167 | } else if (!this._sentSuggestRequest) { |
michael@0 | 168 | // We didn't send a request, so just send back the form history results. |
michael@0 | 169 | this._listener.onSearchResult(this, this._formHistoryResult); |
michael@0 | 170 | this._reset(); |
michael@0 | 171 | } |
michael@0 | 172 | }, |
michael@0 | 173 | |
michael@0 | 174 | /** |
michael@0 | 175 | * This is the URI that the last suggest request was sent to. |
michael@0 | 176 | */ |
michael@0 | 177 | _suggestURI: null, |
michael@0 | 178 | |
michael@0 | 179 | /** |
michael@0 | 180 | * Autocomplete results from the form history service get stored here. |
michael@0 | 181 | */ |
michael@0 | 182 | _formHistoryResult: null, |
michael@0 | 183 | |
michael@0 | 184 | /** |
michael@0 | 185 | * This holds the suggest server timeout timer, if applicable. |
michael@0 | 186 | */ |
michael@0 | 187 | _formHistoryTimer: null, |
michael@0 | 188 | |
michael@0 | 189 | /** |
michael@0 | 190 | * Maximum number of history items displayed. This is capped at 7 |
michael@0 | 191 | * because the primary consumer (Firefox search bar) displays 10 rows |
michael@0 | 192 | * by default, and so we want to leave some space for suggestions |
michael@0 | 193 | * to be visible. |
michael@0 | 194 | */ |
michael@0 | 195 | _historyLimit: 7, |
michael@0 | 196 | |
michael@0 | 197 | /** |
michael@0 | 198 | * This clears all the per-request state. |
michael@0 | 199 | */ |
michael@0 | 200 | _reset: function SAC_reset() { |
michael@0 | 201 | // Don't let go of our listener and form history result if the timer is |
michael@0 | 202 | // still pending, the timer will call _reset() when it fires. |
michael@0 | 203 | if (!this._formHistoryTimer) { |
michael@0 | 204 | this._listener = null; |
michael@0 | 205 | this._formHistoryResult = null; |
michael@0 | 206 | } |
michael@0 | 207 | this._request = null; |
michael@0 | 208 | }, |
michael@0 | 209 | |
michael@0 | 210 | /** |
michael@0 | 211 | * This sends an autocompletion request to the form history service, |
michael@0 | 212 | * which will call onSearchResults with the results of the query. |
michael@0 | 213 | */ |
michael@0 | 214 | _startHistorySearch: function SAC_SHSearch(searchString, searchParam) { |
michael@0 | 215 | var formHistory = |
michael@0 | 216 | Cc["@mozilla.org/autocomplete/search;1?name=form-history"]. |
michael@0 | 217 | createInstance(Ci.nsIAutoCompleteSearch); |
michael@0 | 218 | formHistory.startSearch(searchString, searchParam, this._formHistoryResult, this); |
michael@0 | 219 | }, |
michael@0 | 220 | |
michael@0 | 221 | /** |
michael@0 | 222 | * Makes a note of the fact that we've received a backoff-triggering |
michael@0 | 223 | * response, so that we can adjust the backoff behavior appropriately. |
michael@0 | 224 | */ |
michael@0 | 225 | _noteServerError: function SAC__noteServeError() { |
michael@0 | 226 | var currentTime = Date.now(); |
michael@0 | 227 | |
michael@0 | 228 | this._serverErrorLog.push(currentTime); |
michael@0 | 229 | if (this._serverErrorLog.length > this._maxErrorsBeforeBackoff) |
michael@0 | 230 | this._serverErrorLog.shift(); |
michael@0 | 231 | |
michael@0 | 232 | if ((this._serverErrorLog.length == this._maxErrorsBeforeBackoff) && |
michael@0 | 233 | ((currentTime - this._serverErrorLog[0]) < this._serverErrorPeriod)) { |
michael@0 | 234 | // increase timeout, and then don't request until timeout is over |
michael@0 | 235 | this._serverErrorTimeout = (this._serverErrorTimeout * 2) + |
michael@0 | 236 | this._serverErrorTimeoutIncrement; |
michael@0 | 237 | this._nextRequestTime = currentTime + this._serverErrorTimeout; |
michael@0 | 238 | } |
michael@0 | 239 | }, |
michael@0 | 240 | |
michael@0 | 241 | /** |
michael@0 | 242 | * Resets the backoff behavior; called when we get a successful response. |
michael@0 | 243 | */ |
michael@0 | 244 | _clearServerErrors: function SAC__clearServerErrors() { |
michael@0 | 245 | this._serverErrorLog = []; |
michael@0 | 246 | this._serverErrorTimeout = 0; |
michael@0 | 247 | this._nextRequestTime = 0; |
michael@0 | 248 | }, |
michael@0 | 249 | |
michael@0 | 250 | /** |
michael@0 | 251 | * This checks whether we should send a server request (i.e. we're not |
michael@0 | 252 | * in a error-triggered backoff period. |
michael@0 | 253 | * |
michael@0 | 254 | * @private |
michael@0 | 255 | */ |
michael@0 | 256 | _okToRequest: function SAC__okToRequest() { |
michael@0 | 257 | return Date.now() > this._nextRequestTime; |
michael@0 | 258 | }, |
michael@0 | 259 | |
michael@0 | 260 | /** |
michael@0 | 261 | * This checks to see if the new search engine is different |
michael@0 | 262 | * from the previous one, and if so clears any error state that might |
michael@0 | 263 | * have accumulated for the old engine. |
michael@0 | 264 | * |
michael@0 | 265 | * @param engine The engine that the suggestion request would be sent to. |
michael@0 | 266 | * @private |
michael@0 | 267 | */ |
michael@0 | 268 | _checkForEngineSwitch: function SAC__checkForEngineSwitch(engine) { |
michael@0 | 269 | if (engine == this._serverErrorEngine) |
michael@0 | 270 | return; |
michael@0 | 271 | |
michael@0 | 272 | // must've switched search providers, clear old errors |
michael@0 | 273 | this._serverErrorEngine = engine; |
michael@0 | 274 | this._clearServerErrors(); |
michael@0 | 275 | }, |
michael@0 | 276 | |
michael@0 | 277 | /** |
michael@0 | 278 | * This returns true if the status code of the HTTP response |
michael@0 | 279 | * represents a backoff-triggering error. |
michael@0 | 280 | * |
michael@0 | 281 | * @param status The status code from the HTTP response |
michael@0 | 282 | * @private |
michael@0 | 283 | */ |
michael@0 | 284 | _isBackoffError: function SAC__isBackoffError(status) { |
michael@0 | 285 | return ((status == HTTP_INTERNAL_SERVER_ERROR) || |
michael@0 | 286 | (status == HTTP_BAD_GATEWAY) || |
michael@0 | 287 | (status == HTTP_SERVICE_UNAVAILABLE)); |
michael@0 | 288 | }, |
michael@0 | 289 | |
michael@0 | 290 | /** |
michael@0 | 291 | * Called when the 'readyState' of the XMLHttpRequest changes. We only care |
michael@0 | 292 | * about state 4 (COMPLETED) - handle the response data. |
michael@0 | 293 | * @private |
michael@0 | 294 | */ |
michael@0 | 295 | onReadyStateChange: function() { |
michael@0 | 296 | // xxx use the real const here |
michael@0 | 297 | if (!this._request || this._request.readyState != 4) |
michael@0 | 298 | return; |
michael@0 | 299 | |
michael@0 | 300 | try { |
michael@0 | 301 | var status = this._request.status; |
michael@0 | 302 | } catch (e) { |
michael@0 | 303 | // The XML HttpRequest can throw NS_ERROR_NOT_AVAILABLE. |
michael@0 | 304 | return; |
michael@0 | 305 | } |
michael@0 | 306 | |
michael@0 | 307 | if (this._isBackoffError(status)) { |
michael@0 | 308 | this._noteServerError(); |
michael@0 | 309 | return; |
michael@0 | 310 | } |
michael@0 | 311 | |
michael@0 | 312 | var responseText = this._request.responseText; |
michael@0 | 313 | if (status != HTTP_OK || responseText == "") |
michael@0 | 314 | return; |
michael@0 | 315 | |
michael@0 | 316 | this._clearServerErrors(); |
michael@0 | 317 | |
michael@0 | 318 | try { |
michael@0 | 319 | var serverResults = JSON.parse(responseText); |
michael@0 | 320 | } catch(ex) { |
michael@0 | 321 | Components.utils.reportError("Failed to parse JSON from " + this._suggestURI.spec + ": " + ex); |
michael@0 | 322 | return; |
michael@0 | 323 | } |
michael@0 | 324 | |
michael@0 | 325 | var searchString = serverResults[0] || ""; |
michael@0 | 326 | var results = serverResults[1] || []; |
michael@0 | 327 | |
michael@0 | 328 | var comments = []; // "comments" column values for suggestions |
michael@0 | 329 | var historyResults = []; |
michael@0 | 330 | var historyComments = []; |
michael@0 | 331 | |
michael@0 | 332 | // If form history is enabled and has results, add them to the list. |
michael@0 | 333 | if (this._includeFormHistory && this._formHistoryResult && |
michael@0 | 334 | (this._formHistoryResult.searchResult == |
michael@0 | 335 | Ci.nsIAutoCompleteResult.RESULT_SUCCESS)) { |
michael@0 | 336 | var maxHistoryItems = Math.min(this._formHistoryResult.matchCount, this._historyLimit); |
michael@0 | 337 | for (var i = 0; i < maxHistoryItems; ++i) { |
michael@0 | 338 | var term = this._formHistoryResult.getValueAt(i); |
michael@0 | 339 | |
michael@0 | 340 | // we don't want things to appear in both history and suggestions |
michael@0 | 341 | var dupIndex = results.indexOf(term); |
michael@0 | 342 | if (dupIndex != -1) |
michael@0 | 343 | results.splice(dupIndex, 1); |
michael@0 | 344 | |
michael@0 | 345 | historyResults.push(term); |
michael@0 | 346 | historyComments.push(""); |
michael@0 | 347 | } |
michael@0 | 348 | } |
michael@0 | 349 | |
michael@0 | 350 | // fill out the comment column for the suggestions |
michael@0 | 351 | for (var i = 0; i < results.length; ++i) |
michael@0 | 352 | comments.push(""); |
michael@0 | 353 | |
michael@0 | 354 | // if we have any suggestions, put a label at the top |
michael@0 | 355 | if (comments.length > 0) |
michael@0 | 356 | comments[0] = this._suggestionLabel; |
michael@0 | 357 | |
michael@0 | 358 | // now put the history results above the suggestions |
michael@0 | 359 | var finalResults = historyResults.concat(results); |
michael@0 | 360 | var finalComments = historyComments.concat(comments); |
michael@0 | 361 | |
michael@0 | 362 | // Notify the FE of our new results |
michael@0 | 363 | this.onResultsReady(searchString, finalResults, finalComments, |
michael@0 | 364 | this._formHistoryResult); |
michael@0 | 365 | |
michael@0 | 366 | // Reset our state for next time. |
michael@0 | 367 | this._reset(); |
michael@0 | 368 | }, |
michael@0 | 369 | |
michael@0 | 370 | /** |
michael@0 | 371 | * Notifies the front end of new results. |
michael@0 | 372 | * @param searchString the user's query string |
michael@0 | 373 | * @param results an array of results to the search |
michael@0 | 374 | * @param comments an array of metadata corresponding to the results |
michael@0 | 375 | * @private |
michael@0 | 376 | */ |
michael@0 | 377 | onResultsReady: function(searchString, results, comments, |
michael@0 | 378 | formHistoryResult) { |
michael@0 | 379 | if (this._listener) { |
michael@0 | 380 | var result = new FormAutoCompleteResult( |
michael@0 | 381 | searchString, |
michael@0 | 382 | Ci.nsIAutoCompleteResult.RESULT_SUCCESS, |
michael@0 | 383 | 0, |
michael@0 | 384 | "", |
michael@0 | 385 | results, |
michael@0 | 386 | results, |
michael@0 | 387 | comments, |
michael@0 | 388 | formHistoryResult); |
michael@0 | 389 | |
michael@0 | 390 | this._listener.onSearchResult(this, result); |
michael@0 | 391 | |
michael@0 | 392 | // Null out listener to make sure we don't notify it twice, in case our |
michael@0 | 393 | // timer callback still hasn't run. |
michael@0 | 394 | this._listener = null; |
michael@0 | 395 | } |
michael@0 | 396 | }, |
michael@0 | 397 | |
michael@0 | 398 | /** |
michael@0 | 399 | * Initiates the search result gathering process. Part of |
michael@0 | 400 | * nsIAutoCompleteSearch implementation. |
michael@0 | 401 | * |
michael@0 | 402 | * @param searchString the user's query string |
michael@0 | 403 | * @param searchParam unused, "an extra parameter"; even though |
michael@0 | 404 | * this parameter and the next are unused, pass |
michael@0 | 405 | * them through in case the form history |
michael@0 | 406 | * service wants them |
michael@0 | 407 | * @param previousResult unused, a client-cached store of the previous |
michael@0 | 408 | * generated resultset for faster searching. |
michael@0 | 409 | * @param listener object implementing nsIAutoCompleteObserver which |
michael@0 | 410 | * we notify when results are ready. |
michael@0 | 411 | */ |
michael@0 | 412 | startSearch: function(searchString, searchParam, previousResult, listener) { |
michael@0 | 413 | // Don't reuse a previous form history result when it no longer applies. |
michael@0 | 414 | if (!previousResult) |
michael@0 | 415 | this._formHistoryResult = null; |
michael@0 | 416 | |
michael@0 | 417 | var formHistorySearchParam = searchParam.split("|")[0]; |
michael@0 | 418 | |
michael@0 | 419 | // Receive the information about the privacy mode of the window to which |
michael@0 | 420 | // this search box belongs. The front-end's search.xml bindings passes this |
michael@0 | 421 | // information in the searchParam parameter. The alternative would have |
michael@0 | 422 | // been to modify nsIAutoCompleteSearch to add an argument to startSearch |
michael@0 | 423 | // and patch all of autocomplete to be aware of this, but the searchParam |
michael@0 | 424 | // argument is already an opaque argument, so this solution is hopefully |
michael@0 | 425 | // less hackish (although still gross.) |
michael@0 | 426 | var privacyMode = (searchParam.split("|")[1] == "private"); |
michael@0 | 427 | |
michael@0 | 428 | // Start search immediately if possible, otherwise once the search |
michael@0 | 429 | // service is initialized |
michael@0 | 430 | if (Services.search.isInitialized) { |
michael@0 | 431 | this._triggerSearch(searchString, formHistorySearchParam, listener, privacyMode); |
michael@0 | 432 | return; |
michael@0 | 433 | } |
michael@0 | 434 | |
michael@0 | 435 | Services.search.init((function startSearch_cb(aResult) { |
michael@0 | 436 | if (!Components.isSuccessCode(aResult)) { |
michael@0 | 437 | Cu.reportError("Could not initialize search service, bailing out: " + aResult); |
michael@0 | 438 | return; |
michael@0 | 439 | } |
michael@0 | 440 | this._triggerSearch(searchString, formHistorySearchParam, listener, privacyMode); |
michael@0 | 441 | }).bind(this)); |
michael@0 | 442 | }, |
michael@0 | 443 | |
michael@0 | 444 | /** |
michael@0 | 445 | * Actual implementation of search. |
michael@0 | 446 | */ |
michael@0 | 447 | _triggerSearch: function(searchString, searchParam, listener, privacyMode) { |
michael@0 | 448 | // If there's an existing request, stop it. There is no smart filtering |
michael@0 | 449 | // here as there is when looking through history/form data because the |
michael@0 | 450 | // result set returned by the server is different for every typed value - |
michael@0 | 451 | // "ocean breathes" does not return a subset of the results returned for |
michael@0 | 452 | // "ocean", for example. This does nothing if there is no current request. |
michael@0 | 453 | this.stopSearch(); |
michael@0 | 454 | |
michael@0 | 455 | this._listener = listener; |
michael@0 | 456 | |
michael@0 | 457 | var engine = Services.search.currentEngine; |
michael@0 | 458 | |
michael@0 | 459 | this._checkForEngineSwitch(engine); |
michael@0 | 460 | |
michael@0 | 461 | if (!searchString || |
michael@0 | 462 | !this._suggestEnabled || |
michael@0 | 463 | !engine.supportsResponseType(SEARCH_RESPONSE_SUGGESTION_JSON) || |
michael@0 | 464 | !this._okToRequest()) { |
michael@0 | 465 | // We have an empty search string (user pressed down arrow to see |
michael@0 | 466 | // history), or search suggestions are disabled, or the current engine |
michael@0 | 467 | // has no suggest functionality, or we're in backoff mode; so just use |
michael@0 | 468 | // local history. |
michael@0 | 469 | this._sentSuggestRequest = false; |
michael@0 | 470 | this._startHistorySearch(searchString, searchParam); |
michael@0 | 471 | return; |
michael@0 | 472 | } |
michael@0 | 473 | |
michael@0 | 474 | // Actually do the search |
michael@0 | 475 | this._request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]. |
michael@0 | 476 | createInstance(Ci.nsIXMLHttpRequest); |
michael@0 | 477 | var submission = engine.getSubmission(searchString, |
michael@0 | 478 | SEARCH_RESPONSE_SUGGESTION_JSON); |
michael@0 | 479 | this._suggestURI = submission.uri; |
michael@0 | 480 | var method = (submission.postData ? "POST" : "GET"); |
michael@0 | 481 | this._request.open(method, this._suggestURI.spec, true); |
michael@0 | 482 | this._request.channel.notificationCallbacks = new AuthPromptOverride(); |
michael@0 | 483 | if (this._request.channel instanceof Ci.nsIPrivateBrowsingChannel) { |
michael@0 | 484 | this._request.channel.setPrivate(privacyMode); |
michael@0 | 485 | } |
michael@0 | 486 | |
michael@0 | 487 | var self = this; |
michael@0 | 488 | function onReadyStateChange() { |
michael@0 | 489 | self.onReadyStateChange(); |
michael@0 | 490 | } |
michael@0 | 491 | this._request.onreadystatechange = onReadyStateChange; |
michael@0 | 492 | this._request.send(submission.postData); |
michael@0 | 493 | |
michael@0 | 494 | if (this._includeFormHistory) { |
michael@0 | 495 | this._sentSuggestRequest = true; |
michael@0 | 496 | this._startHistorySearch(searchString, searchParam); |
michael@0 | 497 | } |
michael@0 | 498 | }, |
michael@0 | 499 | |
michael@0 | 500 | /** |
michael@0 | 501 | * Ends the search result gathering process. Part of nsIAutoCompleteSearch |
michael@0 | 502 | * implementation. |
michael@0 | 503 | */ |
michael@0 | 504 | stopSearch: function() { |
michael@0 | 505 | if (this._request) { |
michael@0 | 506 | this._request.abort(); |
michael@0 | 507 | this._reset(); |
michael@0 | 508 | } |
michael@0 | 509 | }, |
michael@0 | 510 | |
michael@0 | 511 | /** |
michael@0 | 512 | * nsIObserver |
michael@0 | 513 | */ |
michael@0 | 514 | observe: function SAC_observe(aSubject, aTopic, aData) { |
michael@0 | 515 | switch (aTopic) { |
michael@0 | 516 | case NS_PREFBRANCH_PREFCHANGE_TOPIC_ID: |
michael@0 | 517 | this._suggestEnabled = Services.prefs.getBoolPref(BROWSER_SUGGEST_PREF); |
michael@0 | 518 | break; |
michael@0 | 519 | case XPCOM_SHUTDOWN_TOPIC: |
michael@0 | 520 | this._removeObservers(); |
michael@0 | 521 | break; |
michael@0 | 522 | } |
michael@0 | 523 | }, |
michael@0 | 524 | |
michael@0 | 525 | _addObservers: function SAC_addObservers() { |
michael@0 | 526 | Services.prefs.addObserver(BROWSER_SUGGEST_PREF, this, false); |
michael@0 | 527 | |
michael@0 | 528 | Services.obs.addObserver(this, XPCOM_SHUTDOWN_TOPIC, false); |
michael@0 | 529 | }, |
michael@0 | 530 | |
michael@0 | 531 | _removeObservers: function SAC_removeObservers() { |
michael@0 | 532 | Services.prefs.removeObserver(BROWSER_SUGGEST_PREF, this); |
michael@0 | 533 | |
michael@0 | 534 | Services.obs.removeObserver(this, XPCOM_SHUTDOWN_TOPIC); |
michael@0 | 535 | }, |
michael@0 | 536 | |
michael@0 | 537 | // nsISupports |
michael@0 | 538 | QueryInterface: XPCOMUtils.generateQI([Ci.nsIAutoCompleteSearch, |
michael@0 | 539 | Ci.nsIAutoCompleteObserver]) |
michael@0 | 540 | }; |
michael@0 | 541 | |
michael@0 | 542 | function AuthPromptOverride() { |
michael@0 | 543 | } |
michael@0 | 544 | AuthPromptOverride.prototype = { |
michael@0 | 545 | // nsIAuthPromptProvider |
michael@0 | 546 | getAuthPrompt: function (reason, iid) { |
michael@0 | 547 | // Return a no-op nsIAuthPrompt2 implementation. |
michael@0 | 548 | return { |
michael@0 | 549 | promptAuth: function () { |
michael@0 | 550 | throw Cr.NS_ERROR_NOT_IMPLEMENTED; |
michael@0 | 551 | }, |
michael@0 | 552 | asyncPromptAuth: function () { |
michael@0 | 553 | throw Cr.NS_ERROR_NOT_IMPLEMENTED; |
michael@0 | 554 | } |
michael@0 | 555 | }; |
michael@0 | 556 | }, |
michael@0 | 557 | |
michael@0 | 558 | // nsIInterfaceRequestor |
michael@0 | 559 | getInterface: function SSLL_getInterface(iid) { |
michael@0 | 560 | return this.QueryInterface(iid); |
michael@0 | 561 | }, |
michael@0 | 562 | |
michael@0 | 563 | // nsISupports |
michael@0 | 564 | QueryInterface: XPCOMUtils.generateQI([Ci.nsIAuthPromptProvider, |
michael@0 | 565 | Ci.nsIInterfaceRequestor]) |
michael@0 | 566 | }; |
michael@0 | 567 | /** |
michael@0 | 568 | * SearchSuggestAutoComplete is a service implementation that handles suggest |
michael@0 | 569 | * results specific to web searches. |
michael@0 | 570 | * @constructor |
michael@0 | 571 | */ |
michael@0 | 572 | function SearchSuggestAutoComplete() { |
michael@0 | 573 | // This calls _init() in the parent class (SuggestAutoComplete) via the |
michael@0 | 574 | // prototype, below. |
michael@0 | 575 | this._init(); |
michael@0 | 576 | } |
michael@0 | 577 | SearchSuggestAutoComplete.prototype = { |
michael@0 | 578 | classID: Components.ID("{aa892eb4-ffbf-477d-9f9a-06c995ae9f27}"), |
michael@0 | 579 | __proto__: SuggestAutoComplete.prototype, |
michael@0 | 580 | serviceURL: "" |
michael@0 | 581 | }; |
michael@0 | 582 | |
michael@0 | 583 | var component = [SearchSuggestAutoComplete]; |
michael@0 | 584 | this.NSGetFactory = XPCOMUtils.generateNSGetFactory(component); |