/* 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/. */

/**
 * A proxy implementing communication between the PerformanceStats.jsm modules
 * of the parent and children processes.
 *
 * This script is loaded in all processes but is essentially a NOOP in the
 * parent process.
 */

"use strict";

var { utils: Cu, classes: Cc, interfaces: Ci } = Components;
const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
const { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});

XPCOMUtils.defineLazyModuleGetter(this, "PerformanceStats",
  "resource://gre/modules/PerformanceStats.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Task",
  "resource://gre/modules/Task.jsm");

/**
 * A global performance monitor used by this process.
 *
 * For the sake of simplicity, rather than attempting to map each PerformanceMonitor
 * of the parent to a PerformanceMonitor in each child process, we maintain a single
 * PerformanceMonitor in each child process. Probes activation/deactivation for this
 * monitor is controlled by the activation/deactivation of probes in the parent.
 *
 * In the parent, this is always an empty monitor.
 */
var gMonitor = PerformanceStats.getMonitor([]);

/**
 * `true` if this is a content process, `false` otherwise.
 */
var isContent = Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT;

/**
 * Handle message `performance-stats-service-acquire`: ensure that the global
 * monitor has a given probe. This message must be sent by the parent process
 * whenever a probe is activated application-wide.
 *
 * Note that we may miss acquire messages if they are sent before this process is
 * launched. For this reason, `performance-stats-service-collect` automatically
 * re-acquires probes if it realizes that they are missing.
 *
 * This operation is a NOOP on the parent process.
 *
 * @param {{payload: Array<string>}} msg.data The message received. `payload`
 * must be an array of probe names.
 */
Services.cpmm.addMessageListener("performance-stats-service-acquire", function(msg) {
  if (!isContent) {
    return;
  }
  let name = msg.data.payload;
  ensureAcquired(name);
});

/**
 * Handle message `performance-stats-service-release`: release a given probe
 * from the global monitor. This message must be sent by the parent process
 * whenever a probe is deactivated application-wide.
 *
 * Note that we may miss release messages if they are sent before this process is
 * launched. This is ok, as probes are inactive by default: if we miss the release
 * message, we have already missed the acquire message, and the effect of both
 * messages together is to reset to the default state.
 *
 * This operation is a NOOP on the parent process.
 *
 * @param {{payload: Array<string>}} msg.data The message received. `payload`
 * must be an array of probe names.
 */
Services.cpmm.addMessageListener("performance-stats-service-release", function(msg) {
  if (!isContent) {
    return;
  }

  // Keep only the probes that do not appear in the payload
  let probes = gMonitor.probeNames
    .filter(x => msg.data.payload.indexOf(x) == -1);
  gMonitor = PerformanceStats.getMonitor(probes);
});

/**
 * Ensure that this process has all the probes it needs.
 *
 * @param {Array<string>} probeNames The name of all probes needed by the
 * process.
 */
function ensureAcquired(probeNames) {
  let alreadyAcquired = gMonitor.probeNames;

  // Algorithm is O(n^2) because we expect that n ≤ 3.
  let shouldAcquire = [];
  for (let probeName of probeNames) {
    if (alreadyAcquired.indexOf(probeName) == -1) {
      shouldAcquire.push(probeName)
    }
  }

  if (shouldAcquire.length == 0) {
    return;
  }
  gMonitor = PerformanceStats.getMonitor([...alreadyAcquired, ...shouldAcquire]);
}

/**
 * Handle message `performance-stats-service-collected`: collect the data
 * obtained by the monitor. This message must be sent by the parent process
 * whenever we grab a performance snapshot of the application.
 *
 * This operation provides `null` on the parent process.
 *
 * @param {{data: {payload: Array<string>}}} msg The message received. `payload`
 * must be an array of probe names.
 */
Services.cpmm.addMessageListener("performance-stats-service-collect", Task.async(function*(msg) {
  let {id, payload: {probeNames}} = msg.data;
  if (!isContent) {
    // This message was sent by the parent process to itself.
    // As per protocol, respond `null`.
    Services.cpmm.sendAsyncMessage("performance-stats-service-collect", {
      id,
      data: null
    });
    return;
  }

  // We may have missed acquire messages if the process was loaded too late.
  // Catch up now.
  ensureAcquired(probeNames);

  // Collect and return data.
  let data = yield gMonitor.promiseSnapshot({probeNames});
  Services.cpmm.sendAsyncMessage("performance-stats-service-collect", {
    id,
    data
  });
}));
