Пример #1
0
/*
 * RdioTab is an EventEmitter, emitting 'ready', and 'closed' events
 * respetively when:
 *   - a tab which URL matches rdio.com's is opened, and ready (DOM loaded...)
 *   - a tab which URL matches rdio.com's is closed.
 */

const tabs = require('tabs');
const { EventEmitter } = require('events');
const rdioURLRegex = /https?:\/\/(www\.)?rdio\.com\/?/;
const RdioTab = EventEmitter.compose({
  constructor: function() {
    var self = this;

    tabs.on('ready', function (tab) {
      if ( tab.url.match(rdioURLRegex) ) {
        self._emit('ready', tab);
      }
    });
    tabs.on('close', function (tab) {
      if ( tab.url.match(rdioURLRegex) ) {
        self._emit('closed', tab);
      }
    });
  } 
});
exports.rdioTab = new RdioTab();
Пример #2
0
var SearchEngines = EventEmitter.compose({
  _emit: EventEmitter.required,
  on: EventEmitter.required,

  _debug : function _debug(isDebug) {
    if (isDebug) {
      this.geolocation = true;
    }
  },

  // Cached list of engines
  _engines : null,

  constructor : function SearchEngines() {
    if (!storage.engines) {
      // list of currently used engines according to user order
      storage.engines = this._engines = [];
      // list of found engines according to how often they are seen
      storage.other = [];
      this._first_run();
    } else {
      this._engines = storage.engines.map(function (engine) {
        //console.log("engine", JSON.stringify(engine));
        return this._engineFromJSON(engine);
      }.bind(this));
      //storage.other.forEach(function(e) { console.log(JSON.stringify(e)); });
      this._upgrade();
    }

    this.on("defaults.added", function (engine) {
      StatisticsReporter.send("defaults.added", engine);
    });
    this.on("defaults.removed", function (engine) {
      StatisticsReporter.send("defaults.removed", engine);
    });

    this.on("others.added", function (engine) {
      StatisticsReporter.send("others.added", engine);
    });
    this.on("others.removed", function (engine) {
      StatisticsReporter.send("others.removed", engine);
    });

    ObserverService.add("search:debug", this._debug.bind(this), this);

    SearchEnginesCollector.on("engine", this._collector.bind(this));

    SimpleStorage.on("OverQuota", this._overQuota.bind(this));

    require("unload").ensure(this);
  },

  // this is designed to run after the add-on has reinitialized and there are
  // some slight data issues we need to deal with
  _upgrade : function _upgrade() {
    var yelp = this.get("http://www.yelp.com/opensearch");
    if (yelp.suggestionURL !== "http://www.yelp.com/search_suggest/json?prefix={searchTerms}&src=firefox&loc={geo:name}") {
      yelp.suggestionURL = "http://www.yelp.com/search_suggest/json?prefix={searchTerms}&src=firefox&loc={geo:name}";
      this._update(yelp);
    }
  },
  // The first run initialization to pull in some default engines from Firefox
  _first_run : function _first_run() {
    var BrowserSearchEngines = require("browser-search-engine")
                              .BrowserSearchEngines;

    // Add in some suggestions for engines we know work but aren't listed
    BrowserSearchEngines.get("Amazon.com").addSuggest("http://completion.amazon.com/search/complete?method=completion&search-alias=aps&mkt=1&q={searchTerms}");

    // Our default order of engines as an array of ids
    var order = [
      "https://www.google.com/",
      "http://d2lo25i6d3q8zm.cloudfront.net/browser-plugins/AmazonSearchSuggestionsOSD.Firefox.xml",
      "http://www.yelp.com/opensearch",
      "http://en.wikipedia.org/w/opensearch_desc.php"
    ];

    // Add LinkedIn to the list of other engines
    this.others.add(new SearchEngine("http://www.linkedin.com/search/fpsearch",
                                     "LinkedIn",
                                     "http://www.linkedin.com/search/fpsearch?keywords={searchTerms}",
                                     "http://www.linkedin.com/ta/federator?query={searchTerms}&types=mynetwork,company,group,sitefeature,skill",
                                     "http://static01.linkedin.com/scds/common/u/img/favicon_v3.ico"));

    // Add Yelp to our list of other engines
    // We'll try to add this to the defaults afterward
    this.others.add(new SearchEngine("http://www.yelp.com/opensearch",
                                     "Yelp",
                                     "http://www.yelp.com/search?find_desc={searchTerms}&src=firefox&find_loc={geo:name}",
                                     "http://www.yelp.com/search_suggest/json?prefix={searchTerms}&src=firefox&loc={geo:name}",
                                     "http://media2.ak.yelpcdn.com/static/201012161623981098/img/ico/favicon.ico"));

    // Provide a mapping of the OpenSearch descriptors to our default engines
    // (which either don't have them or are incorrect)
    var sites   = {
      "Wikipedia (en)" : "http://en.wikipedia.org/w/opensearch_desc.php",
      "Amazon.com" : "http://d2lo25i6d3q8zm.cloudfront.net/browser-plugins/AmazonSearchSuggestionsOSD.Firefox.xml"
    };

    BrowserSearchEngines.getVisible().forEach(function (engine) {
      var queryURL = decodeURIComponent(engine.getSubmission("{searchTerms}")),
          suggestionURL = decodeURIComponent(engine.getSuggestion("{searchTerms}") || ""),
          id = sites[engine.name] || engine.searchForm,
          se = new SearchEngine(id, engine.name, queryURL,
                            suggestionURL, engine.icon);

      if (order.indexOf(id) >= 0) {
        this.defaults.add(se);
      } else {
        this.others.add(se);
      }
    }, this);

    // set our intial default sort order
    this.defaults.sort(order);

    StatisticsReporter.once("allowed", function () {
      // Ask to move Yelp to to the defaults once we've
      // gotten permission to send statistics
      this.defaults.add(this.get("http://www.yelp.com/opensearch"))
                    .then(function (added) {
                      this.defaults.sort(order);
                    }.bind(this));
    }.bind(this));
  },

  get defaults() {
    var self = this;
    return {
      remove : function remove(engine) {
        if (!(engine instanceof SearchEngine)) {
          engine = self._engineFromJSON(engine);
        }
        var index = -1,
            result = null;
        self._engines.every(function (e, i) {
          if (engine.equals(e)) {
            index = i;
            return false;
          }
          return true;
        });
        if (index >= 0) {
          result = self._engines.splice(index, 1)[0];
          if (result) {
            // Save the engine in the other engines
            self.others.add(result);
            // Save our new default engines
            storage.engines = self._engines;
            // send out the removed event
            self._emit("defaults.removed", engine);
          }
        }

        // If we're removing an engine that used geolocation
        // lets double check that we still need it running
        if (engine.usesGeoLocation) {
          Geolocation.allowed = this.usingGeolocation();
        }

        return engine;
      },
      add : function add(engine) {
        if (!(engine instanceof SearchEngine)) {
          engine = self._engineFromJSON(engine);
        }

        var d = defer(),
            promise = d.promise,
            resolve = d.resolve,
            reject = d.reject;

        // if we already have this engine, don't add it again
        if (self._engines.some(function (e) { return engine.equals(e); })) {
          resolve(engine);
          return promise;
        }

        var actuallyAdd = function (engine) {
          // Only add this engine if it doesn't already exist
          if (!self._engines.some(function (e) { return engine.equals(e); })) {
            self._engines.push(engine);
          }

          // Save new default engines to storage
          storage.engines = self._engines;
          // Remove this engine from the others list if it exists
          self.others.remove(engine);
          // Send out the add event
          self._emit("defaults.added", engine);

          resolve(engine);
        };

        if (engine.usesGeoLocation) {

          GeoPermissionPanel.port.once("click", function click(data) {
            searchbar.getSearchTextBox().focus();
            GeoPermissionPanel.hide();
            if (data === "ok") {
              actuallyAdd(engine);
              // If geolocation isn't alredy turned on we can turn it on now
              if (!Geolocation.allowed) {
                Geolocation.allowed = true;
              }
            } else {
              // try to add this to the others list if it doesn't exist
              self.others.add(engine);
              reject(engine);
            }
          });

          // Our permission panel will overrun others who are asking
          GeoPermissionPanel.port.emit("engine", engine);
          GeoPermissionPanel.show(searchbar.getSearchTextBox());
          return promise;

        } else {
          // If this engine doesn't require geolocation just add it
          actuallyAdd(engine);
          return promise;
        }
      },
      get : function get(id) {
        var engine = null;
        self._engines.every(function (e, i) {
          if (e.id === id) {
            engine = e;
            return false;
          }
          return true;
        });
        return engine;
      },
      // Returns the list of all default engines
      get all() { return self._engines; },
      // Examine the ids of our new list order and sort the engines by that
      sort : function sort(newOrder) {
        self._engines.sort(function (a, b) {
          return newOrder.indexOf(a.id) > newOrder.indexOf(b.id);
        });
        // There is a new order in town.  Regulators! Mount Up!
        self._emit("defaults.sorted",
                   self._engines.map(function (engine) { return engine.id; }));
      },
      usingGeolocation : function usingGeolocation() {
        return self._engines.some(function (engine) {
          return engine.usesGeoLocation;
        });
      }
    };
  },

  // Others doesn't check that items are in the default list
  get others() {
    var self = this;
    return {
      remove : function remove(engine) {
        if (!(engine instanceof SearchEngine)) {
          engine = self._engineFromJSON(engine);
        }

        storage.other.every(function (e, i) {
          if (e.id === engine.id) {
            // remove the engine from our others list
            storage.other.splice(i, 1);
            return false;
          }
          return true;
        });

        self._emit("others.removed", engine);

        return engine;
      },
      add : function add(engine) {
        if (!(engine instanceof SearchEngine)) {
          engine = self._engineFromJSON(engine);
        }

        // Only add this engine if it doesn't already exist
        if (!storage.other.some(function (e) { return e.id === engine.id; })) {
          storage.other.push(engine);
        }

        self._emit("others.added", engine);

      },
      get : function get(id) {
        var engine = null;
        storage.other.every(function (e) {
          if (e.id === id) {
            engine = self._engineFromJSON(e);
            return false;
          }
          return true;
        });
        return engine;
      },
      get all() { return storage.other; }
    };
  },

  _engineFromJSON : function _engineFromJSON(engine) {
    if (engine === null) { return null; }
    var e = null;
    try {
      e = new SearchEngine(engine.id,
                           engine.name,
                           engine.queryURL,
                           engine.suggestionURL,
                           engine.icon,
                           engine.type);
    } catch (ex) {
      console.error(ex);
      console.log("ENGINE:", JSON.stringify(engine));
    }
    return e;
  },

  // Delete an engine from defaults and others
  remove : function remove(engine) {
    this.defaults.remove(engine);
    this.others.remove(engine);
    this._emit("removed", engine);
    return engine;
  },

  // Get an engine no matter what list it's in
  get : function get(id) {
    var result = null;
    result = this.defaults.get(id);
    if (result) {
      return result;
    }
    return this.others.get(id);
  },

  _update : function _update(engine) {
    if (this._engines.some(function (e, i, a) {
                              if (e.id === engine.id) {
                                e = engine;
                                return true;
                              }
                              return false;
                            }
                          )) {

      storage.engines = this._engines;

      return;
    }

    storage.other.every(function (e, i, a) {
      if (engine.id === e.id) {
        e = engine;
        return false;
      }
      return true;
    });
  },

  /**
   * Listener function for the SearchEnginesCollector module
   *
   * This listener should have been set in the constructor
   *
   * @param   engine       {Object}
   *          an object that represents and engine pulled from the site offering it
   *          See SearchEnginesCollector._parse
   *
   * Existing search engines are updated with new data
   * New search engines are added with the FOUND_TAG
   *
   */
  _collector : function _collector(collected) {
    var engine = this.get(collected.url.toString());
    //console.log("_collector", collected.url);
    // if this engine already exists lets just update our records for it
    // SECURITY : this should have some kind of security review here
    if (engine) {
      // DATA should be tracking this update to track when sites add suggestions
      if (engine.name !== collected.name                    ||
          engine.queryURL !== collected.queryURL            ||
          engine.suggestionURL !== collected.suggestionURL  ||
          engine.icon !== collected.icon) {

        //console.log(engine.name," != ",collected.name, "\n",
        //            engine.queryURL," != ",collected.queryURL, "\n",
        //            engine.suggestionURL," != ",collected.suggestionURL);

        engine.name = collected.name;
        engine.icon = collected.icon;

        // XXX for now don't let search engines update their URLs,
        // only name and icon
        // we'll send out the fact that they wanted to so we can examine
        // that need from the cloud
        //engine.queryURL = collected.queryURL;
        //engine.suggestionURL = collected.suggestionURL;

        StatisticsReporter.send("update", engine);

        this._update(engine);
      }
    } else {
      this.others.add(new SearchEngine(collected.url,
                                       collected.name,
                                       collected.queryURL,
                                       collected.suggestionURL,
                                       collected.icon));
    }
  },

  // XXX Totally untested
  _overQuota: function _overQuota() {
    while (SimpleStorage.quotaUsage > 1) {
      // remove all the items from other starting with the least seen ones
      if (storage.other.length > 0) {
        storage.other.pop();
      } else {
        // if we don't have any other items left remove used engines
        storage.engines.pop();
      }
    }
    console.warn("_overQuota");
  },

  unload : function unload(reason) {
    storage.engines = this._engines;
    ObserverService.remove("search:debug", this._debug, this);
    SearchEnginesCollector.removeListener("engine", this._collector);
    SimpleStorage.removeListener("OverQuota", this._overQuota);
  }

})();
Пример #3
0
let Bindings = EventEmitter.compose({
  _bindings: undefined,
  _cookieManager: undefined,
  length: 0,
  constructor: function(options) {
    this._cookieManager = options.cookieManager;
    this.clear();

    unload.ensure(this, "teardown");
  },

  teardown: function() {
    this.clear();
    this._cookieManager = null;
    this._bindings = null;
  },

  add: function(binding) {
    let host = binding.host;
    this.remove(host);
    let name = binding.bound_to.name;
    // Save this off so that we can remove it later.
    binding.remove = this.remove.bind(this, host);

    this._cookieManager.watch(host, name, binding.remove);
    this._bindings[host] = binding;
    this.length++;
  },

  get: function(host) {
    let binding = this._bindings[host];
    return binding;
  },

  remove: function(host) {
    let binding = this.get(host);
    
    if(binding) {
      this._bindings[host] = null;
      delete this._bindings[host];
      this.length--;
      this._cookieManager.unwatch(host, binding.bound_to.name, binding.remove);
      this._emit("remove", binding);
    }

    return binding;
  },

  clear: function() {
    for(let host in this._bindings) {
      this.remove(host);
    }
    this._bindings = {};
    this.length = 0;
  }
});
const LoginManager = EventEmitter.compose({
    constructor: function(options) {
        let {document} = options;


        this.box = createNode(document, "box", {
            id: "identity-session-box"
        });

        this.signIn = createNode(document, "label", {
            id: "identity-session-signin",
            value: "Sign in",
            parentNode: this.box
        });
        this.signIn.addEventListener("click", this._emit.bind(this, "login"), false);

        this.userInfo = createNode(document, "label", {
            id: "identity-session-userinfo",
            value: "",
            parentNode: this.box
        });
        this.userInfo.addEventListener("click", this._emit.bind(this, "userinfo"), false);

        this.image = createNode(document, "image", {
            id: "identity-session-icon",
            parentNode: this.box
        });
        this.image.hide();

        this.siteName = createNode(document, "label", {
            id: "identity-session-site",
            value: "site name",
            parentNode: this.box
        });
        this.siteName.hide();

        let insertBefore = document.getElementById("notification-popup-box");
        if(insertBefore) {
            insertBefore.parentNode.insertBefore(this.box, insertBefore);
        }

    },
        
    show: function(host) {
        this.box.show();
        this.siteName.setAttribute("value", host);
    },

    login: function(host) {
        this.show(host);
        this.signIn.show();
        this.userInfo.hide();
    },

    loggedIn: function(username, host) {
        this.show(host);
        this.signIn.hide();
        this.userInfo.setAttribute("value", username);
        this.userInfo.show();
    },

    none: function() {
        this.box.hide();
    },

    setURL: function(url) {
        this.siteName.setAttribute("value", url);
    }

});
Пример #5
0
var RdioWebApp = EventEmitter.compose({
  constructor: function constructor (rdioTab) {
    this.state = {
      playerReady: false
    };
    this._tabEvents(rdioTab);
    return this;
  },

  /*
   * Wether we're ready to accept commands
   * @return {Boolean}
   */
  isReady: function isReady () {
    return this.state.playerReady;
  },

  /*
   * FIXME should accept a callback triggered through worker confirmation.
   * @return {Boolean}
   */
  playPause: function playPause () {
    if ( ! this.state.playerReady ) {
      return false;
    }
    this.worker.port.emit('playPause');

    return true;
  },

  volume: function (value, cb) {
    // Get volume
    if ( 'function' == typeof value ) {
      this.worker.port.once('volume:got', value);
      this.worker.port.emit('volume:get');
    // Set volume
    } else {
      if ( cb ) {
        this.worker.port.once('volume:set', cb);
      }
      this.worker.port.emit('volume:update', value);
    }
    return this;
  },

  /*
   * Initialize browser's content-script when tabs on rdio.com are loaded.
   */
  _tabEvents: function _tabEvents (rdioTab) {
    var self = this;

    rdioTab.on("closed", function (tab) { self.setUnavailable(); });
    rdioTab.on("ready", function (tab) {
      console.debug("Loaded: " + tab.url);
      self.playerReady = false;
      self._attachWorker(tab);
    });

    return self;
  },

  /*
   * Attach worker's content-script on tag, and bind worker-events
   *
   * @param {Tab} browser tab
   */
  _attachWorker: function _attachWorker (tab) {
    var self = this;

    // Attaching content-script
    this.worker = tab.attach({
      contentScriptWhen: 'end',
      contentScriptFile: data.url('worker.js')
    });

    // Check for player on the latest attached tab...
    this.worker.port.on('ready',    function () { return self.setAvailable(); });
    this.worker.port.on('noplayer', function () { return self.setUnavailable(); });

    return this;
  },

  // Update add-on state, player unavailable
  setUnavailable: function setUnavailable () {
    this.state.playerReady = false;
    this._emit('closed');

    return this;
  },

  // Update add-on state, player available
  setAvailable: function setAvailable () {
    this.state.playerReady = true;
    this._emit('ready');

    return this;
  }
});
Пример #6
0
const SessionDisplay = EventEmitter.compose({
    constructor: function(options) {
        let {document, session} = options;
        this.session = session;
        this.document = document;
      
        createUI.call(this, document);    
        attachSessionEvents.call(this);
        
        let identityBox = getIdentityBox.call(this);
        this.origLeft = identityBox && identityBox.style.paddingLeft;
        
        this.hide();
        unload.ensure(this, 'teardown');
    },

    teardown: function() {
        if (this.box && this.box.parentNode) {
            this.box.parentNode.removeChild(this.box);
        }

        let identityBox = getIdentityBox.call(this);
        if (identityBox) {
            identityBox.style.paddingLeft = this.origLeft;
        }
    },
        
    show: function() {
        this.box.show();
        let identityBox = getIdentityBox.call(this);
        if (identityBox) {
            identityBox.style.paddingLeft = "10px";
        }

        this._emit("show");
    },


    hide: function() {
        this.box.hide();
        let identityBox = getIdentityBox.call(this);
        if (identityBox) {
            identityBox.style.paddingLeft = this.origLeft;
        }
        this._emit("hide");
    },

    setStatus: setStatus
});
const {Cu} = require("chrome");
const self = require("self");
const helper = require("helpers");
const {EventEmitter} = require("events");

const search = require("search");
let sr = new search.search();


/* this is used to communicate with the async search script*/
let EventWorker = EventEmitter.compose({
  postMessage: function(data) {
    //console.log("emitting: " + JSON.stringify(data));
    this._emit("searchresults", data);
  }
});
let worker = EventWorker();
/*
worker = {
  postMessage: function(){},
  on: function(){}
}
*/

/**/
exports.getEventWorker = function() {
  return worker;
}

exports.predictSearch = function(latestTitle) {
  let RE_NOUN_VERB = new RegExp(/(^NN)|(^VB)|(^JJ)/);
var SearchEnginesCollector = EventEmitter.compose({
  _emit: EventEmitter.required,
  on: EventEmitter.required,

  _allowed : simpleprefs.prefs[ALLOW_COLLECT_PREF],
  get allowed() { return this._allowed; },
  set allowed(allow) {
    this._allowed = simpleprefs.prefs[ALLOW_COLLECT_PREF] = allow;
  },

  get isOk() {
    // Do not collect links when the user is in private browsing mode
    if (PrivateBrowsing.isActive) {
      console.debug("PrivateBrowsing enabled - not collecting search engines");
    }
    if (!this._allowed) {
      console.debug("Search Engines Collector is not enabled");
    }
    return (!PrivateBrowsing.isActive && this._allowed);
  },

  _onallowed : function _onallowed(subject) {
    this._allowed = simpleprefs.prefs[ALLOW_COLLECT_PREF];
    this._emit("allowed", this._allowed);
  },

  constructor : function SearchEnginesCollector() {
    simpleprefs.on(ALLOW_COLLECT_PREF, this._onallowed.bind(this), this);
    require("unload").ensure(this);
  },

  unload: function _destructor() {
    this._removeAllListeners();
    simpleprefs.removeListener(ALLOW_COLLECT_PREF, this._onallowed);
  },

  /**
   * Receives <link> elements from the PageMod
   *
   * **Note** Will not continue if `PrivateBrowsing.isActive` returns true to
   * avoid collecting OpenSearch description documents while the users is in
   * private browsing mode.
   *
   * @param   links       {Array}
   *          an array of link objects
   *          where objects are { site : document.URL,
   *                              name : <link title>,
   *                              opensearch : <link href> }
   *
   * emits the "link" event
   *
   */
  collect : function collect(links) {
    if (!this.isOk) {
      return null;
    }

    links.forEach(function (link, i, a) {
      // site (may) = "http://google.com/search/path/?q="
      var site = URL.URL(link.site);

      // host = "http://google.com/"
      var host = link.site.replace(site.path, "");

      // opensearch URL could be relative so use the host as a base
      // example: href="/search.xml"
      var href = URL.URL(link.opensearch, host);

      // emit that we found a URL for anyone who wants to listen
      this._emit("link", host, href);

      // retrieve this engine from the URL
      this.getEngineByXMLURL(href.toString());

    }.bind(this));

    return links;
  },

  /**
   * Retrieves and parses OpenSearch engine XML descriptors
   *
   * @param   url       {String}
   *          Absolute URL pointing to an OpenSearch XML file
   *
   * emits the "engine" event
   *
   */
  getEngineByXMLURL : function getEngineByXMLURL(url) {
    var request = new xhr.XMLHttpRequest(),
        collector = this;

    request.open('GET', url, true);
    // Force document parsing in case we get a weird response type
    request.overrideMimeType("text/xml");
    request.onreadystatechange = function (aEvt) {
      if (request.readyState === 4) {
        if (request.status === HTTP_OK ||
            request.status === HTTP_LOCAL_FILE) {
          if (request.responseXML) {
            if (collector.isOk) {
              collector._buildEngine(url, request.responseXML);
            }
          }
        }
      }
    };
    request.send(null);
  },

  /**
   * Wraps up the Async download of the Open Search image
   */
  _buildEngine : function _buildEngine(url, doc) {
    var collector = this,
        engine = this._parse(url, doc),
        href = null;
    try {
      href = URL.URL(engine.icon);
      fetchImageDataASync(href.toString()).then(function (datauri) {
        engine.icon = datauri;
        collector._emit("engine", engine);
      });
    } catch (e) {
      // if there's a problem with our engine icon lets just send this one off
      // without an icon
      collector._emit("engine", engine);
    }
  },

  /**
   * Lightweight parsing of an XML OpenSearch description document
   *
   * @param   url       {String}
   *          Absolute URL pointing to an OpenSearch XML file
   *
   * @param   doc       {Object}
   *          XML document object from request.responseXML xhr call
   *
   * @returns {Object}
   *          { "url", "name", "queryURL", "suggestionURL", "icon" }
   *
   */
  _parse : function _parse(url, doc) {
    var opensearch = { "url" : url, "name" : "", "queryURL" : "",
                       "suggestionURL" : "", "icon" : "" },
        urls = doc.getElementsByTagName("Url"),
        queryObj = null,
        queryMap = function (key) { return (key + "=" + queryObj[key]); };

    for (var i = 0, item; item = urls.item(i); i += 1) {
      //var method = item.getAttribute("method");

      var template = URL.URL(item.getAttribute(ATTR_TEMPLATE)),
          type = item.getAttribute("type"),
          params = item.getElementsByTagName(TAG_PARAM),
          split = template.path.split("?"),
          path = split[0],
          query = split[1];

      queryObj = querystring.parse(query);

      for (var j = 0, p; p = params.item(j); j += 1) {
        queryObj[p.getAttribute(ATTR_NAME)] = p.getAttribute(ATTR_VALUE);
      }

      // remove the original path from our template and make this a string now
      template = template.toString().replace(template.path, "");

      // add back the path with query string
      template += path + "?" + Object.keys(queryObj).map(queryMap).join("&");

      if (URLTYPE_SEARCH_HTML === type) {
        opensearch.queryURL = template;
      } else if (URLTYPE_SUGGEST_JSON === type) {
        opensearch.suggestionURL = template;
      }
    }

    try {
      opensearch.name = doc.getElementsByTagName(TAG_SHORT_NAME)
                           .item(0).textContent;
    } catch (noname) { console.error("engine has no name", noname); }

    try {
      opensearch.icon = doc.getElementsByTagName(TAG_IMAGE).item(0).textContent;
    } catch (noicon) {
      // try to get the favicon using the Favicon service
      // falls back to the default icon if none is found
      opensearch.icon = getFavicon(url);
    }

    return opensearch;
  }

})();