summaryrefslogtreecommitdiffstats
path: root/image/test/browser
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
commit0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch)
treea31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /image/test/browser
parentInitial commit. (diff)
downloadfirefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.tar.xz
firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.zip
Adding upstream version 115.8.0esr.upstream/115.8.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'image/test/browser')
-rw-r--r--image/test/browser/animated.gifbin0 -> 71479 bytes
-rw-r--r--image/test/browser/animated2.gifbin0 -> 66647 bytes
-rw-r--r--image/test/browser/big.pngbin0 -> 129497 bytes
-rw-r--r--image/test/browser/browser.ini20
-rw-r--r--image/test/browser/browser_bug666317.js138
-rw-r--r--image/test/browser/browser_docshell_type_editor.js134
-rw-r--r--image/test/browser/browser_docshell_type_editor/chrome.manifest1
-rw-r--r--image/test/browser/browser_docshell_type_editor/img/privileged.pngbin0 -> 90 bytes
-rw-r--r--image/test/browser/browser_image.js261
-rw-r--r--image/test/browser/browser_mozicon_file.js12
-rw-r--r--image/test/browser/browser_mozicon_file_sandbox_headless.js13
-rw-r--r--image/test/browser/browser_offscreen_image_in_out_of_process_iframe.js164
-rw-r--r--image/test/browser/browser_sandbox_headless.ini8
-rw-r--r--image/test/browser/empty.html2
-rw-r--r--image/test/browser/head.js136
-rw-r--r--image/test/browser/image.html23
-rw-r--r--image/test/browser/imageX2.html14
17 files changed, 926 insertions, 0 deletions
diff --git a/image/test/browser/animated.gif b/image/test/browser/animated.gif
new file mode 100644
index 0000000000..eb034e1501
--- /dev/null
+++ b/image/test/browser/animated.gif
Binary files differ
diff --git a/image/test/browser/animated2.gif b/image/test/browser/animated2.gif
new file mode 100644
index 0000000000..053eaae688
--- /dev/null
+++ b/image/test/browser/animated2.gif
Binary files differ
diff --git a/image/test/browser/big.png b/image/test/browser/big.png
new file mode 100644
index 0000000000..94e7eb6db2
--- /dev/null
+++ b/image/test/browser/big.png
Binary files differ
diff --git a/image/test/browser/browser.ini b/image/test/browser/browser.ini
new file mode 100644
index 0000000000..b5ba8581a3
--- /dev/null
+++ b/image/test/browser/browser.ini
@@ -0,0 +1,20 @@
+[DEFAULT]
+support-files =
+ animated.gif
+ animated2.gif
+ big.png
+ head.js
+ image.html
+ imageX2.html
+ browser_docshell_type_editor/**
+
+[browser_bug666317.js]
+skip-if = true # Bug 1207012 - Permaorange from an uncaught exception that isn't actually turning the suite orange until it hits beta, Bug 948194 - Decoded Images seem to not be discarded on memory-pressure notification
+[browser_image.js]
+skip-if = true # Bug 987616
+[browser_docshell_type_editor.js]
+[browser_offscreen_image_in_out_of_process_iframe.js]
+https_first_disabled = true
+support-files =
+ empty.html
+[browser_mozicon_file.js]
diff --git a/image/test/browser/browser_bug666317.js b/image/test/browser/browser_bug666317.js
new file mode 100644
index 0000000000..7f58c61c56
--- /dev/null
+++ b/image/test/browser/browser_bug666317.js
@@ -0,0 +1,138 @@
+waitForExplicitFinish();
+
+var pageSource =
+ "<html><body>" +
+ '<img id="testImg" src="' +
+ TESTROOT +
+ 'big.png">' +
+ "</body></html>";
+
+var oldDiscardingPref, oldTab, newTab;
+var prefBranch = Services.prefs.getBranch("image.mem.");
+
+var gWaitingForDiscard = false;
+var gScriptedObserver;
+var gClonedRequest;
+
+function ImageObserver(decodeCallback, discardCallback) {
+ this.decodeComplete = function onDecodeComplete(aRequest) {
+ decodeCallback();
+ };
+
+ this.discard = function onDiscard(request) {
+ if (!gWaitingForDiscard) {
+ return;
+ }
+
+ this.synchronous = false;
+ discardCallback();
+ };
+
+ this.synchronous = true;
+}
+
+function currentRequest() {
+ let img = gBrowser
+ .getBrowserForTab(newTab)
+ .contentWindow.document.getElementById("testImg");
+ return img.getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST);
+}
+
+function isImgDecoded() {
+ let request = currentRequest();
+ return !!(request.imageStatus & Ci.imgIRequest.STATUS_DECODE_COMPLETE);
+}
+
+// Ensure that the image is decoded by drawing it to a canvas.
+function forceDecodeImg() {
+ let doc = gBrowser.getBrowserForTab(newTab).contentWindow.document;
+ let img = doc.getElementById("testImg");
+ let canvas = doc.createElement("canvas");
+ let ctx = canvas.getContext("2d");
+ ctx.drawImage(img, 0, 0);
+}
+
+function runAfterAsyncEvents(aCallback) {
+ function handlePostMessage(aEvent) {
+ if (aEvent.data == "next") {
+ window.removeEventListener("message", handlePostMessage);
+ aCallback();
+ }
+ }
+
+ window.addEventListener("message", handlePostMessage);
+
+ // We'll receive the 'message' event after everything else that's currently in
+ // the event queue (which is a stronger guarantee than setTimeout, because
+ // setTimeout events may be coalesced). This lets us ensure that we run
+ // aCallback *after* any asynchronous events are delivered.
+ window.postMessage("next", "*");
+}
+
+function test() {
+ // Enable the discarding pref.
+ oldDiscardingPref = prefBranch.getBoolPref("discardable");
+ prefBranch.setBoolPref("discardable", true);
+
+ // Create and focus a new tab.
+ oldTab = gBrowser.selectedTab;
+ newTab = BrowserTestUtils.addTab(gBrowser, "data:text/html," + pageSource);
+ gBrowser.selectedTab = newTab;
+
+ // Run step2 after the tab loads.
+ gBrowser.getBrowserForTab(newTab).addEventListener("pageshow", step2);
+}
+
+function step2() {
+ // Create the image observer.
+ var observer = new ImageObserver(
+ () => runAfterAsyncEvents(step3), // DECODE_COMPLETE
+ () => runAfterAsyncEvents(step5)
+ ); // DISCARD
+ gScriptedObserver = Cc["@mozilla.org/image/tools;1"]
+ .getService(Ci.imgITools)
+ .createScriptedObserver(observer);
+
+ // Clone the current imgIRequest with our new observer.
+ var request = currentRequest();
+ gClonedRequest = request.clone(gScriptedObserver);
+
+ // Check that the image is decoded.
+ forceDecodeImg();
+
+ // The DECODE_COMPLETE notification is delivered asynchronously. ImageObserver will
+ // eventually call step3.
+}
+
+function step3() {
+ ok(isImgDecoded(), "Image should initially be decoded.");
+
+ // Focus the old tab, then fire a memory-pressure notification. This should
+ // cause the decoded image in the new tab to be discarded.
+ gBrowser.selectedTab = oldTab;
+
+ // Allow time to process the tab change.
+ runAfterAsyncEvents(step4);
+}
+
+function step4() {
+ gWaitingForDiscard = true;
+
+ var os = Services.obs;
+ os.notifyObservers(null, "memory-pressure", "heap-minimize");
+
+ // The DISCARD notification is delivered asynchronously. ImageObserver will
+ // eventually call step5. (Or else, sadly, the test will time out.)
+}
+
+function step5() {
+ ok(true, "Image should be discarded.");
+
+ // And we're done.
+ gBrowser.removeTab(newTab);
+ prefBranch.setBoolPref("discardable", oldDiscardingPref);
+
+ gClonedRequest.cancelAndForgetObserver(0);
+
+ finish();
+}
diff --git a/image/test/browser/browser_docshell_type_editor.js b/image/test/browser/browser_docshell_type_editor.js
new file mode 100644
index 0000000000..baa89c0f07
--- /dev/null
+++ b/image/test/browser/browser_docshell_type_editor.js
@@ -0,0 +1,134 @@
+"use strict";
+
+const SIMPLE_HTML = "data:text/html,<html><head></head><body></body></html>";
+
+/**
+ * Returns the directory where the chrome.manifest file for the test can be found.
+ *
+ * @return nsIFile of the manifest directory
+ */
+function getManifestDir() {
+ let path = getTestFilePath("browser_docshell_type_editor");
+ let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ file.initWithPath(path);
+ return file;
+}
+
+// The following URI is *not* accessible to content, hence loading that URI
+// from an unprivileged site should be blocked. If docshell is of appType
+// APP_TYPE_EDITOR however the load should be allowed.
+// >> chrome://test1/skin/privileged.png
+
+add_task(async function () {
+ info("docshell of appType APP_TYPE_EDITOR can access privileged images.");
+
+ // Load a temporary manifest adding a route to a privileged image
+ let manifestDir = getManifestDir();
+ Components.manager.addBootstrappedManifestLocation(manifestDir);
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: SIMPLE_HTML,
+ },
+ async function (browser) {
+ await SpecialPowers.spawn(browser, [], async function () {
+ let rootDocShell = docShell.sameTypeRootTreeItem.QueryInterface(
+ Ci.nsIDocShell
+ );
+ let defaultAppType = rootDocShell.appType;
+
+ rootDocShell.appType = Ci.nsIDocShell.APP_TYPE_EDITOR;
+
+ is(
+ rootDocShell.appType,
+ Ci.nsIDocShell.APP_TYPE_EDITOR,
+ "sanity check: appType after update should be type editor"
+ );
+
+ return new Promise(resolve => {
+ let doc = content.document;
+ let image = doc.createElement("img");
+ image.onload = function () {
+ ok(true, "APP_TYPE_EDITOR is allowed to load privileged image");
+ // restore appType of rootDocShell before moving on to the next test
+ rootDocShell.appType = defaultAppType;
+ resolve();
+ };
+ image.onerror = function () {
+ ok(false, "APP_TYPE_EDITOR is allowed to load privileged image");
+ // restore appType of rootDocShell before moving on to the next test
+ rootDocShell.appType = defaultAppType;
+ resolve();
+ };
+ doc.body.appendChild(image);
+ image.src = "chrome://test1/skin/privileged.png";
+ });
+ });
+ }
+ );
+
+ Components.manager.removeBootstrappedManifestLocation(manifestDir);
+});
+
+add_task(async function () {
+ info(
+ "docshell of appType APP_TYPE_UNKNOWN can *not* access privileged images."
+ );
+
+ // Load a temporary manifest adding a route to a privileged image
+ let manifestDir = getManifestDir();
+ Components.manager.addBootstrappedManifestLocation(manifestDir);
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: SIMPLE_HTML,
+ },
+ async function (browser) {
+ await SpecialPowers.spawn(browser, [], async function () {
+ let rootDocShell = docShell.sameTypeRootTreeItem.QueryInterface(
+ Ci.nsIDocShell
+ );
+ let defaultAppType = rootDocShell.appType;
+
+ rootDocShell.appType = Ci.nsIDocShell.APP_TYPE_UNKNOWN;
+
+ is(
+ rootDocShell.appType,
+ Ci.nsIDocShell.APP_TYPE_UNKNOWN,
+ "sanity check: appType of docshell should be unknown"
+ );
+
+ return new Promise(resolve => {
+ let doc = content.document;
+ let image = doc.createElement("img");
+ image.onload = function () {
+ ok(
+ false,
+ "APP_TYPE_UNKNOWN is *not* allowed to access privileged image"
+ );
+ // restore appType of rootDocShell before moving on to the next test
+ rootDocShell.appType = defaultAppType;
+ resolve();
+ };
+ image.onerror = function () {
+ ok(
+ true,
+ "APP_TYPE_UNKNOWN is *not* allowed to access privileged image"
+ );
+ // restore appType of rootDocShell before moving on to the next test
+ rootDocShell.appType = defaultAppType;
+ resolve();
+ };
+ doc.body.appendChild(image);
+ // Set the src via wrappedJSObject so the load is triggered with
+ // the content page's principal rather than ours.
+ image.wrappedJSObject.src = "chrome://test1/skin/privileged.png";
+ });
+ });
+ }
+ );
+
+ Components.manager.removeBootstrappedManifestLocation(manifestDir);
+});
diff --git a/image/test/browser/browser_docshell_type_editor/chrome.manifest b/image/test/browser/browser_docshell_type_editor/chrome.manifest
new file mode 100644
index 0000000000..85510a8af9
--- /dev/null
+++ b/image/test/browser/browser_docshell_type_editor/chrome.manifest
@@ -0,0 +1 @@
+skin test1 test img/ \ No newline at end of file
diff --git a/image/test/browser/browser_docshell_type_editor/img/privileged.png b/image/test/browser/browser_docshell_type_editor/img/privileged.png
new file mode 100644
index 0000000000..2bf7b7e828
--- /dev/null
+++ b/image/test/browser/browser_docshell_type_editor/img/privileged.png
Binary files differ
diff --git a/image/test/browser/browser_image.js b/image/test/browser/browser_image.js
new file mode 100644
index 0000000000..0ae55df640
--- /dev/null
+++ b/image/test/browser/browser_image.js
@@ -0,0 +1,261 @@
+waitForExplicitFinish();
+requestLongerTimeout(2); // see bug 660123 -- this test is slow on Mac.
+
+// A hold on the current timer, so it doesn't get GCed out from
+// under us
+var gTimer;
+
+// Browsing to a new URL - pushing us into the bfcache - should cause
+// animations to stop, and resume when we return
+/* global yield */
+function testBFCache() {
+ function theTest() {
+ var abort = false;
+ var chances, gImage, gFrames;
+ gBrowser.selectedTab = BrowserTestUtils.addTab(
+ gBrowser,
+ TESTROOT + "image.html"
+ );
+ gBrowser.selectedBrowser.addEventListener(
+ "pageshow",
+ function () {
+ var window = gBrowser.contentWindow;
+ // If false, we are in an optimized build, and we abort this and
+ // all further tests
+ if (
+ !actOnMozImage(window.document, "img1", function (image) {
+ gImage = image;
+ gFrames = gImage.framesNotified;
+ })
+ ) {
+ gBrowser.removeCurrentTab();
+ abort = true;
+ }
+ goer.next();
+ },
+ { capture: true, once: true }
+ );
+ yield;
+ if (abort) {
+ finish();
+ yield; // optimized build
+ }
+
+ // Let animation run for a bit
+ chances = 120;
+ do {
+ gTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ gTimer.initWithCallback(
+ function () {
+ if (gImage.framesNotified >= 20) {
+ goer.send(true);
+ } else {
+ chances--;
+ goer.send(chances == 0); // maybe if we wait a bit, it will happen
+ }
+ },
+ 500,
+ Ci.nsITimer.TYPE_ONE_SHOT
+ );
+ } while (!yield);
+ is(chances > 0, true, "Must have animated a few frames so far");
+
+ // Browse elsewhere; push our animating page into the bfcache
+ gBrowser.loadURI(Services.io.newURI("about:blank"));
+
+ // Wait a bit for page to fully load, then wait a while and
+ // see that no animation occurs.
+ gTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ gTimer.initWithCallback(
+ function () {
+ gFrames = gImage.framesNotified;
+ gTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ gTimer.initWithCallback(
+ function () {
+ // Might have a few stray frames, until other page totally loads
+ var additionalFrames = gImage.framesNotified - gFrames;
+ is(
+ additionalFrames == 0,
+ true,
+ "Must have not animated in bfcache! Got " +
+ additionalFrames +
+ " additional frames"
+ );
+ goer.next();
+ },
+ 4000,
+ Ci.nsITimer.TYPE_ONE_SHOT
+ ); // 4 seconds - expect 40 frames
+ },
+ 0,
+ Ci.nsITimer.TYPE_ONE_SHOT
+ ); // delay of 0 - wait for next event loop
+ yield;
+
+ // Go back
+ gBrowser.goBack();
+
+ chances = 120;
+ do {
+ gTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ gTimer.initWithCallback(
+ function () {
+ if (gImage.framesNotified - gFrames >= 20) {
+ goer.send(true);
+ } else {
+ chances--;
+ goer.send(chances == 0); // maybe if we wait a bit, it will happen
+ }
+ },
+ 500,
+ Ci.nsITimer.TYPE_ONE_SHOT
+ );
+ } while (!yield);
+ is(chances > 0, true, "Must have animated once out of bfcache!");
+
+ // Finally, check that the css background image has essentially the same
+ // # of frames, implying that it animated at the same times as the regular
+ // image. We can easily retrieve regular images through their HTML image
+ // elements, which is what we did before. For the background image, we
+ // create a regular image now, and read the current frame count.
+ var doc = gBrowser.selectedBrowser.contentWindow.document;
+ var div = doc.getElementById("background_div");
+ div.innerHTML += '<img src="animated2.gif" id="img3">';
+ actOnMozImage(doc, "img3", function (image) {
+ is(
+ Math.abs(image.framesNotified - gImage.framesNotified) /
+ gImage.framesNotified <
+ 0.5,
+ true,
+ "Must have also animated the background image, and essentially the same # of frames. " +
+ "Regular image got " +
+ gImage.framesNotified +
+ " frames but background image got " +
+ image.framesNotified
+ );
+ });
+
+ gBrowser.removeCurrentTab();
+
+ nextTest();
+ }
+
+ var goer = theTest();
+ goer.next();
+}
+
+// Check that imgContainers are shared on the same page and
+// between tabs
+function testSharedContainers() {
+ function theTest() {
+ var gImages = [];
+ var gFrames;
+
+ gBrowser.selectedTab = BrowserTestUtils.addTab(
+ gBrowser,
+ TESTROOT + "image.html"
+ );
+ gBrowser.selectedBrowser.addEventListener(
+ "pageshow",
+ function () {
+ actOnMozImage(gBrowser.contentDocument, "img1", function (image) {
+ gImages[0] = image;
+ gFrames = image.framesNotified; // May in theory have frames from last test
+ // in this counter - so subtract them out
+ });
+ goer.next();
+ },
+ { capture: true, once: true }
+ );
+ yield;
+
+ // Load next tab somewhat later
+ gTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ gTimer.initWithCallback(
+ function () {
+ goer.next();
+ },
+ 1500,
+ Ci.nsITimer.TYPE_ONE_SHOT
+ );
+ yield;
+
+ gBrowser.selectedTab = BrowserTestUtils.addTab(
+ gBrowser,
+ TESTROOT + "imageX2.html"
+ );
+ gBrowser.selectedBrowser.addEventListener(
+ "pageshow",
+ function () {
+ [1, 2].forEach(function (i) {
+ actOnMozImage(gBrowser.contentDocument, "img" + i, function (image) {
+ gImages[i] = image;
+ });
+ });
+ goer.next();
+ },
+ { capture: true, once: true }
+ );
+ yield;
+
+ var chances = 120;
+ do {
+ gTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ gTimer.initWithCallback(
+ function () {
+ if (gImages[0].framesNotified - gFrames >= 10) {
+ goer.send(true);
+ } else {
+ chances--;
+ goer.send(chances == 0); // maybe if we wait a bit, it will happen
+ }
+ },
+ 500,
+ Ci.nsITimer.TYPE_ONE_SHOT
+ );
+ } while (!yield);
+ is(
+ chances > 0,
+ true,
+ "Must have been animating while showing several images"
+ );
+
+ // Check they all have the same frame counts
+ var theFrames = null;
+ [0, 1, 2].forEach(function (i) {
+ var frames = gImages[i].framesNotified;
+ if (theFrames == null) {
+ theFrames = frames;
+ } else {
+ is(
+ theFrames,
+ frames,
+ "Sharing the same imgContainer means *exactly* the same frame counts!"
+ );
+ }
+ });
+
+ gBrowser.removeCurrentTab();
+ gBrowser.removeCurrentTab();
+
+ nextTest();
+ }
+
+ var goer = theTest();
+ goer.next();
+}
+
+var tests = [testBFCache, testSharedContainers];
+
+function nextTest() {
+ if (!tests.length) {
+ finish();
+ return;
+ }
+ tests.shift()();
+}
+
+function test() {
+ ignoreAllUncaughtExceptions();
+ nextTest();
+}
diff --git a/image/test/browser/browser_mozicon_file.js b/image/test/browser/browser_mozicon_file.js
new file mode 100644
index 0000000000..8e01e0484d
--- /dev/null
+++ b/image/test/browser/browser_mozicon_file.js
@@ -0,0 +1,12 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+add_task(async function test_mozicon_file_no_sandbox() {
+ assertFileProcess();
+ await createMozIconInFile("txt");
+ await createMozIconInFile("exe");
+ await createMozIconInFile("non-existent-bidule");
+});
diff --git a/image/test/browser/browser_mozicon_file_sandbox_headless.js b/image/test/browser/browser_mozicon_file_sandbox_headless.js
new file mode 100644
index 0000000000..08e8689904
--- /dev/null
+++ b/image/test/browser/browser_mozicon_file_sandbox_headless.js
@@ -0,0 +1,13 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+add_task(async function test_mozicon_file_with_sandbox() {
+ assertFileProcess();
+ assertSandboxHeadless();
+ await createMozIconInFile("txt");
+ await createMozIconInFile("exe");
+ await createMozIconInFile("non-existent-bidule");
+});
diff --git a/image/test/browser/browser_offscreen_image_in_out_of_process_iframe.js b/image/test/browser/browser_offscreen_image_in_out_of_process_iframe.js
new file mode 100644
index 0000000000..71b34a4715
--- /dev/null
+++ b/image/test/browser/browser_offscreen_image_in_out_of_process_iframe.js
@@ -0,0 +1,164 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const DIRPATH = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content/",
+ ""
+);
+const parentPATH = DIRPATH + "empty.html";
+const iframePATH = DIRPATH + "empty.html";
+
+const parentURL = `http://example.com/${parentPATH}`;
+const iframeURL = `http://example.org/${iframePATH}`;
+
+add_task(async function setup_pref() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ // To avoid throttling requestAnimationFrame callbacks in invisible
+ // iframes
+ ["layout.throttled_frame_rate", 60],
+ ],
+ });
+});
+
+add_task(async function () {
+ const win = await BrowserTestUtils.openNewBrowserWindow({
+ fission: true,
+ });
+
+ try {
+ const browser = win.gBrowser.selectedTab.linkedBrowser;
+
+ BrowserTestUtils.loadURIString(browser, parentURL);
+ await BrowserTestUtils.browserLoaded(browser, false, parentURL);
+
+ async function setup(url) {
+ const scroller = content.document.createElement("div");
+ scroller.style = "width: 300px; height: 300px; overflow: scroll;";
+ scroller.setAttribute("id", "scroller");
+ content.document.body.appendChild(scroller);
+
+ // Make a space bigger than display port.
+ const spacer = content.document.createElement("div");
+ spacer.style = "width: 100%; height: 10000px;";
+ scroller.appendChild(spacer);
+
+ const iframe = content.document.createElement("iframe");
+ scroller.appendChild(iframe);
+
+ iframe.contentWindow.location = url;
+ await new Promise(resolve => {
+ iframe.addEventListener("load", resolve, { once: true });
+ });
+
+ return iframe.browsingContext;
+ }
+
+ async function setupImage() {
+ const img = content.document.createElement("img");
+ // This GIF is a 100ms interval animation.
+ img.setAttribute("src", "animated.gif");
+ img.setAttribute("id", "img");
+ content.document.body.appendChild(img);
+
+ const spacer = content.document.createElement("div");
+ spacer.style = "width: 100%; height: 10000px;";
+ content.document.body.appendChild(spacer);
+ await new Promise(resolve => {
+ img.addEventListener("load", resolve, { once: true });
+ });
+ }
+
+ // Returns the count of frameUpdate during |time| (in ms) period.
+ async function observeFrameUpdate(time) {
+ function ImageDecoderObserverStub() {
+ this.sizeAvailable = function sizeAvailable(aRequest) {};
+ this.frameComplete = function frameComplete(aRequest) {};
+ this.decodeComplete = function decodeComplete(aRequest) {};
+ this.loadComplete = function loadComplete(aRequest) {};
+ this.frameUpdate = function frameUpdate(aRequest) {};
+ this.discard = function discard(aRequest) {};
+ this.isAnimated = function isAnimated(aRequest) {};
+ this.hasTransparency = function hasTransparency(aRequest) {};
+ }
+
+ // Start from the callback of setTimeout.
+ await new Promise(resolve => content.window.setTimeout(resolve, 0));
+
+ let frameCount = 0;
+ const observer = new ImageDecoderObserverStub();
+ observer.frameUpdate = () => {
+ frameCount++;
+ };
+ observer.loadComplete = () => {
+ // Ignore the frameUpdate callback along with loadComplete. It seems
+ // a frameUpdate sometimes happens with a loadComplete when attatching
+ // observer in fission world.
+ frameCount--;
+ };
+
+ const gObserver = SpecialPowers.Cc["@mozilla.org/image/tools;1"]
+ .getService(SpecialPowers.Ci.imgITools)
+ .createScriptedObserver(observer);
+ const img = content.document.getElementById("img");
+
+ SpecialPowers.wrap(img).addObserver(gObserver);
+ await new Promise(resolve => content.window.setTimeout(resolve, time));
+ SpecialPowers.wrap(img).removeObserver(gObserver);
+
+ return frameCount;
+ }
+
+ // Setup an iframe which is initially scrolled out.
+ const iframe = await SpecialPowers.spawn(browser, [iframeURL], setup);
+
+ // Setup a 100ms interval animated GIF image in the iframe.
+ await SpecialPowers.spawn(iframe, [], setupImage);
+
+ let frameCount = await SpecialPowers.spawn(
+ iframe,
+ [1000],
+ observeFrameUpdate
+ );
+ // Bug 1577084.
+ if (frameCount == 0) {
+ is(frameCount, 0, "no frameupdates");
+ } else {
+ todo_is(frameCount, 0, "no frameupdates");
+ }
+
+ // Scroll the iframe into view.
+ await SpecialPowers.spawn(browser, [], async () => {
+ const scroller = content.document.getElementById("scroller");
+ scroller.scrollTo({ left: 0, top: 9800, behavior: "smooth" });
+ await new Promise(resolve => content.window.setTimeout(resolve, 1000));
+ });
+
+ await new Promise(resolve => requestAnimationFrame(resolve));
+
+ frameCount = await SpecialPowers.spawn(iframe, [1000], observeFrameUpdate);
+ ok(frameCount > 0, "There should be frameUpdate(s)");
+
+ await new Promise(resolve => requestAnimationFrame(resolve));
+
+ await SpecialPowers.spawn(iframe, [], async () => {
+ const img = content.document.getElementById("img");
+ // Move the image outside of the scroll port. 'position: absolute' causes
+ // a relow on the image element.
+ img.style = "position: absolute; top: 9000px;";
+ await new Promise(resolve =>
+ content.window.requestAnimationFrame(resolve)
+ );
+ });
+
+ await new Promise(resolve => requestAnimationFrame(resolve));
+
+ frameCount = await SpecialPowers.spawn(iframe, [1000], observeFrameUpdate);
+ is(frameCount, 0, "No frameUpdate should happen");
+ } finally {
+ await BrowserTestUtils.closeWindow(win);
+ }
+});
diff --git a/image/test/browser/browser_sandbox_headless.ini b/image/test/browser/browser_sandbox_headless.ini
new file mode 100644
index 0000000000..70cd0147ca
--- /dev/null
+++ b/image/test/browser/browser_sandbox_headless.ini
@@ -0,0 +1,8 @@
+[DEFAULT]
+support-files =
+ head.js
+prefs =
+ security.sandbox.content.headless=true
+skip-if = (os != 'linux') # the pref is only used on linux
+
+[browser_mozicon_file_sandbox_headless.js]
diff --git a/image/test/browser/empty.html b/image/test/browser/empty.html
new file mode 100644
index 0000000000..a31dad3630
--- /dev/null
+++ b/image/test/browser/empty.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
diff --git a/image/test/browser/head.js b/image/test/browser/head.js
new file mode 100644
index 0000000000..29fc67a1a7
--- /dev/null
+++ b/image/test/browser/head.js
@@ -0,0 +1,136 @@
+const RELATIVE_DIR = "image/test/browser/";
+const TESTROOT = "http://example.com/browser/" + RELATIVE_DIR;
+const TESTROOT2 = "http://example.org/browser/" + RELATIVE_DIR;
+
+var chrome_root = getRootDirectory(gTestPath);
+const CHROMEROOT = chrome_root;
+
+function getImageLoading(doc, id) {
+ return doc.getElementById(id);
+}
+
+// Tries to get the Moz debug image, imgIContainerDebug. Only works
+// in a debug build. If we succeed, we call func().
+function actOnMozImage(doc, id, func) {
+ var imgContainer = getImageLoading(doc, id).getRequest(
+ Ci.nsIImageLoadingContent.CURRENT_REQUEST
+ ).image;
+ var mozImage;
+ try {
+ mozImage = imgContainer.QueryInterface(Ci.imgIContainerDebug);
+ } catch (e) {
+ return false;
+ }
+ func(mozImage);
+ return true;
+}
+
+function assertPrefVal(name, val) {
+ let boolValue = Services.prefs.getBoolPref(name);
+ ok(boolValue === val, `pref ${name} is set to ${val}`);
+ if (boolValue !== val) {
+ throw Error(`pref ${name} is not set to ${val}`);
+ }
+}
+
+function assertFileProcess() {
+ // Ensure that the file content process is enabled.
+ assertPrefVal("browser.tabs.remote.separateFileUriProcess", true);
+}
+
+function assertSandboxHeadless() {
+ assertPrefVal("security.sandbox.content.headless", true);
+}
+
+function getPage() {
+ let filePage = undefined;
+ switch (Services.appinfo.OS) {
+ case "WINNT":
+ filePage = "file:///C:/";
+ break;
+ case "Darwin":
+ filePage = "file:///tmp/";
+ break;
+ case "Linux":
+ filePage = "file:///tmp/";
+ break;
+ default:
+ throw new Error("Unsupported operating system");
+ }
+ return filePage;
+}
+
+function getSize() {
+ let iconSize = undefined;
+ switch (Services.appinfo.OS) {
+ case "WINNT":
+ iconSize = 32;
+ break;
+ case "Darwin":
+ iconSize = 128;
+ break;
+ case "Linux":
+ iconSize = 128;
+ break;
+ default:
+ throw new Error("Unsupported operating system");
+ }
+ return iconSize;
+}
+
+async function createMozIconInFile(ext, expectSuccess = true) {
+ const kPAGE = getPage();
+ const kSize = expectSuccess ? getSize() : 24; // we get 24x24 when failing,
+ // e.g. when remoting is
+ // disabled and the sandbox
+ // headless is enabled
+
+ // open a tab in a file content process
+ let fileTab = await BrowserTestUtils.addTab(gBrowser, kPAGE, {
+ preferredRemoteType: "file",
+ });
+
+ // get the browser for the file content process tab
+ let fileBrowser = gBrowser.getBrowserForTab(fileTab);
+
+ let checkIcon = async (_ext, _kSize, _expectSuccess) => {
+ const img = content.document.createElement("img");
+ let waitLoad = new Promise(resolve => {
+ // only listen to successfull load event if we expect the image to
+ // actually load, e.g. with remoting disabled and sandbox headless
+ // enabled we dont expect it to work, and we will wait for onerror below
+ // to trigger.
+ if (_expectSuccess) {
+ img.addEventListener("load", resolve, { once: true });
+ }
+ img.onerror = () => {
+ // With remoting enabled,
+ // Verified to work by forcing early `return NS_ERROR_NOT_AVAILABLE;`
+ // within `nsIconChannel::GetIcon(nsIURI* aURI, ByteBuf* aDataOut)`
+ //
+ // With remoting disabled and sandbox headless enabled, this should be
+ // the default path, since we don't add the "load" event listener.
+ ok(!_expectSuccess, "Error while loading moz-icon");
+ resolve();
+ };
+ });
+ img.setAttribute("src", `moz-icon://.${_ext}?size=${_kSize}`);
+ img.setAttribute("id", `moz-icon-${_ext}-${_kSize}`);
+ content.document.body.appendChild(img);
+
+ await waitLoad;
+
+ const icon = content.document.getElementById(`moz-icon-${_ext}-${_kSize}`);
+ ok(icon !== null, `got a valid ${_ext} moz-icon`);
+ is(icon.width, _kSize, `${_kSize} px width ${_ext} moz-icon`);
+ is(icon.height, _kSize, `${_kSize} px height ${_ext} moz-icon`);
+ };
+
+ await BrowserTestUtils.browserLoaded(fileBrowser);
+ await SpecialPowers.spawn(
+ fileBrowser,
+ [ext, kSize, expectSuccess],
+ checkIcon
+ );
+ await BrowserTestUtils.removeTab(fileTab);
+}
diff --git a/image/test/browser/image.html b/image/test/browser/image.html
new file mode 100644
index 0000000000..3831ab68a4
--- /dev/null
+++ b/image/test/browser/image.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html>
+ <head>
+ <title>Imagelib2 animation tests</title>
+ <style type="text/css">
+ #background_div {
+ border: 1px black solid;
+ height: 200px;
+ width: 200px;
+ margin: 10px;
+ background: url(animated2.gif) center center no-repeat;
+ }
+ </style>
+ </head>
+ </head>
+<body>
+ <p>Page with image</p>
+ <img src="animated.gif" id="img1">
+ <div id="background_div"></div>
+</body>
+</html>
diff --git a/image/test/browser/imageX2.html b/image/test/browser/imageX2.html
new file mode 100644
index 0000000000..4ce953bfac
--- /dev/null
+++ b/image/test/browser/imageX2.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html>
+ <head>
+ <title>Imagelib2 animation tests</title>
+ </head>
+<body>
+ <p>Page with images</p>
+ <img src="animated.gif" id="img1">
+ <br>
+ <img src="animated.gif" id="img2">
+</body>
+</html>