summaryrefslogtreecommitdiffstats
path: root/layout/base/tests/browser_animatedImageLeak.js
blob: 2c34ed9d896b9fc6f9daf158e4a27d5f4711c6b6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";

requestLongerTimeout(4);

/*
 * This tests that when we have an animated image in a minimized window we
 * don't leak.
 * We've encountered this bug in 3 different ways:
 * -bug 1830753 - images in top level chrome processes
 *  (we avoid processing them due to their CompositorBridgeChild being paused)
 * -bug 1839109 - images in content processes
 *  (we avoid processing them due to their refresh driver being throttled, this
 *   would also fix the above case)
 * -bug 1875100 - images that are in a content iframe that is not the content
 *  of a tab, so something like an extension iframe in the sidebar
 *  (this was fixed by making the content of a tab declare that it manually
 *   manages its activeness and having all other iframes inherit their
 *   activeness from their parent)
 * In order to hit this bug we require
 * -the same image to be in a minimized window and in a non-mininmized window
 *  so that the image is animated.
 * -the animated image to go over the
 *  image.animated.decode-on-demand.threshold-kb threshold so that we do not
 *  keep all of its frames around (if we keep all its frame around then we
 *  don't try to keep allocating frames and not freeing the old ones)
 * -it has to be the same Image object in memory, not just the same uri
 * Then the visible copy of the image keeps generating new frames, those frames
 * get sent to the minimized copies of the image but they never get processed
 * or marked displayed so they can never be freed/reused.
 *
 * Note that due to bug 1889840, in order to test this we can't use an image
 * loaded at the top level (see the last point above). We must use an html page
 * that contains the image.
 */

// this test is based in part on https://searchfox.org/mozilla-central/rev/c09764753ea40725eb50decad2c51edecbd33308/browser/components/extensions/test/browser/browser_ext_sidebarAction.js

async function pushPrefs1() {
  await SpecialPowers.pushPrefEnv({
    set: [
      ["image.animated.decode-on-demand.threshold-kb", 1],
      ["image.animated.decode-on-demand.batch-size", 2],
    ],
  });
}

async function openWindowsAndMinimize(taskToPerformBeforeMinimize) {
  let wins = [null, null, null, null];
  for (let i = 0; i < wins.length; i++) {
    let win = await BrowserTestUtils.openNewBrowserWindow();
    await win.delayedStartupPromise;
    await taskToPerformBeforeMinimize(win);

    // Leave the last window un-minimized.
    if (i < wins.length - 1) {
      let promiseSizeModeChange = BrowserTestUtils.waitForEvent(
        win,
        "sizemodechange"
      );
      win.minimize();
      await promiseSizeModeChange;
    }

    wins[i] = win;
  }
  return wins;
}

async function pushPrefs2() {
  // wait so that at least one frame of the animation has been shown while the
  // below pref is not set so that the counter gets reset.
  await new Promise(resolve => setTimeout(resolve, 500));

  await SpecialPowers.pushPrefEnv({
    set: [["gfx.testing.assert-render-textures-increase", 75]],
  });
}

async function waitForEnoughFrames() {
  // we want to wait for over 75 frames of the image, it has a delay of 200ms
  // Windows debug test machines seem to animate at about 10 fps though
  await new Promise(resolve => setTimeout(resolve, 20000));
}

async function closeWindows(wins) {
  for (let i = 0; i < wins.length; i++) {
    await BrowserTestUtils.closeWindow(wins[i]);
  }
}

async function popPrefs() {
  await SpecialPowers.popPrefEnv();
  await SpecialPowers.popPrefEnv();
}

add_task(async () => {
  async function runTest(theTestPath) {
    await pushPrefs1();

    let wins = await openWindowsAndMinimize(async function (win) {
      let tab = await BrowserTestUtils.openNewForegroundTab(
        win.gBrowser,
        theTestPath
      );
    });

    await pushPrefs2();

    await waitForEnoughFrames();

    await closeWindows(wins);

    await popPrefs();

    ok(true, "got here without assserting");
  }

  function fileURL(filename) {
    let ifile = getChromeDir(getResolvedURI(gTestPath));
    ifile.append(filename);
    return Services.io.newFileURI(ifile).spec;
  }

  // This tests the image in content process case
  await runTest(fileURL("helper_animatedImageLeak.html"));
  // This tests the image in chrome process case
  await runTest(getRootDirectory(gTestPath) + "helper_animatedImageLeak.html");
});

// Now we test the image in a sidebar loaded via an extension case.

/*
 * The data uri below is a 2kb apng that is 3000x200 with 22 frames with delay
 * of 200ms, it just toggles the color of one pixel from black to red so it's
 * tiny. We use the same data uri (although that is not important to this test)
 * in helper_animatedImageLeak.html.
 */

/*
 * This is just data to create a simple extension that creates a sidebar with
 * an image in it.
 */
let extData = {
  manifest: {
    sidebar_action: {
      default_panel: "sidebar.html",
    },
  },
  useAddonManager: "temporary",

  files: {
    "sidebar.html": `
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <script src="sidebar.js"></script>
  </head>
  <body><p>Sidebar</p>
  <img src="data:image/png;base64,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"/>
  </body>
</html>
    `,

    "sidebar.js": function () {
      window.onload = () => {
        browser.test.sendMessage("sidebar");
      };
    },
  },
};

function getExtData(manifestUpdates = {}) {
  return {
    ...extData,
    manifest: {
      ...extData.manifest,
      ...manifestUpdates,
    },
  };
}

async function sendMessage(ext, msg, data = undefined) {
  ext.sendMessage({ msg, data });
  await ext.awaitMessage("done");
}

add_task(async function sidebar_initial_install() {
  await pushPrefs1();

  ok(
    document.getElementById("sidebar-box").hidden,
    "sidebar box is not visible"
  );

  let extension = ExtensionTestUtils.loadExtension(getExtData());
  await extension.startup();
  await extension.awaitMessage("sidebar");

  // Test sidebar is opened on install
  ok(!document.getElementById("sidebar-box").hidden, "sidebar box is visible");

  // the sidebar appears on all new windows automatically.
  let wins = await openWindowsAndMinimize(async function (win) {
    await extension.awaitMessage("sidebar");
  });

  await pushPrefs2();

  await waitForEnoughFrames();

  await extension.unload();
  // Test that the sidebar was closed on unload.
  ok(
    document.getElementById("sidebar-box").hidden,
    "sidebar box is not visible"
  );

  await closeWindows(wins);

  await popPrefs();

  ok(true, "got here without assserting");
});