tor-browser

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

head.js (11174B)


      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 // Recording already set preferences.
      8 const devtoolsPreferences = Services.prefs.getBranch("devtools");
      9 const alreadySetPreferences = new Set();
     10 for (const pref of devtoolsPreferences.getChildList("")) {
     11  if (devtoolsPreferences.prefHasUserValue(pref)) {
     12    alreadySetPreferences.add(pref);
     13  }
     14 }
     15 
     16 // Reset all devtools preferences on test end.
     17 registerCleanupFunction(async () => {
     18  await SpecialPowers.flushPrefEnv();
     19 
     20  // Reset devtools preferences modified by the test.
     21  for (const pref of devtoolsPreferences.getChildList("")) {
     22    if (
     23      devtoolsPreferences.prefHasUserValue(pref) &&
     24      !alreadySetPreferences.has(pref)
     25    ) {
     26      devtoolsPreferences.clearUserPref(pref);
     27    }
     28  }
     29 });
     30 
     31 // Ignore promise rejections for actions triggered after panels are closed.
     32 {
     33  const { PromiseTestUtils } = ChromeUtils.importESModule(
     34    "resource://testing-common/PromiseTestUtils.sys.mjs"
     35  );
     36  PromiseTestUtils.allowMatchingRejectionsGlobally(
     37    /REDUX_MIDDLEWARE_IGNORED_REDUX_ACTION/
     38  );
     39 }
     40 
     41 // Load the tracker very first in order to ensure tracking all objects created by DevTools.
     42 // This is especially important for allocation sites. We need to catch the global the
     43 // earliest possible in order to ensure that all allocation objects come with a stack.
     44 //
     45 // If we want to track DevTools module loader we should ensure loading Loader.sys.mjs within
     46 // the `testScript` Function. i.e. after having calling startRecordingAllocations.
     47 let tracker, releaseTrackerLoader;
     48 {
     49  const {
     50    useDistinctSystemPrincipalLoader,
     51    releaseDistinctSystemPrincipalLoader,
     52  } = ChromeUtils.importESModule(
     53    "resource://devtools/shared/loader/DistinctSystemPrincipalLoader.sys.mjs",
     54    { global: "shared" }
     55  );
     56 
     57  const requester = {};
     58  const loader = useDistinctSystemPrincipalLoader(requester);
     59  releaseTrackerLoader = () => releaseDistinctSystemPrincipalLoader(requester);
     60  const { allocationTracker } = loader.require(
     61    "chrome://mochitests/content/browser/devtools/shared/test-helpers/allocation-tracker.js"
     62  );
     63  tracker = allocationTracker({ watchDevToolsGlobals: true });
     64 }
     65 
     66 // /!\ Be careful about imports/require
     67 //
     68 // Some tests may record the very first time we load a module.
     69 // If we start loading them from here, we might only retrieve the already loaded
     70 // module from the loader's cache. This would no longer highlight the cost
     71 // of loading a new module from scratch.
     72 //
     73 // => Avoid loading devtools module as much as possible
     74 // => If you really have to, lazy load them
     75 
     76 ChromeUtils.defineLazyGetter(this, "TrackedObjects", () => {
     77  return ChromeUtils.importESModule(
     78    "resource://devtools/shared/test-helpers/tracked-objects.sys.mjs"
     79  );
     80 });
     81 
     82 ChromeUtils.defineLazyGetter(this, "TraceObjects", () => {
     83  return ChromeUtils.importESModule(
     84    "chrome://mochitests/content/browser/devtools/shared/test-helpers/trace-objects.sys.mjs"
     85  );
     86 });
     87 
     88 // So that PERFHERDER data can be extracted from the logs.
     89 SimpleTest.requestCompleteLog();
     90 
     91 // We have to disable testing mode, or various debug instructions are enabled.
     92 // We especially want to disable redux store history, which would leak all the actions!
     93 SpecialPowers.pushPrefEnv({
     94  set: [["devtools.testing", false]],
     95 });
     96 
     97 // Set DEBUG_DEVTOOLS_ALLOCATIONS=allocations|leaks in order print debug informations.
     98 const DEBUG_ALLOCATIONS = Services.env.get("DEBUG_DEVTOOLS_ALLOCATIONS");
     99 
    100 async function addTab(url) {
    101  const tab = BrowserTestUtils.addTab(gBrowser, url);
    102  gBrowser.selectedTab = tab;
    103  await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
    104  return tab;
    105 }
    106 
    107 /**
    108 * This function will force some garbage collection before recording
    109 * data about allocated objects.
    110 *
    111 * This accept an optional boolean to also record the content process objects
    112 * of the current tab. That, in addition of objects from the parent process,
    113 * which are always recorded.
    114 *
    115 * This return same data object which is meant to be passed to `stopRecordingAllocations` as-is.
    116 *
    117 * See README.md file in this folder.
    118 */
    119 async function startRecordingAllocations({
    120  alsoRecordContentProcess = false,
    121 } = {}) {
    122  // Also start recording allocations in the content process, if requested
    123  if (alsoRecordContentProcess) {
    124    await SpecialPowers.spawn(
    125      gBrowser.selectedBrowser,
    126      [DEBUG_ALLOCATIONS],
    127      async debug_allocations => {
    128        const { DevToolsLoader } = ChromeUtils.importESModule(
    129          "resource://devtools/shared/loader/Loader.sys.mjs"
    130        );
    131 
    132        const {
    133          useDistinctSystemPrincipalLoader,
    134          releaseDistinctSystemPrincipalLoader,
    135        } = ChromeUtils.importESModule(
    136          "resource://devtools/shared/loader/DistinctSystemPrincipalLoader.sys.mjs"
    137        );
    138 
    139        const requester = {};
    140        const loader = useDistinctSystemPrincipalLoader(requester);
    141        const { allocationTracker } = loader.require(
    142          "chrome://mochitests/content/browser/devtools/shared/test-helpers/allocation-tracker.js"
    143        );
    144        // We watch all globals in the content process, (instead of only DevTools global in parent process)
    145        // because we may easily leak web page objects, which aren't in DevTools global.
    146        const tracker = allocationTracker({ watchAllGlobals: true });
    147 
    148        // /!\ HACK: store tracker and releaseTrackerLoader on DevToolsLoader in order
    149        // to be able to reuse them in a following call to SpecialPowers.spawn
    150        DevToolsLoader.tracker = tracker;
    151        DevToolsLoader.releaseTrackerLoader = () =>
    152          releaseDistinctSystemPrincipalLoader(requester);
    153 
    154        await tracker.startRecordingAllocations(debug_allocations);
    155      }
    156    );
    157    // Trigger a GC in the parent process as this additional ContentTask
    158    // seems to make harder to release objects created before we start recording.
    159    await tracker.doGC();
    160  }
    161 
    162  await tracker.startRecordingAllocations(DEBUG_ALLOCATIONS);
    163 }
    164 
    165 /**
    166 * See doc of startRecordingAllocations
    167 */
    168 async function stopRecordingAllocations(
    169  recordName,
    170  { alsoRecordContentProcess = false } = {}
    171 ) {
    172  // Ensure that Memory API didn't ran out of buffers
    173  ok(!tracker.overflowed, "Allocation were all recorded in the parent process");
    174 
    175  // And finally, retrieve the record *after* having ran the test
    176  const parentProcessData =
    177    await tracker.stopRecordingAllocations(DEBUG_ALLOCATIONS);
    178 
    179  const leakedObjects = TrackedObjects.getStillAllocatedObjects();
    180  if (leakedObjects.length) {
    181    await TraceObjects.traceObjects(leakedObjects, tracker.getSnapshotFile());
    182  }
    183 
    184  let contentProcessData = null;
    185  if (alsoRecordContentProcess) {
    186    contentProcessData = await SpecialPowers.spawn(
    187      gBrowser.selectedBrowser,
    188      [DEBUG_ALLOCATIONS],
    189      debug_allocations => {
    190        const { DevToolsLoader } = ChromeUtils.importESModule(
    191          "resource://devtools/shared/loader/Loader.sys.mjs"
    192        );
    193        const { tracker } = DevToolsLoader;
    194        ok(
    195          !tracker.overflowed,
    196          "Allocation were all recorded in the content process"
    197        );
    198        return tracker.stopRecordingAllocations(debug_allocations);
    199      }
    200    );
    201  }
    202 
    203  const trackedObjectsInContent = await SpecialPowers.spawn(
    204    gBrowser.selectedBrowser,
    205    [],
    206    async () => {
    207      const TrackedObjects = ChromeUtils.importESModule(
    208        "resource://devtools/shared/test-helpers/tracked-objects.sys.mjs"
    209      );
    210      const leakedObjects = TrackedObjects.getStillAllocatedObjects();
    211      if (leakedObjects.length) {
    212        const TraceObjects = ChromeUtils.importESModule(
    213          "chrome://mochitests/content/browser/devtools/shared/test-helpers/trace-objects.sys.mjs"
    214        );
    215        // Only pass 'weakRef' as Memory API and 'ubiNodeId' can only be inspected in the parent process
    216        await TraceObjects.traceObjects(
    217          leakedObjects.map(e => {
    218            return {
    219              weakRef: e.weakRef,
    220            };
    221          })
    222        );
    223        const { DevToolsLoader } = ChromeUtils.importESModule(
    224          "resource://devtools/shared/loader/Loader.sys.mjs"
    225        );
    226        const { tracker } = DevToolsLoader;
    227        // Record the heap snapshot from the content process,
    228        // and pass the record's filepath to the parent process
    229        // As only the parent process can read the file because
    230        // of sandbox restrictions made to content processes regarding file I/O.
    231        const snapshotFile = tracker.getSnapshotFile();
    232        return {
    233          snapshotFile,
    234          // Only pass ubi::Node::Id from this content process to the parent process.
    235          // `leakedObjects`'s `weakRef` attributes can't be transferred across processes.
    236          // TraceObjects.traceObjects in the parent process will only log leaks
    237          // via the Memory API (and Node Id's).
    238          objectUbiNodeIds: leakedObjects.map(e => {
    239            return {
    240              ubiNodeId: e.ubiNodeId,
    241            };
    242          }),
    243        };
    244      }
    245      return null;
    246    }
    247  );
    248  if (trackedObjectsInContent) {
    249    TraceObjects.traceObjects(
    250      trackedObjectsInContent.objectUbiNodeIds,
    251      trackedObjectsInContent.snapshotFile
    252    );
    253  }
    254 
    255  // Craft the JSON object required to save data in talos database
    256  info(
    257    `The ${recordName} test leaked ${parentProcessData.objectsWithStack} objects (${parentProcessData.objectsWithoutStack} with missing allocation site) in the parent process`
    258  );
    259  const PERFHERDER_DATA = {
    260    framework: {
    261      name: "devtools",
    262    },
    263    suites: [
    264      {
    265        name: recordName + ":parent-process",
    266        subtests: [
    267          {
    268            name: "objects-with-stacks",
    269            value: parentProcessData.objectsWithStack,
    270          },
    271          {
    272            name: "memory",
    273            value: parentProcessData.memory,
    274          },
    275        ],
    276      },
    277    ],
    278  };
    279  if (alsoRecordContentProcess) {
    280    info(
    281      `The ${recordName} test leaked ${contentProcessData.objectsWithStack} objects (${contentProcessData.objectsWithoutStack} with missing allocation site) in the content process`
    282    );
    283    PERFHERDER_DATA.suites.push({
    284      name: recordName + ":content-process",
    285      subtests: [
    286        {
    287          name: "objects-with-stacks",
    288          value: contentProcessData.objectsWithStack,
    289        },
    290        {
    291          name: "memory",
    292          value: contentProcessData.memory,
    293        },
    294      ],
    295    });
    296 
    297    // Finally release the tracker loader in content process.
    298    await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
    299      const { DevToolsLoader } = ChromeUtils.importESModule(
    300        "resource://devtools/shared/loader/Loader.sys.mjs"
    301      );
    302      DevToolsLoader.releaseTrackerLoader();
    303    });
    304  }
    305 
    306  // And release the tracker loader in the parent process
    307  releaseTrackerLoader();
    308 
    309  // Log it to stdout so that perfherder can collect this data.
    310  // This only works if we called `SimpleTest.requestCompleteLog()`!
    311  info("PERFHERDER_DATA: " + JSON.stringify(PERFHERDER_DATA));
    312 }