browser/modules/ContentSearch.jsm
author Garrett Robinson <grobinson@mozilla.com>
Wed, 04 Jun 2014 15:24:38 -0700
changeset 186716 bf40eebc65434dd7030506fb1dd6f1e64f34418f
parent 184335 0685ea0268b7d5714192a85fb433ddb5a16a605c
child 189175 c5da3b5f178228e42ac2121aa3b0a423b662e18c
permissions -rw-r--r--
Bug 988616 - Split CSP tests for CSP (1.0) and X-CSP, and update build system files r=sstamm r=ckerschb

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

this.EXPORTED_SYMBOLS = [
  "ContentSearch",
];

const { classes: Cc, interfaces: Ci, utils: Cu } = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "Promise",
  "resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
  "resource://gre/modules/Services.jsm");

const INBOUND_MESSAGE = "ContentSearch";
const OUTBOUND_MESSAGE = INBOUND_MESSAGE;

/**
 * ContentSearch receives messages named INBOUND_MESSAGE and sends messages
 * named OUTBOUND_MESSAGE.  The data of each message is expected to look like
 * { type, data }.  type is the message's type (or subtype if you consider the
 * type of the message itself to be INBOUND_MESSAGE), and data is data that is
 * specific to the type.
 *
 * Inbound messages have the following types:
 *
 *   GetState
 *      Retrieves the current search engine state.
 *      data: null
 *   ManageEngines
 *      Opens the search engine management window.
 *      data: null
 *   Search
 *      Performs a search.
 *      data: an object { engineName, searchString, whence }
 *   SetCurrentEngine
 *      Sets the current engine.
 *      data: the name of the engine
 *
 * Outbound messages have the following types:
 *
 *   CurrentEngine
 *     Sent when the current engine changes.
 *     data: see _currentEngineObj
 *   State
 *     Sent in reply to GetState and when the state changes.
 *     data: see _currentStateObj
 */

this.ContentSearch = {

  init: function () {
    Cc["@mozilla.org/globalmessagemanager;1"].
      getService(Ci.nsIMessageListenerManager).
      addMessageListener(INBOUND_MESSAGE, this);
    Services.obs.addObserver(this, "browser-search-engine-modified", false);
  },

  receiveMessage: function (msg) {
    let methodName = "on" + msg.data.type;
    if (methodName in this) {
      this._initService().then(() => {
        this[methodName](msg, msg.data.data);
      });
    }
  },

  onGetState: function (msg, data) {
    this._reply(msg, "State", this._currentStateObj());
  },

  onSearch: function (msg, data) {
    let expectedDataProps = [
      "engineName",
      "searchString",
      "whence",
    ];
    for (let prop of expectedDataProps) {
      if (!(prop in data)) {
        Cu.reportError("Message data missing required property: " + prop);
        return;
      }
    }
    let browserWin = msg.target.ownerDocument.defaultView;
    let engine = Services.search.getEngineByName(data.engineName);
    browserWin.BrowserSearch.recordSearchInHealthReport(engine, data.whence);
    let submission = engine.getSubmission(data.searchString, "", data.whence);
    browserWin.loadURI(submission.uri.spec, null, submission.postData);
  },

  onSetCurrentEngine: function (msg, data) {
    Services.search.currentEngine = Services.search.getEngineByName(data);
  },

  onManageEngines: function (msg, data) {
    let browserWin = msg.target.ownerDocument.defaultView;
    browserWin.BrowserSearch.searchBar.openManager(null);
  },

  observe: function (subj, topic, data) {
    this._initService().then(() => {
      switch (topic) {
      case "browser-search-engine-modified":
        if (data == "engine-current") {
          this._broadcast("CurrentEngine", this._currentEngineObj());
        }
        else if (data != "engine-default") {
          // engine-default is always sent with engine-current and isn't
          // otherwise relevant to content searches.
          this._broadcast("State", this._currentStateObj());
        }
        break;
      }
    });
  },

  _reply: function (msg, type, data) {
    // Due to _initService, we reply asyncly to messages, and by the time we
    // reply the browser we're responding to may have been destroyed.  In that
    // case messageManager is null.
    if (msg.target.messageManager) {
      msg.target.messageManager.sendAsyncMessage(...this._msgArgs(type, data));
    }
  },

  _broadcast: function (type, data) {
    Cc["@mozilla.org/globalmessagemanager;1"].
      getService(Ci.nsIMessageListenerManager).
      broadcastAsyncMessage(...this._msgArgs(type, data));
  },

  _msgArgs: function (type, data) {
    return [OUTBOUND_MESSAGE, {
      type: type,
      data: data,
    }];
  },

  _currentStateObj: function () {
    return {
      engines: Services.search.getVisibleEngines().map(engine => {
        return {
          name: engine.name,
          iconURI: engine.getIconURLBySize(16, 16),
        };
      }),
      currentEngine: this._currentEngineObj(),
    };
  },

  _currentEngineObj: function () {
    return {
      name: Services.search.currentEngine.name,
      logoURI: Services.search.currentEngine.getIconURLBySize(65, 26),
      logo2xURI: Services.search.currentEngine.getIconURLBySize(130, 52),
    };
  },

  _initService: function () {
    if (!this._initServicePromise) {
      let deferred = Promise.defer();
      this._initServicePromise = deferred.promise;
      Services.search.init(() => deferred.resolve());
    }
    return this._initServicePromise;
  },
};