/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */

#include "nsISupports.idl"

interface nsIDOMWindow;
interface nsIDocShell;
interface nsIContent;
interface nsIFrameLoader;
interface nsIPrincipal;

/**
 * Message managers provide a way for chrome-privileged JS code to
 * communicate with each other, even across process boundaries.
 *
 * Message managers are separated into "parent side" and "child side".
 * These don't always correspond to process boundaries, but can.  For
 * each child-side message manager, there is always exactly one
 * corresponding parent-side message manager that it sends messages
 * to.  However, for each parent-side message manager, there may be
 * either one or many child-side managers it can message.
 *
 * Message managers that always have exactly one "other side" are of
 * type nsIMessageSender.  Parent-side message managers that have many
 * "other sides" are of type nsIMessageBroadcaster.
 *
 * Child-side message managers can send synchronous messages to their
 * parent side, but not the other way around.
 *
 * There are two realms of message manager hierarchies.  One realm
 * approximately corresponds to DOM elements, the other corresponds to
 * process boundaries.
 *
 * Message managers corresponding to DOM elements
 * ==============================================
 *
 * In this realm of message managers, there are
 *  - "frame message managers" which correspond to frame elements
 *  - "window message managers" which correspond to top-level chrome
 *    windows
 *  - "group message managers" which correspond to named message
 *    managers with a specific window MM as the parent
 *  - the "global message manager", on the parent side.  See below.
 *
 * The DOM-realm message managers can communicate in the ways shown by
 * the following diagram.  The parent side and child side can
 * correspond to process boundaries, but don't always.
 *
 *  Parent side                         Child side
 * -------------                       ------------
 *  global MMg
 *   |
 *   +-->window MMw1
 *   |    |
 *   |    +-->frame MMp1_1<------------>frame MMc1_1
 *   |    |
 *   |    +-->frame MMp1_2<------------>frame MMc1_2
 *   |    |
 *   |    +-->group MMgr1
 *   |    |    |
 *   |    |    +-->frame MMp2_1<------->frame MMc2_1
 *   |    |    |
 *   |    |    +-->frame MMp2_2<------->frame MMc2_2
 *   |    |
 *   |    +-->group MMgr2
 *   |    |    ...
 *   |    |
 *   |    ...
 *   |
 *   +-->window MMw2
 *   ...
 *
 * For example: a message sent from MMc1_1, from the child side, is
 * sent only to MMp1_1 on the parent side.  However, note that all
 * message managers in the hierarchy above MMp1_1, in this diagram
 * MMw1 and MMg, will also notify their message listeners when the
 * message arrives.
 *
 * A message sent from MMc2_1 will be sent to MMp2_1 and also notify
 * all message managers in the hierarchy above that, including the
 * group message manager MMgr1.

 * For example: a message broadcast through the global MMg on the
 * parent side would be broadcast to MMw1, which would transitively
 * broadcast it to MMp1_1, MM1p_2.  The message would next be
 * broadcast to MMgr1, which would broadcast it to MMp2_1 and MMp2_2.
 * After that it would broadcast to MMgr2 and then to MMw2, and so
 * on down the hierarchy.
 *
 *   ***** PERFORMANCE AND SECURITY WARNING *****
 * Messages broadcast through the global MM and window or group MMs
 * can result in messages being dispatched across many OS processes,
 * and to many processes with different permissions.  Great care
 * should be taken when broadcasting.
 *
 * Interfaces
 * ----------
 *
 * The global MMg and window MMw's are message broadcasters implementing
 * nsIMessageBroadcaster while the frame MMp's are simple message senders
 * (nsIMessageSender). Their counterparts in the content processes are
 * message senders implementing nsIContentFrameMessageManager.
 *
 *                    nsIMessageListenerManager
 *                  /                           \
 * nsIMessageSender                               nsIMessageBroadcaster
 *       |
 * nsISyncMessageSender (content process/in-process only)
 *       |
 * nsIContentFrameMessageManager (content process/in-process only)
 *       |
 * nsIInProcessContentFrameMessageManager (in-process only)
 *
 *
 * Message managers in the chrome process can also be QI'ed to nsIFrameScriptLoader.
 *
 *
 * Message managers corresponding to process boundaries
 * ====================================================
 *
 * The second realm of message managers is the "process message
 * managers".  With one exception, these always correspond to process
 * boundaries.  The picture looks like
 *
 *  Parent process                      Child processes
 * ----------------                    -----------------
 *  global (GPPMM)
 *   |
 *   +-->parent in-process PIPMM<-->child in-process CIPPMM
 *   |
 *   +-->parent (PPMM1)<------------------>child (CPMM1)
 *   |
 *   +-->parent (PPMM2)<------------------>child (CPMM2)
 *   ...
 *
 * Note, PIPMM and CIPPMM both run in the parent process.
 *
 * For example: the parent-process PPMM1 sends messages to the
 * child-process CPMM1.
 *
 * For example: CPMM1 sends messages directly to PPMM1. The global GPPMM
 * will also notify their message listeners when the message arrives.
 *
 * For example: messages sent through the global GPPMM will be
 * dispatched to the listeners of the same-process, CIPPMM, CPMM1,
 * CPMM2, etc.
 *
 *   ***** PERFORMANCE AND SECURITY WARNING *****
 * Messages broadcast through the GPPMM can result in messages
 * being dispatched across many OS processes, and to many processes
 * with different permissions.  Great care should be taken when
 * broadcasting.
 *
 * Requests sent to parent-process message listeners should usually
 * have replies scoped to the requesting CPMM.  The following pattern
 * is common
 *
 *  const ParentProcessListener = {
 *    receiveMessage: function(aMessage) {
 *      let childMM = aMessage.target.QueryInterface(Ci.nsIMessageSender);
 *      switch (aMessage.name) {
 *      case "Foo:Request":
 *        // service request
 *        childMM.sendAsyncMessage("Foo:Response", { data });
 *      }
 *    }
 *  };
 */

[scriptable, function, uuid(2b44eb57-a9c6-4773-9a1e-fe0818739a4c)]
interface nsIMessageListener : nsISupports
{
  /**
   * This is for JS only.
   * receiveMessage is called with one parameter, which has the following
   * properties:
   *   {
   *     target:    %the target of the message. Either an element owning
   *                 the message manager, or message manager itself if no
   *                 element owns it%
   *     name:      %message name%,
   *     sync:      %true or false%.
   *     data:      %structured clone of the sent message data%,
   *     json:      %same as .data, deprecated%,
   *     objects:   %named table of jsvals/objects, or null%
   *     principal: %principal for the window app
   *   }
   *
   * Each listener is invoked with its own copy of the message
   * parameter.
   *
   * When the listener is called, 'this' value is the target of the message.
   *
   * If the message is synchronous, the possible return value is
   * returned as JSON (will be changed to use structured clones).
   * When there are multiple listeners to sync messages, each
   * listener's return value is sent back as an array.  |undefined|
   * return values show up as undefined values in the array.
   */
  void receiveMessage();
};

[scriptable, builtinclass, uuid(b949bfec-bb7d-47bc-b387-ac6a9b655072)]
interface nsIMessageListenerManager : nsISupports
{
  /**
   * Register |listener| to receive |messageName|.  All listener
   * callbacks for a particular message are invoked when that message
   * is received.
   *
   * The message manager holds a strong ref to |listener|.
   *
   * If the same listener registers twice for the same message, the
   * second registration is ignored.
   *
   * Pass true for listenWhenClosed if you want to receive messages
   * during the short period after a frame has been removed from the
   * DOM and before its frame script has finished unloading. This
   * parameter only has an effect for frame message managers in
   * the main process. Default is false.
   */
  void addMessageListener(in AString messageName,
                          in nsIMessageListener listener,
                          [optional] in boolean listenWhenClosed);

  /**
   * Undo an |addMessageListener| call -- that is, calling this causes us to no
   * longer invoke |listener| when |messageName| is received.
   *
   * removeMessageListener does not remove a message listener added via
   * addWeakMessageListener; use removeWeakMessageListener for that.
   */
  void removeMessageListener(in AString messageName,
                             in nsIMessageListener listener);

  /**
   * This is just like addMessageListener, except the message manager holds a
   * weak ref to |listener|.
   *
   * If you have two weak message listeners for the same message, they may be
   * called in any order.
   */
  void addWeakMessageListener(in AString messageName,
                              in nsIMessageListener listener);

  /**
   * This undoes an |addWeakMessageListener| call.
   */
  void removeWeakMessageListener(in AString messageName,
                                 in nsIMessageListener listener);

  [notxpcom] boolean markForCC();
};

/**
 * Message "senders" have a single "other side" to which messages are
 * sent.  For example, a child-process message manager will send
 * messages that are only delivered to its one parent-process message
 * manager.
 */
[scriptable, builtinclass, uuid(bb5d79e4-e73c-45e7-9651-4d718f4b994c)]
interface nsIMessageSender : nsIMessageListenerManager
{
  /**
   * Send |messageName| and |obj| to the "other side" of this message
   * manager.  This invokes listeners who registered for
   * |messageName|.
   *
   * See nsIMessageListener::receiveMessage() for the format of the
   * data delivered to listeners.
   * @throws NS_ERROR_NOT_INITIALIZED if the sender is not initialized.  For
   *         example, we will throw NS_ERROR_NOT_INITIALIZED if we try to send
   *         a message to a cross-process frame but the other process has not
   *         yet been set up.
   * @throws NS_ERROR_FAILURE when the message receiver cannot be found.  For
   *         example, we will throw NS_ERROR_FAILURE if we try to send a message
   *         to a cross-process frame whose process has crashed.
   */
  [implicit_jscontext, optional_argc]
  void sendAsyncMessage([optional] in AString messageName,
                        [optional] in jsval obj,
                        [optional] in jsval objects,
                        [optional] in nsIPrincipal principal);
};

/**
 * Message "broadcasters" don't have a single "other side" that they
 * send messages to, but rather a set of subordinate message managers.
 * For example, broadcasting a message through a window message
 * manager will broadcast the message to all frame message managers
 * within its window.
 */
[scriptable, builtinclass, uuid(4d7d62ad-4725-4f39-86cf-8fb22bf9c1d8)]
interface nsIMessageBroadcaster : nsIMessageListenerManager
{
  /**
   * Like |sendAsyncMessage()|, but also broadcasts this message to
   * all "child" message managers of this message manager.  See long
   * comment above for details.
   *
   * WARNING: broadcasting messages can be very expensive and leak
   * sensitive data.  Use with extreme caution.
   */
  [implicit_jscontext, optional_argc]
  void broadcastAsyncMessage([optional] in AString messageName,
                             [optional] in jsval obj,
                             [optional] in jsval objects);

  /**
   * Number of subordinate message managers.
   */
  readonly attribute unsigned long childCount;

  /**
   * Return a single subordinate message manager.
   */
  nsIMessageListenerManager getChildAt(in unsigned long aIndex);
};

[scriptable, builtinclass, uuid(0e602c9e-1977-422a-a8e4-fe0d4a4f78d0)]
interface nsISyncMessageSender : nsIMessageSender
{
  /**
   * Like |sendAsyncMessage()|, except blocks the sender until all
   * listeners of the message have been invoked.  Returns an array
   * containing return values from each listener invoked.
   */
  [implicit_jscontext, optional_argc]
  jsval sendSyncMessage([optional] in AString messageName,
                        [optional] in jsval obj,
                        [optional] in jsval objects,
                        [optional] in nsIPrincipal principal);

  /**
   * Like |sendSyncMessage()|, except re-entrant. New RPC messages may be
   * issued even if, earlier on the call stack, we are waiting for a reply
   * to an earlier sendRpcMessage() call.
   *
   * Both sendSyncMessage and sendRpcMessage will block until a reply is
   * received, but they may be temporarily interrupted to process an urgent
   * incoming message (such as a CPOW request).
   */
  [implicit_jscontext, optional_argc]
  jsval sendRpcMessage([optional] in AString messageName,
                       [optional] in jsval obj,
                       [optional] in jsval objects,
                       [optional] in nsIPrincipal principal);
};

[scriptable, builtinclass, uuid(13f3555f-769e-44ea-b607-5239230c3162)]
interface nsIMessageManagerGlobal : nsISyncMessageSender
{
  /**
   * Print a string to stdout.
   */
  void dump(in DOMString aStr);

  /**
   * If leak detection is enabled, print a note to the leak log that this
   * process will intentionally crash.
   */
  void privateNoteIntentionalCrash();

  /**
   * Ascii base64 data to binary data and vice versa
   */
  DOMString atob(in DOMString aAsciiString);
  DOMString btoa(in DOMString aBase64Data);
};

[scriptable, builtinclass, uuid(fff36099-9f84-4c7c-b69a-1cbf103d1708)]
interface nsIContentFrameMessageManager : nsIMessageManagerGlobal
{
  /**
   * The current top level window in the frame or null.
   */
  readonly attribute nsIDOMWindow content;

  /**
   * The top level docshell or null.
   */
  readonly attribute nsIDocShell docShell;
};

[uuid(9c6bd4d7-88d2-46d6-8606-f2d57d46f051)]
interface nsIInProcessContentFrameMessageManager : nsIContentFrameMessageManager
{
  [notxpcom] nsIContent getOwnerContent();
  [notxpcom] void cacheFrameLoader(in nsIFrameLoader aFrameLoader);
};

[scriptable, builtinclass, uuid(6d12e467-2446-46db-9965-e4e93cb87ca5)]
interface nsIContentProcessMessageManager : nsIMessageManagerGlobal
{
  /**
   * Read out a copy of the object that was initialized in the parent
   * process via nsIProcessScriptLoader.initialProcessData.
   */
  [implicit_jscontext]
  readonly attribute jsval initialProcessData;
};

[scriptable, builtinclass, uuid(bf61446b-ba24-4b1d-88c7-4f94724b9ce1)]
interface nsIFrameScriptLoader : nsISupports
{
  /**
   * Load a script in the (remote) frame. aURL must be the absolute URL.
   * data: URLs are also supported. For example data:,dump("foo\n");
   * If aAllowDelayedLoad is true, script will be loaded when the
   * remote frame becomes available. Otherwise the script will be loaded
   * only if the frame is already available.
   */
  void loadFrameScript(in AString aURL, in boolean aAllowDelayedLoad,
                       [optional] in boolean aRunInGlobalScope);

  /**
   * Removes aURL from the list of scripts which support delayed load.
   */
  void removeDelayedFrameScript(in AString aURL);

  /**
   * Returns all delayed scripts that will be loaded once a (remote)
   * frame becomes available. The return value is a list of pairs
   * [<URL>, <WasLoadedInGlobalScope>].
   */
  [implicit_jscontext]
  jsval getDelayedFrameScripts();
};

[scriptable, builtinclass, uuid(7e1e1a20-b24f-11e4-ab27-0800200c9a66)]
interface nsIProcessScriptLoader : nsISupports
{
  /**
   * Load a script in the (possibly remote) process. aURL must be the absolute URL.
   * data: URLs are also supported. For example data:,dump("foo\n");
   * If aAllowDelayedLoad is true, script will be loaded when the
   * remote frame becomes available. Otherwise the script will be loaded
   * only if the frame is already available.
   */
  void loadProcessScript(in AString aURL, in boolean aAllowDelayedLoad);

  /**
   * Removes aURL from the list of scripts which support delayed load.
   */
  void removeDelayedProcessScript(in AString aURL);

  /**
   * Returns all delayed scripts that will be loaded once a (remote)
   * frame becomes available. The return value is a list of URLs.
   */
  [implicit_jscontext]
  jsval getDelayedProcessScripts();
};

[scriptable, builtinclass, uuid(5b390753-abb3-49b0-ae3b-b803dab58144)]
interface nsIGlobalProcessScriptLoader : nsIProcessScriptLoader
{
  /**
   * Allows the parent process to set the initial process data for
   * new, not-yet-created child processes. This attribute should only
   * be used by the global parent process message manager. When a new
   * process is created, it gets a copy of this data (via structured
   * cloning). It can access the data via the initialProcessData
   * attribute of its childprocessmessagemanager.
   *
   * This value will always be a JS object. Different users are
   * expected to set properties on this object. The property name
   * should be unique enough that other Gecko consumers won't
   * accidentally choose it.
   */
  [implicit_jscontext]
  readonly attribute jsval initialProcessData;
};

[scriptable, builtinclass, uuid(637e8538-4f8f-4a3d-8510-e74386233e19)]
interface nsIProcessChecker : nsISupports
{
  bool killChild();

  /**
   * Return true if the "remote" process has |aPermission|.  This is
   * intended to be used by JS implementations of cross-process DOM
   * APIs, like so
   *
   *   recvFooRequest: function(message) {
   *     if (!message.target.assertPermission("foo")) {
   *       return false;
   *     }
   *     // service foo request
   *
   * This interface only returns meaningful data when our content is
   * in a separate process.  If it shares the same OS process as us,
   * then applying this permission check doesn't add any security,
   * though it doesn't hurt anything either.
   *
   * Note: If the remote content process does *not* have |aPermission|,
   * it will be killed as a precaution.
   */
  boolean assertPermission(in DOMString aPermission);

  /**
   * Return true if the "remote" process has |aManifestURL|.  This is
   * intended to be used by JS implementations of cross-process DOM
   * APIs, like so
   *
   *   recvFooRequest: function(message) {
   *     if (!message.target.assertContainApp("foo")) {
   *       return false;
   *     }
   *     // service foo request
   *
   * This interface only returns meaningful data when our content is
   * in a separate process.  If it shares the same OS process as us,
   * then applying this manifest URL check doesn't add any security,
   * though it doesn't hurt anything either.
   *
   * Note: If the remote content process does *not* contain |aManifestURL|,
   * it will be killed as a precaution.
   */
  boolean assertContainApp(in DOMString aManifestURL);

  boolean assertAppHasPermission(in DOMString aPermission);

  /**
   * Return true if the "remote" process' principal has an appStatus equal to
   * |aStatus|.
   *
   * This interface only returns meaningful data when our content is
   * in a separate process.  If it shares the same OS process as us,
   * then applying this permission check doesn't add any security,
   * though it doesn't hurt anything either.
   *
   * Note: If the remote content process does *not* has the |aStatus|,
   * it will be killed as a precaution.
   */
  boolean assertAppHasStatus(in unsigned short aStatus);

};
