home *** CD-ROM | disk | FTP | other *** search
/ PC Welt 2006 November (DVD) / PCWELT_11_2006.ISO / casper / filesystem.squashfs / usr / lib / firefox / components / nsSearchSuggestions.js < prev    next >
Encoding:
Text File  |  2006-08-18  |  24.0 KB  |  765 lines

  1. /* ***** BEGIN LICENSE BLOCK *****
  2.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Mozilla Public License Version
  5.  * 1.1 (the "License"); you may not use this file except in compliance with
  6.  * the License. You may obtain a copy of the License at
  7.  * http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the
  12.  * License.
  13.  *
  14.  * The Original Code is Google Suggest Autocomplete Implementation for Firefox.
  15.  *
  16.  * The Initial Developer of the Original Code is Google Inc.
  17.  * Portions created by the Initial Developer are Copyright (C) 2006
  18.  * the Initial Developer. All Rights Reserved.
  19.  *
  20.  * Contributor(s):
  21.  *   Ben Goodger <beng@google.com>
  22.  *   Mike Connor <mconnor@mozilla.com>
  23.  *   Joe Hughes  <joe@retrovirus.com>
  24.  *
  25.  * Alternatively, the contents of this file may be used under the terms of
  26.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  27.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  28.  * in which case the provisions of the GPL or the LGPL are applicable instead
  29.  * of those above. If you wish to allow use of your version of this file only
  30.  * under the terms of either the GPL or the LGPL, and not to allow others to
  31.  * use your version of this file under the terms of the MPL, indicate your
  32.  * decision by deleting the provisions above and replace them with the notice
  33.  * and other provisions required by the GPL or the LGPL. If you do not delete
  34.  * the provisions above, a recipient may use your version of this file under
  35.  * the terms of any one of the MPL, the GPL or the LGPL.
  36.  *
  37.  * ***** END LICENSE BLOCK ***** */
  38.  
  39. const SEARCH_RESPONSE_SUGGESTION_JSON = "application/x-suggestions+json";
  40.  
  41. /**
  42.  * Metadata describing the Web Search suggest mode
  43.  */
  44. const SEARCH_SUGGEST_CONTRACTID =
  45.   "@mozilla.org/autocomplete/search;1?name=search-autocomplete";
  46. const SEARCH_SUGGEST_CLASSNAME = "Remote Search Suggestions";
  47. const SEARCH_SUGGEST_CLASSID =
  48.   Components.ID("{aa892eb4-ffbf-477d-9f9a-06c995ae9f27}");
  49.  
  50. const SEARCH_BUNDLE = "chrome://browser/locale/search.properties";
  51.  
  52. const Cc = Components.classes;
  53. const Ci = Components.interfaces;
  54. const Cr = Components.results;
  55.  
  56. const HTTP_OK                    = 200;
  57. const HTTP_INTERNAL_SERVER_ERROR = 500;
  58. const HTTP_BAD_GATEWAY           = 502;
  59. const HTTP_SERVICE_UNAVAILABLE   = 503;
  60.  
  61. /**
  62.  * SuggestAutoCompleteResult contains the results returned by the Suggest
  63.  * service - it implements nsIAutoCompleteResult and is used by the auto-
  64.  * complete controller to populate the front end.
  65.  * @constructor
  66.  */
  67. function SuggestAutoCompleteResult(searchString,
  68.                                    searchResult,
  69.                                    defaultIndex,
  70.                                    errorDescription,
  71.                                    results,
  72.                                    comments) {
  73.   this._searchString = searchString;
  74.   this._searchResult = searchResult;
  75.   this._defaultIndex = defaultIndex;
  76.   this._errorDescription = errorDescription;
  77.   this._results = results;
  78.   this._comments = comments;
  79. }
  80. SuggestAutoCompleteResult.prototype = {
  81.   /**
  82.    * The user's query string
  83.    * @private
  84.    */
  85.   _searchString: "",
  86.  
  87.   /**
  88.    * The result code of this result object, see |get searchResult| for possible
  89.    * values.
  90.    * @private
  91.    */
  92.   _searchResult: 0,
  93.  
  94.   /**
  95.    * The default item that should be entered if none is selected
  96.    * @private
  97.    */
  98.   _defaultIndex: 0,
  99.  
  100.   /**
  101.    * The reason the search failed
  102.    * @private
  103.    */
  104.   _errorDescription: "",
  105.  
  106.   /**
  107.    * The list of URLs returned by the Suggest Service
  108.    * @private
  109.    */
  110.   _results: [],
  111.  
  112.   /**
  113.    * The list of Comments (number of results - or page titles) returned by the
  114.    * Suggest Service.
  115.    * @private
  116.    */
  117.   _comments: [],
  118.  
  119.   /**
  120.    * @return the user's query string
  121.    */
  122.   get searchString() {
  123.     return this._searchString;
  124.   },
  125.  
  126.   /**
  127.    * @return the result code of this result object, either:
  128.    *         RESULT_IGNORED   (invalid searchString)
  129.    *         RESULT_FAILURE   (failure)
  130.    *         RESULT_NOMATCH   (no matches found)
  131.    *         RESULT_SUCCESS   (matches found)
  132.    */
  133.   get searchResult() {
  134.     return this._searchResult;
  135.   },
  136.  
  137.   /**
  138.    * @return the default item that should be entered if none is selected
  139.    */
  140.   get defaultIndex() {
  141.     return this._defaultIndex;
  142.   },
  143.  
  144.   /**
  145.    * @return the reason the search failed
  146.    */
  147.   get errorDescription() {
  148.     return this._errorDescription;
  149.   },
  150.  
  151.   /**
  152.    * @return the number of results
  153.    */
  154.   get matchCount() {
  155.     return this._results.length;
  156.   },
  157.  
  158.   /**
  159.    * Retrieves a result
  160.    * @param  index    the index of the result requested
  161.    * @return          the result at the specified index
  162.    */
  163.   getValueAt: function(index) {
  164.     return this._results[index];
  165.   },
  166.  
  167.   /**
  168.    * Retrieves a comment (metadata instance)
  169.    * @param  index    the index of the comment requested
  170.    * @return          the comment at the specified index
  171.    */
  172.   getCommentAt: function(index) {
  173.     return this._comments[index];
  174.   },
  175.  
  176.   /**
  177.    * Retrieves a style hint specific to a particular index.
  178.    * @param  index    the index of the style hint requested
  179.    * @return          the style hint at the specified index
  180.    */
  181.   getStyleAt: function(index) {
  182.     if (!this._comments[index])
  183.       return null;  // not a category label, so no special styling
  184.  
  185.     if (index == 0)
  186.       return "suggestfirst";  // category label on first line of results
  187.  
  188.     return "suggesthint";   // category label on any other line of results
  189.   },
  190.  
  191.   /**
  192.    * Removes a result from the resultset
  193.    * @param  index    the index of the result to remove
  194.    */
  195.   removeValueAt: function(index, removeFromDatabase) {
  196.     this._results.splice(index, 1);
  197.     this._comments.splice(index, 1);
  198.   },
  199.  
  200.   /**
  201.    * Part of nsISupports implementation.
  202.    * @param   iid     requested interface identifier
  203.    * @return  this object (XPConnect handles the magic of telling the caller that
  204.    *                       we're the type it requested)
  205.    */
  206.   QueryInterface: function(iid) {
  207.     if (!iid.equals(Ci.nsIAutoCompleteResult) &&
  208.         !iid.equals(Ci.nsISupports))
  209.       throw Cr.NS_ERROR_NO_INTERFACE;
  210.     return this;
  211.   }
  212. };
  213.  
  214. /**
  215.  * SuggestAutoComplete is a base class that implements nsIAutoCompleteSearch
  216.  * and can collect results for a given search by using the search URL supplied
  217.  * by the subclass. We do it this way since the AutoCompleteController in
  218.  * Mozilla requires a unique XPCOM Service for every search provider, even if
  219.  * the logic for two providers is identical.
  220.  * @constructor
  221.  */
  222. function SuggestAutoComplete() {}
  223. SuggestAutoComplete.prototype = {
  224.  
  225.   /**
  226.    * this._strings is the string bundle for message internationalization.
  227.    */
  228.   get _strings() {
  229.     if (!this.__strings) {
  230.       var sbs = Cc["@mozilla.org/intl/stringbundle;1"].
  231.                 getService(Ci.nsIStringBundleService);
  232.  
  233.       this.__strings = sbs.createBundle(SEARCH_BUNDLE);
  234.     }
  235.     return this.__strings;
  236.   },
  237.   __strings: null,
  238.  
  239.   /*************************************************************************
  240.    * Server request backoff implementation fields below
  241.    * These allow us to throttle requests if the server is getting hammered.
  242.    **************************************************************************/
  243.  
  244.   /**
  245.    * This is an array that contains the timestamps (in unixtime) of
  246.    * the last few backoff-triggering errors.
  247.    */
  248.   _serverErrorLog: [],
  249.  
  250.   /**
  251.    * If we receive this number of backoff errors within the amount of time
  252.    * specified by _serverErrorPeriod, then we initiate backoff.
  253.    */
  254.   _maxErrorsBeforeBackoff: 3,
  255.  
  256.   /**
  257.    * If we receive enough consecutive errors (where "enough" is defined by
  258.    * _maxErrorsBeforeBackoff above) within this time period,
  259.    * we trigger the backoff behavior.
  260.    */
  261.   _serverErrorPeriod: 600000,  // 10 minutes in milliseconds
  262.  
  263.   /**
  264.    * If we get another backoff error immediately after timeout, we increase the
  265.    * backoff to (2 x old period) + this value.
  266.    */
  267.   _serverErrorTimeoutIncrement: 600000,  // 10 minutes in milliseconds
  268.  
  269.   /**
  270.    * The current amount of time to wait before trying a server request
  271.    * after receiving a backoff error.
  272.    */
  273.   _serverErrorTimeout: 0,
  274.  
  275.   /**
  276.    * Time (in unixtime) after which we're allowed to try requesting again.
  277.    */
  278.   _nextRequestTime: 0,
  279.  
  280.   /**
  281.    * The last engine we requested against (so that we can tell if the
  282.    * user switched engines).
  283.    */
  284.   _serverErrorEngine: null,
  285.  
  286.   /**
  287.    * The XMLHttpRequest object.
  288.    * @private
  289.    */
  290.   _request: null,
  291.  
  292.   /**
  293.    * The object implementing nsIAutoCompleteObserver that we notify when
  294.    * we have found results
  295.    * @private
  296.    */
  297.   _listener: null,
  298.  
  299.   /**
  300.    * If this is true, we'll integrate form history results with the
  301.    * suggest results.
  302.    */
  303.   _includeFormHistory: true,
  304.  
  305.   /**
  306.    * True if a request for remote suggestions was sent. This is used to
  307.    * differentiate between the "_request is null because the request has
  308.    * already returned a result" and "_request is null because no request was
  309.    * sent" cases.
  310.    */
  311.   _sentSuggestRequest: false,
  312.  
  313.   /**
  314.    * This is the callback for the suggest timeout timer.  If this gets
  315.    * called, it means that we've given up on receiving a reply from the
  316.    * search engine's suggestion server in a timely manner.
  317.    */
  318.   notify: function SAC_notify(timer) {
  319.     // make sure we're still waiting for this response before sending
  320.     if ((timer != this._formHistoryTimer) || !this._listener)
  321.       return;
  322.  
  323.     this._listener.onSearchResult(this, this._formHistoryResult);
  324.     this._reset();
  325.   },
  326.  
  327.   /**
  328.    * This determines how long (in ms) we should wait before giving up on
  329.    * the suggestions and just showing local form history results.
  330.    */
  331.   _suggestionTimeout: 500,
  332.  
  333.   /**
  334.    * This is the callback for that the form history service uses to
  335.    * send us results.
  336.    */
  337.   onSearchResult: function SAC_onSearchResult(search, result) {
  338.     this._formHistoryResult = result;
  339.  
  340.     if (this._request) {
  341.       // We still have a pending request, wait a bit to give it a chance to
  342.       // finish.
  343.       this._formHistoryTimer = Cc["@mozilla.org/timer;1"].
  344.                                createInstance(Ci.nsITimer);
  345.       this._formHistoryTimer.initWithCallback(this, this._suggestionTimeout,
  346.                                               Ci.nsITimer.TYPE_ONE_SHOT);
  347.     } else if (!this._sentSuggestRequest) {
  348.       // We didn't send a request, so just send back the form history results.
  349.       this._listener.onSearchResult(this, this._formHistoryResult);
  350.     }
  351.   },
  352.  
  353.   /**
  354.    * This is the URI that the last suggest request was sent to.
  355.    */
  356.   _suggestURI: null,
  357.  
  358.   /**
  359.    * Autocomplete results from the form history service get stored here.
  360.    */
  361.   _formHistoryResult: null,
  362.  
  363.   /**
  364.    * This holds the suggest server timeout timer, if applicable.
  365.    */
  366.   _formHistoryTimer: null,
  367.  
  368.   /**
  369.    * This clears all the per-request state.
  370.    */
  371.   _reset: function SAC_reset() {
  372.     if (this._formHistoryTimer)
  373.       this._formHistoryTimer.cancel();
  374.     this._formHistoryTimer = null;
  375.     this._formHistoryResult = null;
  376.     this._listener = null;
  377.     this._request = null;
  378.   },
  379.  
  380.   /**
  381.    * This sends an autocompletion request to the form history service,
  382.    * which will call onSearchResults with the results of the query.
  383.    */
  384.   _startHistorySearch: function SAC_SHSearch(searchString, searchParam, previousResult) {
  385.     var formHistory =
  386.       Cc["@mozilla.org/autocomplete/search;1?name=form-history"].
  387.       createInstance(Ci.nsIAutoCompleteSearch);
  388.     formHistory.startSearch(searchString, searchParam, previousResult, this);
  389.   },
  390.  
  391.   /**
  392.    * Makes a note of the fact that we've recieved a backoff-triggering
  393.    * response, so that we can adjust the backoff behavior appropriately.
  394.    */
  395.   _noteServerError: function SAC__noteServeError() {
  396.     var currentTime = Date.now();
  397.  
  398.     this._serverErrorLog.push(currentTime);
  399.     if (this._serverErrorLog.length > this._maxErrorsBeforeBackoff)
  400.       this._serverErrorLog.shift();
  401.  
  402.     if ((this._serverErrorLog.length == this._maxErrorsBeforeBackoff) &&
  403.         ((currentTime - this._serverErrorLog[0]) < this._serverErrorPeriod)) {
  404.       // increase timeout, and then don't request until timeout is over
  405.       this._serverErrorTimeout = (this._serverErrorTimeout * 2) +
  406.                                  this._serverErrorTimeoutIncrement;
  407.       this._nextRequestTime = currentTime + this._serverErrorTimeout;
  408.     }
  409.   },
  410.  
  411.   /**
  412.    * Resets the backoff behavior; called when we get a successful response.
  413.    */
  414.   _clearServerErrors: function SAC__clearServerErrors() {
  415.     this._serverErrorLog = [];
  416.     this._serverErrorTimeout = 0;
  417.     this._nextRequestTime = 0;
  418.   },
  419.  
  420.   /**
  421.    * This checks whether we should send a server request (i.e. we're not
  422.    * in a error-triggered backoff period.
  423.    *
  424.    * @private
  425.    */
  426.   _okToRequest: function SAC__okToRequest() {
  427.     return Date.now() > this._nextRequestTime;
  428.   },
  429.  
  430.   /**
  431.    * This checks to see if the new search engine is different
  432.    * from the previous one, and if so clears any error state that might
  433.    * have accumulated for the old engine.
  434.    *
  435.    * @param engine The engine that the suggestion request would be sent to.
  436.    * @private
  437.    */
  438.   _checkForEngineSwitch: function SAC__checkForEngineSwitch(engine) {
  439.     if (engine == this._serverErrorEngine)
  440.       return;
  441.  
  442.     // must've switched search providers, clear old errors
  443.     this._serverErrorEngine = engine;
  444.     this._clearServerErrors();
  445.   },
  446.  
  447.   /**
  448.    * This returns true if the status code of the HTTP response
  449.    * represents a backoff-triggering error.
  450.    *
  451.    * @param status  The status code from the HTTP response
  452.    * @private
  453.    */
  454.   _isBackoffError: function SAC__isBackoffError(status) {
  455.     return ((status == HTTP_INTERNAL_SERVER_ERROR) ||
  456.             (status == HTTP_BAD_GATEWAY) ||
  457.             (status == HTTP_SERVICE_UNAVAILABLE));
  458.   },
  459.  
  460.   /**
  461.    * Called when the 'readyState' of the XMLHttpRequest changes. We only care
  462.    * about state 4 (COMPLETED) - handle the response data.
  463.    * @private
  464.    */
  465.   onReadyStateChange: function() {
  466.     // xxx use the real const here
  467.     if (!this._request || this._request.readyState != 4)
  468.       return;
  469.  
  470.     try {
  471.       var status = this._request.status;
  472.     } catch (e) {
  473.       // The XML HttpRequest can throw NS_ERROR_NOT_AVAILABLE.
  474.       return;
  475.     }
  476.  
  477.     if (this._isBackoffError(status)) {
  478.       this._noteServerError();
  479.       return;
  480.     }
  481.  
  482.     var responseText = this._request.responseText;
  483.     if (status != HTTP_OK || responseText == "")
  484.       return;
  485.  
  486.     this._clearServerErrors();
  487.  
  488.     var searchString, results, queryURLs;
  489.     var sandbox = new Components.utils.Sandbox(this._suggestURI.prePath);
  490.     var results2 = Components.utils.evalInSandbox(responseText, sandbox);
  491.  
  492.     if (results2[0]) {
  493.       searchString = results2[0] ? results2[0] : "";
  494.       results = results2[1] ? results2[1] : [];
  495.     } else {
  496.       // this is backwards compat code for Google Suggest, to be removed
  497.       // once they shift to the new format
  498.       // The responseText is formatted like so:
  499.       // searchString\n"r1","r2","r3"\n"c1","c2","c3"\n"p1","p2","p3"
  500.       // ... where all values are escaped:
  501.       //  rX = result  (search term or URL)
  502.       //  cX = comment (number of results or page title)
  503.       //  pX = prefix
  504.  
  505.       // Note that right now we're using the "comment" column of the
  506.       // autocomplete dropdown to indicate where the suggestions
  507.       // begin, so we're discarding the comments from the server.
  508.       var parts = responseText.split("\n");
  509.       results = parts[1] ? parts[1].split(",") : [];
  510.       for (var i = 0; i < results.length; ++i) {
  511.         results[i] = unescape(results[i]);
  512.         results[i] = results[i].substr(1, results[i].length - 2);
  513.       }
  514.     }
  515.  
  516.     var comments = [];  // "comments" column values for suggestions
  517.     var historyResults = [];
  518.     var historyComments = [];
  519.  
  520.     // If form history is enabled and has results, add them to the list.
  521.     if (this._includeFormHistory && this._formHistoryResult &&
  522.         (this._formHistoryResult.searchResult ==
  523.          Ci.nsIAutoCompleteResult.RESULT_SUCCESS)) {
  524.       for (var i = 0; i < this._formHistoryResult.matchCount; ++i) {
  525.         var term = this._formHistoryResult.getValueAt(i);
  526.  
  527.         // we don't want things to appear in both history and suggestions
  528.         var dupIndex = results.indexOf(term);
  529.         if (dupIndex != -1)
  530.           results.splice(dupIndex, 1);
  531.  
  532.         historyResults.push(term);
  533.         historyComments.push("");
  534.       }
  535.  
  536.       this._formHistoryResult = null;
  537.     }
  538.  
  539.     // fill out the comment column for the suggestions
  540.     for (var i = 0; i < results.length; ++i)
  541.       comments.push("");
  542.  
  543.     // if we have any suggestions, put a label at the top
  544.     if (comments.length > 0)
  545.       comments[0] = this._strings.GetStringFromName("suggestion_label");
  546.  
  547.     // now put the history results above the suggestions
  548.     var finalResults = historyResults.concat(results);
  549.     var finalComments = historyComments.concat(comments);
  550.  
  551.     // Notify the FE of our new results
  552.     this.onResultsReady(searchString, finalResults, finalComments);
  553.  
  554.     // Reset our state for next time.
  555.     this._reset();
  556.   },
  557.  
  558.   /**
  559.    * Notifies the front end of new results.
  560.    * @param searchString  the user's query string
  561.    * @param results       an array of results to the search
  562.    * @param comments      an array of metadata corresponding to the results
  563.    * @private
  564.    */
  565.   onResultsReady: function(searchString, results, comments) {
  566.     if (this._listener) {
  567.       var result = new SuggestAutoCompleteResult(
  568.           searchString,
  569.           Ci.nsIAutoCompleteResult.RESULT_SUCCESS,
  570.           0,
  571.           "",
  572.           results,
  573.           comments);
  574.       this._listener.onSearchResult(this, result);
  575.     }
  576.   },
  577.  
  578.   /**
  579.    * Initiates the search result gathering process. Part of
  580.    * nsIAutoCompleteSearch implementation.
  581.    *
  582.    * @param searchString    the user's query string
  583.    * @param searchParam     unused, "an extra parameter"; even though
  584.    *                        this parameter and the next are unused, pass
  585.    *                        them through in case the form history
  586.    *                        service wants them
  587.    * @param previousResult  unused, a client-cached store of the previous
  588.    *                        generated resultset for faster searching.
  589.    * @param listener        object implementing nsIAutoCompleteObserver which
  590.    *                        we notify when results are ready.
  591.    */
  592.   startSearch: function(searchString, searchParam, previousResult, listener) {
  593.     var searchService = Cc["@mozilla.org/browser/search-service;1"].
  594.                         getService(Ci.nsIBrowserSearchService);
  595.  
  596.     // If there's an existing request, stop it. There is no smart filtering
  597.     // here as there is when looking through history/form data because the
  598.     // result set returned by the server is different for every typed value -
  599.     // "ocean breathes" does not return a subset of the results returned for
  600.     // "ocean", for example. This does nothing if there is no current request.
  601.     this.stopSearch();
  602.  
  603.     this._listener = listener;
  604.  
  605.     var engine = searchService.currentEngine;
  606.  
  607.     this._checkForEngineSwitch(engine);
  608.  
  609.     if (!searchString ||
  610.         !engine.supportsResponseType(SEARCH_RESPONSE_SUGGESTION_JSON) ||
  611.         !this._okToRequest()) {
  612.       // We have an empty search string (user pressed down arrow to see
  613.       // history), or the current engine has no suggest functionality,
  614.       // or we're in backoff mode; so just use local history.
  615.       this._sentSuggestRequest = false;
  616.       this._startHistorySearch(searchString, searchParam, previousResult);
  617.       return;
  618.     }
  619.  
  620.     // Actually do the search
  621.     this._request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
  622.                     createInstance(Ci.nsIXMLHttpRequest);
  623.     var submission = engine.getSubmission(searchString,
  624.                                           SEARCH_RESPONSE_SUGGESTION_JSON);
  625.     this._suggestURI = submission.uri;
  626.     var method = (submission.postData ? "POST" : "GET");
  627.     this._request.open(method, this._suggestURI.spec, true);
  628.  
  629.     var self = this;
  630.     function onReadyStateChange() {
  631.       self.onReadyStateChange();
  632.     }
  633.     this._request.onreadystatechange = onReadyStateChange;
  634.     this._request.send(submission.postData);
  635.  
  636.     if (this._includeFormHistory) {
  637.       this._sentSuggestRequest = true;
  638.       this._startHistorySearch(searchString, searchParam, previousResult);
  639.     }
  640.   },
  641.  
  642.   /**
  643.    * Ends the search result gathering process. Part of nsIAutoCompleteSearch
  644.    * implementation.
  645.    */
  646.   stopSearch: function() {
  647.     if (this._request) {
  648.       this._request.abort();
  649.       this._reset();
  650.     }
  651.   },
  652.  
  653.   /**
  654.    * Part of nsISupports implementation.
  655.    * @param   iid     requested interface identifier
  656.    * @return  this object (XPConnect handles the magic of telling the caller that
  657.    *                       we're the type it requested)
  658.    */
  659.   QueryInterface: function(iid) {
  660.     if (!iid.equals(Ci.nsIAutoCompleteSearch) &&
  661.         !iid.equals(Ci.nsIAutoCompleteObserver) &&
  662.         !iid.equals(Ci.nsISupports))
  663.       throw Cr.NS_ERROR_NO_INTERFACE;
  664.     return this;
  665.   }
  666. };
  667.  
  668. /**
  669.  * SearchSuggestAutoComplete is a service implementation that handles suggest
  670.  * results specific to web searches.
  671.  * @constructor
  672.  */
  673. function SearchSuggestAutoComplete() {
  674. }
  675. SearchSuggestAutoComplete.prototype = {
  676.   __proto__: SuggestAutoComplete.prototype,
  677.   serviceURL: ""
  678. };
  679.  
  680. var gModule = {
  681.   /**
  682.    * Registers all the components supplied by this module. Part of nsIModule
  683.    * implementation.
  684.    * @param componentManager  the XPCOM component manager
  685.    * @param location          the location of the module on disk
  686.    * @param loaderString      opaque loader specific string
  687.    * @param type              loader type being used to load this module
  688.    */
  689.   registerSelf: function(componentManager, location, loaderString, type) {
  690.     if (this._firstTime) {
  691.       this._firstTime = false;
  692.       throw Cr.NS_ERROR_FACTORY_REGISTER_AGAIN;
  693.     }
  694.     componentManager =
  695.       componentManager.QueryInterface(Ci.nsIComponentRegistrar);
  696.  
  697.     for (var key in this.objects) {
  698.       var obj = this.objects[key];
  699.       componentManager.registerFactoryLocation(obj.CID, obj.className, obj.contractID,
  700.                                                location, loaderString, type);
  701.     }
  702.   },
  703.  
  704.   /**
  705.    * Retrieves a Factory for the given ClassID. Part of nsIModule
  706.    * implementation.
  707.    * @param componentManager  the XPCOM component manager
  708.    * @param cid               the ClassID of the object for which a factory
  709.    *                          has been requested
  710.    * @param iid               the IID of the interface requested
  711.    */
  712.   getClassObject: function(componentManager, cid, iid) {
  713.     if (!iid.equals(Ci.nsIFactory))
  714.       throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  715.  
  716.     for (var key in this.objects) {
  717.       if (cid.equals(this.objects[key].CID))
  718.         return this.objects[key].factory;
  719.     }
  720.  
  721.     throw Cr.NS_ERROR_NO_INTERFACE;
  722.   },
  723.  
  724.   /**
  725.    * Create a Factory object that can construct an instance of an object.
  726.    * @param constructor   the constructor used to create the object
  727.    * @private
  728.    */
  729.   _makeFactory: function(constructor) {
  730.     function createInstance(outer, iid) {
  731.       if (outer != null)
  732.         throw Cr.NS_ERROR_NO_AGGREGATION;
  733.       return (new constructor()).QueryInterface(iid);
  734.     }
  735.     return { createInstance: createInstance };
  736.   },
  737.  
  738.   /**
  739.    * Determines whether or not this module can be unloaded.
  740.    * @return returning true indicates that this module can be unloaded.
  741.    */
  742.   canUnload: function(componentManager) {
  743.     return true;
  744.   }
  745. };
  746.  
  747. /**
  748.  * Entry point for registering the components supplied by this JavaScript
  749.  * module.
  750.  * @param componentManager  the XPCOM component manager
  751.  * @param location          the location of this module on disk
  752.  */
  753. function NSGetModule(componentManager, location) {
  754.   // Metadata about the objects this module can construct
  755.   gModule.objects = {
  756.     search: {
  757.       CID: SEARCH_SUGGEST_CLASSID,
  758.       contractID: SEARCH_SUGGEST_CONTRACTID,
  759.       className: SEARCH_SUGGEST_CLASSNAME,
  760.       factory: gModule._makeFactory(SearchSuggestAutoComplete)
  761.     },
  762.   };
  763.   return gModule;
  764. }
  765.