tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

ConsoleAPIStorage.sys.mjs (6360B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 const STORAGE_MAX_EVENTS = 1000;
      6 
      7 var _consoleStorage = new Map();
      8 
      9 // NOTE: these listeners used to just be added as observers and notified via
     10 // Services.obs.notifyObservers. However, that has enough overhead to be a
     11 // problem for this. Using an explicit global array is much cheaper, and
     12 // should be equivalent.
     13 var _logEventListeners = [];
     14 
     15 const CONSOLEAPISTORAGE_CID = Components.ID(
     16  "{96cf7855-dfa9-4c6d-8276-f9705b4890f2}"
     17 );
     18 
     19 /**
     20 * The ConsoleAPIStorage is meant to cache window.console API calls for later
     21 * reuse by other components when needed. For example, the Web Console code can
     22 * display the cached messages when it opens for the active tab.
     23 *
     24 * ConsoleAPI messages are stored as they come from the ConsoleAPI code, with
     25 * all their properties. They are kept around until the inner window object that
     26 * created the messages is destroyed. Messages are indexed by the inner window
     27 * ID.
     28 *
     29 * Usage:
     30 *    let ConsoleAPIStorage = Cc["@mozilla.org/consoleAPI-storage;1"]
     31 *                              .getService(Ci.nsIConsoleAPIStorage);
     32 *
     33 *    // Get the cached events array for the window you want (use the inner
     34 *    // window ID).
     35 *    let events = ConsoleAPIStorage.getEvents(innerWindowID);
     36 *    events.forEach(function(event) { ... });
     37 *
     38 *    // Clear the events for the given inner window ID.
     39 *    ConsoleAPIStorage.clearEvents(innerWindowID);
     40 */
     41 export function ConsoleAPIStorageService() {
     42  this.init();
     43 }
     44 
     45 ConsoleAPIStorageService.prototype = {
     46  classID: CONSOLEAPISTORAGE_CID,
     47  QueryInterface: ChromeUtils.generateQI([
     48    "nsIConsoleAPIStorage",
     49    "nsIObserver",
     50  ]),
     51 
     52  observe: function CS_observe(aSubject, aTopic) {
     53    if (aTopic == "xpcom-shutdown") {
     54      Services.obs.removeObserver(this, "xpcom-shutdown");
     55      Services.obs.removeObserver(this, "inner-window-destroyed");
     56      Services.obs.removeObserver(this, "memory-pressure");
     57    } else if (aTopic == "inner-window-destroyed") {
     58      let innerWindowID = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;
     59      this.clearEvents(innerWindowID + "");
     60    } else if (aTopic == "memory-pressure") {
     61      this.clearEvents();
     62    }
     63  },
     64 
     65  /** @private */
     66  init: function CS_init() {
     67    Services.obs.addObserver(this, "xpcom-shutdown");
     68    Services.obs.addObserver(this, "inner-window-destroyed");
     69    Services.obs.addObserver(this, "memory-pressure");
     70  },
     71 
     72  /**
     73   * Get the events array by inner window ID or all events from all windows.
     74   *
     75   * @param string [aId]
     76   *        Optional, the inner window ID for which you want to get the array of
     77   *        cached events.
     78   * @returns array
     79   *          The array of cached events for the given window. If no |aId| is
     80   *          given this function returns all of the cached events, from any
     81   *          window.
     82   */
     83  getEvents: function CS_getEvents(aId) {
     84    if (aId != null) {
     85      return (_consoleStorage.get(aId) || []).slice(0);
     86    }
     87 
     88    let result = [];
     89 
     90    for (let [, events] of _consoleStorage) {
     91      result.push.apply(result, events);
     92    }
     93 
     94    return result.sort(function (a, b) {
     95      return a.timeStamp - b.timeStamp;
     96    });
     97  },
     98 
     99  /**
    100   * Adds a listener to be notified of log events.
    101   *
    102   * @param jsval [aListener]
    103   *        A JS listener which will be notified with the message object when
    104   *        a log event occurs.
    105   * @param nsIPrincipal [aPrincipal]
    106   *        The principal of the listener - used to determine if we need to
    107   *        clone the message before forwarding it.
    108   */
    109  addLogEventListener: function CS_addLogEventListener(aListener, aPrincipal) {
    110    // If our listener has a less-privileged principal than us, then they won't
    111    // be able to access the log event object which was populated for our
    112    // scope. Accordingly we need to clone it for these listeners.
    113    //
    114    // XXX: AFAICT these listeners which we need to clone messages for are all
    115    // tests. Alternative solutions are welcome.
    116    const clone = !aPrincipal.subsumes(
    117      Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal)
    118    );
    119    _logEventListeners.push({
    120      callback: aListener,
    121      clone,
    122    });
    123  },
    124 
    125  /**
    126   * Removes a listener added with `addLogEventListener`.
    127   *
    128   * @param jsval [aListener]
    129   *        A JS listener which was added with `addLogEventListener`.
    130   */
    131  removeLogEventListener: function CS_removeLogEventListener(aListener) {
    132    const index = _logEventListeners.findIndex(l => l.callback === aListener);
    133    if (index != -1) {
    134      _logEventListeners.splice(index, 1);
    135    } else {
    136      console.error(
    137        "Attempted to remove a log event listener that does not exist."
    138      );
    139    }
    140  },
    141 
    142  /**
    143   * Record an event associated with the given window ID.
    144   *
    145   * @param string aId
    146   *        The ID of the inner window for which the event occurred or "jsm" for
    147   *        messages logged from JavaScript modules..
    148   * @param object aEvent
    149   *        A JavaScript object you want to store.
    150   */
    151  recordEvent: function CS_recordEvent(aId, aEvent) {
    152    if (!_consoleStorage.has(aId)) {
    153      _consoleStorage.set(aId, []);
    154    }
    155 
    156    let storage = _consoleStorage.get(aId);
    157 
    158    storage.push(aEvent);
    159 
    160    // truncate
    161    if (storage.length > STORAGE_MAX_EVENTS) {
    162      storage.shift();
    163    }
    164 
    165    for (let { callback, clone } of _logEventListeners) {
    166      try {
    167        if (clone) {
    168          callback(Cu.cloneInto(aEvent, callback));
    169        } else {
    170          callback(aEvent);
    171        }
    172      } catch (e) {
    173        // A failing listener should not prevent from calling other listeners.
    174        console.error(e);
    175      }
    176    }
    177  },
    178 
    179  /**
    180   * Clear storage data for the given window.
    181   *
    182   * @param string [aId]
    183   *        Optional, the inner window ID for which you want to clear the
    184   *        messages. If this is not specified all of the cached messages are
    185   *        cleared, from all window objects.
    186   */
    187  clearEvents: function CS_clearEvents(aId) {
    188    if (aId != null) {
    189      _consoleStorage.delete(aId);
    190    } else {
    191      _consoleStorage.clear();
    192      Services.obs.notifyObservers(null, "console-storage-reset");
    193    }
    194  },
    195 };