253 lines
10 KiB
JavaScript
253 lines
10 KiB
JavaScript
/* -*- 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],
|
|
],
|
|
});
|
|
}
|
|
|
|
// maximize = false then minimize all windows but the last one
|
|
// this tests that minimized windows don't accumulate animated images frames
|
|
// maximize = true then maximize the last window
|
|
// this tests that occluded windows don't accumulate animated images frames
|
|
async function openWindows(maximize, taskToPerformBeforeSizeChange) {
|
|
let wins = [null, null, null, null];
|
|
for (let i = 0; i < wins.length; i++) {
|
|
let win = await BrowserTestUtils.openNewBrowserWindow();
|
|
await win.delayedStartupPromise;
|
|
await taskToPerformBeforeSizeChange(win);
|
|
|
|
if (
|
|
(!maximize && i < wins.length - 1) ||
|
|
(maximize && i == wins.length - 1)
|
|
) {
|
|
// the window might be maximized already, but it won't be minimized, only wait for
|
|
// size change if the size is actually changing.
|
|
if (!maximize || (maximize && win.windowState != win.STATE_MAXIMIZED)) {
|
|
let promiseSizeModeChange = BrowserTestUtils.waitForEvent(
|
|
win,
|
|
"sizemodechange"
|
|
);
|
|
if (maximize) {
|
|
win.maximize();
|
|
} else {
|
|
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, maximize) {
|
|
await pushPrefs1();
|
|
|
|
let wins = await openWindows(maximize, 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
|
|
let contentURL = fileURL("helper_animatedImageLeak.html");
|
|
await runTest(contentURL, /* maximize = */ true);
|
|
await runTest(contentURL, /* maximize = */ false);
|
|
|
|
// This tests the image in chrome process case
|
|
let chromeURL = getRootDirectory(gTestPath) + "helper_animatedImageLeak.html";
|
|
await runTest(chromeURL, /* maximize = */ true);
|
|
await runTest(chromeURL, /* maximize = */ false);
|
|
});
|
|
|
|
// 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() {
|
|
async function runTest(maximize) {
|
|
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 openWindows(maximize, 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");
|
|
}
|
|
|
|
await runTest(/* maximize = */ true);
|
|
await runTest(/* maximize = */ false);
|
|
});
|