tor-browser

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

browser_destroy_callbacks.js (6277B)


      1 /* Any copyright is dedicated to the Public Domain.
      2   http://creativecommons.org/publicdomain/zero/1.0/ */
      3 "use strict";
      4 
      5 declTest("destroy actor by iframe remove", {
      6  allFrames: true,
      7 
      8  async test(browser) {
      9    await SpecialPowers.spawn(browser, [], async function () {
     10      // Create and append an iframe into the window's document.
     11      let frame = content.document.createElement("iframe");
     12      frame.id = "frame";
     13      content.document.body.appendChild(frame);
     14      is(content.window.frames.length, 1, "There should be an iframe.");
     15      let child = frame.contentWindow.windowGlobalChild;
     16      let actorChild = child.getActor("TestWindow");
     17      ok(actorChild, "JSWindowActorChild should have value.");
     18 
     19      {
     20        let error = actorChild.uninitializedGetterError;
     21        const prop = "contentWindow";
     22        Assert.ok(
     23          error,
     24          `Should get error accessing '${prop}' before actor initialization`
     25        );
     26        if (error) {
     27          Assert.equal(
     28            error.name,
     29            "InvalidStateError",
     30            "Error should be an InvalidStateError"
     31          );
     32          Assert.equal(
     33            error.message,
     34            `JSWindowActorChild.${prop} getter: Cannot access property '${prop}' before actor is initialized`,
     35            "Error should have informative message"
     36          );
     37        }
     38      }
     39 
     40      let didDestroyPromise = new Promise(resolve => {
     41        const TOPIC = "test-js-window-actor-diddestroy";
     42        Services.obs.addObserver(function obs(subject, topic, data) {
     43          ok(data, "didDestroyCallback data should be true.");
     44          is(subject, actorChild, "Should have this value");
     45 
     46          Services.obs.removeObserver(obs, TOPIC);
     47          // Make a trip through the event loop to ensure that the
     48          // actor's manager has been cleared before running remaining
     49          // checks.
     50          Services.tm.dispatchToMainThread(resolve);
     51        }, TOPIC);
     52      });
     53 
     54      info("Remove frame");
     55      content.document.getElementById("frame").remove();
     56      await didDestroyPromise;
     57 
     58      Assert.throws(
     59        () => child.getActor("TestWindow"),
     60        /InvalidStateError/,
     61        "Should throw if frame destroy."
     62      );
     63 
     64      for (let prop of [
     65        "document",
     66        "browsingContext",
     67        "docShell",
     68        "contentWindow",
     69      ]) {
     70        let error;
     71        try {
     72          void actorChild[prop];
     73        } catch (e) {
     74          error = e;
     75        }
     76        Assert.ok(
     77          error,
     78          `Should get error accessing '${prop}' after actor destruction`
     79        );
     80        if (error) {
     81          Assert.equal(
     82            error.name,
     83            "InvalidStateError",
     84            "Error should be an InvalidStateError"
     85          );
     86          Assert.equal(
     87            error.message,
     88            `JSWindowActorChild.${prop} getter: Cannot access property '${prop}' after actor 'TestWindow' has been destroyed`,
     89            "Error should have informative message"
     90          );
     91        }
     92      }
     93    });
     94  },
     95 });
     96 
     97 declTest("destroy actor by page navigates", {
     98  allFrames: true,
     99 
    100  async test(browser) {
    101    info("creating an in-process frame");
    102    await SpecialPowers.spawn(browser, [URL], async function (url) {
    103      let frame = content.document.createElement("iframe");
    104      frame.src = url;
    105      content.document.body.appendChild(frame);
    106    });
    107 
    108    info("navigating page");
    109    await SpecialPowers.spawn(browser, [TEST_URL], async function (url) {
    110      let frame = content.document.querySelector("iframe");
    111      frame.contentWindow.location = url;
    112      let child = frame.contentWindow.windowGlobalChild;
    113      let actorChild = child.getActor("TestWindow");
    114      ok(actorChild, "JSWindowActorChild should have value.");
    115 
    116      let didDestroyPromise = new Promise(resolve => {
    117        const TOPIC = "test-js-window-actor-diddestroy";
    118        Services.obs.addObserver(function obs(subject, topic, data) {
    119          ok(data, "didDestroyCallback data should be true.");
    120          is(subject, actorChild, "Should have this value");
    121 
    122          Services.obs.removeObserver(obs, TOPIC);
    123          resolve();
    124        }, TOPIC);
    125      });
    126 
    127      await Promise.all([
    128        didDestroyPromise,
    129        ContentTaskUtils.waitForEvent(frame, "load"),
    130      ]);
    131 
    132      Assert.throws(
    133        () => child.getActor("TestWindow"),
    134        /InvalidStateError/,
    135        "Should throw if frame destroy."
    136      );
    137    });
    138  },
    139 });
    140 
    141 declTest("destroy actor by tab being closed", {
    142  allFrames: true,
    143 
    144  async test() {
    145    info("creating a new tab");
    146    let newTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, URL);
    147    let newTabBrowser = newTab.linkedBrowser;
    148 
    149    let parent =
    150      newTabBrowser.browsingContext.currentWindowGlobal.getActor("TestWindow");
    151    ok(parent, "JSWindowActorParent should have value.");
    152 
    153    // We can't depend on `SpecialPowers.spawn` to resolve our promise, as the
    154    // frame message manager will be being shut down at the same time. Instead
    155    // send messages over the per-process message manager which should still be
    156    // active.
    157    let didDestroyPromise = new Promise(resolve => {
    158      Services.ppmm.addMessageListener(
    159        "test-jswindowactor-diddestroy",
    160        function onmessage() {
    161          Services.ppmm.removeMessageListener(
    162            "test-jswindowactor-diddestroy",
    163            onmessage
    164          );
    165          resolve();
    166        }
    167      );
    168    });
    169 
    170    info("setting up destroy listeners");
    171    await SpecialPowers.spawn(newTabBrowser, [], () => {
    172      let child = content.windowGlobalChild;
    173      let actorChild = child.getActor("TestWindow");
    174      ok(actorChild, "JSWindowActorChild should have value.");
    175 
    176      Services.obs.addObserver(function obs(subject, topic, data) {
    177        if (subject != actorChild) {
    178          return;
    179        }
    180        dump("DidDestroy called\n");
    181        Services.obs.removeObserver(obs, "test-js-window-actor-diddestroy");
    182        Services.cpmm.sendAsyncMessage("test-jswindowactor-diddestroy", data);
    183      }, "test-js-window-actor-diddestroy");
    184    });
    185 
    186    info("removing new tab");
    187    await BrowserTestUtils.removeTab(newTab);
    188    info("waiting for destroy callbacks to fire");
    189    await didDestroyPromise;
    190    info("got didDestroy callback");
    191  },
    192 });