From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- image/test/browser/animated.gif | Bin 0 -> 71479 bytes image/test/browser/animated2.gif | Bin 0 -> 66647 bytes image/test/browser/big.png | Bin 0 -> 129497 bytes image/test/browser/browser.toml | 27 +++ image/test/browser/browser_bug1869938.js | 87 +++++++ image/test/browser/browser_bug666317.js | 138 +++++++++++ image/test/browser/browser_docshell_type_editor.js | 134 +++++++++++ .../browser_docshell_type_editor/chrome.manifest | 1 + .../img/privileged.png | Bin 0 -> 90 bytes image/test/browser/browser_image.js | 261 +++++++++++++++++++++ image/test/browser/browser_mozicon_file.js | 12 + .../browser_mozicon_file_sandbox_headless.js | 13 + ...ser_offscreen_image_in_out_of_process_iframe.js | 164 +++++++++++++ image/test/browser/browser_sandbox_headless.toml | 6 + image/test/browser/empty.html | 2 + image/test/browser/head.js | 136 +++++++++++ image/test/browser/helper1869938.html | 12 + image/test/browser/image.html | 23 ++ image/test/browser/imageX2.html | 14 ++ 19 files changed, 1030 insertions(+) create mode 100644 image/test/browser/animated.gif create mode 100644 image/test/browser/animated2.gif create mode 100644 image/test/browser/big.png create mode 100644 image/test/browser/browser.toml create mode 100644 image/test/browser/browser_bug1869938.js create mode 100644 image/test/browser/browser_bug666317.js create mode 100644 image/test/browser/browser_docshell_type_editor.js create mode 100644 image/test/browser/browser_docshell_type_editor/chrome.manifest create mode 100644 image/test/browser/browser_docshell_type_editor/img/privileged.png create mode 100644 image/test/browser/browser_image.js create mode 100644 image/test/browser/browser_mozicon_file.js create mode 100644 image/test/browser/browser_mozicon_file_sandbox_headless.js create mode 100644 image/test/browser/browser_offscreen_image_in_out_of_process_iframe.js create mode 100644 image/test/browser/browser_sandbox_headless.toml create mode 100644 image/test/browser/empty.html create mode 100644 image/test/browser/head.js create mode 100644 image/test/browser/helper1869938.html create mode 100644 image/test/browser/image.html create mode 100644 image/test/browser/imageX2.html (limited to 'image/test/browser') diff --git a/image/test/browser/animated.gif b/image/test/browser/animated.gif new file mode 100644 index 0000000000..eb034e1501 Binary files /dev/null and b/image/test/browser/animated.gif differ diff --git a/image/test/browser/animated2.gif b/image/test/browser/animated2.gif new file mode 100644 index 0000000000..053eaae688 Binary files /dev/null and b/image/test/browser/animated2.gif differ diff --git a/image/test/browser/big.png b/image/test/browser/big.png new file mode 100644 index 0000000000..94e7eb6db2 Binary files /dev/null and b/image/test/browser/big.png differ diff --git a/image/test/browser/browser.toml b/image/test/browser/browser.toml new file mode 100644 index 0000000000..f264e1f6ab --- /dev/null +++ b/image/test/browser/browser.toml @@ -0,0 +1,27 @@ +[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_bug1869938.js"] +support-files = ["helper1869938.html"] + +["browser_docshell_type_editor.js"] + +["browser_image.js"] +skip-if = ["true"] # Bug 987616 + +["browser_mozicon_file.js"] + +["browser_offscreen_image_in_out_of_process_iframe.js"] +https_first_disabled = true +support-files = ["empty.html"] diff --git a/image/test/browser/browser_bug1869938.js b/image/test/browser/browser_bug1869938.js new file mode 100644 index 0000000000..39956d8601 --- /dev/null +++ b/image/test/browser/browser_bug1869938.js @@ -0,0 +1,87 @@ +/* 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/. */ + +/* + * This test opens a private browsing window, then opens a content page in it + * that sets a favicon that is a data uri containing a svg file, which + * contains an image element that refers to a data uri containing a png image. + * This tests that we don't hit an assert when loading the png in the favicon + * in the parent process with this setup. + */ + +function waitForLinkAvailable(browser, win) { + let resolve, reject; + + let listener = { + onLinkIconAvailable(b, dataURI, iconURI) { + // Ignore icons for other browsers or empty icons. + if (browser !== b || !iconURI) { + return; + } + + win.gBrowser.removeTabsProgressListener(listener); + resolve(iconURI); + }, + }; + + let promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + + win.gBrowser.addTabsProgressListener(listener); + }); + + promise.cancel = () => { + win.gBrowser.removeTabsProgressListener(listener); + + reject(); + }; + + return promise; +} + +add_task(async function test() { + function httpURL(filename) { + let chromeURL = getRootDirectory(gTestPath) + filename; + return chromeURL.replace( + "chrome://mochitests/content/", + "http://mochi.test:8888/" + ); + } + + let win = await BrowserTestUtils.openNewBrowserWindow({ private: true }); + + const pageUrl = httpURL("helper1869938.html"); + + let tab = (win.gBrowser.selectedTab = BrowserTestUtils.addTab( + win.gBrowser, + "about:blank" + )); + + await BrowserTestUtils.browserLoaded(tab.linkedBrowser); + + let faviconPromise = waitForLinkAvailable(tab.linkedBrowser, win); + + BrowserTestUtils.startLoadingURIString(tab.linkedBrowser, pageUrl); + + await BrowserTestUtils.browserLoaded(tab.linkedBrowser); + + await new Promise(resolve => { + waitForFocus(resolve, win); + }); + + await faviconPromise; + + // do a couple rafs here to ensure its loaded and displayed + await new Promise(r => requestAnimationFrame(r)); + await new Promise(r => requestAnimationFrame(r)); + + await BrowserTestUtils.closeWindow(win); + + win = null; + tab = null; + faviconPromise = null; + + ok(true, "we got here and didn't crash/assert"); +}); 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 = + "" + + '' + + ""; + +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,"; + +/** + * 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 Binary files /dev/null and b/image/test/browser/browser_docshell_type_editor/img/privileged.png 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 += ''; + 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..b431902eb7 --- /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.startLoadingURIString(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.toml b/image/test/browser/browser_sandbox_headless.toml new file mode 100644 index 0000000000..bf9517c3af --- /dev/null +++ b/image/test/browser/browser_sandbox_headless.toml @@ -0,0 +1,6 @@ +[DEFAULT] +support-files = ["head.js"] +prefs = ["security.sandbox.content.headless=true"] +run-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 @@ + + 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/helper1869938.html b/image/test/browser/helper1869938.html new file mode 100644 index 0000000000..3e13963c92 --- /dev/null +++ b/image/test/browser/helper1869938.html @@ -0,0 +1,12 @@ + + + + + + + + + + + + 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 @@ + + + + + Imagelib2 animation tests + + + + +

Page with image

+ +
+ + 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 @@ + + + + + Imagelib2 animation tests + + +

Page with images

+ +
+ + + -- cgit v1.2.3