tor-browser

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

browser_animatedImageLeak.js (10504B)


      1 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
      2 /* vim: set sts=2 sw=2 et tw=80: */
      3 "use strict";
      4 
      5 requestLongerTimeout(4);
      6 
      7 /*
      8 * This tests that when we have an animated image in a minimized window we
      9 * don't leak.
     10 * We've encountered this bug in 3 different ways:
     11 * -bug 1830753 - images in top level chrome processes
     12 *  (we avoid processing them due to their CompositorBridgeChild being paused)
     13 * -bug 1839109 - images in content processes
     14 *  (we avoid processing them due to their refresh driver being throttled, this
     15 *   would also fix the above case)
     16 * -bug 1875100 - images that are in a content iframe that is not the content
     17 *  of a tab, so something like an extension iframe in the sidebar
     18 *  (this was fixed by making the content of a tab declare that it manually
     19 *   manages its activeness and having all other iframes inherit their
     20 *   activeness from their parent)
     21 * In order to hit this bug we require
     22 * -the same image to be in a minimized window and in a non-mininmized window
     23 *  so that the image is animated.
     24 * -the animated image to go over the
     25 *  image.animated.decode-on-demand.threshold-kb threshold so that we do not
     26 *  keep all of its frames around (if we keep all its frame around then we
     27 *  don't try to keep allocating frames and not freeing the old ones)
     28 * -it has to be the same Image object in memory, not just the same uri
     29 * Then the visible copy of the image keeps generating new frames, those frames
     30 * get sent to the minimized copies of the image but they never get processed
     31 * or marked displayed so they can never be freed/reused.
     32 *
     33 * Note that due to bug 1889840, in order to test this we can't use an image
     34 * loaded at the top level (see the last point above). We must use an html page
     35 * that contains the image.
     36 */
     37 
     38 // this test is based in part on https://searchfox.org/mozilla-central/rev/c09764753ea40725eb50decad2c51edecbd33308/browser/components/extensions/test/browser/browser_ext_sidebarAction.js
     39 
     40 async function pushPrefs1() {
     41  await SpecialPowers.pushPrefEnv({
     42    set: [
     43      ["image.animated.decode-on-demand.threshold-kb", 1],
     44      ["image.animated.decode-on-demand.batch-size", 2],
     45    ],
     46  });
     47 }
     48 
     49 // maximize = false then minimize all windows but the last one
     50 // this tests that minimized windows don't accumulate animated images frames
     51 // maximize = true then maximize the last window
     52 // this tests that occluded windows don't accumulate animated images frames
     53 async function openWindows(maximize, taskToPerformBeforeSizeChange) {
     54  let wins = [null, null, null, null];
     55  for (let i = 0; i < wins.length; i++) {
     56    let win = await BrowserTestUtils.openNewBrowserWindow();
     57    await win.delayedStartupPromise;
     58    await taskToPerformBeforeSizeChange(win);
     59 
     60    if (
     61      (!maximize && i < wins.length - 1) ||
     62      (maximize && i == wins.length - 1)
     63    ) {
     64      // the window might be maximized already, but it won't be minimized, only wait for
     65      // size change if the size is actually changing.
     66      if (!maximize || (maximize && win.windowState != win.STATE_MAXIMIZED)) {
     67        let promiseSizeModeChange = BrowserTestUtils.waitForEvent(
     68          win,
     69          "sizemodechange"
     70        );
     71        if (maximize) {
     72          win.maximize();
     73        } else {
     74          win.minimize();
     75        }
     76        await promiseSizeModeChange;
     77      }
     78    }
     79 
     80    wins[i] = win;
     81  }
     82  return wins;
     83 }
     84 
     85 async function pushPrefs2() {
     86  // wait so that at least one frame of the animation has been shown while the
     87  // below pref is not set so that the counter gets reset.
     88  await new Promise(resolve => setTimeout(resolve, 500));
     89 
     90  await SpecialPowers.pushPrefEnv({
     91    set: [["gfx.testing.assert-render-textures-increase", 75]],
     92  });
     93 }
     94 
     95 async function waitForEnoughFrames() {
     96  // we want to wait for over 75 frames of the image, it has a delay of 200ms
     97  // Windows debug test machines seem to animate at about 10 fps though
     98  await new Promise(resolve => setTimeout(resolve, 20000));
     99 }
    100 
    101 async function closeWindows(wins) {
    102  for (let i = 0; i < wins.length; i++) {
    103    await BrowserTestUtils.closeWindow(wins[i]);
    104  }
    105 }
    106 
    107 async function popPrefs() {
    108  await SpecialPowers.popPrefEnv();
    109  await SpecialPowers.popPrefEnv();
    110 }
    111 
    112 add_task(async () => {
    113  async function runTest(theTestPath, maximize) {
    114    await pushPrefs1();
    115 
    116    let wins = await openWindows(maximize, async function (win) {
    117      let tab = await BrowserTestUtils.openNewForegroundTab(
    118        win.gBrowser,
    119        theTestPath
    120      );
    121    });
    122 
    123    await pushPrefs2();
    124 
    125    await waitForEnoughFrames();
    126 
    127    await closeWindows(wins);
    128 
    129    await popPrefs();
    130 
    131    ok(true, "got here without assserting");
    132  }
    133 
    134  function fileURL(filename) {
    135    let ifile = getChromeDir(getResolvedURI(gTestPath));
    136    ifile.append(filename);
    137    return Services.io.newFileURI(ifile).spec;
    138  }
    139 
    140  // This tests the image in content process case
    141  let contentURL = fileURL("helper_animatedImageLeak.html");
    142  await runTest(contentURL, /* maximize = */ true);
    143  await runTest(contentURL, /* maximize = */ false);
    144 
    145  // This tests the image in chrome process case
    146  let chromeURL = getRootDirectory(gTestPath) + "helper_animatedImageLeak.html";
    147  await runTest(chromeURL, /* maximize = */ true);
    148  await runTest(chromeURL, /* maximize = */ false);
    149 });
    150 
    151 // Now we test the image in a sidebar loaded via an extension case.
    152 
    153 /*
    154 * The data uri below is a 2kb apng that is 3000x200 with 22 frames with delay
    155 * of 200ms, it just toggles the color of one pixel from black to red so it's
    156 * tiny. We use the same data uri (although that is not important to this test)
    157 * in helper_animatedImageLeak.html.
    158 */
    159 
    160 /*
    161 * This is just data to create a simple extension that creates a sidebar with
    162 * an image in it.
    163 */
    164 let extData = {
    165  manifest: {
    166    sidebar_action: {
    167      default_panel: "sidebar.html",
    168    },
    169  },
    170  useAddonManager: "temporary",
    171 
    172  files: {
    173    "sidebar.html": `
    174 <!DOCTYPE html>
    175 <html>
    176  <head>
    177    <meta charset="UTF-8">
    178    <script src="sidebar.js"></script>
    179  </head>
    180  <body><p>Sidebar</p>
    181  <img src="data:image/png;base64,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"/>
    182  </body>
    183 </html>
    184    `,
    185 
    186    "sidebar.js": function () {
    187      window.onload = () => {
    188        browser.test.sendMessage("sidebar");
    189      };
    190    },
    191  },
    192 };
    193 
    194 function getExtData(manifestUpdates = {}) {
    195  return {
    196    ...extData,
    197    manifest: {
    198      ...extData.manifest,
    199      ...manifestUpdates,
    200    },
    201  };
    202 }
    203 
    204 async function sendMessage(ext, msg, data = undefined) {
    205  ext.sendMessage({ msg, data });
    206  await ext.awaitMessage("done");
    207 }
    208 
    209 add_task(async function sidebar_initial_install() {
    210  async function runTest(maximize) {
    211    await pushPrefs1();
    212 
    213    ok(
    214      document.getElementById("sidebar-box").hidden,
    215      "sidebar box is not visible"
    216    );
    217 
    218    let extension = ExtensionTestUtils.loadExtension(getExtData());
    219    await extension.startup();
    220    await extension.awaitMessage("sidebar");
    221 
    222    // Test sidebar is opened on install
    223    ok(
    224      !document.getElementById("sidebar-box").hidden,
    225      "sidebar box is visible"
    226    );
    227 
    228    // the sidebar appears on all new windows automatically.
    229    let wins = await openWindows(maximize, async function (win) {
    230      await extension.awaitMessage("sidebar");
    231    });
    232 
    233    await pushPrefs2();
    234 
    235    await waitForEnoughFrames();
    236 
    237    await extension.unload();
    238    // Test that the sidebar was closed on unload.
    239    ok(
    240      document.getElementById("sidebar-box").hidden,
    241      "sidebar box is not visible"
    242    );
    243 
    244    await closeWindows(wins);
    245 
    246    await popPrefs();
    247 
    248    ok(true, "got here without assserting");
    249  }
    250 
    251  await runTest(/* maximize = */ true);
    252  await runTest(/* maximize = */ false);
    253 });