tor-browser

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

har-automation.js (6457B)


      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 "use strict";
      6 
      7 const {
      8  HarCollector,
      9 } = require("resource://devtools/client/netmonitor/src/har/har-collector.js");
     10 const {
     11  HarExporter,
     12 } = require("resource://devtools/client/netmonitor/src/har/har-exporter.js");
     13 const {
     14  HarUtils,
     15 } = require("resource://devtools/client/netmonitor/src/har/har-utils.js");
     16 const {
     17  getLongStringFullText,
     18 } = require("resource://devtools/client/shared/string-utils.js");
     19 
     20 const prefDomain = "devtools.netmonitor.har.";
     21 
     22 // Helper tracer. Should be generic sharable by other modules (bug 1171927)
     23 const trace = {
     24  log() {},
     25 };
     26 
     27 /**
     28 * This object is responsible for automated HAR export. It listens
     29 * for Network activity, collects all HTTP data and triggers HAR
     30 * export when the page is loaded.
     31 *
     32 * The user needs to enable the following preference to make the
     33 * auto-export work: devtools.netmonitor.har.enableAutoExportToFile
     34 *
     35 * HAR files are stored within directory that is specified in this
     36 * preference: devtools.netmonitor.har.defaultLogDir
     37 *
     38 * If the default log directory preference isn't set the following
     39 * directory is used by default: <profile>/har/logs
     40 */
     41 class HarAutomation {
     42  // Initialization
     43 
     44  async initialize(toolbox) {
     45    this.toolbox = toolbox;
     46    this.commands = toolbox.commands;
     47 
     48    await this.startMonitoring();
     49  }
     50 
     51  destroy() {
     52    if (this.collector) {
     53      this.collector.stop();
     54    }
     55 
     56    if (this.tabWatcher) {
     57      this.tabWatcher.disconnect();
     58    }
     59  }
     60 
     61  // Automation
     62 
     63  async startMonitoring() {
     64    await this.commands.resourceCommand.watchResources(
     65      [this.commands.resourceCommand.TYPES.DOCUMENT_EVENT],
     66      {
     67        onAvailable: resources => {
     68          // Only consider top level document, and ignore remote iframes top document
     69          if (
     70            resources.find(
     71              r => r.name == "will-navigate" && r.targetFront.isTopLevel
     72            )
     73          ) {
     74            this.pageLoadBegin();
     75          }
     76          if (
     77            resources.find(
     78              r => r.name == "dom-complete" && r.targetFront.isTopLevel
     79            )
     80          ) {
     81            this.pageLoadDone();
     82          }
     83        },
     84        ignoreExistingResources: true,
     85      }
     86    );
     87  }
     88 
     89  pageLoadBegin() {
     90    this.resetCollector();
     91  }
     92 
     93  resetCollector() {
     94    if (this.collector) {
     95      this.collector.stop();
     96    }
     97 
     98    // A page is about to be loaded, start collecting HTTP
     99    // data from events sent from the backend.
    100    this.collector = new HarCollector({
    101      commands: this.commands,
    102    });
    103 
    104    this.collector.start();
    105  }
    106 
    107  /**
    108   * A page is done loading, export collected data. Note that
    109   * some requests for additional page resources might be pending,
    110   * so export all after all has been properly received from the backend.
    111   *
    112   * This collector still works and collects any consequent HTTP
    113   * traffic (e.g. XHRs) happening after the page is loaded and
    114   * The additional traffic can be exported by executing
    115   * triggerExport on this object.
    116   */
    117  pageLoadDone(response) {
    118    trace.log("HarAutomation.pageLoadDone; ", response);
    119 
    120    if (this.collector) {
    121      this.collector.waitForHarLoad().then(() => {
    122        return this.autoExport();
    123      });
    124    }
    125  }
    126 
    127  autoExport() {
    128    const autoExport = Services.prefs.getBoolPref(
    129      prefDomain + "enableAutoExportToFile"
    130    );
    131 
    132    if (!autoExport) {
    133      return Promise.resolve();
    134    }
    135 
    136    // Auto export to file is enabled, so save collected data
    137    // into a file and use all the default options.
    138    const data = {
    139      fileName: Services.prefs.getCharPref(prefDomain + "defaultFileName"),
    140    };
    141 
    142    return this.executeExport(data);
    143  }
    144 
    145  // Public API
    146 
    147  /**
    148   * Export all what is currently collected.
    149   */
    150  triggerExport(data) {
    151    if (!data.fileName) {
    152      data.fileName = Services.prefs.getCharPref(
    153        prefDomain + "defaultFileName"
    154      );
    155    }
    156 
    157    return this.executeExport(data);
    158  }
    159 
    160  /**
    161   * Clear currently collected data.
    162   */
    163  clear() {
    164    this.resetCollector();
    165  }
    166 
    167  // HAR Export
    168 
    169  /**
    170   * Execute HAR export. This method fetches all data from the
    171   * Network panel (asynchronously) and saves it into a file.
    172   */
    173  async executeExport(data) {
    174    const items = this.collector.getItems();
    175    const { title } = this.commands.targetCommand.targetFront;
    176 
    177    const netMonitor = await this.toolbox.getNetMonitorAPI();
    178    const connector = await netMonitor.getHarExportConnector();
    179 
    180    const options = {
    181      connector,
    182      requestData: null,
    183      getTimingMarker: null,
    184      getString: this.getString.bind(this),
    185      view: this,
    186      items,
    187    };
    188 
    189    options.defaultFileName = data.fileName;
    190    options.compress = data.compress;
    191    options.title = data.title || title;
    192    options.id = data.id;
    193    options.jsonp = data.jsonp;
    194    options.includeResponseBodies = data.includeResponseBodies;
    195    options.jsonpCallback = data.jsonpCallback;
    196    options.forceExport = data.forceExport;
    197 
    198    trace.log("HarAutomation.executeExport; " + data.fileName, options);
    199 
    200    const jsonString = await HarExporter.fetchHarData(options);
    201 
    202    // Save the HAR file if the file name is provided.
    203    if (jsonString && options.defaultFileName) {
    204      const file = getDefaultTargetFile(options);
    205      if (file) {
    206        HarUtils.saveToFile(file, jsonString, options.compress);
    207      }
    208    }
    209 
    210    return jsonString;
    211  }
    212 
    213  /**
    214   * Fetches the full text of a string.
    215   */
    216  async getString(stringGrip) {
    217    const fullText = await getLongStringFullText(
    218      this.commands.client,
    219      stringGrip
    220    );
    221    return fullText;
    222  }
    223 }
    224 
    225 // Protocol Helpers
    226 
    227 /**
    228 * Returns target file for exported HAR data.
    229 */
    230 function getDefaultTargetFile(options) {
    231  const path =
    232    options.defaultLogDir ||
    233    Services.prefs.getCharPref("devtools.netmonitor.har.defaultLogDir");
    234  const folder = HarUtils.getLocalDirectory(path);
    235 
    236  const host = new URL(options.connector.currentTarget.url);
    237  const fileName = HarUtils.getHarFileName(
    238    options.defaultFileName,
    239    options.jsonp,
    240    options.compress,
    241    host.hostname
    242  );
    243 
    244  folder.append(fileName);
    245  folder.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("0666", 8));
    246 
    247  return folder;
    248 }
    249 
    250 // Exports from this module
    251 exports.HarAutomation = HarAutomation;