import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo

This commit is contained in:
Roy Tam 2018-01-19 03:59:58 +08:00
commit dcd9973243
150858 changed files with 23884658 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

BIN
image/test/browser/big.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

View file

@ -0,0 +1,14 @@
[DEFAULT]
support-files =
animated.gif
animated2.gif
big.png
head.js
image.html
imageX2.html
[browser_bug666317.js]
skip-if = true || e10s # 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 with e10s enabled
[browser_image.js]
skip-if = true # Bug 987616
[browser_docshell_type_editor.js]

View file

@ -0,0 +1,140 @@
waitForExplicitFinish();
var pageSource =
'<html><body>' +
'<img id="testImg" src="' + TESTROOT + 'big.png">' +
'</body></html>';
var oldDiscardingPref, oldTab, newTab;
var prefBranch = Cc["@mozilla.org/preferences-service;1"]
.getService(Ci.nsIPrefService)
.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');
img.QueryInterface(Ci.nsIImageLoadingContent);
return img.getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST);
}
function isImgDecoded() {
let request = currentRequest();
return request.imageStatus & Ci.imgIRequest.STATUS_DECODE_COMPLETE ? true : false;
}
// 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, false);
aCallback();
}
}
window.addEventListener('message', handlePostMessage, false);
// 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 = gBrowser.addTab('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 = Cc["@mozilla.org/observer-service;1"]
.getService(Ci.nsIObserverService);
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();
}

View file

@ -0,0 +1,92 @@
"use strict";
const Ci = Components.interfaces;
const SIMPLE_HTML = "data:text/html,<html><head></head><body></body></html>";
// 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://devtools/content/framework/dev-edition-promo/dev-edition-logo.png
add_task(function* () {
info("docshell of appType APP_TYPE_EDITOR can access privileged images.");
yield BrowserTestUtils.withNewTab({
gBrowser,
url: SIMPLE_HTML
}, function* (browser) {
yield ContentTask.spawn(browser, null, function* () {
let rootDocShell = docShell.QueryInterface(Ci.nsIDocShellTreeItem)
.rootTreeItem
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(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://devtools/content/framework/dev-edition-promo/dev-edition-logo.png";
});
});
});
});
add_task(function* () {
info("docshell of appType APP_TYPE_UNKNOWN can *not* access privileged images.");
yield BrowserTestUtils.withNewTab({
gBrowser,
url: SIMPLE_HTML
}, function* (browser) {
yield ContentTask.spawn(browser, null, function* () {
let rootDocShell = docShell.QueryInterface(Ci.nsIDocShellTreeItem)
.rootTreeItem
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(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 acces 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 acces privileged image");
// restore appType of rootDocShell before moving on to the next test
rootDocShell.appType = defaultAppType;
resolve();
}
doc.body.appendChild(image);
image.src = "chrome://devtools/content/framework/dev-edition-promo/dev-edition-logo.png";
});
});
});
});

View file

@ -0,0 +1,195 @@
waitForExplicitFinish();
requestLongerTimeout(2); // see bug 660123 -- this test is slow on Mac.
// A hold on the current timer, so it doens'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
function testBFCache() {
function theTest() {
var abort = false;
var chances, gImage, gFrames;
gBrowser.selectedTab = gBrowser.addTab(TESTROOT + "image.html");
gBrowser.selectedBrowser.addEventListener("pageshow", function () {
gBrowser.selectedBrowser.removeEventListener("pageshow", arguments.callee, true);
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();
}, 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("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 = gBrowser.addTab(TESTROOT + "image.html");
gBrowser.selectedBrowser.addEventListener("pageshow", function () {
gBrowser.selectedBrowser.removeEventListener("pageshow", arguments.callee, true);
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();
}, 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 = gBrowser.addTab(TESTROOT + "imageX2.html");
gBrowser.selectedBrowser.addEventListener("pageshow", function () {
gBrowser.selectedBrowser.removeEventListener("pageshow", arguments.callee, true);
[1,2].forEach(function(i) {
actOnMozImage(gBrowser.contentDocument, "img"+i, function(image) {
gImages[i] = image;
});
});
goer.next();
}, 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 == 0) {
finish();
return;
}
tests.shift()();
}
function test() {
ignoreAllUncaughtExceptions();
nextTest();
}

View file

@ -0,0 +1,26 @@
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) {
var htmlImg = doc.getElementById(id);
return htmlImg.QueryInterface(Ci.nsIImageLoadingContent);
}
// 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;
}

View file

@ -0,0 +1,24 @@
<!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>

View file

@ -0,0 +1,15 @@
<!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>

View file

@ -0,0 +1,36 @@
<!DOCTYPE html>
<html class="reftest-wait">
<body>
</body>
<script>
function createImage(loadHandler) {
var newImage = new Image;
newImage.id = "thepreviewimage";
newImage.setAttribute("src", "unsized-svg.svg");
if (loadHandler) {
newImage.onload = loadHandler;
}
// Query width & height, and display them in document:
physWidth = newImage.width;
physHeight = newImage.height;
document.documentElement.innerHTML +=
physWidth + " x " + physHeight + "<br>";
}
function part2() {
// Load image again:
createImage();
// End the crashtest.
document.documentElement.removeAttribute("class");
}
function startTest() {
// Trigger image load, and call part2() when it's loaded:
createImage(part2);
}
startTest();
</script>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 B

View file

@ -0,0 +1,16 @@
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<filter id="f">
<!-- the image data is
<svg xmlns="http://www.w3.org/2000/svg" width="-5" height="1%" />
-->
<feImage xlink:href="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSItNSIgaGVpZ2h0
PSIxJSIgLz4K" />
</filter>
</defs>
<rect width="1" height="1" filter="url(#f)" />
</svg>

After

Width:  |  Height:  |  Size: 464 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -0,0 +1,17 @@
<html>
<body>
<img id='m1' src='data:image/bmp;base64,Qk16AAAAAAAAAJUAABMoAAAAIA4AACAAACYBAAQAAgAAAAB/AAQCAADE9Sj/AAkAAAAAAAAR4AAAAAD/AAAA//8A////AAAAAAAAAAAAAAAAADAAMzMzMzMzMDMzMwMzGAMzMzMzMzMwABEIAf8AAAAzMzMzMzMwADIzMxERERMzMxERERGAADMzMwAAAIAzMzMzMzMzPzMzMwAAADAAADNGMzMzM0JNegAAAAAAABN2AAAAKAAAACAOAAAgFgAAAQAEAAIAAAAAfxYAEgsAABILAAAAABp/VLge/AAA/+D6AAAAAP8AAP//AP///wAAAAAwAAMzMzMzIgOPMzMzOxCA/42NjY2NjY2NjY0zM0EcMzLgMzAzMzMzMzMzMzMzMzMfAAAREREz/xE2MzczMzwWMzMAAACAMzMzLzMzMz8zMzMAAAAwAAAzRkZGRkYzMzAzMwkDMzMwSjMzMxEzMzMwABERAR4EADPyMzMzMzMwFhYWLQAAAIAzERYGFhYWFhYWVhYWFhYWFhYWFhYWFhYWFhYWFhsWAxYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYzM0EcMzLgMzAzMzMWFhYWFhYW//8AAAMWFhYWFhYWFhEZEREDMzMzMzMzMzMwfzMzM1AzMAMzMzQgAAAAEQMzMzMwFwAIAIMzMzMeYRAcMzLgMzAzMzMzAzL4MzMzMzPoAzMzMzMv+i/uMzMzMzM='>
<img id='m2' height='2' width='2'>
<canvas id='c1'></canvas>
<script>
var im1=document.getElementById('m1');
var im2=document.getElementById('m2');
im2.src=im1.src;
window.onload=function(){
var ctx=document.getElementById('c1').getContext('2d');
ctx.drawImage(im1, 0, 0);
ctx.drawImage(im2, 0, 0);
}
</script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 548 B

View file

@ -0,0 +1,5 @@
<html>
<body>
<img src="1241729-1.bmp" style="width: 10px; height: 10px;">
</body>
</html>

View file

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<head>
<meta charset='UTF-8'>
<meta http-equiv='Cache-control' content='no-cache'>
</head>
<body>
<img id='m1' src='data:image/bmp;base64,Qk1mDgAAAAAAAHYAAAAoAAAAQAEAAPAAAAABAAQAAgAAAPANAADoAwAA6AMAABAAAAAAAAAA/wD/AAAAAAAAAP8AAP8AAP8AAAD///8AzMzMAMzMzADMzMwAzMzMAMzMzADMzMwAzMzMAMzMzADMzMwAzMzMAAACAAEAAgABAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAAAAAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRCAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBpRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAIAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICADgRMCIAAmoAakQAAAACAgA4ETAiAAJqAGpEAAAAAgIABhEBVQURA1UDEQFVCREEVQIRBFUCEQFVCBEBVQYRMCIAAmoAakQAAAACAgAGEQFVBBEBVQMRAVUCEQFVCREBVQURAVUFEQFVCBEBVQYRMCIAAmoAakQAAAACAgAGEQFVBBEBVQMRAVUCEQFVCREBVQURAVUFEQFVCBEBVQYRMCIAAmoAakQAAAACAgAGEQFVBBEBVQMRAVUCEQRVBhEBVQURA1UDEQNVBhEBVQYRMCIAAmoAakQAAAACAgAGEQFVBBEBVQMRAVUCEQFVAxEBVQURAVUFEQFVBREBVQgRAVUGETAiAAJqAGpEAAAAAgIABhEBVQQRAVUDEQFVAhEBVQMRAVUFEQFVBREBVQURAVUIEQFVBhEwIgACagBqRAAAAAICAAMRB1UCEQNVAxEEVQYRAVUFEQRVAhEEVQIRB1UDETAiAAJqAGpEAAAAAgIAOBEwIgACagBqRAAAAAICADgRMCIAAmoAakQAAAACAAEAAgABAAE='>
<img id='m2' height='2' width='2'>
<canvas id='c1'></canvas>
<script>
var im1=document.getElementById('m1');
var im2=document.getElementById('m2');
im2.src=im1.src;
window.onload=function(){
var ctx=document.getElementById('c1').getContext('2d');
ctx.drawImage(im1, 0, 0); // sync docoder call
ctx.drawImage(im2, 0, 0); // sync downscaler call
}
</script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,51 @@
<!doctype html>
<html class="reftest-wait">
<head>
<script type="text/javascript">
var gl;
function start() {
var canvas = document.getElementById("glcanvas");
gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
if (gl) {
initTextures();
} else {
finish();
}
}
function initTextures() {
var cubeTexture = gl.createTexture();
var cubeImage = document.getElementById("i");
cubeImage.onload = function() { handleTextureLoaded(cubeImage, cubeTexture); }
cubeImage.onerror = function() { finish(); }
cubeImage.src = "1251091-1.png";
}
function handleTextureLoaded(image, texture) {
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST);
gl.generateMipmap(gl.TEXTURE_2D);
gl.bindTexture(gl.TEXTURE_2D, null);
setTimeout(showit,0);
}
function showit() {
document.getElementById("i").style.display = "";
finish();
}
function finish() {
document.documentElement.removeAttribute("class");
}
</script>
</head>
<body onload="start()">
<canvas id="glcanvas" width="640" height="480"></canvas>
<img id="i" style="display: none;">
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

View file

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<div style="content: url(data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20style%3D%22background%3A%20url%28data%3Aimage%2Fsvg%2Bxml%2C%253Csvg%2520xmlns%253D%2522http%253A%252F%252Fwww.w3.org%252F2000%252Fsvg%2522%253E%253C%252Fsvg%253E%29%22%3E%3C%2Fsvg%3E%0D%0A)"></div>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 B

View file

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<body>
<canvas id="crashtest" width="50000" height="50000" />
<script>
document.getElementById("crashtest").mozGetAsFile("foo.png", "image/png");
document.getElementById("crashtest").mozGetAsFile("foo.jpeg", "image/jpeg");
</script>
</body>
</html>

View file

@ -0,0 +1,510 @@
<!DOCTYPE html [
<!ENTITY thousand-g-elements "
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;&lt;g/&gt;
">
]>
<html xmlns="http://www.w3.org/1999/xhtml">
<img src='data:image/svg+xml,&lt;svg xmlns="http://www.w3.org/2000/svg"&gt;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&thousand-g-elements;
&lt;/svg&gt;'/>
</html>

View file

@ -0,0 +1,2 @@
<!DOCTYPE html>
<img crossorigin src="data:a/a,">

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 B

View file

@ -0,0 +1,10 @@
<color style='background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSINCiAgICAgd2lkdGg9IjEwMCIgaGVpZ2h0PSIxMDAiPg0KICA8cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJibHVlIi8%2BDQo8L3N2Zz4NCg%3D%3D");'></color>
<iframe contenteditable=true></iframe>
<script>
document.body.innerHTML = '';
function onload() {
e = document.createElementNS("http://www.w3.org/1998/Math/MathML", "share");
document.body.appendChild(e);
}
document.addEventListener("DOMContentLoaded", onload, false);
</script>

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 769 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 597 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

View file

@ -0,0 +1,51 @@
# Bug 668068 - Maximum (256) width and height icons that we currently interpret as 0-width and 0-height.
load 256-height.ico
load 256-width.ico
load 83804-1.gif
load 89341-1.gif
load 463696.bmp
load 570451.png
skip-if(Android) load 694165-1.xhtml
load 681190.html
load 732319-1.html
load 844403-1.html
load 856616.gif
load 944353.jpg
load 1205923-1.html
# Ensure we handle detecting that an image is animated, then failing to decode
# it. (See bug 1210745.)
load 1210745-1.gif
load 1212954-1.svg
load 1235605.gif
load 1241728-1.html
load 1241729-1.html
load 1242093-1.html
load 1242778-1.png
load 1249576-1.png
load 1253362-1.html
load colormap-range.gif
HTTP load delayedframe.sjs # A 3-frame animated GIF with an inordinate delay between the second and third frame
# Animated gifs with a very large canvas, but tiny actual content.
load delaytest.html?523528-1.gif
load delaytest.html?523528-2.gif
# Bug 1160801 - Ensure that we handle invalid disposal types.
load invalid-disposal-method-1.gif
load invalid-disposal-method-2.gif
load invalid-disposal-method-3.gif
load invalid-icc-profile.jpg # This would have exposed the leak discovered in bug 642902
# Ensure we handle ICO directory entries which specify the wrong size for the contained resource.
load invalid_ico_height.ico
load invalid_ico_width.ico
# Bug 525326 - Test image sizes of 65535x65535 which is larger than we allow)
load invalid-size.gif
load invalid-size-second-frame.gif
load multiple-png-hassize.ico # Bug 863958 - This icon's size is such that it leads to multiple writes to the PNG decoder after we've gotten our size.
asserts(0-2) load ownerdiscard.html # Bug 807211
load truncated-second-frame.png # Bug 863975

View file

@ -0,0 +1,44 @@
function getFileStream(filename)
{
// Get the location of this sjs file, and then use that to figure out where
// to find where our other files are.
var self = Components.classes["@mozilla.org/file/local;1"]
.createInstance(Components.interfaces.nsILocalFile);
self.initWithPath(getState("__LOCATION__"));
var file = self.parent;
file.append(filename);
dump(file.path + "\n");
var fileStream = Components.classes['@mozilla.org/network/file-input-stream;1']
.createInstance(Components.interfaces.nsIFileInputStream);
fileStream.init(file, 1, 0, false);
return fileStream;
}
var gTimer;
function handleRequest(request, response)
{
response.processAsync();
response.setStatusLine(request.httpVersion, 200, "OK");
response.setHeader("Content-Type", "image/gif", false);
var firststream = getFileStream("threeframes-start.gif");
response.bodyOutputStream.writeFrom(firststream, firststream.available())
firststream.close();
gTimer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);
gTimer.initWithCallback(function()
{
var secondstream = getFileStream("threeframes-end.gif");
response.bodyOutputStream.writeFrom(secondstream, secondstream.available())
secondstream.close();
response.finish();
// This time needs to be longer than the animation timer in
// threeframes-start.gif. That's specified as 100ms; just use 5 seconds as
// a reasonable upper bound. Since this is just a crashtest, timeouts
// aren't a big deal.
}, 5 * 1000 /* milliseconds */, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
}

View file

@ -0,0 +1,44 @@
<!DOCTYPE HTML>
<html class="reftest-wait">
<head>
<title>Delayed image reftest wrapper</title>
</head>
<body>
<img id="image1">
<script>
// This loads a externally specified image, forces a draw (in case of
// decode-on-draw), waits 350ms, and then triggers the reftest snapshot.
// This allows the animation on the page to complete.
//
// Use as "delaytest.html?animation.png"
//
// Get the image URL from our URL
var imgURL = document.location.search.substr(1);
// Load the image
var img = document.images[0];
img.src = imgURL;
img.onload = forceDecode;
img.onerror = forceDecode;
function forceDecode() {
try {
// We need to force drawing of the image in an invisible context
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
} catch(e) {}
// We've force the decode. start the timer to trigger the reftest
startTimer();
}
function startTimer() {
const delay = 350;
setTimeout("document.documentElement.className = '';", delay);
}
</script>
</body>
</html>

View file

@ -0,0 +1 @@
<img src=ie.png>

Binary file not shown.

After

Width:  |  Height:  |  Size: 456 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 673 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 329 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 894 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 894 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View file

@ -0,0 +1,49 @@
<!DOCTYPE html>
<html class="reftest-wait">
<body>
<div id=tCF0>
<img id=victim src=ie.png>
<iframe src=discardframe.htm></iframe>
</div>
<script>
const Ci = SpecialPowers.Ci;
const Cc = SpecialPowers.Cc;
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) {}
}
var img = document.getElementById('victim');
var observer = new ImageDecoderObserverStub();
observer.discard = function() {
cleanupAndFinish();
}
observer = SpecialPowers.wrapCallbackObject(observer);
var gObserver = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
.createScriptedObserver(observer);
var imgLoadingContent =
SpecialPowers.wrap(img).QueryInterface(Ci.nsIImageLoadingContent);
imgLoadingContent.addObserver(gObserver);
function initCF() {
setTimeout(function() { document.adoptNode(tCF0); }, 0);
}
document.addEventListener("DOMContentLoaded", initCF, false);
function cleanupAndFinish() {
imgLoadingContent.removeObserver(gObserver);
setTimeout("document.documentElement.className = '';", 0);
}
function loadhandler() {
setTimeout("cleanupAndFinish();", 30000);
}
window.addEventListener("load", function() {
SpecialPowers.pushPrefEnv({"set":[["min_discard_timeout_ms", 1]]}, loadhandler)}, false);
</script>
</body>
</html>

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg"></svg>

After

Width:  |  Height:  |  Size: 47 B

673
image/test/gtest/Common.cpp Normal file
View file

@ -0,0 +1,673 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#include "Common.h"
#include <cstdlib>
#include "nsDirectoryServiceDefs.h"
#include "nsIDirectoryService.h"
#include "nsIFile.h"
#include "nsIInputStream.h"
#include "nsIProperties.h"
#include "nsNetUtil.h"
#include "mozilla/RefPtr.h"
#include "nsStreamUtils.h"
#include "nsString.h"
namespace mozilla {
namespace image {
using namespace gfx;
using std::abs;
using std::vector;
///////////////////////////////////////////////////////////////////////////////
// General Helpers
///////////////////////////////////////////////////////////////////////////////
// These macros work like gtest's ASSERT_* macros, except that they can be used
// in functions that return values.
#define ASSERT_TRUE_OR_RETURN(e, rv) \
EXPECT_TRUE(e); \
if (!(e)) { \
return rv; \
}
#define ASSERT_EQ_OR_RETURN(a, b, rv) \
EXPECT_EQ(a, b); \
if ((a) != (b)) { \
return rv; \
}
#define ASSERT_GE_OR_RETURN(a, b, rv) \
EXPECT_GE(a, b); \
if (!((a) >= (b))) { \
return rv; \
}
#define ASSERT_LE_OR_RETURN(a, b, rv) \
EXPECT_LE(a, b); \
if (!((a) <= (b))) { \
return rv; \
}
#define ASSERT_LT_OR_RETURN(a, b, rv) \
EXPECT_LT(a, b); \
if (!((a) < (b))) { \
return rv; \
}
already_AddRefed<nsIInputStream>
LoadFile(const char* aRelativePath)
{
nsresult rv;
nsCOMPtr<nsIProperties> dirService =
do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID);
ASSERT_TRUE_OR_RETURN(dirService != nullptr, nullptr);
// Retrieve the current working directory.
nsCOMPtr<nsIFile> file;
rv = dirService->Get(NS_OS_CURRENT_WORKING_DIR,
NS_GET_IID(nsIFile), getter_AddRefs(file));
ASSERT_TRUE_OR_RETURN(NS_SUCCEEDED(rv), nullptr);
// Construct the final path by appending the working path to the current
// working directory.
file->AppendNative(nsDependentCString(aRelativePath));
// Construct an input stream for the requested file.
nsCOMPtr<nsIInputStream> inputStream;
rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), file);
ASSERT_TRUE_OR_RETURN(NS_SUCCEEDED(rv), nullptr);
// Ensure the resulting input stream is buffered.
if (!NS_InputStreamIsBuffered(inputStream)) {
nsCOMPtr<nsIInputStream> bufStream;
rv = NS_NewBufferedInputStream(getter_AddRefs(bufStream),
inputStream, 1024);
ASSERT_TRUE_OR_RETURN(NS_SUCCEEDED(rv), nullptr);
inputStream = bufStream;
}
return inputStream.forget();
}
bool
IsSolidColor(SourceSurface* aSurface,
BGRAColor aColor,
uint8_t aFuzz /* = 0 */)
{
IntSize size = aSurface->GetSize();
return RectIsSolidColor(aSurface, IntRect(0, 0, size.width, size.height),
aColor, aFuzz);
}
bool
IsSolidPalettedColor(Decoder* aDecoder, uint8_t aColor)
{
RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
return PalettedRectIsSolidColor(aDecoder, currentFrame->GetRect(), aColor);
}
bool
RowsAreSolidColor(SourceSurface* aSurface,
int32_t aStartRow,
int32_t aRowCount,
BGRAColor aColor,
uint8_t aFuzz /* = 0 */)
{
IntSize size = aSurface->GetSize();
return RectIsSolidColor(aSurface, IntRect(0, aStartRow, size.width, aRowCount),
aColor, aFuzz);
}
bool
PalettedRowsAreSolidColor(Decoder* aDecoder,
int32_t aStartRow,
int32_t aRowCount,
uint8_t aColor)
{
RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
IntRect frameRect = currentFrame->GetRect();
IntRect solidColorRect(frameRect.x, aStartRow, frameRect.width, aRowCount);
return PalettedRectIsSolidColor(aDecoder, solidColorRect, aColor);
}
bool
RectIsSolidColor(SourceSurface* aSurface,
const IntRect& aRect,
BGRAColor aColor,
uint8_t aFuzz /* = 0 */)
{
IntSize surfaceSize = aSurface->GetSize();
IntRect rect =
aRect.Intersect(IntRect(0, 0, surfaceSize.width, surfaceSize.height));
RefPtr<DataSourceSurface> dataSurface = aSurface->GetDataSurface();
ASSERT_TRUE_OR_RETURN(dataSurface != nullptr, false);
ASSERT_EQ_OR_RETURN(dataSurface->Stride(), surfaceSize.width * 4, false);
DataSourceSurface::ScopedMap mapping(dataSurface,
DataSourceSurface::MapType::READ);
ASSERT_TRUE_OR_RETURN(mapping.IsMapped(), false);
uint8_t* data = dataSurface->GetData();
ASSERT_TRUE_OR_RETURN(data != nullptr, false);
int32_t rowLength = dataSurface->Stride();
for (int32_t row = rect.y; row < rect.YMost(); ++row) {
for (int32_t col = rect.x; col < rect.XMost(); ++col) {
int32_t i = row * rowLength + col * 4;
if (aFuzz != 0) {
ASSERT_LE_OR_RETURN(abs(aColor.mBlue - data[i + 0]), aFuzz, false);
ASSERT_LE_OR_RETURN(abs(aColor.mGreen - data[i + 1]), aFuzz, false);
ASSERT_LE_OR_RETURN(abs(aColor.mRed - data[i + 2]), aFuzz, false);
ASSERT_LE_OR_RETURN(abs(aColor.mAlpha - data[i + 3]), aFuzz, false);
} else {
ASSERT_EQ_OR_RETURN(aColor.mBlue, data[i + 0], false);
ASSERT_EQ_OR_RETURN(aColor.mGreen, data[i + 1], false);
ASSERT_EQ_OR_RETURN(aColor.mRed, data[i + 2], false);
ASSERT_EQ_OR_RETURN(aColor.mAlpha, data[i + 3], false);
}
}
}
return true;
}
bool
PalettedRectIsSolidColor(Decoder* aDecoder, const IntRect& aRect, uint8_t aColor)
{
RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
uint8_t* imageData;
uint32_t imageLength;
currentFrame->GetImageData(&imageData, &imageLength);
ASSERT_TRUE_OR_RETURN(imageData, false);
// Clamp to the frame rect. If any pixels outside the frame rect are included,
// we immediately fail, because such pixels don't have any "color" in the
// sense this function measures - they're transparent, and that doesn't
// necessarily correspond to any color palette index at all.
IntRect frameRect = currentFrame->GetRect();
ASSERT_EQ_OR_RETURN(imageLength, uint32_t(frameRect.Area()), false);
IntRect rect = aRect.Intersect(frameRect);
ASSERT_EQ_OR_RETURN(rect.Area(), aRect.Area(), false);
// Translate |rect| by |frameRect.TopLeft()| to reflect the fact that the
// frame rect's offset doesn't actually mean anything in terms of the
// in-memory representation of the surface. The image data starts at the upper
// left corner of the frame rect, in other words.
rect -= frameRect.TopLeft();
// Walk through the image data and make sure that the entire rect has the
// palette index |aColor|.
int32_t rowLength = frameRect.width;
for (int32_t row = rect.y; row < rect.YMost(); ++row) {
for (int32_t col = rect.x; col < rect.XMost(); ++col) {
int32_t i = row * rowLength + col;
ASSERT_EQ_OR_RETURN(aColor, imageData[i], false);
}
}
return true;
}
bool
RowHasPixels(SourceSurface* aSurface,
int32_t aRow,
const vector<BGRAColor>& aPixels)
{
ASSERT_GE_OR_RETURN(aRow, 0, false);
IntSize surfaceSize = aSurface->GetSize();
ASSERT_EQ_OR_RETURN(aPixels.size(), size_t(surfaceSize.width), false);
ASSERT_LT_OR_RETURN(aRow, surfaceSize.height, false);
RefPtr<DataSourceSurface> dataSurface = aSurface->GetDataSurface();
ASSERT_TRUE_OR_RETURN(dataSurface, false);
ASSERT_EQ_OR_RETURN(dataSurface->Stride(), surfaceSize.width * 4, false);
DataSourceSurface::ScopedMap mapping(dataSurface,
DataSourceSurface::MapType::READ);
ASSERT_TRUE_OR_RETURN(mapping.IsMapped(), false);
uint8_t* data = dataSurface->GetData();
ASSERT_TRUE_OR_RETURN(data != nullptr, false);
int32_t rowLength = dataSurface->Stride();
for (int32_t col = 0; col < surfaceSize.width; ++col) {
int32_t i = aRow * rowLength + col * 4;
ASSERT_EQ_OR_RETURN(aPixels[col].mBlue, data[i + 0], false);
ASSERT_EQ_OR_RETURN(aPixels[col].mGreen, data[i + 1], false);
ASSERT_EQ_OR_RETURN(aPixels[col].mRed, data[i + 2], false);
ASSERT_EQ_OR_RETURN(aPixels[col].mAlpha, data[i + 3], false);
}
return true;
}
///////////////////////////////////////////////////////////////////////////////
// SurfacePipe Helpers
///////////////////////////////////////////////////////////////////////////////
already_AddRefed<Decoder>
CreateTrivialDecoder()
{
gfxPrefs::GetSingleton();
DecoderType decoderType = DecoderFactory::GetDecoderType("image/gif");
NotNull<RefPtr<SourceBuffer>> sourceBuffer = WrapNotNull(new SourceBuffer());
RefPtr<Decoder> decoder =
DecoderFactory::CreateAnonymousDecoder(decoderType, sourceBuffer, Nothing(),
DefaultSurfaceFlags());
return decoder.forget();
}
void
AssertCorrectPipelineFinalState(SurfaceFilter* aFilter,
const gfx::IntRect& aInputSpaceRect,
const gfx::IntRect& aOutputSpaceRect)
{
EXPECT_TRUE(aFilter->IsSurfaceFinished());
Maybe<SurfaceInvalidRect> invalidRect = aFilter->TakeInvalidRect();
EXPECT_TRUE(invalidRect.isSome());
EXPECT_EQ(aInputSpaceRect, invalidRect->mInputSpaceRect);
EXPECT_EQ(aOutputSpaceRect, invalidRect->mOutputSpaceRect);
}
void
CheckGeneratedImage(Decoder* aDecoder,
const IntRect& aRect,
uint8_t aFuzz /* = 0 */)
{
RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
RefPtr<SourceSurface> surface = currentFrame->GetSourceSurface();
const IntSize surfaceSize = surface->GetSize();
// This diagram shows how the surface is divided into regions that the code
// below tests for the correct content. The output rect is the bounds of the
// region labeled 'C'.
//
// +---------------------------+
// | A |
// +---------+--------+--------+
// | B | C | D |
// +---------+--------+--------+
// | E |
// +---------------------------+
// Check that the output rect itself is green. (Region 'C'.)
EXPECT_TRUE(RectIsSolidColor(surface, aRect, BGRAColor::Green(), aFuzz));
// Check that the area above the output rect is transparent. (Region 'A'.)
EXPECT_TRUE(RectIsSolidColor(surface,
IntRect(0, 0, surfaceSize.width, aRect.y),
BGRAColor::Transparent(), aFuzz));
// Check that the area to the left of the output rect is transparent. (Region 'B'.)
EXPECT_TRUE(RectIsSolidColor(surface,
IntRect(0, aRect.y, aRect.x, aRect.YMost()),
BGRAColor::Transparent(), aFuzz));
// Check that the area to the right of the output rect is transparent. (Region 'D'.)
const int32_t widthOnRight = surfaceSize.width - aRect.XMost();
EXPECT_TRUE(RectIsSolidColor(surface,
IntRect(aRect.XMost(), aRect.y, widthOnRight, aRect.YMost()),
BGRAColor::Transparent(), aFuzz));
// Check that the area below the output rect is transparent. (Region 'E'.)
const int32_t heightBelow = surfaceSize.height - aRect.YMost();
EXPECT_TRUE(RectIsSolidColor(surface,
IntRect(0, aRect.YMost(), surfaceSize.width, heightBelow),
BGRAColor::Transparent(), aFuzz));
}
void
CheckGeneratedPalettedImage(Decoder* aDecoder, const IntRect& aRect)
{
RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
IntSize imageSize = currentFrame->GetImageSize();
// This diagram shows how the surface is divided into regions that the code
// below tests for the correct content. The output rect is the bounds of the
// region labeled 'C'.
//
// +---------------------------+
// | A |
// +---------+--------+--------+
// | B | C | D |
// +---------+--------+--------+
// | E |
// +---------------------------+
// Check that the output rect itself is all 255's. (Region 'C'.)
EXPECT_TRUE(PalettedRectIsSolidColor(aDecoder, aRect, 255));
// Check that the area above the output rect is all 0's. (Region 'A'.)
EXPECT_TRUE(PalettedRectIsSolidColor(aDecoder,
IntRect(0, 0, imageSize.width, aRect.y),
0));
// Check that the area to the left of the output rect is all 0's. (Region 'B'.)
EXPECT_TRUE(PalettedRectIsSolidColor(aDecoder,
IntRect(0, aRect.y, aRect.x, aRect.YMost()),
0));
// Check that the area to the right of the output rect is all 0's. (Region 'D'.)
const int32_t widthOnRight = imageSize.width - aRect.XMost();
EXPECT_TRUE(PalettedRectIsSolidColor(aDecoder,
IntRect(aRect.XMost(), aRect.y, widthOnRight, aRect.YMost()),
0));
// Check that the area below the output rect is transparent. (Region 'E'.)
const int32_t heightBelow = imageSize.height - aRect.YMost();
EXPECT_TRUE(PalettedRectIsSolidColor(aDecoder,
IntRect(0, aRect.YMost(), imageSize.width, heightBelow),
0));
}
void
CheckWritePixels(Decoder* aDecoder,
SurfaceFilter* aFilter,
Maybe<IntRect> aOutputRect /* = Nothing() */,
Maybe<IntRect> aInputRect /* = Nothing() */,
Maybe<IntRect> aInputWriteRect /* = Nothing() */,
Maybe<IntRect> aOutputWriteRect /* = Nothing() */,
uint8_t aFuzz /* = 0 */)
{
IntRect outputRect = aOutputRect.valueOr(IntRect(0, 0, 100, 100));
IntRect inputRect = aInputRect.valueOr(IntRect(0, 0, 100, 100));
IntRect inputWriteRect = aInputWriteRect.valueOr(inputRect);
IntRect outputWriteRect = aOutputWriteRect.valueOr(outputRect);
// Fill the image.
int32_t count = 0;
auto result = aFilter->WritePixels<uint32_t>([&] {
++count;
return AsVariant(BGRAColor::Green().AsPixel());
});
EXPECT_EQ(WriteState::FINISHED, result);
EXPECT_EQ(inputWriteRect.width * inputWriteRect.height, count);
AssertCorrectPipelineFinalState(aFilter, inputRect, outputRect);
// Attempt to write more data and make sure nothing changes.
const int32_t oldCount = count;
result = aFilter->WritePixels<uint32_t>([&] {
++count;
return AsVariant(BGRAColor::Green().AsPixel());
});
EXPECT_EQ(oldCount, count);
EXPECT_EQ(WriteState::FINISHED, result);
EXPECT_TRUE(aFilter->IsSurfaceFinished());
Maybe<SurfaceInvalidRect> invalidRect = aFilter->TakeInvalidRect();
EXPECT_TRUE(invalidRect.isNothing());
// Attempt to advance to the next row and make sure nothing changes.
aFilter->AdvanceRow();
EXPECT_TRUE(aFilter->IsSurfaceFinished());
invalidRect = aFilter->TakeInvalidRect();
EXPECT_TRUE(invalidRect.isNothing());
// Check that the generated image is correct.
CheckGeneratedImage(aDecoder, outputWriteRect, aFuzz);
}
void
CheckPalettedWritePixels(Decoder* aDecoder,
SurfaceFilter* aFilter,
Maybe<IntRect> aOutputRect /* = Nothing() */,
Maybe<IntRect> aInputRect /* = Nothing() */,
Maybe<IntRect> aInputWriteRect /* = Nothing() */,
Maybe<IntRect> aOutputWriteRect /* = Nothing() */,
uint8_t aFuzz /* = 0 */)
{
IntRect outputRect = aOutputRect.valueOr(IntRect(0, 0, 100, 100));
IntRect inputRect = aInputRect.valueOr(IntRect(0, 0, 100, 100));
IntRect inputWriteRect = aInputWriteRect.valueOr(inputRect);
IntRect outputWriteRect = aOutputWriteRect.valueOr(outputRect);
// Fill the image.
int32_t count = 0;
auto result = aFilter->WritePixels<uint8_t>([&] {
++count;
return AsVariant(uint8_t(255));
});
EXPECT_EQ(WriteState::FINISHED, result);
EXPECT_EQ(inputWriteRect.width * inputWriteRect.height, count);
AssertCorrectPipelineFinalState(aFilter, inputRect, outputRect);
// Attempt to write more data and make sure nothing changes.
const int32_t oldCount = count;
result = aFilter->WritePixels<uint8_t>([&] {
++count;
return AsVariant(uint8_t(255));
});
EXPECT_EQ(oldCount, count);
EXPECT_EQ(WriteState::FINISHED, result);
EXPECT_TRUE(aFilter->IsSurfaceFinished());
Maybe<SurfaceInvalidRect> invalidRect = aFilter->TakeInvalidRect();
EXPECT_TRUE(invalidRect.isNothing());
// Attempt to advance to the next row and make sure nothing changes.
aFilter->AdvanceRow();
EXPECT_TRUE(aFilter->IsSurfaceFinished());
invalidRect = aFilter->TakeInvalidRect();
EXPECT_TRUE(invalidRect.isNothing());
// Check that the generated image is correct.
RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
uint8_t* imageData;
uint32_t imageLength;
currentFrame->GetImageData(&imageData, &imageLength);
ASSERT_TRUE(imageData != nullptr);
ASSERT_EQ(outputWriteRect.width * outputWriteRect.height, int32_t(imageLength));
for (uint32_t i = 0; i < imageLength; ++i) {
ASSERT_EQ(uint8_t(255), imageData[i]);
}
}
///////////////////////////////////////////////////////////////////////////////
// Test Data
///////////////////////////////////////////////////////////////////////////////
ImageTestCase GreenPNGTestCase()
{
return ImageTestCase("green.png", "image/png", IntSize(100, 100));
}
ImageTestCase GreenGIFTestCase()
{
return ImageTestCase("green.gif", "image/gif", IntSize(100, 100));
}
ImageTestCase GreenJPGTestCase()
{
return ImageTestCase("green.jpg", "image/jpeg", IntSize(100, 100),
TEST_CASE_IS_FUZZY);
}
ImageTestCase GreenBMPTestCase()
{
return ImageTestCase("green.bmp", "image/bmp", IntSize(100, 100));
}
ImageTestCase GreenICOTestCase()
{
// This ICO contains a 32-bit BMP, and we use a BMP's alpha data by default
// when the BMP is embedded in an ICO, so it's transparent.
return ImageTestCase("green.ico", "image/x-icon", IntSize(100, 100),
TEST_CASE_IS_TRANSPARENT);
}
ImageTestCase GreenIconTestCase()
{
return ImageTestCase("green.icon", "image/icon", IntSize(100, 100),
TEST_CASE_IS_TRANSPARENT);
}
ImageTestCase GreenFirstFrameAnimatedGIFTestCase()
{
return ImageTestCase("first-frame-green.gif", "image/gif", IntSize(100, 100),
TEST_CASE_IS_ANIMATED);
}
ImageTestCase GreenFirstFrameAnimatedPNGTestCase()
{
return ImageTestCase("first-frame-green.png", "image/png", IntSize(100, 100),
TEST_CASE_IS_TRANSPARENT | TEST_CASE_IS_ANIMATED);
}
ImageTestCase CorruptTestCase()
{
return ImageTestCase("corrupt.jpg", "image/jpeg", IntSize(100, 100),
TEST_CASE_HAS_ERROR);
}
ImageTestCase CorruptBMPWithTruncatedHeader()
{
// This BMP has a header which is truncated right between the BIH and the
// bitfields, which is a particularly error-prone place w.r.t. the BMP decoder
// state machine.
return ImageTestCase("invalid-truncated-metadata.bmp", "image/bmp",
IntSize(100, 100), TEST_CASE_HAS_ERROR);
}
ImageTestCase CorruptICOWithBadBMPWidthTestCase()
{
// This ICO contains a BMP icon which has a width that doesn't match the size
// listed in the corresponding ICO directory entry.
return ImageTestCase("corrupt-with-bad-bmp-width.ico", "image/x-icon",
IntSize(100, 100), TEST_CASE_HAS_ERROR);
}
ImageTestCase CorruptICOWithBadBMPHeightTestCase()
{
// This ICO contains a BMP icon which has a height that doesn't match the size
// listed in the corresponding ICO directory entry.
return ImageTestCase("corrupt-with-bad-bmp-height.ico", "image/x-icon",
IntSize(100, 100), TEST_CASE_HAS_ERROR);
}
ImageTestCase TransparentPNGTestCase()
{
return ImageTestCase("transparent.png", "image/png", IntSize(32, 32),
TEST_CASE_IS_TRANSPARENT);
}
ImageTestCase TransparentGIFTestCase()
{
return ImageTestCase("transparent.gif", "image/gif", IntSize(16, 16),
TEST_CASE_IS_TRANSPARENT);
}
ImageTestCase FirstFramePaddingGIFTestCase()
{
return ImageTestCase("transparent.gif", "image/gif", IntSize(16, 16),
TEST_CASE_IS_TRANSPARENT);
}
ImageTestCase TransparentIfWithinICOBMPTestCase(TestCaseFlags aFlags)
{
// This is a BMP that is only transparent when decoded as if it is within an
// ICO file. (Note: aFlags needs to be set to TEST_CASE_DEFAULT_FLAGS or
// TEST_CASE_IS_TRANSPARENT accordingly.)
return ImageTestCase("transparent-if-within-ico.bmp", "image/bmp",
IntSize(32, 32), aFlags);
}
ImageTestCase RLE4BMPTestCase()
{
return ImageTestCase("rle4.bmp", "image/bmp", IntSize(320, 240),
TEST_CASE_IS_TRANSPARENT);
}
ImageTestCase RLE8BMPTestCase()
{
return ImageTestCase("rle8.bmp", "image/bmp", IntSize(32, 32),
TEST_CASE_IS_TRANSPARENT);
}
ImageTestCase NoFrameDelayGIFTestCase()
{
// This is an invalid (or at least, questionably valid) GIF that's animated
// even though it specifies a frame delay of zero. It's animated, but it's not
// marked TEST_CASE_IS_ANIMATED because the metadata decoder can't detect that
// it's animated.
return ImageTestCase("no-frame-delay.gif", "image/gif", IntSize(100, 100));
}
ImageTestCase ExtraImageSubBlocksAnimatedGIFTestCase()
{
// This is a corrupt GIF that has extra image sub blocks between the first and
// second frame.
return ImageTestCase("animated-with-extra-image-sub-blocks.gif", "image/gif",
IntSize(100, 100));
}
ImageTestCase DownscaledPNGTestCase()
{
// This testcase (and all the other "downscaled") testcases) consists of 25
// lines of green, followed by 25 lines of red, followed by 25 lines of green,
// followed by 25 more lines of red. It's intended that tests downscale it
// from 100x100 to 20x20, so we specify a 20x20 output size.
return ImageTestCase("downscaled.png", "image/png", IntSize(100, 100),
IntSize(20, 20));
}
ImageTestCase DownscaledGIFTestCase()
{
return ImageTestCase("downscaled.gif", "image/gif", IntSize(100, 100),
IntSize(20, 20));
}
ImageTestCase DownscaledJPGTestCase()
{
return ImageTestCase("downscaled.jpg", "image/jpeg", IntSize(100, 100),
IntSize(20, 20));
}
ImageTestCase DownscaledBMPTestCase()
{
return ImageTestCase("downscaled.bmp", "image/bmp", IntSize(100, 100),
IntSize(20, 20));
}
ImageTestCase DownscaledICOTestCase()
{
return ImageTestCase("downscaled.ico", "image/x-icon", IntSize(100, 100),
IntSize(20, 20), TEST_CASE_IS_TRANSPARENT);
}
ImageTestCase DownscaledIconTestCase()
{
return ImageTestCase("downscaled.icon", "image/icon", IntSize(100, 100),
IntSize(20, 20), TEST_CASE_IS_TRANSPARENT);
}
ImageTestCase DownscaledTransparentICOWithANDMaskTestCase()
{
// This test case is an ICO with AND mask transparency. We want to ensure that
// we can downscale it without crashing or triggering ASAN failures, but its
// content isn't simple to verify, so for now we don't check the output.
return ImageTestCase("transparent-ico-with-and-mask.ico", "image/x-icon",
IntSize(32, 32), IntSize(20, 20),
TEST_CASE_IS_TRANSPARENT | TEST_CASE_IGNORE_OUTPUT);
}
ImageTestCase TruncatedSmallGIFTestCase()
{
return ImageTestCase("green-1x1-truncated.gif", "image/gif", IntSize(1, 1));
}
} // namespace image
} // namespace mozilla

419
image/test/gtest/Common.h Normal file
View file

@ -0,0 +1,419 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#ifndef mozilla_image_test_gtest_Common_h
#define mozilla_image_test_gtest_Common_h
#include <vector>
#include "gtest/gtest.h"
#include "mozilla/Maybe.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/gfx/2D.h"
#include "Decoder.h"
#include "gfxColor.h"
#include "imgITools.h"
#include "nsCOMPtr.h"
#include "SurfacePipe.h"
#include "SurfacePipeFactory.h"
class nsIInputStream;
namespace mozilla {
namespace image {
///////////////////////////////////////////////////////////////////////////////
// Types
///////////////////////////////////////////////////////////////////////////////
enum TestCaseFlags
{
TEST_CASE_DEFAULT_FLAGS = 0,
TEST_CASE_IS_FUZZY = 1 << 0,
TEST_CASE_HAS_ERROR = 1 << 1,
TEST_CASE_IS_TRANSPARENT = 1 << 2,
TEST_CASE_IS_ANIMATED = 1 << 3,
TEST_CASE_IGNORE_OUTPUT = 1 << 4,
};
struct ImageTestCase
{
ImageTestCase(const char* aPath,
const char* aMimeType,
gfx::IntSize aSize,
uint32_t aFlags = TEST_CASE_DEFAULT_FLAGS)
: mPath(aPath)
, mMimeType(aMimeType)
, mSize(aSize)
, mOutputSize(aSize)
, mFlags(aFlags)
{ }
ImageTestCase(const char* aPath,
const char* aMimeType,
gfx::IntSize aSize,
gfx::IntSize aOutputSize,
uint32_t aFlags = TEST_CASE_DEFAULT_FLAGS)
: mPath(aPath)
, mMimeType(aMimeType)
, mSize(aSize)
, mOutputSize(aOutputSize)
, mFlags(aFlags)
{ }
const char* mPath;
const char* mMimeType;
gfx::IntSize mSize;
gfx::IntSize mOutputSize;
uint32_t mFlags;
};
struct BGRAColor
{
BGRAColor() : BGRAColor(0, 0, 0, 0) { }
BGRAColor(uint8_t aBlue, uint8_t aGreen, uint8_t aRed, uint8_t aAlpha)
: mBlue(aBlue)
, mGreen(aGreen)
, mRed(aRed)
, mAlpha(aAlpha)
{ }
static BGRAColor Green() { return BGRAColor(0x00, 0xFF, 0x00, 0xFF); }
static BGRAColor Red() { return BGRAColor(0x00, 0x00, 0xFF, 0xFF); }
static BGRAColor Blue() { return BGRAColor(0xFF, 0x00, 0x00, 0xFF); }
static BGRAColor Transparent() { return BGRAColor(0x00, 0x00, 0x00, 0x00); }
uint32_t AsPixel() const { return gfxPackedPixel(mAlpha, mRed, mGreen, mBlue); }
uint8_t mBlue;
uint8_t mGreen;
uint8_t mRed;
uint8_t mAlpha;
};
///////////////////////////////////////////////////////////////////////////////
// General Helpers
///////////////////////////////////////////////////////////////////////////////
/**
* A RAII class that ensure that ImageLib services are available. Any tests that
* require ImageLib to be initialized (for example, any test that uses the
* SurfaceCache; see image::EnsureModuleInitialized() for the full list) can
* use this class to ensure that ImageLib services are available. Failure to do
* so can result in strange, non-deterministic failures.
*/
struct AutoInitializeImageLib
{
AutoInitializeImageLib()
{
// Ensure that ImageLib services are initialized.
nsCOMPtr<imgITools> imgTools = do_CreateInstance("@mozilla.org/image/tools;1");
EXPECT_TRUE(imgTools != nullptr);
}
};
/// Loads a file from the current directory. @return an nsIInputStream for it.
already_AddRefed<nsIInputStream> LoadFile(const char* aRelativePath);
/**
* @returns true if every pixel of @aSurface is @aColor.
*
* If @aFuzz is nonzero, a tolerance of @aFuzz is allowed in each color
* component. This may be necessary for tests that involve JPEG images or
* downscaling.
*/
bool IsSolidColor(gfx::SourceSurface* aSurface,
BGRAColor aColor,
uint8_t aFuzz = 0);
/**
* @returns true if every pixel of @aDecoder's surface has the palette index
* specified by @aColor.
*/
bool IsSolidPalettedColor(Decoder* aDecoder, uint8_t aColor);
/**
* @returns true if every pixel in the range of rows specified by @aStartRow and
* @aRowCount of @aSurface is @aColor.
*
* If @aFuzz is nonzero, a tolerance of @aFuzz is allowed in each color
* component. This may be necessary for tests that involve JPEG images or
* downscaling.
*/
bool RowsAreSolidColor(gfx::SourceSurface* aSurface,
int32_t aStartRow,
int32_t aRowCount,
BGRAColor aColor,
uint8_t aFuzz = 0);
/**
* @returns true if every pixel in the range of rows specified by @aStartRow and
* @aRowCount of @aDecoder's surface has the palette index specified by @aColor.
*/
bool PalettedRowsAreSolidColor(Decoder* aDecoder,
int32_t aStartRow,
int32_t aRowCount,
uint8_t aColor);
/**
* @returns true if every pixel in the rect specified by @aRect is @aColor.
*
* If @aFuzz is nonzero, a tolerance of @aFuzz is allowed in each color
* component. This may be necessary for tests that involve JPEG images or
* downscaling.
*/
bool RectIsSolidColor(gfx::SourceSurface* aSurface,
const gfx::IntRect& aRect,
BGRAColor aColor,
uint8_t aFuzz = 0);
/**
* @returns true if every pixel in the rect specified by @aRect has the palette
* index specified by @aColor.
*/
bool PalettedRectIsSolidColor(Decoder* aDecoder,
const gfx::IntRect& aRect,
uint8_t aColor);
/**
* @returns true if the pixels in @aRow of @aSurface match the pixels given in
* @aPixels.
*/
bool RowHasPixels(gfx::SourceSurface* aSurface,
int32_t aRow,
const std::vector<BGRAColor>& aPixels);
// ExpectNoResume is an IResumable implementation for use by tests that expect
// Resume() to never get called.
class ExpectNoResume final : public IResumable
{
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ExpectNoResume, override)
void Resume() override { FAIL() << "Resume() should not get called"; }
private:
~ExpectNoResume() override { }
};
// CountResumes is an IResumable implementation for use by tests that expect
// Resume() to get called a certain number of times.
class CountResumes : public IResumable
{
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CountResumes, override)
CountResumes() : mCount(0) { }
void Resume() override { mCount++; }
uint32_t Count() const { return mCount; }
private:
~CountResumes() override { }
uint32_t mCount;
};
///////////////////////////////////////////////////////////////////////////////
// SurfacePipe Helpers
///////////////////////////////////////////////////////////////////////////////
/**
* Creates a decoder with no data associated with, suitable for testing code
* that requires a decoder to initialize or to allocate surfaces but doesn't
* actually need the decoder to do any decoding.
*
* XXX(seth): We only need this because SurfaceSink and PalettedSurfaceSink
* defer to the decoder for surface allocation. Once all decoders use
* SurfacePipe we won't need to do that anymore and we can remove this function.
*/
already_AddRefed<Decoder> CreateTrivialDecoder();
/**
* Creates a pipeline of SurfaceFilters from a list of Config structs and passes
* it to the provided lambda @aFunc. Assertions that the pipeline is constructly
* correctly and cleanup of any allocated surfaces is handled automatically.
*
* @param aDecoder The decoder to use for allocating surfaces.
* @param aFunc The lambda function to pass the filter pipeline to.
* @param aConfigs The configuration for the pipeline.
*/
template <typename Func, typename... Configs>
void WithFilterPipeline(Decoder* aDecoder, Func aFunc, Configs... aConfigs)
{
auto pipe = MakeUnique<typename detail::FilterPipeline<Configs...>::Type>();
nsresult rv = pipe->Configure(aConfigs...);
ASSERT_TRUE(NS_SUCCEEDED(rv));
aFunc(aDecoder, pipe.get());
RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
if (currentFrame) {
currentFrame->Finish();
}
}
/**
* Creates a pipeline of SurfaceFilters from a list of Config structs and
* asserts that configuring it fails. Cleanup of any allocated surfaces is
* handled automatically.
*
* @param aDecoder The decoder to use for allocating surfaces.
* @param aConfigs The configuration for the pipeline.
*/
template <typename... Configs>
void AssertConfiguringPipelineFails(Decoder* aDecoder, Configs... aConfigs)
{
auto pipe = MakeUnique<typename detail::FilterPipeline<Configs...>::Type>();
nsresult rv = pipe->Configure(aConfigs...);
// Callers expect configuring the pipeline to fail.
ASSERT_TRUE(NS_FAILED(rv));
RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
if (currentFrame) {
currentFrame->Finish();
}
}
/**
* Asserts that the provided filter pipeline is in the correct final state,
* which is to say, the entire surface has been written to (IsSurfaceFinished()
* returns true) and the invalid rects are as expected.
*
* @param aFilter The filter pipeline to check.
* @param aInputSpaceRect The expect invalid rect, in input space.
* @param aoutputSpaceRect The expect invalid rect, in output space.
*/
void AssertCorrectPipelineFinalState(SurfaceFilter* aFilter,
const gfx::IntRect& aInputSpaceRect,
const gfx::IntRect& aOutputSpaceRect);
/**
* Checks a generated image for correctness. Reports any unexpected deviation
* from the expected image as GTest failures.
*
* @param aDecoder The decoder which contains the image. The decoder's current
* frame will be checked.
* @param aRect The region in the space of the output surface that the filter
* pipeline will actually write to. It's expected that pixels in
* this region are green, while pixels outside this region are
* transparent.
* @param aFuzz The amount of fuzz to use in pixel comparisons.
*/
void CheckGeneratedImage(Decoder* aDecoder,
const gfx::IntRect& aRect,
uint8_t aFuzz = 0);
/**
* Checks a generated paletted image for correctness. Reports any unexpected
* deviation from the expected image as GTest failures.
*
* @param aDecoder The decoder which contains the image. The decoder's current
* frame will be checked.
* @param aRect The region in the space of the output surface that the filter
* pipeline will actually write to. It's expected that pixels in
* this region have a palette index of 255, while pixels outside
* this region have a palette index of 0.
*/
void CheckGeneratedPalettedImage(Decoder* aDecoder, const gfx::IntRect& aRect);
/**
* Tests the result of calling WritePixels() using the provided SurfaceFilter
* pipeline. The pipeline must be a normal (i.e., non-paletted) pipeline.
*
* The arguments are specified in the an order intended to minimize the number
* of arguments that most test cases need to pass.
*
* @param aDecoder The decoder whose current frame will be written to.
* @param aFilter The SurfaceFilter pipeline to use.
* @param aOutputRect The region in the space of the output surface that will be
* invalidated by the filter pipeline. Defaults to
* (0, 0, 100, 100).
* @param aInputRect The region in the space of the input image that will be
* invalidated by the filter pipeline. Defaults to
* (0, 0, 100, 100).
* @param aInputWriteRect The region in the space of the input image that the
* filter pipeline will allow writes to. Note the
* difference from @aInputRect: @aInputRect is the actual
* region invalidated, while @aInputWriteRect is the
* region that is written to. These can differ in cases
* where the input is not clipped to the size of the image.
* Defaults to the entire input rect.
* @param aOutputWriteRect The region in the space of the output surface that
* the filter pipeline will actually write to. It's
* expected that pixels in this region are green, while
* pixels outside this region are transparent. Defaults
* to the entire output rect.
*/
void CheckWritePixels(Decoder* aDecoder,
SurfaceFilter* aFilter,
Maybe<gfx::IntRect> aOutputRect = Nothing(),
Maybe<gfx::IntRect> aInputRect = Nothing(),
Maybe<gfx::IntRect> aInputWriteRect = Nothing(),
Maybe<gfx::IntRect> aOutputWriteRect = Nothing(),
uint8_t aFuzz = 0);
/**
* Tests the result of calling WritePixels() using the provided SurfaceFilter
* pipeline. The pipeline must be a paletted pipeline.
* @see CheckWritePixels() for documentation of the arguments.
*/
void CheckPalettedWritePixels(Decoder* aDecoder,
SurfaceFilter* aFilter,
Maybe<gfx::IntRect> aOutputRect = Nothing(),
Maybe<gfx::IntRect> aInputRect = Nothing(),
Maybe<gfx::IntRect> aInputWriteRect = Nothing(),
Maybe<gfx::IntRect> aOutputWriteRect = Nothing(),
uint8_t aFuzz = 0);
///////////////////////////////////////////////////////////////////////////////
// Test Data
///////////////////////////////////////////////////////////////////////////////
ImageTestCase GreenPNGTestCase();
ImageTestCase GreenGIFTestCase();
ImageTestCase GreenJPGTestCase();
ImageTestCase GreenBMPTestCase();
ImageTestCase GreenICOTestCase();
ImageTestCase GreenIconTestCase();
ImageTestCase GreenFirstFrameAnimatedGIFTestCase();
ImageTestCase GreenFirstFrameAnimatedPNGTestCase();
ImageTestCase CorruptTestCase();
ImageTestCase CorruptBMPWithTruncatedHeader();
ImageTestCase CorruptICOWithBadBMPWidthTestCase();
ImageTestCase CorruptICOWithBadBMPHeightTestCase();
ImageTestCase TransparentPNGTestCase();
ImageTestCase TransparentGIFTestCase();
ImageTestCase FirstFramePaddingGIFTestCase();
ImageTestCase NoFrameDelayGIFTestCase();
ImageTestCase ExtraImageSubBlocksAnimatedGIFTestCase();
ImageTestCase TransparentBMPWhenBMPAlphaEnabledTestCase();
ImageTestCase RLE4BMPTestCase();
ImageTestCase RLE8BMPTestCase();
ImageTestCase DownscaledPNGTestCase();
ImageTestCase DownscaledGIFTestCase();
ImageTestCase DownscaledJPGTestCase();
ImageTestCase DownscaledBMPTestCase();
ImageTestCase DownscaledICOTestCase();
ImageTestCase DownscaledIconTestCase();
ImageTestCase DownscaledTransparentICOWithANDMaskTestCase();
ImageTestCase TruncatedSmallGIFTestCase();
} // namespace image
} // namespace mozilla
#endif // mozilla_image_test_gtest_Common_h

View file

@ -0,0 +1,671 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */
#include <algorithm>
#include <vector>
#include "gtest/gtest.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/Maybe.h"
#include "Common.h"
#include "Decoder.h"
#include "DecoderFactory.h"
#include "SourceBuffer.h"
#include "SurfaceFilters.h"
#include "SurfacePipe.h"
using namespace mozilla;
using namespace mozilla::gfx;
using namespace mozilla::image;
using std::generate;
using std::vector;
template <typename Func> void
WithADAM7InterpolatingFilter(const IntSize& aSize, Func aFunc)
{
RefPtr<Decoder> decoder = CreateTrivialDecoder();
ASSERT_TRUE(bool(decoder));
WithFilterPipeline(decoder, Forward<Func>(aFunc),
ADAM7InterpolatingConfig { },
SurfaceConfig { decoder, 0, aSize,
SurfaceFormat::B8G8R8A8, false });
}
void
AssertConfiguringADAM7InterpolatingFilterFails(const IntSize& aSize)
{
RefPtr<Decoder> decoder = CreateTrivialDecoder();
ASSERT_TRUE(bool(decoder));
AssertConfiguringPipelineFails(decoder,
ADAM7InterpolatingConfig { },
SurfaceConfig { decoder, 0, aSize,
SurfaceFormat::B8G8R8A8, false });
}
uint8_t
InterpolateByte(uint8_t aByteA, uint8_t aByteB, float aWeight)
{
return uint8_t(aByteA * aWeight + aByteB * (1.0f - aWeight));
}
BGRAColor
InterpolateColors(BGRAColor aColor1, BGRAColor aColor2, float aWeight)
{
return BGRAColor(InterpolateByte(aColor1.mBlue, aColor2.mBlue, aWeight),
InterpolateByte(aColor1.mGreen, aColor2.mGreen, aWeight),
InterpolateByte(aColor1.mRed, aColor2.mRed, aWeight),
InterpolateByte(aColor1.mAlpha, aColor2.mAlpha, aWeight));
}
enum class ShouldInterpolate
{
eYes,
eNo
};
BGRAColor
HorizontallyInterpolatedPixel(uint32_t aCol,
uint32_t aWidth,
const vector<float>& aWeights,
ShouldInterpolate aShouldInterpolate,
const vector<BGRAColor>& aColors)
{
// We cycle through the vector of weights forever.
float weight = aWeights[aCol % aWeights.size()];
// Find the columns of the two final pixels for this set of weights.
uint32_t finalPixel1 = aCol - aCol % aWeights.size();
uint32_t finalPixel2 = finalPixel1 + aWeights.size();
// If |finalPixel2| is past the end of the row, that means that there is no
// final pixel after the pixel at |finalPixel1|. In that case, we just want to
// duplicate |finalPixel1|'s color until the end of the row. We can do that by
// setting |finalPixel2| equal to |finalPixel1| so that the interpolation has
// no effect.
if (finalPixel2 >= aWidth) {
finalPixel2 = finalPixel1;
}
// We cycle through the vector of colors forever (subject to the above
// constraint about the end of the row).
BGRAColor color1 = aColors[finalPixel1 % aColors.size()];
BGRAColor color2 = aColors[finalPixel2 % aColors.size()];
// If we're not interpolating, we treat all pixels which aren't final as
// transparent. Since the number of weights we have is equal to the stride
// between final pixels, we can check if |aCol| is a final pixel by checking
// whether |aCol| is a multiple of |aWeights.size()|.
if (aShouldInterpolate == ShouldInterpolate::eNo) {
return aCol % aWeights.size() == 0 ? color1
: BGRAColor::Transparent();
}
// Interpolate.
return InterpolateColors(color1, color2, weight);
}
vector<float>&
InterpolationWeights(int32_t aStride)
{
// Precalculated interpolation weights. These are used to interpolate
// between final pixels or between important rows. Although no interpolation
// is actually applied to the previous final pixel or important row value,
// the arrays still start with 1.0f, which is always skipped, primarily
// because otherwise |stride1Weights| would have zero elements.
static vector<float> stride8Weights =
{ 1.0f, 7 / 8.0f, 6 / 8.0f, 5 / 8.0f, 4 / 8.0f, 3 / 8.0f, 2 / 8.0f, 1 / 8.0f };
static vector<float> stride4Weights = { 1.0f, 3 / 4.0f, 2 / 4.0f, 1 / 4.0f };
static vector<float> stride2Weights = { 1.0f, 1 / 2.0f };
static vector<float> stride1Weights = { 1.0f };
switch (aStride) {
case 8: return stride8Weights;
case 4: return stride4Weights;
case 2: return stride2Weights;
case 1: return stride1Weights;
default:
MOZ_CRASH();
}
}
int32_t
ImportantRowStride(uint8_t aPass)
{
// The stride between important rows for each pass, with a dummy value for
// the nonexistent pass 0 and for pass 8, since the tests run an extra pass to
// make sure nothing breaks.
static int32_t strides[] = { 1, 8, 8, 4, 4, 2, 2, 1, 1 };
return strides[aPass];
}
size_t
FinalPixelStride(uint8_t aPass)
{
// The stride between the final pixels in important rows for each pass, with
// a dummy value for the nonexistent pass 0 and for pass 8, since the tests
// run an extra pass to make sure nothing breaks.
static size_t strides[] = { 1, 8, 4, 4, 2, 2, 1, 1, 1 };
return strides[aPass];
}
bool
IsImportantRow(int32_t aRow, uint8_t aPass)
{
return aRow % ImportantRowStride(aPass) == 0;
}
/**
* ADAM7 breaks up the image into 8x8 blocks. On each of the 7 passes, a new
* set of pixels in each block receives their final values, according to the
* following pattern:
*
* 1 6 4 6 2 6 4 6
* 7 7 7 7 7 7 7 7
* 5 6 5 6 5 6 5 6
* 7 7 7 7 7 7 7 7
* 3 6 4 6 3 6 4 6
* 7 7 7 7 7 7 7 7
* 5 6 5 6 5 6 5 6
* 7 7 7 7 7 7 7 7
*
* This function produces a row of pixels @aWidth wide, suitable for testing
* horizontal interpolation on pass @aPass. The pattern of pixels used is
* determined by @aPass and @aRow, which determine which pixels are final
* according to the table above, and @aColors, from which the pixel values
* are selected.
*
* There are two different behaviors: if |eNo| is passed for
* @aShouldInterpolate, non-final pixels are treated as transparent. If |eNo|
* is passed, non-final pixels get interpolated in from the surrounding final
* pixels. The intention is that |eNo| is passed to generate input which will
* be run through ADAM7InterpolatingFilter, and |eYes| is passed to generate
* reference data to check that the filter is performing horizontal
* interpolation correctly.
*
* This function does not perform vertical interpolation. Rows which aren't on
* the current pass are filled with transparent pixels.
*
* @return a vector<BGRAColor> representing a row of pixels.
*/
vector<BGRAColor>
ADAM7HorizontallyInterpolatedRow(uint8_t aPass,
uint32_t aRow,
uint32_t aWidth,
ShouldInterpolate aShouldInterpolate,
const vector<BGRAColor>& aColors)
{
EXPECT_GT(aPass, 0);
EXPECT_LE(aPass, 8);
EXPECT_GT(aColors.size(), 0u);
vector<BGRAColor> result(aWidth);
if (IsImportantRow(aRow, aPass)) {
vector<float>& weights = InterpolationWeights(FinalPixelStride(aPass));
// Compute the horizontally interpolated row.
uint32_t col = 0;
generate(result.begin(), result.end(), [&]{
return HorizontallyInterpolatedPixel(col++, aWidth, weights,
aShouldInterpolate, aColors);
});
} else {
// This is an unimportant row; just make the entire thing transparent.
generate(result.begin(), result.end(), []{
return BGRAColor::Transparent();
});
}
EXPECT_EQ(result.size(), size_t(aWidth));
return result;
}
WriteState
WriteUninterpolatedPixels(SurfaceFilter* aFilter,
const IntSize& aSize,
uint8_t aPass,
const vector<BGRAColor>& aColors)
{
WriteState result = WriteState::NEED_MORE_DATA;
for (int32_t row = 0; row < aSize.height; ++row) {
// Compute uninterpolated pixels for this row.
vector<BGRAColor> pixels =
Move(ADAM7HorizontallyInterpolatedRow(aPass, row, aSize.width,
ShouldInterpolate::eNo, aColors));
// Write them to the surface.
auto pixelIterator = pixels.cbegin();
result = aFilter->WritePixelsToRow<uint32_t>([&]{
return AsVariant((*pixelIterator++).AsPixel());
});
if (result != WriteState::NEED_MORE_DATA) {
break;
}
}
return result;
}
bool
CheckHorizontallyInterpolatedImage(Decoder* aDecoder,
const IntSize& aSize,
uint8_t aPass,
const vector<BGRAColor>& aColors)
{
RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
RefPtr<SourceSurface> surface = currentFrame->GetSourceSurface();
for (int32_t row = 0; row < aSize.height; ++row) {
if (!IsImportantRow(row, aPass)) {
continue; // Don't check rows which aren't important on this pass.
}
// Compute the expected pixels, *with* interpolation to match what the
// filter should have done.
vector<BGRAColor> expectedPixels =
Move(ADAM7HorizontallyInterpolatedRow(aPass, row, aSize.width,
ShouldInterpolate::eYes, aColors));
if (!RowHasPixels(surface, row, expectedPixels)) {
return false;
}
}
return true;
}
void
CheckHorizontalInterpolation(const IntSize& aSize,
const vector<BGRAColor>& aColors)
{
const IntRect surfaceRect(IntPoint(0, 0), aSize);
WithADAM7InterpolatingFilter(aSize,
[&](Decoder* aDecoder, SurfaceFilter* aFilter) {
// We check horizontal interpolation behavior for each pass individually. In
// addition to the normal 7 passes that ADAM7 includes, we also check an
// eighth pass to verify that nothing breaks if extra data is written.
for (uint8_t pass = 1; pass <= 8; ++pass) {
// Write our color pattern to the surface. We don't perform any
// interpolation when writing to the filter so that we can check that the
// filter itself *does*.
WriteState result =
WriteUninterpolatedPixels(aFilter, aSize, pass, aColors);
EXPECT_EQ(WriteState::FINISHED, result);
AssertCorrectPipelineFinalState(aFilter, surfaceRect, surfaceRect);
// Check that the generated image matches the expected pattern, with
// interpolation applied.
EXPECT_TRUE(CheckHorizontallyInterpolatedImage(aDecoder, aSize,
pass, aColors));
// Prepare for the next pass.
aFilter->ResetToFirstRow();
}
});
}
BGRAColor
ADAM7RowColor(int32_t aRow,
uint8_t aPass,
const vector<BGRAColor>& aColors)
{
EXPECT_LT(0, aPass);
EXPECT_GE(8, aPass);
EXPECT_LT(0u, aColors.size());
// If this is an important row, select the color from the provided vector of
// colors, which we cycle through infinitely. If not, just fill the row with
// transparent pixels.
return IsImportantRow(aRow, aPass) ? aColors[aRow % aColors.size()]
: BGRAColor::Transparent();
}
WriteState
WriteRowColorPixels(SurfaceFilter* aFilter,
const IntSize& aSize,
uint8_t aPass,
const vector<BGRAColor>& aColors)
{
WriteState result = WriteState::NEED_MORE_DATA;
for (int32_t row = 0; row < aSize.height; ++row) {
const uint32_t color = ADAM7RowColor(row, aPass, aColors).AsPixel();
// Fill the surface with |color| pixels.
result = aFilter->WritePixelsToRow<uint32_t>([&]{ return AsVariant(color); });
if (result != WriteState::NEED_MORE_DATA) {
break;
}
}
return result;
}
bool
CheckVerticallyInterpolatedImage(Decoder* aDecoder,
const IntSize& aSize,
uint8_t aPass,
const vector<BGRAColor>& aColors)
{
vector<float>& weights = InterpolationWeights(ImportantRowStride(aPass));
for (int32_t row = 0; row < aSize.height; ++row) {
// Vertically interpolation takes place between two important rows. The
// separation between the important rows is determined by the stride of this
// pass. When there is no "next" important row because we'd run off the
// bottom of the image, we use the same row for both. This matches
// ADAM7InterpolatingFilter's behavior of duplicating the last important row
// since there isn't another important row to vertically interpolate it
// with.
const int32_t stride = ImportantRowStride(aPass);
const int32_t prevImportantRow = row - row % stride;
const int32_t maybeNextImportantRow = prevImportantRow + stride;
const int32_t nextImportantRow = maybeNextImportantRow < aSize.height
? maybeNextImportantRow
: prevImportantRow;
// Retrieve the colors for the important rows we're going to interpolate.
const BGRAColor prevImportantRowColor =
ADAM7RowColor(prevImportantRow, aPass, aColors);
const BGRAColor nextImportantRowColor =
ADAM7RowColor(nextImportantRow, aPass, aColors);
// The weight we'll use for interpolation is also determined by the stride.
// A row halfway between two important rows should have pixels that have a
// 50% contribution from each of the important rows, for example.
const float weight = weights[row % stride];
const BGRAColor interpolatedColor =
InterpolateColors(prevImportantRowColor, nextImportantRowColor, weight);
// Generate a row of expected pixels. Every pixel in the row is always the
// same color since we're only testing vertical interpolation between
// solid-colored rows.
vector<BGRAColor> expectedPixels(aSize.width);
generate(expectedPixels.begin(), expectedPixels.end(), [&]{
return interpolatedColor;
});
// Check that the pixels match.
RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
RefPtr<SourceSurface> surface = currentFrame->GetSourceSurface();
if (!RowHasPixels(surface, row, expectedPixels)) {
return false;
}
}
return true;
}
void
CheckVerticalInterpolation(const IntSize& aSize,
const vector<BGRAColor>& aColors)
{
const IntRect surfaceRect(IntPoint(0, 0), aSize);
WithADAM7InterpolatingFilter(aSize,
[&](Decoder* aDecoder, SurfaceFilter* aFilter) {
for (uint8_t pass = 1; pass <= 8; ++pass) {
// Write a pattern of rows to the surface. Important rows will receive a
// color selected from |aColors|; unimportant rows will be transparent.
WriteState result = WriteRowColorPixels(aFilter, aSize, pass, aColors);
EXPECT_EQ(WriteState::FINISHED, result);
AssertCorrectPipelineFinalState(aFilter, surfaceRect, surfaceRect);
// Check that the generated image matches the expected pattern, with
// interpolation applied.
EXPECT_TRUE(CheckVerticallyInterpolatedImage(aDecoder, aSize,
pass, aColors));
// Prepare for the next pass.
aFilter->ResetToFirstRow();
}
});
}
void
CheckInterpolation(const IntSize& aSize, const vector<BGRAColor>& aColors)
{
CheckHorizontalInterpolation(aSize, aColors);
CheckVerticalInterpolation(aSize, aColors);
}
void
CheckADAM7InterpolatingWritePixels(const IntSize& aSize)
{
// This test writes 8 passes of green pixels (the seven ADAM7 passes, plus one
// extra to make sure nothing goes wrong if we write too much input) and verifies
// that the output is a solid green surface each time. Because all the pixels
// are the same color, interpolation doesn't matter; we test the correctness
// of the interpolation algorithm itself separately.
WithADAM7InterpolatingFilter(aSize,
[&](Decoder* aDecoder, SurfaceFilter* aFilter) {
IntRect rect(IntPoint(0, 0), aSize);
for (int32_t pass = 1; pass <= 8; ++pass) {
// We only actually write up to the last important row for each pass,
// because that row unambiguously determines the remaining rows.
const int32_t lastRow = aSize.height - 1;
const int32_t lastImportantRow =
lastRow - (lastRow % ImportantRowStride(pass));
const IntRect inputWriteRect(0, 0, aSize.width, lastImportantRow + 1);
CheckWritePixels(aDecoder, aFilter,
/* aOutputRect = */ Some(rect),
/* aInputRect = */ Some(rect),
/* aInputWriteRect = */ Some(inputWriteRect));
aFilter->ResetToFirstRow();
EXPECT_FALSE(aFilter->IsSurfaceFinished());
Maybe<SurfaceInvalidRect> invalidRect = aFilter->TakeInvalidRect();
EXPECT_TRUE(invalidRect.isNothing());
}
});
}
TEST(ImageADAM7InterpolatingFilter, WritePixels100_100)
{
CheckADAM7InterpolatingWritePixels(IntSize(100, 100));
}
TEST(ImageADAM7InterpolatingFilter, WritePixels99_99)
{
CheckADAM7InterpolatingWritePixels(IntSize(99, 99));
}
TEST(ImageADAM7InterpolatingFilter, WritePixels66_33)
{
CheckADAM7InterpolatingWritePixels(IntSize(66, 33));
}
TEST(ImageADAM7InterpolatingFilter, WritePixels33_66)
{
CheckADAM7InterpolatingWritePixels(IntSize(33, 66));
}
TEST(ImageADAM7InterpolatingFilter, WritePixels15_15)
{
CheckADAM7InterpolatingWritePixels(IntSize(15, 15));
}
TEST(ImageADAM7InterpolatingFilter, WritePixels9_9)
{
CheckADAM7InterpolatingWritePixels(IntSize(9, 9));
}
TEST(ImageADAM7InterpolatingFilter, WritePixels8_8)
{
CheckADAM7InterpolatingWritePixels(IntSize(8, 8));
}
TEST(ImageADAM7InterpolatingFilter, WritePixels7_7)
{
CheckADAM7InterpolatingWritePixels(IntSize(7, 7));
}
TEST(ImageADAM7InterpolatingFilter, WritePixels3_3)
{
CheckADAM7InterpolatingWritePixels(IntSize(3, 3));
}
TEST(ImageADAM7InterpolatingFilter, WritePixels1_1)
{
CheckADAM7InterpolatingWritePixels(IntSize(1, 1));
}
TEST(ImageADAM7InterpolatingFilter, TrivialInterpolation48_48)
{
CheckInterpolation(IntSize(48, 48), { BGRAColor::Green() });
}
TEST(ImageADAM7InterpolatingFilter, InterpolationOutput33_17)
{
// We check interpolation using irregular patterns to make sure that the
// interpolation will look different for different passes.
CheckInterpolation(IntSize(33, 17), {
BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Green(), BGRAColor::Blue(),
BGRAColor::Blue(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Green(),
BGRAColor::Red(), BGRAColor::Red(), BGRAColor::Blue(), BGRAColor::Blue(),
BGRAColor::Green(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Blue(),
BGRAColor::Red(), BGRAColor::Green(), BGRAColor::Blue(), BGRAColor::Red(),
BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Red(), BGRAColor::Blue(),
BGRAColor::Blue(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Green(),
BGRAColor::Green(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Blue()
});
}
TEST(ImageADAM7InterpolatingFilter, InterpolationOutput32_16)
{
CheckInterpolation(IntSize(32, 16), {
BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Green(), BGRAColor::Blue(),
BGRAColor::Blue(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Green(),
BGRAColor::Red(), BGRAColor::Red(), BGRAColor::Blue(), BGRAColor::Blue(),
BGRAColor::Green(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Blue(),
BGRAColor::Red(), BGRAColor::Green(), BGRAColor::Blue(), BGRAColor::Red(),
BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Red(), BGRAColor::Blue(),
BGRAColor::Blue(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Green(),
BGRAColor::Green(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Blue()
});
}
TEST(ImageADAM7InterpolatingFilter, InterpolationOutput31_15)
{
CheckInterpolation(IntSize(31, 15), {
BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Green(), BGRAColor::Blue(),
BGRAColor::Blue(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Green(),
BGRAColor::Red(), BGRAColor::Red(), BGRAColor::Blue(), BGRAColor::Blue(),
BGRAColor::Green(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Blue(),
BGRAColor::Red(), BGRAColor::Green(), BGRAColor::Blue(), BGRAColor::Red(),
BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Red(), BGRAColor::Blue(),
BGRAColor::Blue(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Green(),
BGRAColor::Green(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Blue()
});
}
TEST(ImageADAM7InterpolatingFilter, InterpolationOutput17_33)
{
CheckInterpolation(IntSize(17, 33), {
BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Green(), BGRAColor::Blue(),
BGRAColor::Red(), BGRAColor::Green(), BGRAColor::Blue(), BGRAColor::Red(),
BGRAColor::Blue(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Green(),
BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Red(), BGRAColor::Blue()
});
}
TEST(ImageADAM7InterpolatingFilter, InterpolationOutput16_32)
{
CheckInterpolation(IntSize(16, 32), {
BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Green(), BGRAColor::Blue(),
BGRAColor::Red(), BGRAColor::Green(), BGRAColor::Blue(), BGRAColor::Red(),
BGRAColor::Blue(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Green(),
BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Red(), BGRAColor::Blue()
});
}
TEST(ImageADAM7InterpolatingFilter, InterpolationOutput15_31)
{
CheckInterpolation(IntSize(15, 31), {
BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Green(), BGRAColor::Blue(),
BGRAColor::Red(), BGRAColor::Green(), BGRAColor::Blue(), BGRAColor::Red(),
BGRAColor::Blue(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Green(),
BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Red(), BGRAColor::Blue()
});
}
TEST(ImageADAM7InterpolatingFilter, InterpolationOutput9_9)
{
CheckInterpolation(IntSize(9, 9), {
BGRAColor::Blue(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Green(),
BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Red(), BGRAColor::Blue()
});
}
TEST(ImageADAM7InterpolatingFilter, InterpolationOutput8_8)
{
CheckInterpolation(IntSize(8, 8), {
BGRAColor::Blue(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Green(),
BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Red(), BGRAColor::Blue()
});
}
TEST(ImageADAM7InterpolatingFilter, InterpolationOutput7_7)
{
CheckInterpolation(IntSize(7, 7), {
BGRAColor::Blue(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Green(),
BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Red(), BGRAColor::Blue()
});
}
TEST(ImageADAM7InterpolatingFilter, InterpolationOutput3_3)
{
CheckInterpolation(IntSize(3, 3), {
BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Blue(), BGRAColor::Red()
});
}
TEST(ImageADAM7InterpolatingFilter, InterpolationOutput1_1)
{
CheckInterpolation(IntSize(1, 1), { BGRAColor::Blue() });
}
TEST(ImageADAM7InterpolatingFilter, ADAM7InterpolationFailsFor0_0)
{
// A 0x0 input size is invalid, so configuration should fail.
AssertConfiguringADAM7InterpolatingFilterFails(IntSize(0, 0));
}
TEST(ImageADAM7InterpolatingFilter, ADAM7InterpolationFailsForMinus1_Minus1)
{
// A negative input size is invalid, so configuration should fail.
AssertConfiguringADAM7InterpolatingFilterFails(IntSize(-1, -1));
}
TEST(ImageADAM7InterpolatingFilter, ConfiguringPalettedADAM7InterpolatingFilterFails)
{
RefPtr<Decoder> decoder = CreateTrivialDecoder();
ASSERT_TRUE(decoder != nullptr);
// ADAM7InterpolatingFilter does not support paletted images, so configuration
// should fail.
AssertConfiguringPipelineFails(decoder,
ADAM7InterpolatingConfig { },
PalettedSurfaceConfig { decoder, 0, IntSize(100, 100),
IntRect(0, 0, 50, 50),
SurfaceFormat::B8G8R8A8, 8,
false });
}

View file

@ -0,0 +1,235 @@
/* 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/. */
#include "gtest/gtest.h"
#include "CopyOnWrite.h"
using namespace mozilla;
using namespace mozilla::image;
struct ValueStats
{
int32_t mCopies = 0;
int32_t mFrees = 0;
int32_t mCalls = 0;
int32_t mConstCalls = 0;
int32_t mSerial = 0;
};
struct Value
{
NS_INLINE_DECL_REFCOUNTING(Value)
explicit Value(ValueStats& aStats)
: mStats(aStats)
, mSerial(mStats.mSerial++)
{ }
Value(const Value& aOther)
: mStats(aOther.mStats)
, mSerial(mStats.mSerial++)
{
mStats.mCopies++;
}
void Go() { mStats.mCalls++; }
void Go() const { mStats.mConstCalls++; }
int32_t Serial() const { return mSerial; }
protected:
~Value() { mStats.mFrees++; }
private:
ValueStats& mStats;
int32_t mSerial;
};
TEST(ImageCopyOnWrite, Read)
{
ValueStats stats;
{
CopyOnWrite<Value> cow(new Value(stats));
EXPECT_EQ(0, stats.mCopies);
EXPECT_EQ(0, stats.mFrees);
EXPECT_TRUE(cow.CanRead());
cow.Read([&](const Value* aValue) {
EXPECT_EQ(0, stats.mCopies);
EXPECT_EQ(0, stats.mFrees);
EXPECT_EQ(0, aValue->Serial());
EXPECT_TRUE(cow.CanRead());
EXPECT_TRUE(cow.CanWrite());
aValue->Go();
EXPECT_EQ(0, stats.mCalls);
EXPECT_EQ(1, stats.mConstCalls);
});
EXPECT_EQ(0, stats.mCopies);
EXPECT_EQ(0, stats.mFrees);
EXPECT_EQ(0, stats.mCalls);
EXPECT_EQ(1, stats.mConstCalls);
}
EXPECT_EQ(0, stats.mCopies);
EXPECT_EQ(1, stats.mFrees);
}
TEST(ImageCopyOnWrite, RecursiveRead)
{
ValueStats stats;
{
CopyOnWrite<Value> cow(new Value(stats));
EXPECT_EQ(0, stats.mCopies);
EXPECT_EQ(0, stats.mFrees);
EXPECT_TRUE(cow.CanRead());
cow.Read([&](const Value* aValue) {
EXPECT_EQ(0, stats.mCopies);
EXPECT_EQ(0, stats.mFrees);
EXPECT_EQ(0, aValue->Serial());
EXPECT_TRUE(cow.CanRead());
EXPECT_TRUE(cow.CanWrite());
// Make sure that Read() inside a Read() succeeds.
cow.Read([&](const Value* aValue) {
EXPECT_EQ(0, stats.mCopies);
EXPECT_EQ(0, stats.mFrees);
EXPECT_EQ(0, aValue->Serial());
EXPECT_TRUE(cow.CanRead());
EXPECT_TRUE(cow.CanWrite());
aValue->Go();
EXPECT_EQ(0, stats.mCalls);
EXPECT_EQ(1, stats.mConstCalls);
}, []() {
// This gets called if we can't read. We shouldn't get here.
EXPECT_TRUE(false);
});
});
EXPECT_EQ(0, stats.mCopies);
EXPECT_EQ(0, stats.mFrees);
EXPECT_EQ(0, stats.mCalls);
EXPECT_EQ(1, stats.mConstCalls);
}
EXPECT_EQ(0, stats.mCopies);
EXPECT_EQ(1, stats.mFrees);
}
TEST(ImageCopyOnWrite, Write)
{
ValueStats stats;
{
CopyOnWrite<Value> cow(new Value(stats));
EXPECT_EQ(0, stats.mCopies);
EXPECT_EQ(0, stats.mFrees);
EXPECT_TRUE(cow.CanRead());
EXPECT_TRUE(cow.CanWrite());
cow.Write([&](Value* aValue) {
EXPECT_EQ(0, stats.mCopies);
EXPECT_EQ(0, stats.mFrees);
EXPECT_EQ(0, aValue->Serial());
EXPECT_TRUE(!cow.CanRead());
EXPECT_TRUE(!cow.CanWrite());
aValue->Go();
EXPECT_EQ(1, stats.mCalls);
EXPECT_EQ(0, stats.mConstCalls);
});
EXPECT_EQ(0, stats.mCopies);
EXPECT_EQ(0, stats.mFrees);
EXPECT_EQ(1, stats.mCalls);
EXPECT_EQ(0, stats.mConstCalls);
}
EXPECT_EQ(0, stats.mCopies);
EXPECT_EQ(1, stats.mFrees);
}
TEST(ImageCopyOnWrite, WriteRecursive)
{
ValueStats stats;
{
CopyOnWrite<Value> cow(new Value(stats));
EXPECT_EQ(0, stats.mCopies);
EXPECT_EQ(0, stats.mFrees);
EXPECT_TRUE(cow.CanRead());
EXPECT_TRUE(cow.CanWrite());
cow.Read([&](const Value* aValue) {
EXPECT_EQ(0, stats.mCopies);
EXPECT_EQ(0, stats.mFrees);
EXPECT_EQ(0, aValue->Serial());
EXPECT_TRUE(cow.CanRead());
EXPECT_TRUE(cow.CanWrite());
// Make sure Write() inside a Read() succeeds.
cow.Write([&](Value* aValue) {
EXPECT_EQ(1, stats.mCopies);
EXPECT_EQ(0, stats.mFrees);
EXPECT_EQ(1, aValue->Serial());
EXPECT_TRUE(!cow.CanRead());
EXPECT_TRUE(!cow.CanWrite());
aValue->Go();
EXPECT_EQ(1, stats.mCalls);
EXPECT_EQ(0, stats.mConstCalls);
// Make sure Read() inside a Write() fails.
cow.Read([](const Value* aValue) {
// This gets called if we can read. We shouldn't get here.
EXPECT_TRUE(false);
}, []() {
// This gets called if we can't read. We *should* get here.
EXPECT_TRUE(true);
});
// Make sure Write() inside a Write() fails.
cow.Write([](Value* aValue) {
// This gets called if we can write. We shouldn't get here.
EXPECT_TRUE(false);
}, []() {
// This gets called if we can't write. We *should* get here.
EXPECT_TRUE(true);
});
}, []() {
// This gets called if we can't write. We shouldn't get here.
EXPECT_TRUE(false);
});
aValue->Go();
EXPECT_EQ(1, stats.mCopies);
EXPECT_EQ(0, stats.mFrees);
EXPECT_EQ(1, stats.mCalls);
EXPECT_EQ(1, stats.mConstCalls);
});
EXPECT_EQ(1, stats.mCopies);
EXPECT_EQ(1, stats.mFrees);
EXPECT_EQ(1, stats.mCalls);
EXPECT_EQ(1, stats.mConstCalls);
}
EXPECT_EQ(1, stats.mCopies);
EXPECT_EQ(2, stats.mFrees);
}

View file

@ -0,0 +1,123 @@
/* 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/. */
#include "gtest/gtest.h"
#include "Common.h"
#include "imgIContainer.h"
#include "imgITools.h"
#include "ImageOps.h"
#include "mozilla/gfx/2D.h"
#include "nsComponentManagerUtils.h"
#include "nsCOMPtr.h"
#include "nsIInputStream.h"
#include "nsIRunnable.h"
#include "nsIThread.h"
#include "mozilla/RefPtr.h"
#include "nsString.h"
#include "nsThreadUtils.h"
using namespace mozilla;
using namespace mozilla::gfx;
using namespace mozilla::image;
class DecodeToSurfaceRunnable : public Runnable
{
public:
DecodeToSurfaceRunnable(RefPtr<SourceSurface>& aSurface,
nsIInputStream* aInputStream,
const ImageTestCase& aTestCase)
: mSurface(aSurface)
, mInputStream(aInputStream)
, mTestCase(aTestCase)
{ }
NS_IMETHOD Run() override
{
Go();
return NS_OK;
}
void Go()
{
mSurface =
ImageOps::DecodeToSurface(mInputStream,
nsDependentCString(mTestCase.mMimeType),
imgIContainer::DECODE_FLAGS_DEFAULT);
ASSERT_TRUE(mSurface != nullptr);
EXPECT_EQ(SurfaceType::DATA, mSurface->GetType());
EXPECT_TRUE(mSurface->GetFormat() == SurfaceFormat::B8G8R8X8 ||
mSurface->GetFormat() == SurfaceFormat::B8G8R8A8);
EXPECT_EQ(mTestCase.mSize, mSurface->GetSize());
EXPECT_TRUE(IsSolidColor(mSurface, BGRAColor::Green(),
mTestCase.mFlags & TEST_CASE_IS_FUZZY ? 1 : 0));
}
private:
RefPtr<SourceSurface>& mSurface;
nsCOMPtr<nsIInputStream> mInputStream;
ImageTestCase mTestCase;
};
static void
RunDecodeToSurface(const ImageTestCase& aTestCase)
{
nsCOMPtr<nsIInputStream> inputStream = LoadFile(aTestCase.mPath);
ASSERT_TRUE(inputStream != nullptr);
nsCOMPtr<nsIThread> thread;
nsresult rv = NS_NewThread(getter_AddRefs(thread), nullptr);
ASSERT_TRUE(NS_SUCCEEDED(rv));
// We run the DecodeToSurface tests off-main-thread to ensure that
// DecodeToSurface doesn't require any main-thread-only code.
RefPtr<SourceSurface> surface;
nsCOMPtr<nsIRunnable> runnable =
new DecodeToSurfaceRunnable(surface, inputStream, aTestCase);
thread->Dispatch(runnable, nsIThread::DISPATCH_SYNC);
thread->Shutdown();
// Explicitly release the SourceSurface on the main thread.
surface = nullptr;
}
class ImageDecodeToSurface : public ::testing::Test
{
protected:
AutoInitializeImageLib mInit;
};
TEST_F(ImageDecodeToSurface, PNG) { RunDecodeToSurface(GreenPNGTestCase()); }
TEST_F(ImageDecodeToSurface, GIF) { RunDecodeToSurface(GreenGIFTestCase()); }
TEST_F(ImageDecodeToSurface, JPG) { RunDecodeToSurface(GreenJPGTestCase()); }
TEST_F(ImageDecodeToSurface, BMP) { RunDecodeToSurface(GreenBMPTestCase()); }
TEST_F(ImageDecodeToSurface, ICO) { RunDecodeToSurface(GreenICOTestCase()); }
TEST_F(ImageDecodeToSurface, Icon) { RunDecodeToSurface(GreenIconTestCase()); }
TEST_F(ImageDecodeToSurface, AnimatedGIF)
{
RunDecodeToSurface(GreenFirstFrameAnimatedGIFTestCase());
}
TEST_F(ImageDecodeToSurface, AnimatedPNG)
{
RunDecodeToSurface(GreenFirstFrameAnimatedPNGTestCase());
}
TEST_F(ImageDecodeToSurface, Corrupt)
{
ImageTestCase testCase = CorruptTestCase();
nsCOMPtr<nsIInputStream> inputStream = LoadFile(testCase.mPath);
ASSERT_TRUE(inputStream != nullptr);
RefPtr<SourceSurface> surface =
ImageOps::DecodeToSurface(inputStream,
nsDependentCString(testCase.mMimeType),
imgIContainer::DECODE_FLAGS_DEFAULT);
EXPECT_TRUE(surface == nullptr);
}

View file

@ -0,0 +1,669 @@
/* 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/. */
#include "gtest/gtest.h"
#include "Common.h"
#include "Decoder.h"
#include "DecoderFactory.h"
#include "decoders/nsBMPDecoder.h"
#include "IDecodingTask.h"
#include "imgIContainer.h"
#include "imgITools.h"
#include "ImageFactory.h"
#include "mozilla/gfx/2D.h"
#include "nsComponentManagerUtils.h"
#include "nsCOMPtr.h"
#include "nsIInputStream.h"
#include "nsIRunnable.h"
#include "nsIThread.h"
#include "mozilla/RefPtr.h"
#include "nsStreamUtils.h"
#include "nsString.h"
#include "nsThreadUtils.h"
#include "ProgressTracker.h"
#include "SourceBuffer.h"
using namespace mozilla;
using namespace mozilla::gfx;
using namespace mozilla::image;
static already_AddRefed<SourceSurface>
CheckDecoderState(const ImageTestCase& aTestCase, Decoder* aDecoder)
{
EXPECT_TRUE(aDecoder->GetDecodeDone());
EXPECT_EQ(bool(aTestCase.mFlags & TEST_CASE_HAS_ERROR),
aDecoder->HasError());
// Verify that the decoder made the expected progress.
Progress progress = aDecoder->TakeProgress();
EXPECT_EQ(bool(aTestCase.mFlags & TEST_CASE_HAS_ERROR),
bool(progress & FLAG_HAS_ERROR));
if (aTestCase.mFlags & TEST_CASE_HAS_ERROR) {
return nullptr; // That's all we can check for bad images.
}
EXPECT_TRUE(bool(progress & FLAG_SIZE_AVAILABLE));
EXPECT_TRUE(bool(progress & FLAG_DECODE_COMPLETE));
EXPECT_TRUE(bool(progress & FLAG_FRAME_COMPLETE));
EXPECT_EQ(bool(aTestCase.mFlags & TEST_CASE_IS_TRANSPARENT),
bool(progress & FLAG_HAS_TRANSPARENCY));
EXPECT_EQ(bool(aTestCase.mFlags & TEST_CASE_IS_ANIMATED),
bool(progress & FLAG_IS_ANIMATED));
// The decoder should get the correct size.
IntSize size = aDecoder->Size();
EXPECT_EQ(aTestCase.mSize.width, size.width);
EXPECT_EQ(aTestCase.mSize.height, size.height);
// Get the current frame, which is always the first frame of the image
// because CreateAnonymousDecoder() forces a first-frame-only decode.
RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
RefPtr<SourceSurface> surface = currentFrame->GetSourceSurface();
// Verify that the resulting surfaces matches our expectations.
EXPECT_EQ(SurfaceType::DATA, surface->GetType());
EXPECT_TRUE(surface->GetFormat() == SurfaceFormat::B8G8R8X8 ||
surface->GetFormat() == SurfaceFormat::B8G8R8A8);
EXPECT_EQ(aTestCase.mOutputSize, surface->GetSize());
return surface.forget();
}
static void
CheckDecoderResults(const ImageTestCase& aTestCase, Decoder* aDecoder)
{
RefPtr<SourceSurface> surface = CheckDecoderState(aTestCase, aDecoder);
if (!surface) {
return;
}
if (aTestCase.mFlags & TEST_CASE_IGNORE_OUTPUT) {
return;
}
// Check the output.
EXPECT_TRUE(IsSolidColor(surface, BGRAColor::Green(),
aTestCase.mFlags & TEST_CASE_IS_FUZZY ? 1 : 0));
}
template <typename Func>
void WithSingleChunkDecode(const ImageTestCase& aTestCase,
const Maybe<IntSize>& aOutputSize,
Func aResultChecker)
{
nsCOMPtr<nsIInputStream> inputStream = LoadFile(aTestCase.mPath);
ASSERT_TRUE(inputStream != nullptr);
// Figure out how much data we have.
uint64_t length;
nsresult rv = inputStream->Available(&length);
ASSERT_TRUE(NS_SUCCEEDED(rv));
// Write the data into a SourceBuffer.
NotNull<RefPtr<SourceBuffer>> sourceBuffer = WrapNotNull(new SourceBuffer());
sourceBuffer->ExpectLength(length);
rv = sourceBuffer->AppendFromInputStream(inputStream, length);
ASSERT_TRUE(NS_SUCCEEDED(rv));
sourceBuffer->Complete(NS_OK);
// Create a decoder.
DecoderType decoderType =
DecoderFactory::GetDecoderType(aTestCase.mMimeType);
RefPtr<Decoder> decoder =
DecoderFactory::CreateAnonymousDecoder(decoderType, sourceBuffer, aOutputSize,
DefaultSurfaceFlags());
ASSERT_TRUE(decoder != nullptr);
RefPtr<IDecodingTask> task = new AnonymousDecodingTask(WrapNotNull(decoder));
// Run the full decoder synchronously.
task->Run();
// Call the lambda to verify the expected results.
aResultChecker(decoder);
}
static void
CheckDecoderSingleChunk(const ImageTestCase& aTestCase)
{
WithSingleChunkDecode(aTestCase, Nothing(), [&](Decoder* aDecoder) {
CheckDecoderResults(aTestCase, aDecoder);
});
}
static void
CheckDecoderMultiChunk(const ImageTestCase& aTestCase)
{
nsCOMPtr<nsIInputStream> inputStream = LoadFile(aTestCase.mPath);
ASSERT_TRUE(inputStream != nullptr);
// Figure out how much data we have.
uint64_t length;
nsresult rv = inputStream->Available(&length);
ASSERT_TRUE(NS_SUCCEEDED(rv));
// Create a SourceBuffer and a decoder.
NotNull<RefPtr<SourceBuffer>> sourceBuffer = WrapNotNull(new SourceBuffer());
sourceBuffer->ExpectLength(length);
DecoderType decoderType =
DecoderFactory::GetDecoderType(aTestCase.mMimeType);
RefPtr<Decoder> decoder =
DecoderFactory::CreateAnonymousDecoder(decoderType, sourceBuffer, Nothing(),
DefaultSurfaceFlags());
ASSERT_TRUE(decoder != nullptr);
RefPtr<IDecodingTask> task = new AnonymousDecodingTask(WrapNotNull(decoder));
for (uint64_t read = 0; read < length ; ++read) {
uint64_t available = 0;
rv = inputStream->Available(&available);
ASSERT_TRUE(available > 0);
ASSERT_TRUE(NS_SUCCEEDED(rv));
rv = sourceBuffer->AppendFromInputStream(inputStream, 1);
ASSERT_TRUE(NS_SUCCEEDED(rv));
task->Run();
}
sourceBuffer->Complete(NS_OK);
task->Run();
CheckDecoderResults(aTestCase, decoder);
}
static void
CheckDownscaleDuringDecode(const ImageTestCase& aTestCase)
{
// This function expects that |aTestCase| consists of 25 lines of green,
// followed by 25 lines of red, followed by 25 lines of green, followed by 25
// more lines of red. We'll downscale it from 100x100 to 20x20.
IntSize outputSize(20, 20);
WithSingleChunkDecode(aTestCase, Some(outputSize), [&](Decoder* aDecoder) {
RefPtr<SourceSurface> surface = CheckDecoderState(aTestCase, aDecoder);
// There are no downscale-during-decode tests that have TEST_CASE_HAS_ERROR
// set, so we expect to always get a surface here.
EXPECT_TRUE(surface != nullptr);
if (aTestCase.mFlags & TEST_CASE_IGNORE_OUTPUT) {
return;
}
// Check that the downscaled image is correct. Note that we skip rows near
// the transitions between colors, since the downscaler does not produce a
// sharp boundary at these points. Even some of the rows we test need a
// small amount of fuzz; this is just the nature of Lanczos downscaling.
EXPECT_TRUE(RowsAreSolidColor(surface, 0, 4, BGRAColor::Green(), /* aFuzz = */ 47));
EXPECT_TRUE(RowsAreSolidColor(surface, 6, 3, BGRAColor::Red(), /* aFuzz = */ 27));
EXPECT_TRUE(RowsAreSolidColor(surface, 11, 3, BGRAColor::Green(), /* aFuzz = */ 47));
EXPECT_TRUE(RowsAreSolidColor(surface, 16, 4, BGRAColor::Red(), /* aFuzz = */ 27));
});
}
class ImageDecoders : public ::testing::Test
{
protected:
AutoInitializeImageLib mInit;
};
TEST_F(ImageDecoders, PNGSingleChunk)
{
CheckDecoderSingleChunk(GreenPNGTestCase());
}
TEST_F(ImageDecoders, PNGMultiChunk)
{
CheckDecoderMultiChunk(GreenPNGTestCase());
}
TEST_F(ImageDecoders, PNGDownscaleDuringDecode)
{
CheckDownscaleDuringDecode(DownscaledPNGTestCase());
}
TEST_F(ImageDecoders, GIFSingleChunk)
{
CheckDecoderSingleChunk(GreenGIFTestCase());
}
TEST_F(ImageDecoders, GIFMultiChunk)
{
CheckDecoderMultiChunk(GreenGIFTestCase());
}
TEST_F(ImageDecoders, GIFDownscaleDuringDecode)
{
CheckDownscaleDuringDecode(DownscaledGIFTestCase());
}
TEST_F(ImageDecoders, JPGSingleChunk)
{
CheckDecoderSingleChunk(GreenJPGTestCase());
}
TEST_F(ImageDecoders, JPGMultiChunk)
{
CheckDecoderMultiChunk(GreenJPGTestCase());
}
TEST_F(ImageDecoders, JPGDownscaleDuringDecode)
{
CheckDownscaleDuringDecode(DownscaledJPGTestCase());
}
TEST_F(ImageDecoders, BMPSingleChunk)
{
CheckDecoderSingleChunk(GreenBMPTestCase());
}
TEST_F(ImageDecoders, BMPMultiChunk)
{
CheckDecoderMultiChunk(GreenBMPTestCase());
}
TEST_F(ImageDecoders, BMPDownscaleDuringDecode)
{
CheckDownscaleDuringDecode(DownscaledBMPTestCase());
}
TEST_F(ImageDecoders, ICOSingleChunk)
{
CheckDecoderSingleChunk(GreenICOTestCase());
}
TEST_F(ImageDecoders, ICOMultiChunk)
{
CheckDecoderMultiChunk(GreenICOTestCase());
}
TEST_F(ImageDecoders, ICODownscaleDuringDecode)
{
CheckDownscaleDuringDecode(DownscaledICOTestCase());
}
TEST_F(ImageDecoders, ICOWithANDMaskDownscaleDuringDecode)
{
CheckDownscaleDuringDecode(DownscaledTransparentICOWithANDMaskTestCase());
}
TEST_F(ImageDecoders, IconSingleChunk)
{
CheckDecoderSingleChunk(GreenIconTestCase());
}
TEST_F(ImageDecoders, IconMultiChunk)
{
CheckDecoderMultiChunk(GreenIconTestCase());
}
TEST_F(ImageDecoders, IconDownscaleDuringDecode)
{
CheckDownscaleDuringDecode(DownscaledIconTestCase());
}
TEST_F(ImageDecoders, AnimatedGIFSingleChunk)
{
CheckDecoderSingleChunk(GreenFirstFrameAnimatedGIFTestCase());
}
TEST_F(ImageDecoders, AnimatedGIFMultiChunk)
{
CheckDecoderMultiChunk(GreenFirstFrameAnimatedGIFTestCase());
}
TEST_F(ImageDecoders, AnimatedPNGSingleChunk)
{
CheckDecoderSingleChunk(GreenFirstFrameAnimatedPNGTestCase());
}
TEST_F(ImageDecoders, AnimatedPNGMultiChunk)
{
CheckDecoderMultiChunk(GreenFirstFrameAnimatedPNGTestCase());
}
TEST_F(ImageDecoders, CorruptSingleChunk)
{
CheckDecoderSingleChunk(CorruptTestCase());
}
TEST_F(ImageDecoders, CorruptMultiChunk)
{
CheckDecoderMultiChunk(CorruptTestCase());
}
TEST_F(ImageDecoders, CorruptBMPWithTruncatedHeaderSingleChunk)
{
CheckDecoderSingleChunk(CorruptBMPWithTruncatedHeader());
}
TEST_F(ImageDecoders, CorruptBMPWithTruncatedHeaderMultiChunk)
{
CheckDecoderMultiChunk(CorruptBMPWithTruncatedHeader());
}
TEST_F(ImageDecoders, CorruptICOWithBadBMPWidthSingleChunk)
{
CheckDecoderSingleChunk(CorruptICOWithBadBMPWidthTestCase());
}
TEST_F(ImageDecoders, CorruptICOWithBadBMPWidthMultiChunk)
{
CheckDecoderMultiChunk(CorruptICOWithBadBMPWidthTestCase());
}
TEST_F(ImageDecoders, CorruptICOWithBadBMPHeightSingleChunk)
{
CheckDecoderSingleChunk(CorruptICOWithBadBMPHeightTestCase());
}
TEST_F(ImageDecoders, CorruptICOWithBadBMPHeightMultiChunk)
{
CheckDecoderMultiChunk(CorruptICOWithBadBMPHeightTestCase());
}
TEST_F(ImageDecoders, AnimatedGIFWithFRAME_FIRST)
{
ImageTestCase testCase = GreenFirstFrameAnimatedGIFTestCase();
// Verify that we can decode this test case and retrieve the first frame using
// imgIContainer::FRAME_FIRST. This ensures that we correctly trigger a
// single-frame decode rather than an animated decode when
// imgIContainer::FRAME_FIRST is requested.
// Create an image.
RefPtr<Image> image =
ImageFactory::CreateAnonymousImage(nsDependentCString(testCase.mMimeType));
ASSERT_TRUE(!image->HasError());
nsCOMPtr<nsIInputStream> inputStream = LoadFile(testCase.mPath);
ASSERT_TRUE(inputStream);
// Figure out how much data we have.
uint64_t length;
nsresult rv = inputStream->Available(&length);
ASSERT_TRUE(NS_SUCCEEDED(rv));
// Write the data into the image.
rv = image->OnImageDataAvailable(nullptr, nullptr, inputStream, 0,
static_cast<uint32_t>(length));
ASSERT_TRUE(NS_SUCCEEDED(rv));
// Let the image know we've sent all the data.
rv = image->OnImageDataComplete(nullptr, nullptr, NS_OK, true);
ASSERT_TRUE(NS_SUCCEEDED(rv));
RefPtr<ProgressTracker> tracker = image->GetProgressTracker();
tracker->SyncNotifyProgress(FLAG_LOAD_COMPLETE);
// Lock the image so its surfaces don't disappear during the test.
image->LockImage();
// Use GetFrame() to force a sync decode of the image, specifying FRAME_FIRST
// to ensure that we don't get an animated decode.
RefPtr<SourceSurface> surface =
image->GetFrame(imgIContainer::FRAME_FIRST,
imgIContainer::FLAG_SYNC_DECODE);
// Ensure that the image's metadata meets our expectations.
IntSize imageSize(0, 0);
rv = image->GetWidth(&imageSize.width);
EXPECT_TRUE(NS_SUCCEEDED(rv));
rv = image->GetHeight(&imageSize.height);
EXPECT_TRUE(NS_SUCCEEDED(rv));
EXPECT_EQ(testCase.mSize.width, imageSize.width);
EXPECT_EQ(testCase.mSize.height, imageSize.height);
Progress imageProgress = tracker->GetProgress();
EXPECT_TRUE(bool(imageProgress & FLAG_HAS_TRANSPARENCY) == false);
EXPECT_TRUE(bool(imageProgress & FLAG_IS_ANIMATED) == true);
// Ensure that we decoded the static version of the image.
{
LookupResult result =
SurfaceCache::Lookup(ImageKey(image.get()),
RasterSurfaceKey(imageSize,
DefaultSurfaceFlags(),
PlaybackType::eStatic));
ASSERT_EQ(MatchType::EXACT, result.Type());
EXPECT_TRUE(bool(result.Surface()));
}
// Ensure that we didn't decode the animated version of the image.
{
LookupResult result =
SurfaceCache::Lookup(ImageKey(image.get()),
RasterSurfaceKey(imageSize,
DefaultSurfaceFlags(),
PlaybackType::eAnimated));
ASSERT_EQ(MatchType::NOT_FOUND, result.Type());
}
// Use GetFrame() to force a sync decode of the image, this time specifying
// FRAME_CURRENT to ensure that we get an animated decode.
RefPtr<SourceSurface> animatedSurface =
image->GetFrame(imgIContainer::FRAME_CURRENT,
imgIContainer::FLAG_SYNC_DECODE);
// Ensure that we decoded both frames of the animated version of the image.
{
LookupResult result =
SurfaceCache::Lookup(ImageKey(image.get()),
RasterSurfaceKey(imageSize,
DefaultSurfaceFlags(),
PlaybackType::eAnimated));
ASSERT_EQ(MatchType::EXACT, result.Type());
EXPECT_TRUE(NS_SUCCEEDED(result.Surface().Seek(0)));
EXPECT_TRUE(bool(result.Surface()));
EXPECT_TRUE(NS_SUCCEEDED(result.Surface().Seek(1)));
EXPECT_TRUE(bool(result.Surface()));
}
// Ensure that the static version is still around.
{
LookupResult result =
SurfaceCache::Lookup(ImageKey(image.get()),
RasterSurfaceKey(imageSize,
DefaultSurfaceFlags(),
PlaybackType::eStatic));
ASSERT_EQ(MatchType::EXACT, result.Type());
EXPECT_TRUE(bool(result.Surface()));
}
}
TEST_F(ImageDecoders, AnimatedGIFWithFRAME_CURRENT)
{
ImageTestCase testCase = GreenFirstFrameAnimatedGIFTestCase();
// Verify that we can decode this test case and retrieve the entire sequence
// of frames using imgIContainer::FRAME_CURRENT. This ensures that we
// correctly trigger an animated decode rather than a single-frame decode when
// imgIContainer::FRAME_CURRENT is requested.
// Create an image.
RefPtr<Image> image =
ImageFactory::CreateAnonymousImage(nsDependentCString(testCase.mMimeType));
ASSERT_TRUE(!image->HasError());
nsCOMPtr<nsIInputStream> inputStream = LoadFile(testCase.mPath);
ASSERT_TRUE(inputStream);
// Figure out how much data we have.
uint64_t length;
nsresult rv = inputStream->Available(&length);
ASSERT_TRUE(NS_SUCCEEDED(rv));
// Write the data into the image.
rv = image->OnImageDataAvailable(nullptr, nullptr, inputStream, 0,
static_cast<uint32_t>(length));
ASSERT_TRUE(NS_SUCCEEDED(rv));
// Let the image know we've sent all the data.
rv = image->OnImageDataComplete(nullptr, nullptr, NS_OK, true);
ASSERT_TRUE(NS_SUCCEEDED(rv));
RefPtr<ProgressTracker> tracker = image->GetProgressTracker();
tracker->SyncNotifyProgress(FLAG_LOAD_COMPLETE);
// Lock the image so its surfaces don't disappear during the test.
image->LockImage();
// Use GetFrame() to force a sync decode of the image, specifying
// FRAME_CURRENT to ensure we get an animated decode.
RefPtr<SourceSurface> surface =
image->GetFrame(imgIContainer::FRAME_CURRENT,
imgIContainer::FLAG_SYNC_DECODE);
// Ensure that the image's metadata meets our expectations.
IntSize imageSize(0, 0);
rv = image->GetWidth(&imageSize.width);
EXPECT_TRUE(NS_SUCCEEDED(rv));
rv = image->GetHeight(&imageSize.height);
EXPECT_TRUE(NS_SUCCEEDED(rv));
EXPECT_EQ(testCase.mSize.width, imageSize.width);
EXPECT_EQ(testCase.mSize.height, imageSize.height);
Progress imageProgress = tracker->GetProgress();
EXPECT_TRUE(bool(imageProgress & FLAG_HAS_TRANSPARENCY) == false);
EXPECT_TRUE(bool(imageProgress & FLAG_IS_ANIMATED) == true);
// Ensure that we decoded both frames of the animated version of the image.
{
LookupResult result =
SurfaceCache::Lookup(ImageKey(image.get()),
RasterSurfaceKey(imageSize,
DefaultSurfaceFlags(),
PlaybackType::eAnimated));
ASSERT_EQ(MatchType::EXACT, result.Type());
EXPECT_TRUE(NS_SUCCEEDED(result.Surface().Seek(0)));
EXPECT_TRUE(bool(result.Surface()));
EXPECT_TRUE(NS_SUCCEEDED(result.Surface().Seek(1)));
EXPECT_TRUE(bool(result.Surface()));
}
// Ensure that we didn't decode the static version of the image.
{
LookupResult result =
SurfaceCache::Lookup(ImageKey(image.get()),
RasterSurfaceKey(imageSize,
DefaultSurfaceFlags(),
PlaybackType::eStatic));
ASSERT_EQ(MatchType::NOT_FOUND, result.Type());
}
// Use GetFrame() to force a sync decode of the image, this time specifying
// FRAME_FIRST to ensure that we get a single-frame decode.
RefPtr<SourceSurface> animatedSurface =
image->GetFrame(imgIContainer::FRAME_FIRST,
imgIContainer::FLAG_SYNC_DECODE);
// Ensure that we decoded the static version of the image.
{
LookupResult result =
SurfaceCache::Lookup(ImageKey(image.get()),
RasterSurfaceKey(imageSize,
DefaultSurfaceFlags(),
PlaybackType::eStatic));
ASSERT_EQ(MatchType::EXACT, result.Type());
EXPECT_TRUE(bool(result.Surface()));
}
// Ensure that both frames of the animated version are still around.
{
LookupResult result =
SurfaceCache::Lookup(ImageKey(image.get()),
RasterSurfaceKey(imageSize,
DefaultSurfaceFlags(),
PlaybackType::eAnimated));
ASSERT_EQ(MatchType::EXACT, result.Type());
EXPECT_TRUE(NS_SUCCEEDED(result.Surface().Seek(0)));
EXPECT_TRUE(bool(result.Surface()));
EXPECT_TRUE(NS_SUCCEEDED(result.Surface().Seek(1)));
EXPECT_TRUE(bool(result.Surface()));
}
}
TEST_F(ImageDecoders, AnimatedGIFWithExtraImageSubBlocks)
{
ImageTestCase testCase = ExtraImageSubBlocksAnimatedGIFTestCase();
// Verify that we can decode this test case and get two frames, even though
// there are extra image sub blocks between the first and second frame. The
// extra data shouldn't confuse the decoder or cause the decode to fail.
// Create an image.
RefPtr<Image> image =
ImageFactory::CreateAnonymousImage(nsDependentCString(testCase.mMimeType));
ASSERT_TRUE(!image->HasError());
nsCOMPtr<nsIInputStream> inputStream = LoadFile(testCase.mPath);
ASSERT_TRUE(inputStream);
// Figure out how much data we have.
uint64_t length;
nsresult rv = inputStream->Available(&length);
ASSERT_TRUE(NS_SUCCEEDED(rv));
// Write the data into the image.
rv = image->OnImageDataAvailable(nullptr, nullptr, inputStream, 0,
static_cast<uint32_t>(length));
ASSERT_TRUE(NS_SUCCEEDED(rv));
// Let the image know we've sent all the data.
rv = image->OnImageDataComplete(nullptr, nullptr, NS_OK, true);
ASSERT_TRUE(NS_SUCCEEDED(rv));
RefPtr<ProgressTracker> tracker = image->GetProgressTracker();
tracker->SyncNotifyProgress(FLAG_LOAD_COMPLETE);
// Use GetFrame() to force a sync decode of the image.
RefPtr<SourceSurface> surface =
image->GetFrame(imgIContainer::FRAME_CURRENT,
imgIContainer::FLAG_SYNC_DECODE);
// Ensure that the image's metadata meets our expectations.
IntSize imageSize(0, 0);
rv = image->GetWidth(&imageSize.width);
EXPECT_TRUE(NS_SUCCEEDED(rv));
rv = image->GetHeight(&imageSize.height);
EXPECT_TRUE(NS_SUCCEEDED(rv));
EXPECT_EQ(testCase.mSize.width, imageSize.width);
EXPECT_EQ(testCase.mSize.height, imageSize.height);
Progress imageProgress = tracker->GetProgress();
EXPECT_TRUE(bool(imageProgress & FLAG_HAS_TRANSPARENCY) == false);
EXPECT_TRUE(bool(imageProgress & FLAG_IS_ANIMATED) == true);
// Ensure that we decoded both frames of the image.
LookupResult result =
SurfaceCache::Lookup(ImageKey(image.get()),
RasterSurfaceKey(imageSize,
DefaultSurfaceFlags(),
PlaybackType::eAnimated));
ASSERT_EQ(MatchType::EXACT, result.Type());
EXPECT_TRUE(NS_SUCCEEDED(result.Surface().Seek(0)));
EXPECT_TRUE(bool(result.Surface()));
EXPECT_TRUE(NS_SUCCEEDED(result.Surface().Seek(1)));
EXPECT_TRUE(bool(result.Surface()));
}
TEST_F(ImageDecoders, TruncatedSmallGIFSingleChunk)
{
CheckDecoderSingleChunk(TruncatedSmallGIFTestCase());
}

View file

@ -0,0 +1,672 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */
#include "gtest/gtest.h"
#include "mozilla/gfx/2D.h"
#include "Common.h"
#include "Decoder.h"
#include "DecoderFactory.h"
#include "SourceBuffer.h"
#include "SurfaceFilters.h"
#include "SurfacePipe.h"
using namespace mozilla;
using namespace mozilla::gfx;
using namespace mozilla::image;
template <typename Func> void
WithDeinterlacingFilter(const IntSize& aSize,
bool aProgressiveDisplay,
Func aFunc)
{
RefPtr<Decoder> decoder = CreateTrivialDecoder();
ASSERT_TRUE(bool(decoder));
WithFilterPipeline(decoder, Forward<Func>(aFunc),
DeinterlacingConfig<uint32_t> { aProgressiveDisplay },
SurfaceConfig { decoder, 0, aSize,
SurfaceFormat::B8G8R8A8, false });
}
template <typename Func> void
WithPalettedDeinterlacingFilter(const IntSize& aSize,
Func aFunc)
{
RefPtr<Decoder> decoder = CreateTrivialDecoder();
ASSERT_TRUE(decoder != nullptr);
WithFilterPipeline(decoder, Forward<Func>(aFunc),
DeinterlacingConfig<uint8_t> { /* mProgressiveDisplay = */ true },
PalettedSurfaceConfig { decoder, 0, aSize,
IntRect(0, 0, 100, 100),
SurfaceFormat::B8G8R8A8, 8,
false });
}
void
AssertConfiguringDeinterlacingFilterFails(const IntSize& aSize)
{
RefPtr<Decoder> decoder = CreateTrivialDecoder();
ASSERT_TRUE(decoder != nullptr);
AssertConfiguringPipelineFails(decoder,
DeinterlacingConfig<uint32_t> { /* mProgressiveDisplay = */ true},
SurfaceConfig { decoder, 0, aSize,
SurfaceFormat::B8G8R8A8, false });
}
class ImageDeinterlacingFilter : public ::testing::Test
{
protected:
AutoInitializeImageLib mInit;
};
TEST_F(ImageDeinterlacingFilter, WritePixels100_100)
{
WithDeinterlacingFilter(IntSize(100, 100), /* aProgressiveDisplay = */ true,
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
CheckWritePixels(aDecoder, aFilter,
/* aOutputRect = */ Some(IntRect(0, 0, 100, 100)),
/* aInputRect = */ Some(IntRect(0, 0, 100, 100)));
});
}
TEST_F(ImageDeinterlacingFilter, WritePixels99_99)
{
WithDeinterlacingFilter(IntSize(99, 99), /* aProgressiveDisplay = */ true,
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
CheckWritePixels(aDecoder, aFilter,
/* aOutputRect = */ Some(IntRect(0, 0, 99, 99)),
/* aInputRect = */ Some(IntRect(0, 0, 99, 99)));
});
}
TEST_F(ImageDeinterlacingFilter, WritePixels8_8)
{
WithDeinterlacingFilter(IntSize(8, 8), /* aProgressiveDisplay = */ true,
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
CheckWritePixels(aDecoder, aFilter,
/* aOutputRect = */ Some(IntRect(0, 0, 8, 8)),
/* aInputRect = */ Some(IntRect(0, 0, 8, 8)));
});
}
TEST_F(ImageDeinterlacingFilter, WritePixels7_7)
{
WithDeinterlacingFilter(IntSize(7, 7), /* aProgressiveDisplay = */ true,
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
CheckWritePixels(aDecoder, aFilter,
/* aOutputRect = */ Some(IntRect(0, 0, 7, 7)),
/* aInputRect = */ Some(IntRect(0, 0, 7, 7)));
});
}
TEST_F(ImageDeinterlacingFilter, WritePixels3_3)
{
WithDeinterlacingFilter(IntSize(3, 3), /* aProgressiveDisplay = */ true,
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
CheckWritePixels(aDecoder, aFilter,
/* aOutputRect = */ Some(IntRect(0, 0, 3, 3)),
/* aInputRect = */ Some(IntRect(0, 0, 3, 3)));
});
}
TEST_F(ImageDeinterlacingFilter, WritePixels1_1)
{
WithDeinterlacingFilter(IntSize(1, 1), /* aProgressiveDisplay = */ true,
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
CheckWritePixels(aDecoder, aFilter,
/* aOutputRect = */ Some(IntRect(0, 0, 1, 1)),
/* aInputRect = */ Some(IntRect(0, 0, 1, 1)));
});
}
TEST_F(ImageDeinterlacingFilter, PalettedWritePixels)
{
WithPalettedDeinterlacingFilter(IntSize(100, 100),
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
CheckPalettedWritePixels(aDecoder, aFilter);
});
}
TEST_F(ImageDeinterlacingFilter, WritePixelsNonProgressiveOutput51_52)
{
WithDeinterlacingFilter(IntSize(51, 52), /* aProgressiveDisplay = */ false,
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
// Fill the image. The output should be green for even rows and red for odd
// rows but we need to write the rows in the order that the deinterlacer
// expects them.
uint32_t count = 0;
auto result = aFilter->WritePixels<uint32_t>([&]() {
uint32_t row = count / 51; // Integer division.
++count;
// Note that we use a switch statement here, even though it's quite
// verbose, because it's useful to have the mappings between input and
// output rows available when debugging these tests.
switch (row) {
// First pass. Output rows are positioned at 8n + 0.
case 0: // Output row 0.
case 1: // Output row 8.
case 2: // Output row 16.
case 3: // Output row 24.
case 4: // Output row 32.
case 5: // Output row 40.
case 6: // Output row 48.
return AsVariant(BGRAColor::Green().AsPixel());
// Second pass. Rows are positioned at 8n + 4.
case 7: // Output row 4.
case 8: // Output row 12.
case 9: // Output row 20.
case 10: // Output row 28.
case 11: // Output row 36.
case 12: // Output row 44.
return AsVariant(BGRAColor::Green().AsPixel());
// Third pass. Rows are positioned at 4n + 2.
case 13: // Output row 2.
case 14: // Output row 6.
case 15: // Output row 10.
case 16: // Output row 14.
case 17: // Output row 18.
case 18: // Output row 22.
case 19: // Output row 26.
case 20: // Output row 30.
case 21: // Output row 34.
case 22: // Output row 38.
case 23: // Output row 42.
case 24: // Output row 46.
case 25: // Output row 50.
return AsVariant(BGRAColor::Green().AsPixel());
// Fourth pass. Rows are positioned at 2n + 1.
case 26: // Output row 1.
case 27: // Output row 3.
case 28: // Output row 5.
case 29: // Output row 7.
case 30: // Output row 9.
case 31: // Output row 11.
case 32: // Output row 13.
case 33: // Output row 15.
case 34: // Output row 17.
case 35: // Output row 19.
case 36: // Output row 21.
case 37: // Output row 23.
case 38: // Output row 25.
case 39: // Output row 27.
case 40: // Output row 29.
case 41: // Output row 31.
case 42: // Output row 33.
case 43: // Output row 35.
case 44: // Output row 37.
case 45: // Output row 39.
case 46: // Output row 41.
case 47: // Output row 43.
case 48: // Output row 45.
case 49: // Output row 47.
case 50: // Output row 49.
case 51: // Output row 51.
return AsVariant(BGRAColor::Red().AsPixel());
default:
MOZ_ASSERT_UNREACHABLE("Unexpected row");
return AsVariant(BGRAColor::Transparent().AsPixel());
}
});
EXPECT_EQ(WriteState::FINISHED, result);
EXPECT_EQ(51u * 52u, count);
AssertCorrectPipelineFinalState(aFilter,
IntRect(0, 0, 51, 52),
IntRect(0, 0, 51, 52));
// Check that the generated image is correct. As mentioned above, we expect
// even rows to be green and odd rows to be red.
RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
RefPtr<SourceSurface> surface = currentFrame->GetSourceSurface();
for (uint32_t row = 0; row < 52; ++row) {
EXPECT_TRUE(RowsAreSolidColor(surface, row, 1,
row % 2 == 0 ? BGRAColor::Green()
: BGRAColor::Red()));
}
});
}
TEST_F(ImageDeinterlacingFilter, WritePixelsOutput20_20)
{
WithDeinterlacingFilter(IntSize(20, 20), /* aProgressiveDisplay = */ true,
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
// Fill the image. The output should be green for even rows and red for odd
// rows but we need to write the rows in the order that the deinterlacer
// expects them.
uint32_t count = 0;
auto result = aFilter->WritePixels<uint32_t>([&]() {
uint32_t row = count / 20; // Integer division.
++count;
// Note that we use a switch statement here, even though it's quite
// verbose, because it's useful to have the mappings between input and
// output rows available when debugging these tests.
switch (row) {
// First pass. Output rows are positioned at 8n + 0.
case 0: // Output row 0.
case 1: // Output row 8.
case 2: // Output row 16.
return AsVariant(BGRAColor::Green().AsPixel());
// Second pass. Rows are positioned at 8n + 4.
case 3: // Output row 4.
case 4: // Output row 12.
return AsVariant(BGRAColor::Green().AsPixel());
// Third pass. Rows are positioned at 4n + 2.
case 5: // Output row 2.
case 6: // Output row 6.
case 7: // Output row 10.
case 8: // Output row 14.
case 9: // Output row 18.
return AsVariant(BGRAColor::Green().AsPixel());
// Fourth pass. Rows are positioned at 2n + 1.
case 10: // Output row 1.
case 11: // Output row 3.
case 12: // Output row 5.
case 13: // Output row 7.
case 14: // Output row 9.
case 15: // Output row 11.
case 16: // Output row 13.
case 17: // Output row 15.
case 18: // Output row 17.
case 19: // Output row 19.
return AsVariant(BGRAColor::Red().AsPixel());
default:
MOZ_ASSERT_UNREACHABLE("Unexpected row");
return AsVariant(BGRAColor::Transparent().AsPixel());
}
});
EXPECT_EQ(WriteState::FINISHED, result);
EXPECT_EQ(20u * 20u, count);
AssertCorrectPipelineFinalState(aFilter,
IntRect(0, 0, 20, 20),
IntRect(0, 0, 20, 20));
// Check that the generated image is correct. As mentioned above, we expect
// even rows to be green and odd rows to be red.
RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
RefPtr<SourceSurface> surface = currentFrame->GetSourceSurface();
for (uint32_t row = 0; row < 20; ++row) {
EXPECT_TRUE(RowsAreSolidColor(surface, row, 1,
row % 2 == 0 ? BGRAColor::Green()
: BGRAColor::Red()));
}
});
}
TEST_F(ImageDeinterlacingFilter, WritePixelsOutput7_7)
{
WithDeinterlacingFilter(IntSize(7, 7), /* aProgressiveDisplay = */ true,
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
// Fill the image. The output should be a repeating pattern of two green
// rows followed by two red rows but we need to write the rows in the order
// that the deinterlacer expects them.
uint32_t count = 0;
auto result = aFilter->WritePixels<uint32_t>([&]() {
uint32_t row = count / 7; // Integer division.
++count;
switch (row) {
// First pass. Output rows are positioned at 8n + 0.
case 0: // Output row 0.
return AsVariant(BGRAColor::Green().AsPixel());
// Second pass. Rows are positioned at 8n + 4.
case 1: // Output row 4.
return AsVariant(BGRAColor::Green().AsPixel());
// Third pass. Rows are positioned at 4n + 2.
case 2: // Output row 2.
case 3: // Output row 6.
return AsVariant(BGRAColor::Red().AsPixel());
// Fourth pass. Rows are positioned at 2n + 1.
case 4: // Output row 1.
return AsVariant(BGRAColor::Green().AsPixel());
case 5: // Output row 3.
return AsVariant(BGRAColor::Red().AsPixel());
case 6: // Output row 5.
return AsVariant(BGRAColor::Green().AsPixel());
default:
MOZ_ASSERT_UNREACHABLE("Unexpected row");
return AsVariant(BGRAColor::Transparent().AsPixel());
}
});
EXPECT_EQ(WriteState::FINISHED, result);
EXPECT_EQ(7u * 7u, count);
AssertCorrectPipelineFinalState(aFilter,
IntRect(0, 0, 7, 7),
IntRect(0, 0, 7, 7));
// Check that the generated image is correct. As mentioned above, we expect
// two green rows, followed by two red rows, then two green rows, etc.
RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
RefPtr<SourceSurface> surface = currentFrame->GetSourceSurface();
for (uint32_t row = 0; row < 7; ++row) {
BGRAColor color = row == 0 || row == 1 || row == 4 || row == 5
? BGRAColor::Green()
: BGRAColor::Red();
EXPECT_TRUE(RowsAreSolidColor(surface, row, 1, color));
}
});
}
TEST_F(ImageDeinterlacingFilter, WritePixelsOutput3_3)
{
WithDeinterlacingFilter(IntSize(3, 3), /* aProgressiveDisplay = */ true,
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
// Fill the image. The output should be green, red, green in that order, but
// we need to write the rows in the order that the deinterlacer expects
// them.
uint32_t count = 0;
auto result = aFilter->WritePixels<uint32_t>([&]() {
uint32_t row = count / 3; // Integer division.
++count;
switch (row) {
// First pass. Output rows are positioned at 8n + 0.
case 0: // Output row 0.
return AsVariant(BGRAColor::Green().AsPixel());
// Second pass. Rows are positioned at 8n + 4.
// No rows for this pass.
// Third pass. Rows are positioned at 4n + 2.
case 1: // Output row 2.
return AsVariant(BGRAColor::Green().AsPixel());
// Fourth pass. Rows are positioned at 2n + 1.
case 2: // Output row 1.
return AsVariant(BGRAColor::Red().AsPixel());
default:
MOZ_ASSERT_UNREACHABLE("Unexpected row");
return AsVariant(BGRAColor::Transparent().AsPixel());
}
});
EXPECT_EQ(WriteState::FINISHED, result);
EXPECT_EQ(3u * 3u, count);
AssertCorrectPipelineFinalState(aFilter,
IntRect(0, 0, 3, 3),
IntRect(0, 0, 3, 3));
// Check that the generated image is correct. As mentioned above, we expect
// green, red, green in that order.
RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
RefPtr<SourceSurface> surface = currentFrame->GetSourceSurface();
for (uint32_t row = 0; row < 3; ++row) {
EXPECT_TRUE(RowsAreSolidColor(surface, row, 1,
row == 0 || row == 2 ? BGRAColor::Green()
: BGRAColor::Red()));
}
});
}
TEST_F(ImageDeinterlacingFilter, WritePixelsOutput1_1)
{
WithDeinterlacingFilter(IntSize(1, 1), /* aProgressiveDisplay = */ true,
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
// Fill the image. The output should be a single red row.
uint32_t count = 0;
auto result = aFilter->WritePixels<uint32_t>([&]() {
++count;
return AsVariant(BGRAColor::Red().AsPixel());
});
EXPECT_EQ(WriteState::FINISHED, result);
EXPECT_EQ(1u, count);
AssertCorrectPipelineFinalState(aFilter,
IntRect(0, 0, 1, 1),
IntRect(0, 0, 1, 1));
// Check that the generated image is correct. As mentioned above, we expect
// a single red row.
RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
RefPtr<SourceSurface> surface = currentFrame->GetSourceSurface();
EXPECT_TRUE(RowsAreSolidColor(surface, 0, 1, BGRAColor::Red()));
});
}
void
WriteRowAndCheckInterlacerOutput(Decoder* aDecoder,
SurfaceFilter* aFilter,
BGRAColor aColor,
WriteState aNextState,
IntRect aInvalidRect,
uint32_t aFirstHaeberliRow,
uint32_t aLastHaeberliRow)
{
uint32_t count = 0;
auto result = aFilter->WritePixels<uint32_t>([&]() -> NextPixel<uint32_t> {
if (count < 7) {
++count;
return AsVariant(aColor.AsPixel());
}
return AsVariant(WriteState::NEED_MORE_DATA);
});
EXPECT_EQ(aNextState, result);
EXPECT_EQ(7u, count);
// Assert that we got the expected invalidation region.
Maybe<SurfaceInvalidRect> invalidRect = aFilter->TakeInvalidRect();
EXPECT_TRUE(invalidRect.isSome());
EXPECT_EQ(aInvalidRect, invalidRect->mInputSpaceRect);
EXPECT_EQ(aInvalidRect, invalidRect->mOutputSpaceRect);
// Check that the portion of the image generated so far is correct. The rows
// from aFirstHaeberliRow to aLastHaeberliRow should be filled with aColor.
// Note that this is not the same as the set of rows in aInvalidRect, because
// after writing a row the deinterlacer seeks to the next row to write, which
// may involve copying previously-written rows in the buffer to the output
// even though they don't change in this pass.
RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
RefPtr<SourceSurface> surface = currentFrame->GetSourceSurface();
for (uint32_t row = aFirstHaeberliRow; row <= aLastHaeberliRow; ++row) {
EXPECT_TRUE(RowsAreSolidColor(surface, row, 1, aColor));
}
}
TEST_F(ImageDeinterlacingFilter, WritePixelsIntermediateOutput7_7)
{
WithDeinterlacingFilter(IntSize(7, 7), /* aProgressiveDisplay = */ true,
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
// Fill the image. The output should be a repeating pattern of two green
// rows followed by two red rows but we need to write the rows in the order
// that the deinterlacer expects them.
// First pass. Output rows are positioned at 8n + 0.
// Output row 0. The invalid rect is the entire image because this is the
// end of the first pass.
WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Green(),
WriteState::NEED_MORE_DATA,
IntRect(0, 0, 7, 7), 0, 4);
// Second pass. Rows are positioned at 8n + 4.
// Output row 4. The invalid rect is the entire image because this is the
// end of the second pass.
WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Green(),
WriteState::NEED_MORE_DATA,
IntRect(0, 0, 7, 7), 1, 4);
// Third pass. Rows are positioned at 4n + 2.
// Output row 2. The invalid rect contains the Haeberli rows for this output
// row (rows 2 and 3) as well as the rows that we copy from previous passes
// when seeking to the next output row (rows 4 and 5).
WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Red(),
WriteState::NEED_MORE_DATA,
IntRect(0, 2, 7, 4), 2, 3);
// Output row 6. The invalid rect is the entire image because this is the
// end of the third pass.
WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Red(),
WriteState::NEED_MORE_DATA,
IntRect(0, 0, 7, 7), 6, 6);
// Fourth pass. Rows are positioned at 2n + 1.
// Output row 1. The invalid rect contains the Haeberli rows for this output
// row (just row 1) as well as the rows that we copy from previous passes
// when seeking to the next output row (row 2).
WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Green(),
WriteState::NEED_MORE_DATA,
IntRect(0, 1, 7, 2), 1, 1);
// Output row 3. The invalid rect contains the Haeberli rows for this output
// row (just row 3) as well as the rows that we copy from previous passes
// when seeking to the next output row (row 4).
WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Red(),
WriteState::NEED_MORE_DATA,
IntRect(0, 3, 7, 2), 3, 3);
// Output row 5. The invalid rect contains the Haeberli rows for this output
// row (just row 5) as well as the rows that we copy from previous passes
// when seeking to the next output row (row 6).
WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Green(),
WriteState::FINISHED,
IntRect(0, 5, 7, 2), 5, 5);
// Assert that we're in the expected final state.
EXPECT_TRUE(aFilter->IsSurfaceFinished());
Maybe<SurfaceInvalidRect> invalidRect = aFilter->TakeInvalidRect();
EXPECT_TRUE(invalidRect.isNothing());
// Check that the generated image is correct. As mentioned above, we expect
// two green rows, followed by two red rows, then two green rows, etc.
RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
RefPtr<SourceSurface> surface = currentFrame->GetSourceSurface();
for (uint32_t row = 0; row < 7; ++row) {
BGRAColor color = row == 0 || row == 1 || row == 4 || row == 5
? BGRAColor::Green()
: BGRAColor::Red();
EXPECT_TRUE(RowsAreSolidColor(surface, row, 1, color));
}
});
}
TEST_F(ImageDeinterlacingFilter, WritePixelsNonProgressiveIntermediateOutput7_7)
{
WithDeinterlacingFilter(IntSize(7, 7), /* aProgressiveDisplay = */ false,
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
// Fill the image. The output should be a repeating pattern of two green
// rows followed by two red rows but we need to write the rows in the order
// that the deinterlacer expects them.
// First pass. Output rows are positioned at 8n + 0.
// Output row 0. The invalid rect is the entire image because this is the
// end of the first pass.
WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Green(),
WriteState::NEED_MORE_DATA,
IntRect(0, 0, 7, 7), 0, 0);
// Second pass. Rows are positioned at 8n + 4.
// Output row 4. The invalid rect is the entire image because this is the
// end of the second pass.
WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Green(),
WriteState::NEED_MORE_DATA,
IntRect(0, 0, 7, 7), 4, 4);
// Third pass. Rows are positioned at 4n + 2.
// Output row 2. The invalid rect contains the Haeberli rows for this output
// row (rows 2 and 3) as well as the rows that we copy from previous passes
// when seeking to the next output row (rows 4 and 5).
WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Red(),
WriteState::NEED_MORE_DATA,
IntRect(0, 2, 7, 4), 2, 2);
// Output row 6. The invalid rect is the entire image because this is the
// end of the third pass.
WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Red(),
WriteState::NEED_MORE_DATA,
IntRect(0, 0, 7, 7), 6, 6);
// Fourth pass. Rows are positioned at 2n + 1.
// Output row 1. The invalid rect contains the Haeberli rows for this output
// row (just row 1) as well as the rows that we copy from previous passes
// when seeking to the next output row (row 2).
WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Green(),
WriteState::NEED_MORE_DATA,
IntRect(0, 1, 7, 2), 1, 1);
// Output row 3. The invalid rect contains the Haeberli rows for this output
// row (just row 3) as well as the rows that we copy from previous passes
// when seeking to the next output row (row 4).
WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Red(),
WriteState::NEED_MORE_DATA,
IntRect(0, 3, 7, 2), 3, 3);
// Output row 5. The invalid rect contains the Haeberli rows for this output
// row (just row 5) as well as the rows that we copy from previous passes
// when seeking to the next output row (row 6).
WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Green(),
WriteState::FINISHED,
IntRect(0, 5, 7, 2), 5, 5);
// Assert that we're in the expected final state.
EXPECT_TRUE(aFilter->IsSurfaceFinished());
Maybe<SurfaceInvalidRect> invalidRect = aFilter->TakeInvalidRect();
EXPECT_TRUE(invalidRect.isNothing());
// Check that the generated image is correct. As mentioned above, we expect
// two green rows, followed by two red rows, then two green rows, etc.
RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
RefPtr<SourceSurface> surface = currentFrame->GetSourceSurface();
for (uint32_t row = 0; row < 7; ++row) {
BGRAColor color = row == 0 || row == 1 || row == 4 || row == 5
? BGRAColor::Green()
: BGRAColor::Red();
EXPECT_TRUE(RowsAreSolidColor(surface, row, 1, color));
}
});
}
TEST_F(ImageDeinterlacingFilter, DeinterlacingFailsFor0_0)
{
// A 0x0 input size is invalid, so configuration should fail.
AssertConfiguringDeinterlacingFilterFails(IntSize(0, 0));
}
TEST_F(ImageDeinterlacingFilter, DeinterlacingFailsForMinus1_Minus1)
{
// A negative input size is invalid, so configuration should fail.
AssertConfiguringDeinterlacingFilterFails(IntSize(-1, -1));
}

View file

@ -0,0 +1,231 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */
#include "gtest/gtest.h"
#include "mozilla/gfx/2D.h"
#include "Common.h"
#include "Decoder.h"
#include "DecoderFactory.h"
#include "SourceBuffer.h"
#include "SurfaceFilters.h"
#include "SurfacePipe.h"
using namespace mozilla;
using namespace mozilla::gfx;
using namespace mozilla::image;
template <typename Func> void
WithDownscalingFilter(const IntSize& aInputSize,
const IntSize& aOutputSize,
Func aFunc)
{
RefPtr<Decoder> decoder = CreateTrivialDecoder();
ASSERT_TRUE(decoder != nullptr);
WithFilterPipeline(decoder, Forward<Func>(aFunc),
DownscalingConfig { aInputSize,
SurfaceFormat::B8G8R8A8 },
SurfaceConfig { decoder, 0, aOutputSize,
SurfaceFormat::B8G8R8A8, false });
}
void
AssertConfiguringDownscalingFilterFails(const IntSize& aInputSize,
const IntSize& aOutputSize)
{
RefPtr<Decoder> decoder = CreateTrivialDecoder();
ASSERT_TRUE(decoder != nullptr);
AssertConfiguringPipelineFails(decoder,
DownscalingConfig { aInputSize,
SurfaceFormat::B8G8R8A8 },
SurfaceConfig { decoder, 0, aOutputSize,
SurfaceFormat::B8G8R8A8, false });
}
TEST(ImageDownscalingFilter, WritePixels100_100to99_99)
{
WithDownscalingFilter(IntSize(100, 100), IntSize(99, 99),
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
CheckWritePixels(aDecoder, aFilter,
/* aOutputRect = */ Some(IntRect(0, 0, 99, 99)));
});
}
TEST(ImageDownscalingFilter, WritePixels100_100to33_33)
{
WithDownscalingFilter(IntSize(100, 100), IntSize(33, 33),
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
CheckWritePixels(aDecoder, aFilter,
/* aOutputRect = */ Some(IntRect(0, 0, 33, 33)));
});
}
TEST(ImageDownscalingFilter, WritePixels100_100to1_1)
{
WithDownscalingFilter(IntSize(100, 100), IntSize(1, 1),
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
CheckWritePixels(aDecoder, aFilter,
/* aOutputRect = */ Some(IntRect(0, 0, 1, 1)));
});
}
TEST(ImageDownscalingFilter, WritePixels100_100to33_99)
{
WithDownscalingFilter(IntSize(100, 100), IntSize(33, 99),
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
CheckWritePixels(aDecoder, aFilter,
/* aOutputRect = */ Some(IntRect(0, 0, 33, 99)));
});
}
TEST(ImageDownscalingFilter, WritePixels100_100to99_33)
{
WithDownscalingFilter(IntSize(100, 100), IntSize(99, 33),
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
CheckWritePixels(aDecoder, aFilter,
/* aOutputRect = */ Some(IntRect(0, 0, 99, 33)));
});
}
TEST(ImageDownscalingFilter, WritePixels100_100to99_1)
{
WithDownscalingFilter(IntSize(100, 100), IntSize(99, 1),
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
CheckWritePixels(aDecoder, aFilter,
/* aOutputRect = */ Some(IntRect(0, 0, 99, 1)));
});
}
TEST(ImageDownscalingFilter, WritePixels100_100to1_99)
{
WithDownscalingFilter(IntSize(100, 100), IntSize(1, 99),
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
CheckWritePixels(aDecoder, aFilter,
/* aOutputRect = */ Some(IntRect(0, 0, 1, 99)));
});
}
TEST(ImageDownscalingFilter, DownscalingFailsFor100_100to101_101)
{
// Upscaling is disallowed.
AssertConfiguringDownscalingFilterFails(IntSize(100, 100), IntSize(101, 101));
}
TEST(ImageDownscalingFilter, DownscalingFailsFor100_100to100_100)
{
// "Scaling" to the same size is disallowed.
AssertConfiguringDownscalingFilterFails(IntSize(100, 100), IntSize(100, 100));
}
TEST(ImageDownscalingFilter, DownscalingFailsFor0_0toMinus1_Minus1)
{
// A 0x0 input size is disallowed.
AssertConfiguringDownscalingFilterFails(IntSize(0, 0), IntSize(-1, -1));
}
TEST(ImageDownscalingFilter, DownscalingFailsForMinus1_Minus1toMinus2_Minus2)
{
// A negative input size is disallowed.
AssertConfiguringDownscalingFilterFails(IntSize(-1, -1), IntSize(-2, -2));
}
TEST(ImageDownscalingFilter, DownscalingFailsFor100_100to0_0)
{
// A 0x0 output size is disallowed.
AssertConfiguringDownscalingFilterFails(IntSize(100, 100), IntSize(0, 0));
}
TEST(ImageDownscalingFilter, DownscalingFailsFor100_100toMinus1_Minus1)
{
// A negative output size is disallowed.
AssertConfiguringDownscalingFilterFails(IntSize(100, 100), IntSize(-1, -1));
}
TEST(ImageDownscalingFilter, WritePixelsOutput100_100to20_20)
{
WithDownscalingFilter(IntSize(100, 100), IntSize(20, 20),
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
// Fill the image. It consists of 25 lines of green, followed by 25 lines of
// red, followed by 25 lines of green, followed by 25 more lines of red.
uint32_t count = 0;
auto result = aFilter->WritePixels<uint32_t>([&]() -> NextPixel<uint32_t> {
uint32_t color = (count <= 25 * 100) || (count > 50 * 100 && count <= 75 * 100)
? BGRAColor::Green().AsPixel()
: BGRAColor::Red().AsPixel();
++count;
return AsVariant(color);
});
EXPECT_EQ(WriteState::FINISHED, result);
EXPECT_EQ(100u * 100u, count);
AssertCorrectPipelineFinalState(aFilter,
IntRect(0, 0, 100, 100),
IntRect(0, 0, 20, 20));
// Check that the generated image is correct. Note that we skip rows near
// the transitions between colors, since the downscaler does not produce a
// sharp boundary at these points. Even some of the rows we test need a
// small amount of fuzz; this is just the nature of Lanczos downscaling.
RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
RefPtr<SourceSurface> surface = currentFrame->GetSourceSurface();
EXPECT_TRUE(RowsAreSolidColor(surface, 0, 4, BGRAColor::Green(), /* aFuzz = */ 2));
EXPECT_TRUE(RowsAreSolidColor(surface, 6, 3, BGRAColor::Red(), /* aFuzz = */ 3));
EXPECT_TRUE(RowsAreSolidColor(surface, 11, 3, BGRAColor::Green(), /* aFuzz = */ 3));
EXPECT_TRUE(RowsAreSolidColor(surface, 16, 4, BGRAColor::Red(), /* aFuzz = */ 3));
});
}
TEST(ImageDownscalingFilter, WritePixelsOutput100_100to10_20)
{
WithDownscalingFilter(IntSize(100, 100), IntSize(10, 20),
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
// Fill the image. It consists of 25 lines of green, followed by 25 lines of
// red, followed by 25 lines of green, followed by 25 more lines of red.
uint32_t count = 0;
auto result = aFilter->WritePixels<uint32_t>([&]() -> NextPixel<uint32_t> {
uint32_t color = (count <= 25 * 100) || (count > 50 * 100 && count <= 75 * 100)
? BGRAColor::Green().AsPixel()
: BGRAColor::Red().AsPixel();
++count;
return AsVariant(color);
});
EXPECT_EQ(WriteState::FINISHED, result);
EXPECT_EQ(100u * 100u, count);
AssertCorrectPipelineFinalState(aFilter,
IntRect(0, 0, 100, 100),
IntRect(0, 0, 10, 20));
// Check that the generated image is correct. Note that we skip rows near
// the transitions between colors, since the downscaler does not produce a
// sharp boundary at these points. Even some of the rows we test need a
// small amount of fuzz; this is just the nature of Lanczos downscaling.
RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
RefPtr<SourceSurface> surface = currentFrame->GetSourceSurface();
EXPECT_TRUE(RowsAreSolidColor(surface, 0, 4, BGRAColor::Green(), /* aFuzz = */ 2));
EXPECT_TRUE(RowsAreSolidColor(surface, 6, 3, BGRAColor::Red(), /* aFuzz = */ 3));
EXPECT_TRUE(RowsAreSolidColor(surface, 11, 3, BGRAColor::Green(), /* aFuzz = */ 3));
EXPECT_TRUE(RowsAreSolidColor(surface, 16, 4, BGRAColor::Red(), /* aFuzz = */ 3));
});
}
TEST(ImageDownscalingFilter, ConfiguringPalettedDownscaleFails)
{
RefPtr<Decoder> decoder = CreateTrivialDecoder();
ASSERT_TRUE(decoder != nullptr);
// DownscalingFilter does not support paletted images, so configuration should
// fail.
AssertConfiguringPipelineFails(decoder,
DownscalingConfig { IntSize(100, 100),
SurfaceFormat::B8G8R8A8 },
PalettedSurfaceConfig { decoder, 0, IntSize(20, 20),
IntRect(0, 0, 20, 20),
SurfaceFormat::B8G8R8A8, 8,
false });
}

View file

@ -0,0 +1,57 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */
#include "gtest/gtest.h"
#include "mozilla/gfx/2D.h"
#include "Decoder.h"
#include "DecoderFactory.h"
#include "SourceBuffer.h"
#include "SurfacePipe.h"
// We want to ensure that we're testing the non-Skia fallback version of
// DownscalingFilter, but there are two issues:
// (1) We don't know whether Skia is currently enabled.
// (2) If we force disable it, the disabled version will get linked into the
// binary and will cause the tests in TestDownscalingFilter to fail.
// To avoid these problems, we ensure that MOZ_ENABLE_SKIA is defined when
// including DownscalingFilter.h, and we use the preprocessor to redefine the
// DownscalingFilter class to DownscalingFilterNoSkia.
#define DownscalingFilter DownscalingFilterNoSkia
#ifdef MOZ_ENABLE_SKIA
#undef MOZ_ENABLE_SKIA
#include "Common.h"
#include "DownscalingFilter.h"
#define MOZ_ENABLE_SKIA
#else
#include "Common.h"
#include "DownscalingFilter.h"
#endif
#undef DownscalingFilter
using namespace mozilla;
using namespace mozilla::gfx;
using namespace mozilla::image;
TEST(ImageDownscalingFilter, NoSkia)
{
RefPtr<Decoder> decoder = CreateTrivialDecoder();
ASSERT_TRUE(bool(decoder));
// Configuring a DownscalingFilter should fail without Skia.
AssertConfiguringPipelineFails(decoder,
DownscalingConfig { IntSize(100, 100),
SurfaceFormat::B8G8R8A8 },
SurfaceConfig { decoder, 0, IntSize(50, 50),
SurfaceFormat::B8G8R8A8, false });
}

View file

@ -0,0 +1,255 @@
/* 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/. */
#include "gtest/gtest.h"
#include "Common.h"
#include "Decoder.h"
#include "DecoderFactory.h"
#include "decoders/nsBMPDecoder.h"
#include "IDecodingTask.h"
#include "imgIContainer.h"
#include "imgITools.h"
#include "ImageFactory.h"
#include "mozilla/gfx/2D.h"
#include "nsComponentManagerUtils.h"
#include "nsCOMPtr.h"
#include "nsIInputStream.h"
#include "nsIRunnable.h"
#include "nsIThread.h"
#include "mozilla/RefPtr.h"
#include "nsStreamUtils.h"
#include "nsString.h"
#include "nsThreadUtils.h"
#include "ProgressTracker.h"
#include "SourceBuffer.h"
using namespace mozilla;
using namespace mozilla::gfx;
using namespace mozilla::image;
enum class BMPWithinICO
{
NO,
YES
};
static void
CheckMetadata(const ImageTestCase& aTestCase,
BMPWithinICO aBMPWithinICO = BMPWithinICO::NO)
{
nsCOMPtr<nsIInputStream> inputStream = LoadFile(aTestCase.mPath);
ASSERT_TRUE(inputStream != nullptr);
// Figure out how much data we have.
uint64_t length;
nsresult rv = inputStream->Available(&length);
ASSERT_TRUE(NS_SUCCEEDED(rv));
// Write the data into a SourceBuffer.
NotNull<RefPtr<SourceBuffer>> sourceBuffer = WrapNotNull(new SourceBuffer());
sourceBuffer->ExpectLength(length);
rv = sourceBuffer->AppendFromInputStream(inputStream, length);
ASSERT_TRUE(NS_SUCCEEDED(rv));
sourceBuffer->Complete(NS_OK);
// Create a metadata decoder.
DecoderType decoderType =
DecoderFactory::GetDecoderType(aTestCase.mMimeType);
RefPtr<Decoder> decoder =
DecoderFactory::CreateAnonymousMetadataDecoder(decoderType, sourceBuffer);
ASSERT_TRUE(decoder != nullptr);
RefPtr<IDecodingTask> task = new AnonymousDecodingTask(WrapNotNull(decoder));
if (aBMPWithinICO == BMPWithinICO::YES) {
static_cast<nsBMPDecoder*>(decoder.get())->SetIsWithinICO();
}
// Run the metadata decoder synchronously.
task->Run();
// Ensure that the metadata decoder didn't make progress it shouldn't have
// (which would indicate that it decoded past the header of the image).
Progress metadataProgress = decoder->TakeProgress();
EXPECT_TRUE(0 == (metadataProgress & ~(FLAG_SIZE_AVAILABLE |
FLAG_HAS_TRANSPARENCY |
FLAG_IS_ANIMATED)));
// If the test case is corrupt, assert what we can and return early.
if (aTestCase.mFlags & TEST_CASE_HAS_ERROR) {
EXPECT_TRUE(decoder->GetDecodeDone());
EXPECT_TRUE(decoder->HasError());
return;
}
EXPECT_TRUE(decoder->GetDecodeDone() && !decoder->HasError());
// Check that we got the expected metadata.
EXPECT_TRUE(metadataProgress & FLAG_SIZE_AVAILABLE);
IntSize metadataSize = decoder->Size();
EXPECT_EQ(aTestCase.mSize.width, metadataSize.width);
EXPECT_EQ(aTestCase.mSize.height, metadataSize.height);
bool expectTransparency = aBMPWithinICO == BMPWithinICO::YES
? true
: bool(aTestCase.mFlags & TEST_CASE_IS_TRANSPARENT);
EXPECT_EQ(expectTransparency, bool(metadataProgress & FLAG_HAS_TRANSPARENCY));
EXPECT_EQ(bool(aTestCase.mFlags & TEST_CASE_IS_ANIMATED),
bool(metadataProgress & FLAG_IS_ANIMATED));
// Create a full decoder, so we can compare the result.
decoder =
DecoderFactory::CreateAnonymousDecoder(decoderType, sourceBuffer, Nothing(),
DefaultSurfaceFlags());
ASSERT_TRUE(decoder != nullptr);
task = new AnonymousDecodingTask(WrapNotNull(decoder));
if (aBMPWithinICO == BMPWithinICO::YES) {
static_cast<nsBMPDecoder*>(decoder.get())->SetIsWithinICO();
}
// Run the full decoder synchronously.
task->Run();
EXPECT_TRUE(decoder->GetDecodeDone() && !decoder->HasError());
Progress fullProgress = decoder->TakeProgress();
// If the metadata decoder set a progress bit, the full decoder should also
// have set the same bit.
EXPECT_EQ(fullProgress, metadataProgress | fullProgress);
// The full decoder and the metadata decoder should agree on the image's size.
IntSize fullSize = decoder->Size();
EXPECT_EQ(metadataSize.width, fullSize.width);
EXPECT_EQ(metadataSize.height, fullSize.height);
// We should not discover transparency during the full decode that we didn't
// discover during the metadata decode, unless the image is animated.
EXPECT_TRUE(!(fullProgress & FLAG_HAS_TRANSPARENCY) ||
(metadataProgress & FLAG_HAS_TRANSPARENCY) ||
(fullProgress & FLAG_IS_ANIMATED));
}
class ImageDecoderMetadata : public ::testing::Test
{
protected:
AutoInitializeImageLib mInit;
};
TEST_F(ImageDecoderMetadata, PNG) { CheckMetadata(GreenPNGTestCase()); }
TEST_F(ImageDecoderMetadata, TransparentPNG) { CheckMetadata(TransparentPNGTestCase()); }
TEST_F(ImageDecoderMetadata, GIF) { CheckMetadata(GreenGIFTestCase()); }
TEST_F(ImageDecoderMetadata, TransparentGIF) { CheckMetadata(TransparentGIFTestCase()); }
TEST_F(ImageDecoderMetadata, JPG) { CheckMetadata(GreenJPGTestCase()); }
TEST_F(ImageDecoderMetadata, BMP) { CheckMetadata(GreenBMPTestCase()); }
TEST_F(ImageDecoderMetadata, ICO) { CheckMetadata(GreenICOTestCase()); }
TEST_F(ImageDecoderMetadata, Icon) { CheckMetadata(GreenIconTestCase()); }
TEST_F(ImageDecoderMetadata, AnimatedGIF)
{
CheckMetadata(GreenFirstFrameAnimatedGIFTestCase());
}
TEST_F(ImageDecoderMetadata, AnimatedPNG)
{
CheckMetadata(GreenFirstFrameAnimatedPNGTestCase());
}
TEST_F(ImageDecoderMetadata, FirstFramePaddingGIF)
{
CheckMetadata(FirstFramePaddingGIFTestCase());
}
TEST_F(ImageDecoderMetadata, TransparentIfWithinICOBMPNotWithinICO)
{
CheckMetadata(TransparentIfWithinICOBMPTestCase(TEST_CASE_DEFAULT_FLAGS),
BMPWithinICO::NO);
}
TEST_F(ImageDecoderMetadata, TransparentIfWithinICOBMPWithinICO)
{
CheckMetadata(TransparentIfWithinICOBMPTestCase(TEST_CASE_IS_TRANSPARENT),
BMPWithinICO::YES);
}
TEST_F(ImageDecoderMetadata, RLE4BMP) { CheckMetadata(RLE4BMPTestCase()); }
TEST_F(ImageDecoderMetadata, RLE8BMP) { CheckMetadata(RLE8BMPTestCase()); }
TEST_F(ImageDecoderMetadata, Corrupt) { CheckMetadata(CorruptTestCase()); }
TEST_F(ImageDecoderMetadata, NoFrameDelayGIF)
{
CheckMetadata(NoFrameDelayGIFTestCase());
}
TEST_F(ImageDecoderMetadata, NoFrameDelayGIFFullDecode)
{
ImageTestCase testCase = NoFrameDelayGIFTestCase();
// The previous test (NoFrameDelayGIF) verifies that we *don't* detect that
// this test case is animated, because it has a zero frame delay for the first
// frame. This test verifies that when we do a full decode, we detect the
// animation at that point and successfully decode all the frames.
// Create an image.
RefPtr<Image> image =
ImageFactory::CreateAnonymousImage(nsDependentCString(testCase.mMimeType));
ASSERT_TRUE(!image->HasError());
nsCOMPtr<nsIInputStream> inputStream = LoadFile(testCase.mPath);
ASSERT_TRUE(inputStream != nullptr);
// Figure out how much data we have.
uint64_t length;
nsresult rv = inputStream->Available(&length);
ASSERT_TRUE(NS_SUCCEEDED(rv));
// Write the data into the image.
rv = image->OnImageDataAvailable(nullptr, nullptr, inputStream, 0,
static_cast<uint32_t>(length));
ASSERT_TRUE(NS_SUCCEEDED(rv));
// Let the image know we've sent all the data.
rv = image->OnImageDataComplete(nullptr, nullptr, NS_OK, true);
ASSERT_TRUE(NS_SUCCEEDED(rv));
RefPtr<ProgressTracker> tracker = image->GetProgressTracker();
tracker->SyncNotifyProgress(FLAG_LOAD_COMPLETE);
// Use GetFrame() to force a sync decode of the image.
RefPtr<SourceSurface> surface =
image->GetFrame(imgIContainer::FRAME_CURRENT,
imgIContainer::FLAG_SYNC_DECODE);
// Ensure that the image's metadata meets our expectations.
IntSize imageSize(0, 0);
rv = image->GetWidth(&imageSize.width);
EXPECT_TRUE(NS_SUCCEEDED(rv));
rv = image->GetHeight(&imageSize.height);
EXPECT_TRUE(NS_SUCCEEDED(rv));
EXPECT_EQ(testCase.mSize.width, imageSize.width);
EXPECT_EQ(testCase.mSize.height, imageSize.height);
Progress imageProgress = tracker->GetProgress();
EXPECT_TRUE(bool(imageProgress & FLAG_HAS_TRANSPARENCY) == false);
EXPECT_TRUE(bool(imageProgress & FLAG_IS_ANIMATED) == true);
// Ensure that we decoded both frames of the image.
LookupResult result =
SurfaceCache::Lookup(ImageKey(image.get()),
RasterSurfaceKey(imageSize,
DefaultSurfaceFlags(),
PlaybackType::eAnimated));
ASSERT_EQ(MatchType::EXACT, result.Type());
EXPECT_TRUE(NS_SUCCEEDED(result.Surface().Seek(0)));
EXPECT_TRUE(bool(result.Surface()));
EXPECT_TRUE(NS_SUCCEEDED(result.Surface().Seek(1)));
EXPECT_TRUE(bool(result.Surface()));
}

View file

@ -0,0 +1,327 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */
#include "gtest/gtest.h"
#include "mozilla/gfx/2D.h"
#include "Common.h"
#include "Decoder.h"
#include "DecoderFactory.h"
#include "SourceBuffer.h"
#include "SurfaceFilters.h"
#include "SurfacePipe.h"
using namespace mozilla;
using namespace mozilla::gfx;
using namespace mozilla::image;
template <typename Func> void
WithRemoveFrameRectFilter(const IntSize& aSize,
const IntRect& aFrameRect,
Func aFunc)
{
RefPtr<Decoder> decoder = CreateTrivialDecoder();
ASSERT_TRUE(decoder != nullptr);
WithFilterPipeline(decoder, Forward<Func>(aFunc),
RemoveFrameRectConfig { aFrameRect },
SurfaceConfig { decoder, 0, aSize,
SurfaceFormat::B8G8R8A8, false });
}
void
AssertConfiguringRemoveFrameRectFilterFails(const IntSize& aSize,
const IntRect& aFrameRect)
{
RefPtr<Decoder> decoder = CreateTrivialDecoder();
ASSERT_TRUE(decoder != nullptr);
AssertConfiguringPipelineFails(decoder,
RemoveFrameRectConfig { aFrameRect },
SurfaceConfig { decoder, 0, aSize,
SurfaceFormat::B8G8R8A8, false });
}
TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_0_0_100_100)
{
WithRemoveFrameRectFilter(IntSize(100, 100),
IntRect(0, 0, 100, 100),
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
CheckWritePixels(aDecoder, aFilter,
/* aOutputRect = */ Some(IntRect(0, 0, 100, 100)),
/* aInputRect = */ Some(IntRect(0, 0, 100, 100)),
/* aInputWriteRect = */ Some(IntRect(0, 0, 100, 100)));
});
}
TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_0_0_0_0)
{
WithRemoveFrameRectFilter(IntSize(100, 100),
IntRect(0, 0, 0, 0),
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
CheckWritePixels(aDecoder, aFilter,
/* aOutputRect = */ Some(IntRect(0, 0, 100, 100)),
/* aInputRect = */ Some(IntRect(0, 0, 100, 100)),
/* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)),
/* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0)));
});
}
TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_Minus50_50_0_0)
{
WithRemoveFrameRectFilter(IntSize(100, 100),
IntRect(-50, 50, 0, 0),
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
CheckWritePixels(aDecoder, aFilter,
/* aOutputRect = */ Some(IntRect(0, 0, 100, 100)),
/* aInputRect = */ Some(IntRect(0, 0, 100, 100)),
/* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)),
/* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0)));
});
}
TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_50_Minus50_0_0)
{
WithRemoveFrameRectFilter(IntSize(100, 100),
IntRect(50, -50, 0, 0),
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
CheckWritePixels(aDecoder, aFilter,
/* aOutputRect = */ Some(IntRect(0, 0, 100, 100)),
/* aInputRect = */ Some(IntRect(0, 0, 100, 100)),
/* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)),
/* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0)));
});
}
TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_150_50_0_0)
{
WithRemoveFrameRectFilter(IntSize(100, 100),
IntRect(150, 50, 0, 0),
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
CheckWritePixels(aDecoder, aFilter,
/* aOutputRect = */ Some(IntRect(0, 0, 100, 100)),
/* aInputRect = */ Some(IntRect(0, 0, 100, 100)),
/* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)),
/* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0)));
});
}
TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_50_150_0_0)
{
WithRemoveFrameRectFilter(IntSize(100, 100),
IntRect(50, 150, 0, 0),
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
CheckWritePixels(aDecoder, aFilter,
/* aOutputRect = */ Some(IntRect(0, 0, 100, 100)),
/* aInputRect = */ Some(IntRect(0, 0, 100, 100)),
/* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)),
/* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0)));
});
}
TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_200_200_100_100)
{
WithRemoveFrameRectFilter(IntSize(100, 100),
IntRect(200, 200, 100, 100),
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
// Note that aInputRect is zero-size because RemoveFrameRectFilter ignores
// trailing rows that don't show up in the output. (Leading rows
// unfortunately can't be ignored.)
CheckWritePixels(aDecoder, aFilter,
/* aOutputRect = */ Some(IntRect(0, 0, 100, 100)),
/* aInputRect = */ Some(IntRect(0, 0, 100, 100)),
/* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)),
/* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0)));
});
}
TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_Minus200_25_100_100)
{
WithRemoveFrameRectFilter(IntSize(100, 100),
IntRect(-200, 25, 100, 100),
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
// Note that aInputRect is zero-size because RemoveFrameRectFilter ignores
// trailing rows that don't show up in the output. (Leading rows
// unfortunately can't be ignored.)
CheckWritePixels(aDecoder, aFilter,
/* aOutputRect = */ Some(IntRect(0, 0, 100, 100)),
/* aInputRect = */ Some(IntRect(0, 0, 100, 100)),
/* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)),
/* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0)));
});
}
TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_25_Minus200_100_100)
{
WithRemoveFrameRectFilter(IntSize(100, 100),
IntRect(25, -200, 100, 100),
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
// Note that aInputRect is zero-size because RemoveFrameRectFilter ignores
// trailing rows that don't show up in the output. (Leading rows
// unfortunately can't be ignored.)
CheckWritePixels(aDecoder, aFilter,
/* aOutputRect = */ Some(IntRect(0, 0, 100, 100)),
/* aInputRect = */ Some(IntRect(0, 0, 100, 100)),
/* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)),
/* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0)));
});
}
TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_200_25_100_100)
{
WithRemoveFrameRectFilter(IntSize(100, 100),
IntRect(200, 25, 100, 100),
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
// Note that aInputRect is zero-size because RemoveFrameRectFilter ignores
// trailing rows that don't show up in the output. (Leading rows
// unfortunately can't be ignored.)
CheckWritePixels(aDecoder, aFilter,
/* aOutputRect = */ Some(IntRect(0, 0, 100, 100)),
/* aInputRect = */ Some(IntRect(0, 0, 100, 100)),
/* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)),
/* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0)));
});
}
TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_25_200_100_100)
{
WithRemoveFrameRectFilter(IntSize(100, 100),
IntRect(25, 200, 100, 100),
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
// Note that aInputRect is zero-size because RemoveFrameRectFilter ignores
// trailing rows that don't show up in the output. (Leading rows
// unfortunately can't be ignored.)
CheckWritePixels(aDecoder, aFilter,
/* aOutputRect = */ Some(IntRect(0, 0, 100, 100)),
/* aInputRect = */ Some(IntRect(0, 0, 100, 100)),
/* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)),
/* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0)));
});
}
TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_Minus200_Minus200_100_100)
{
WithRemoveFrameRectFilter(IntSize(100, 100),
IntRect(-200, -200, 100, 100),
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
CheckWritePixels(aDecoder, aFilter,
/* aOutputRect = */ Some(IntRect(0, 0, 100, 100)),
/* aInputRect = */ Some(IntRect(0, 0, 100, 100)),
/* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)),
/* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0)));
});
}
TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_Minus50_Minus50_100_100)
{
WithRemoveFrameRectFilter(IntSize(100, 100),
IntRect(-50, -50, 100, 100),
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
CheckWritePixels(aDecoder, aFilter,
/* aOutputRect = */ Some(IntRect(0, 0, 100, 100)),
/* aInputRect = */ Some(IntRect(0, 0, 100, 100)),
/* aInputWriteRect = */ Some(IntRect(0, 0, 100, 100)),
/* aOutputWriteRect = */ Some(IntRect(0, 0, 50, 50)));
});
}
TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_Minus50_25_100_50)
{
WithRemoveFrameRectFilter(IntSize(100, 100),
IntRect(-50, 25, 100, 50),
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
CheckWritePixels(aDecoder, aFilter,
/* aOutputRect = */ Some(IntRect(0, 0, 100, 100)),
/* aInputRect = */ Some(IntRect(0, 0, 100, 100)),
/* aInputWriteRect = */ Some(IntRect(0, 0, 100, 50)),
/* aOutputWriteRect = */ Some(IntRect(0, 25, 50, 50)));
});
}
TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_25_Minus50_50_100)
{
WithRemoveFrameRectFilter(IntSize(100, 100),
IntRect(25, -50, 50, 100),
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
CheckWritePixels(aDecoder, aFilter,
/* aOutputRect = */ Some(IntRect(0, 0, 100, 100)),
/* aInputRect = */ Some(IntRect(0, 0, 100, 100)),
/* aInputWriteRect = */ Some(IntRect(0, 0, 50, 100)),
/* aOutputWriteRect = */ Some(IntRect(25, 0, 50, 50)));
});
}
TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_50_25_100_50)
{
WithRemoveFrameRectFilter(IntSize(100, 100),
IntRect(50, 25, 100, 50),
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
CheckWritePixels(aDecoder, aFilter,
/* aOutputRect = */ Some(IntRect(0, 0, 100, 100)),
/* aInputRect = */ Some(IntRect(0, 0, 100, 100)),
/* aInputWriteRect = */ Some(IntRect(0, 0, 100, 50)),
/* aOutputWriteRect = */ Some(IntRect(50, 25, 50, 50)));
});
}
TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_25_50_50_100)
{
WithRemoveFrameRectFilter(IntSize(100, 100),
IntRect(25, 50, 50, 100),
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
// Note that aInputRect is 50x50 because RemoveFrameRectFilter ignores
// trailing rows that don't show up in the output. (Leading rows
// unfortunately can't be ignored.)
CheckWritePixels(aDecoder, aFilter,
/* aOutputRect = */ Some(IntRect(0, 0, 100, 100)),
/* aInputRect = */ Some(IntRect(0, 0, 100, 100)),
/* aInputWriteRect = */ Some(IntRect(0, 0, 50, 50)),
/* aOutputWriteRect = */ Some(IntRect(25, 50, 50, 100)));
});
}
TEST(ImageRemoveFrameRectFilter, RemoveFrameRectFailsFor0_0_to_0_0_100_100)
{
// A zero-size image is disallowed.
AssertConfiguringRemoveFrameRectFilterFails(IntSize(0, 0),
IntRect(0, 0, 100, 100));
}
TEST(ImageRemoveFrameRectFilter, RemoveFrameRectFailsForMinus1_Minus1_to_0_0_100_100)
{
// A negative-size image is disallowed.
AssertConfiguringRemoveFrameRectFilterFails(IntSize(-1, -1),
IntRect(0, 0, 100, 100));
}
TEST(ImageRemoveFrameRectFilter, RemoveFrameRectFailsFor100_100_to_0_0_0_0)
{
// A zero size frame rect is disallowed.
AssertConfiguringRemoveFrameRectFilterFails(IntSize(100, 100),
IntRect(0, 0, -1, -1));
}
TEST(ImageRemoveFrameRectFilter, RemoveFrameRectFailsFor100_100_to_0_0_Minus1_Minus1)
{
// A negative size frame rect is disallowed.
AssertConfiguringRemoveFrameRectFilterFails(IntSize(100, 100),
IntRect(0, 0, -1, -1));
}
TEST(ImageRemoveFrameRectFilter, ConfiguringPalettedRemoveFrameRectFails)
{
RefPtr<Decoder> decoder = CreateTrivialDecoder();
ASSERT_TRUE(decoder != nullptr);
// RemoveFrameRectFilter does not support paletted images, so configuration
// should fail.
AssertConfiguringPipelineFails(decoder,
RemoveFrameRectConfig { IntRect(0, 0, 50, 50) },
PalettedSurfaceConfig { decoder, 0, IntSize(100, 100),
IntRect(0, 0, 50, 50),
SurfaceFormat::B8G8R8A8, 8,
false });
}

View file

@ -0,0 +1,810 @@
/* 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/. */
#include "gtest/gtest.h"
#include <algorithm>
#include <cstdint>
#include "mozilla/Move.h"
#include "SourceBuffer.h"
#include "SurfaceCache.h"
using namespace mozilla;
using namespace mozilla::image;
using std::min;
void
ExpectChunkAndByteCount(const SourceBufferIterator& aIterator,
uint32_t aChunks,
size_t aBytes)
{
EXPECT_EQ(aChunks, aIterator.ChunkCount());
EXPECT_EQ(aBytes, aIterator.ByteCount());
}
void
ExpectRemainingBytes(const SourceBufferIterator& aIterator, size_t aBytes)
{
EXPECT_TRUE(aIterator.RemainingBytesIsNoMoreThan(aBytes));
EXPECT_TRUE(aIterator.RemainingBytesIsNoMoreThan(aBytes + 1));
if (aBytes > 0) {
EXPECT_FALSE(aIterator.RemainingBytesIsNoMoreThan(0));
EXPECT_FALSE(aIterator.RemainingBytesIsNoMoreThan(aBytes - 1));
}
}
char
GenerateByte(size_t aIndex)
{
uint8_t byte = aIndex % 256;
return *reinterpret_cast<char*>(&byte);
}
void
GenerateData(char* aOutput, size_t aOffset, size_t aLength)
{
for (size_t i = 0; i < aLength; ++i) {
aOutput[i] = GenerateByte(aOffset + i);
}
}
void
GenerateData(char* aOutput, size_t aLength)
{
GenerateData(aOutput, 0, aLength);
}
void
CheckData(const char* aData, size_t aOffset, size_t aLength)
{
for (size_t i = 0; i < aLength; ++i) {
ASSERT_EQ(GenerateByte(aOffset + i), aData[i]);
}
}
enum class AdvanceMode
{
eAdvanceAsMuchAsPossible,
eAdvanceByLengthExactly
};
class ImageSourceBuffer : public ::testing::Test
{
public:
ImageSourceBuffer()
: mSourceBuffer(new SourceBuffer)
, mExpectNoResume(new ExpectNoResume)
, mCountResumes(new CountResumes)
{
GenerateData(mData, sizeof(mData));
EXPECT_FALSE(mSourceBuffer->IsComplete());
}
protected:
void CheckedAppendToBuffer(const char* aData, size_t aLength)
{
EXPECT_TRUE(NS_SUCCEEDED(mSourceBuffer->Append(aData, aLength)));
}
void CheckedAppendToBufferLastByteForLength(size_t aLength)
{
const char lastByte = GenerateByte(aLength);
CheckedAppendToBuffer(&lastByte, 1);
}
void CheckedAppendToBufferInChunks(size_t aChunkLength, size_t aTotalLength)
{
char* data = new char[aChunkLength];
size_t bytesWritten = 0;
while (bytesWritten < aTotalLength) {
GenerateData(data, bytesWritten, aChunkLength);
size_t toWrite = min(aChunkLength, aTotalLength - bytesWritten);
CheckedAppendToBuffer(data, toWrite);
bytesWritten += toWrite;
}
delete[] data;
}
void CheckedCompleteBuffer(nsresult aCompletionStatus = NS_OK)
{
mSourceBuffer->Complete(aCompletionStatus);
EXPECT_TRUE(mSourceBuffer->IsComplete());
}
void CheckedCompleteBuffer(SourceBufferIterator& aIterator,
size_t aLength,
nsresult aCompletionStatus = NS_OK)
{
CheckedCompleteBuffer(aCompletionStatus);
ExpectRemainingBytes(aIterator, aLength);
}
void CheckedAdvanceIteratorStateOnly(SourceBufferIterator& aIterator,
size_t aLength,
uint32_t aChunks,
size_t aTotalLength,
AdvanceMode aAdvanceMode
= AdvanceMode::eAdvanceAsMuchAsPossible)
{
const size_t advanceBy = aAdvanceMode == AdvanceMode::eAdvanceAsMuchAsPossible
? SIZE_MAX
: aLength;
auto state = aIterator.AdvanceOrScheduleResume(advanceBy, mExpectNoResume);
ASSERT_EQ(SourceBufferIterator::READY, state);
EXPECT_TRUE(aIterator.Data());
EXPECT_EQ(aLength, aIterator.Length());
ExpectChunkAndByteCount(aIterator, aChunks, aTotalLength);
}
void CheckedAdvanceIteratorStateOnly(SourceBufferIterator& aIterator,
size_t aLength)
{
CheckedAdvanceIteratorStateOnly(aIterator, aLength, 1, aLength);
}
void CheckedAdvanceIterator(SourceBufferIterator& aIterator,
size_t aLength,
uint32_t aChunks,
size_t aTotalLength,
AdvanceMode aAdvanceMode
= AdvanceMode::eAdvanceAsMuchAsPossible)
{
// Check that the iterator is in the expected state.
CheckedAdvanceIteratorStateOnly(aIterator, aLength, aChunks,
aTotalLength, aAdvanceMode);
// Check that we read the expected data. To do this, we need to compute our
// offset in the SourceBuffer, but fortunately that's pretty easy: it's the
// total number of bytes the iterator has advanced through, minus the length
// of the current chunk.
const size_t offset = aIterator.ByteCount() - aIterator.Length();
CheckData(aIterator.Data(), offset, aIterator.Length());
}
void CheckedAdvanceIterator(SourceBufferIterator& aIterator, size_t aLength)
{
CheckedAdvanceIterator(aIterator, aLength, 1, aLength);
}
void CheckIteratorMustWait(SourceBufferIterator& aIterator,
IResumable* aOnResume)
{
auto state = aIterator.AdvanceOrScheduleResume(1, aOnResume);
EXPECT_EQ(SourceBufferIterator::WAITING, state);
}
void CheckIteratorIsComplete(SourceBufferIterator& aIterator,
uint32_t aChunks,
size_t aTotalLength,
nsresult aCompletionStatus = NS_OK)
{
ASSERT_TRUE(mSourceBuffer->IsComplete());
auto state = aIterator.AdvanceOrScheduleResume(1, mExpectNoResume);
ASSERT_EQ(SourceBufferIterator::COMPLETE, state);
EXPECT_EQ(aCompletionStatus, aIterator.CompletionStatus());
ExpectRemainingBytes(aIterator, 0);
ExpectChunkAndByteCount(aIterator, aChunks, aTotalLength);
}
void CheckIteratorIsComplete(SourceBufferIterator& aIterator,
size_t aTotalLength)
{
CheckIteratorIsComplete(aIterator, 1, aTotalLength);
}
AutoInitializeImageLib mInit;
char mData[9];
RefPtr<SourceBuffer> mSourceBuffer;
RefPtr<ExpectNoResume> mExpectNoResume;
RefPtr<CountResumes> mCountResumes;
};
TEST_F(ImageSourceBuffer, InitialState)
{
SourceBufferIterator iterator = mSourceBuffer->Iterator();
// RemainingBytesIsNoMoreThan() should always return false in the initial
// state, since we can't know the answer until Complete() has been called.
EXPECT_FALSE(iterator.RemainingBytesIsNoMoreThan(0));
EXPECT_FALSE(iterator.RemainingBytesIsNoMoreThan(SIZE_MAX));
// We haven't advanced our iterator at all, so its counters should be zero.
ExpectChunkAndByteCount(iterator, 0, 0);
// Attempt to advance; we should fail, and end up in the WAITING state. We
// expect no resumes because we don't actually append anything to the
// SourceBuffer in this test.
CheckIteratorMustWait(iterator, mExpectNoResume);
}
TEST_F(ImageSourceBuffer, ZeroLengthBufferAlwaysFails)
{
SourceBufferIterator iterator = mSourceBuffer->Iterator();
// Complete the buffer without writing to it, providing a successful
// completion status.
CheckedCompleteBuffer(iterator, 0);
// Completing a buffer without writing to it results in an automatic failure;
// make sure that the actual completion status we get from the iterator
// reflects this.
CheckIteratorIsComplete(iterator, 0, 0, NS_ERROR_FAILURE);
}
TEST_F(ImageSourceBuffer, CompleteSuccess)
{
SourceBufferIterator iterator = mSourceBuffer->Iterator();
// Write a single byte to the buffer and complete the buffer. (We have to
// write at least one byte because completing a zero length buffer always
// fails; see the ZeroLengthBufferAlwaysFails test.)
CheckedAppendToBuffer(mData, 1);
CheckedCompleteBuffer(iterator, 1);
// We should be able to advance once (to read the single byte) and then should
// reach the COMPLETE state with a successful status.
CheckedAdvanceIterator(iterator, 1);
CheckIteratorIsComplete(iterator, 1);
}
TEST_F(ImageSourceBuffer, CompleteFailure)
{
SourceBufferIterator iterator = mSourceBuffer->Iterator();
// Write a single byte to the buffer and complete the buffer. (We have to
// write at least one byte because completing a zero length buffer always
// fails; see the ZeroLengthBufferAlwaysFails test.)
CheckedAppendToBuffer(mData, 1);
CheckedCompleteBuffer(iterator, 1, NS_ERROR_FAILURE);
// Advance the iterator. Because a failing status is propagated to the
// iterator as soon as it advances, we won't be able to read the single byte
// that we wrote above; we go directly into the COMPLETE state.
CheckIteratorIsComplete(iterator, 0, 0, NS_ERROR_FAILURE);
}
TEST_F(ImageSourceBuffer, Append)
{
SourceBufferIterator iterator = mSourceBuffer->Iterator();
// Write test data to the buffer.
EXPECT_TRUE(NS_SUCCEEDED(mSourceBuffer->ExpectLength(sizeof(mData))));
CheckedAppendToBuffer(mData, sizeof(mData));
CheckedCompleteBuffer(iterator, sizeof(mData));
// Verify that we can read it back via the iterator, and that the final state
// is what we expect.
CheckedAdvanceIterator(iterator, sizeof(mData));
CheckIteratorIsComplete(iterator, sizeof(mData));
}
TEST_F(ImageSourceBuffer, HugeAppendFails)
{
SourceBufferIterator iterator = mSourceBuffer->Iterator();
// We should fail to append anything bigger than what the SurfaceCache can
// hold, so use the SurfaceCache's maximum capacity to calculate what a
// "massive amount of data" (see below) consists of on this platform.
ASSERT_LT(SurfaceCache::MaximumCapacity(), SIZE_MAX);
const size_t hugeSize = SurfaceCache::MaximumCapacity() + 1;
// Attempt to write a massive amount of data and verify that it fails. (We'd
// get a buffer overrun during the test if it succeeds, but if it succeeds
// that's the least of our problems.)
EXPECT_TRUE(NS_FAILED(mSourceBuffer->Append(mData, hugeSize)));
EXPECT_TRUE(mSourceBuffer->IsComplete());
CheckIteratorIsComplete(iterator, 0, 0, NS_ERROR_OUT_OF_MEMORY);
}
TEST_F(ImageSourceBuffer, AppendFromInputStream)
{
SourceBufferIterator iterator = mSourceBuffer->Iterator();
// Construct an input stream with some arbitrary data. (We use test data from
// one of the decoder tests.)
nsCOMPtr<nsIInputStream> inputStream = LoadFile(GreenPNGTestCase().mPath);
ASSERT_TRUE(inputStream != nullptr);
// Figure out how much data we have.
uint64_t length;
ASSERT_TRUE(NS_SUCCEEDED(inputStream->Available(&length)));
// Write test data to the buffer.
EXPECT_TRUE(NS_SUCCEEDED(mSourceBuffer->AppendFromInputStream(inputStream,
length)));
CheckedCompleteBuffer(iterator, length);
// Verify that the iterator sees the appropriate amount of data.
CheckedAdvanceIteratorStateOnly(iterator, length);
CheckIteratorIsComplete(iterator, length);
}
TEST_F(ImageSourceBuffer, AppendAfterComplete)
{
SourceBufferIterator iterator = mSourceBuffer->Iterator();
// Write test data to the buffer.
EXPECT_TRUE(NS_SUCCEEDED(mSourceBuffer->ExpectLength(sizeof(mData))));
CheckedAppendToBuffer(mData, sizeof(mData));
CheckedCompleteBuffer(iterator, sizeof(mData));
// Verify that we can read it back via the iterator, and that the final state
// is what we expect.
CheckedAdvanceIterator(iterator, sizeof(mData));
CheckIteratorIsComplete(iterator, sizeof(mData));
// Write more data to the completed buffer.
EXPECT_TRUE(NS_FAILED(mSourceBuffer->Append(mData, sizeof(mData))));
// Try to read with a new iterator and verify that the new data got ignored.
SourceBufferIterator iterator2 = mSourceBuffer->Iterator();
CheckedAdvanceIterator(iterator2, sizeof(mData));
CheckIteratorIsComplete(iterator2, sizeof(mData));
}
TEST_F(ImageSourceBuffer, MinChunkCapacity)
{
SourceBufferIterator iterator = mSourceBuffer->Iterator();
// Write test data to the buffer using many small appends. Since
// ExpectLength() isn't being called, we should be able to write up to
// SourceBuffer::MIN_CHUNK_CAPACITY bytes without a second chunk being
// allocated.
CheckedAppendToBufferInChunks(10, SourceBuffer::MIN_CHUNK_CAPACITY);
// Verify that the iterator sees the appropriate amount of data.
CheckedAdvanceIterator(iterator, SourceBuffer::MIN_CHUNK_CAPACITY);
// Write one more byte; we expect to see that it triggers an allocation.
CheckedAppendToBufferLastByteForLength(SourceBuffer::MIN_CHUNK_CAPACITY);
CheckedCompleteBuffer(iterator, 1);
// Verify that the iterator sees the new byte and a new chunk has been
// allocated.
CheckedAdvanceIterator(iterator, 1, 2, SourceBuffer::MIN_CHUNK_CAPACITY + 1);
CheckIteratorIsComplete(iterator, 2, SourceBuffer::MIN_CHUNK_CAPACITY + 1);
}
TEST_F(ImageSourceBuffer, ExpectLengthDoesNotShrinkBelowMinCapacity)
{
SourceBufferIterator iterator = mSourceBuffer->Iterator();
// Write SourceBuffer::MIN_CHUNK_CAPACITY bytes of test data to the buffer,
// but call ExpectLength() first to make SourceBuffer expect only a single
// byte. We expect this to still result in only one chunk, because
// regardless of ExpectLength() we won't allocate a chunk smaller than
// MIN_CHUNK_CAPACITY bytes.
EXPECT_TRUE(NS_SUCCEEDED(mSourceBuffer->ExpectLength(1)));
CheckedAppendToBufferInChunks(10, SourceBuffer::MIN_CHUNK_CAPACITY);
CheckedCompleteBuffer(iterator, SourceBuffer::MIN_CHUNK_CAPACITY);
// Verify that the iterator sees a single chunk.
CheckedAdvanceIterator(iterator, SourceBuffer::MIN_CHUNK_CAPACITY);
CheckIteratorIsComplete(iterator, 1, SourceBuffer::MIN_CHUNK_CAPACITY);
}
TEST_F(ImageSourceBuffer, ExpectLengthGrowsAboveMinCapacity)
{
SourceBufferIterator iterator = mSourceBuffer->Iterator();
// Write two times SourceBuffer::MIN_CHUNK_CAPACITY bytes of test data to the
// buffer, calling ExpectLength() with the correct length first. We expect
// this to result in only one chunk, because ExpectLength() allows us to
// allocate a larger first chunk than MIN_CHUNK_CAPACITY bytes.
const size_t length = 2 * SourceBuffer::MIN_CHUNK_CAPACITY;
EXPECT_TRUE(NS_SUCCEEDED(mSourceBuffer->ExpectLength(length)));
CheckedAppendToBufferInChunks(10, length);
// Verify that the iterator sees a single chunk.
CheckedAdvanceIterator(iterator, length);
// Write one more byte; we expect to see that it triggers an allocation.
CheckedAppendToBufferLastByteForLength(length);
CheckedCompleteBuffer(iterator, 1);
// Verify that the iterator sees the new byte and a new chunk has been
// allocated.
CheckedAdvanceIterator(iterator, 1, 2, length + 1);
CheckIteratorIsComplete(iterator, 2, length + 1);
}
TEST_F(ImageSourceBuffer, HugeExpectLengthFails)
{
SourceBufferIterator iterator = mSourceBuffer->Iterator();
// ExpectLength() should fail if the length is bigger than what the
// SurfaceCache can hold, so use the SurfaceCache's maximum capacity to
// calculate what a "massive amount of data" (see below) consists of on this
// platform.
ASSERT_LT(SurfaceCache::MaximumCapacity(), SIZE_MAX);
const size_t hugeSize = SurfaceCache::MaximumCapacity() + 1;
// Attempt to write a massive amount of data and verify that it fails. (We'd
// get a buffer overrun during the test if it succeeds, but if it succeeds
// that's the least of our problems.)
EXPECT_TRUE(NS_FAILED(mSourceBuffer->ExpectLength(hugeSize)));
EXPECT_TRUE(mSourceBuffer->IsComplete());
CheckIteratorIsComplete(iterator, 0, 0, NS_ERROR_OUT_OF_MEMORY);
}
TEST_F(ImageSourceBuffer, LargeAppendsAllocateOnlyOneChunk)
{
SourceBufferIterator iterator = mSourceBuffer->Iterator();
// Write two times SourceBuffer::MIN_CHUNK_CAPACITY bytes of test data to the
// buffer in a single Append() call. We expect this to result in only one
// chunk even though ExpectLength() wasn't called, because we should always
// allocate a new chunk large enough to store the data we have at hand.
constexpr size_t length = 2 * SourceBuffer::MIN_CHUNK_CAPACITY;
char data[length];
GenerateData(data, sizeof(data));
CheckedAppendToBuffer(data, length);
// Verify that the iterator sees a single chunk.
CheckedAdvanceIterator(iterator, length);
// Write one more byte; we expect to see that it triggers an allocation.
CheckedAppendToBufferLastByteForLength(length);
CheckedCompleteBuffer(iterator, 1);
// Verify that the iterator sees the new byte and a new chunk has been
// allocated.
CheckedAdvanceIterator(iterator, 1, 2, length + 1);
CheckIteratorIsComplete(iterator, 2, length + 1);
}
TEST_F(ImageSourceBuffer, LargeAppendsAllocateAtMostOneChunk)
{
SourceBufferIterator iterator = mSourceBuffer->Iterator();
// Allocate some data we'll use below.
constexpr size_t firstWriteLength = SourceBuffer::MIN_CHUNK_CAPACITY / 2;
constexpr size_t secondWriteLength = 3 * SourceBuffer::MIN_CHUNK_CAPACITY;
constexpr size_t totalLength = firstWriteLength + secondWriteLength;
char data[totalLength];
GenerateData(data, sizeof(data));
// Write half of SourceBuffer::MIN_CHUNK_CAPACITY bytes of test data to the
// buffer in a single Append() call. This should fill half of the first chunk.
CheckedAppendToBuffer(data, firstWriteLength);
// Write three times SourceBuffer::MIN_CHUNK_CAPACITY bytes of test data to the
// buffer in a single Append() call. We expect this to result in the first of
// the first chunk being filled and a new chunk being allocated for the
// remainder.
CheckedAppendToBuffer(data + firstWriteLength, secondWriteLength);
// Verify that the iterator sees a MIN_CHUNK_CAPACITY-length chunk.
CheckedAdvanceIterator(iterator, SourceBuffer::MIN_CHUNK_CAPACITY);
// Verify that the iterator sees a second chunk of the length we expect.
const size_t expectedSecondChunkLength =
totalLength - SourceBuffer::MIN_CHUNK_CAPACITY;
CheckedAdvanceIterator(iterator, expectedSecondChunkLength, 2, totalLength);
// Write one more byte; we expect to see that it triggers an allocation.
CheckedAppendToBufferLastByteForLength(totalLength);
CheckedCompleteBuffer(iterator, 1);
// Verify that the iterator sees the new byte and a new chunk has been
// allocated.
CheckedAdvanceIterator(iterator, 1, 3, totalLength + 1);
CheckIteratorIsComplete(iterator, 3, totalLength + 1);
}
TEST_F(ImageSourceBuffer, CompactionHappensWhenBufferIsComplete)
{
constexpr size_t chunkLength = SourceBuffer::MIN_CHUNK_CAPACITY;
constexpr size_t totalLength = 2 * chunkLength;
// Write enough data to create two chunks.
CheckedAppendToBufferInChunks(chunkLength, totalLength);
{
SourceBufferIterator iterator = mSourceBuffer->Iterator();
// Verify that the iterator sees two chunks.
CheckedAdvanceIterator(iterator, chunkLength);
CheckedAdvanceIterator(iterator, chunkLength, 2, totalLength);
}
// Complete the buffer, which should trigger compaction implicitly.
CheckedCompleteBuffer();
{
SourceBufferIterator iterator = mSourceBuffer->Iterator();
// Verify that compaction happened and there's now only one chunk.
CheckedAdvanceIterator(iterator, totalLength);
CheckIteratorIsComplete(iterator, 1, totalLength);
}
}
TEST_F(ImageSourceBuffer, CompactionIsDelayedWhileIteratorsExist)
{
constexpr size_t chunkLength = SourceBuffer::MIN_CHUNK_CAPACITY;
constexpr size_t totalLength = 2 * chunkLength;
{
SourceBufferIterator outerIterator = mSourceBuffer->Iterator();
{
SourceBufferIterator iterator = mSourceBuffer->Iterator();
// Write enough data to create two chunks.
CheckedAppendToBufferInChunks(chunkLength, totalLength);
CheckedCompleteBuffer(iterator, totalLength);
// Verify that the iterator sees two chunks. Since there are live
// iterators, compaction shouldn't have happened when we completed the
// buffer.
CheckedAdvanceIterator(iterator, chunkLength);
CheckedAdvanceIterator(iterator, chunkLength, 2, totalLength);
CheckIteratorIsComplete(iterator, 2, totalLength);
}
// Now |iterator| has been destroyed, but |outerIterator| still exists, so
// we expect no compaction to have occurred at this point.
CheckedAdvanceIterator(outerIterator, chunkLength);
CheckedAdvanceIterator(outerIterator, chunkLength, 2, totalLength);
CheckIteratorIsComplete(outerIterator, 2, totalLength);
}
// Now all iterators have been destroyed. Since the buffer was already
// complete, we expect compaction to happen implicitly here.
{
SourceBufferIterator iterator = mSourceBuffer->Iterator();
// Verify that compaction happened and there's now only one chunk.
CheckedAdvanceIterator(iterator, totalLength);
CheckIteratorIsComplete(iterator, 1, totalLength);
}
}
TEST_F(ImageSourceBuffer, SourceBufferIteratorsCanBeMoved)
{
constexpr size_t chunkLength = SourceBuffer::MIN_CHUNK_CAPACITY;
constexpr size_t totalLength = 2 * chunkLength;
// Write enough data to create two chunks. We create an iterator here to make
// sure that compaction doesn't happen during the test.
SourceBufferIterator iterator = mSourceBuffer->Iterator();
CheckedAppendToBufferInChunks(chunkLength, totalLength);
CheckedCompleteBuffer(iterator, totalLength);
auto GetIterator = [&]{
SourceBufferIterator lambdaIterator = mSourceBuffer->Iterator();
CheckedAdvanceIterator(lambdaIterator, chunkLength);
return lambdaIterator;
};
// Move-construct |movedIterator| from the iterator returned from
// GetIterator() and check that its state is as we expect.
SourceBufferIterator movedIterator = Move(GetIterator());
EXPECT_TRUE(movedIterator.Data());
EXPECT_EQ(chunkLength, movedIterator.Length());
ExpectChunkAndByteCount(movedIterator, 1, chunkLength);
// Make sure that we can advance the iterator.
CheckedAdvanceIterator(movedIterator, chunkLength, 2, totalLength);
// Make sure that the iterator handles completion properly.
CheckIteratorIsComplete(movedIterator, 2, totalLength);
// Move-assign |movedIterator| from the iterator returned from
// GetIterator() and check that its state is as we expect.
movedIterator = Move(GetIterator());
EXPECT_TRUE(movedIterator.Data());
EXPECT_EQ(chunkLength, movedIterator.Length());
ExpectChunkAndByteCount(movedIterator, 1, chunkLength);
// Make sure that we can advance the iterator.
CheckedAdvanceIterator(movedIterator, chunkLength, 2, totalLength);
// Make sure that the iterator handles completion properly.
CheckIteratorIsComplete(movedIterator, 2, totalLength);
}
TEST_F(ImageSourceBuffer, SubchunkAdvance)
{
constexpr size_t chunkLength = SourceBuffer::MIN_CHUNK_CAPACITY;
constexpr size_t totalLength = 2 * chunkLength;
// Write enough data to create two chunks. We create our iterator here to make
// sure that compaction doesn't happen during the test.
SourceBufferIterator iterator = mSourceBuffer->Iterator();
CheckedAppendToBufferInChunks(chunkLength, totalLength);
CheckedCompleteBuffer(iterator, totalLength);
// Advance through the first chunk. The chunk count should not increase.
// We check that by always passing 1 for the |aChunks| parameter of
// CheckedAdvanceIteratorStateOnly(). We have to call CheckData() manually
// because the offset calculation in CheckedAdvanceIterator() assumes that
// we're advancing a chunk at a time.
size_t offset = 0;
while (offset < chunkLength) {
CheckedAdvanceIteratorStateOnly(iterator, 1, 1, chunkLength,
AdvanceMode::eAdvanceByLengthExactly);
CheckData(iterator.Data(), offset++, iterator.Length());
}
// Read the first byte of the second chunk. This is the point at which we
// can't advance within the same chunk, so the chunk count should increase. We
// check that by passing 2 for the |aChunks| parameter of
// CheckedAdvanceIteratorStateOnly().
CheckedAdvanceIteratorStateOnly(iterator, 1, 2, totalLength,
AdvanceMode::eAdvanceByLengthExactly);
CheckData(iterator.Data(), offset++, iterator.Length());
// Read the rest of the second chunk. The chunk count should not increase.
while (offset < totalLength) {
CheckedAdvanceIteratorStateOnly(iterator, 1, 2, totalLength,
AdvanceMode::eAdvanceByLengthExactly);
CheckData(iterator.Data(), offset++, iterator.Length());
}
// Make sure we reached the end.
CheckIteratorIsComplete(iterator, 2, totalLength);
}
TEST_F(ImageSourceBuffer, SubchunkZeroByteAdvance)
{
constexpr size_t chunkLength = SourceBuffer::MIN_CHUNK_CAPACITY;
constexpr size_t totalLength = 2 * chunkLength;
// Write enough data to create two chunks. We create our iterator here to make
// sure that compaction doesn't happen during the test.
SourceBufferIterator iterator = mSourceBuffer->Iterator();
CheckedAppendToBufferInChunks(chunkLength, totalLength);
CheckedCompleteBuffer(iterator, totalLength);
// Make an initial zero-length advance. Although a zero-length advance
// normally won't cause us to read a chunk from the SourceBuffer, we'll do so
// if the iterator is in the initial state to keep the invariant that
// SourceBufferIterator in the READY state always returns a non-null pointer
// from Data().
CheckedAdvanceIteratorStateOnly(iterator, 0, 1, chunkLength,
AdvanceMode::eAdvanceByLengthExactly);
// Advance through the first chunk. As in the |SubchunkAdvance| test, the
// chunk count should not increase. We do a zero-length advance after each
// normal advance to ensure that zero-length advances do not change the
// iterator's position or cause a new chunk to be read.
size_t offset = 0;
while (offset < chunkLength) {
CheckedAdvanceIteratorStateOnly(iterator, 1, 1, chunkLength,
AdvanceMode::eAdvanceByLengthExactly);
CheckData(iterator.Data(), offset++, iterator.Length());
CheckedAdvanceIteratorStateOnly(iterator, 0, 1, chunkLength,
AdvanceMode::eAdvanceByLengthExactly);
}
// Read the first byte of the second chunk. This is the point at which we
// can't advance within the same chunk, so the chunk count should increase. As
// before, we do a zero-length advance afterward.
CheckedAdvanceIteratorStateOnly(iterator, 1, 2, totalLength,
AdvanceMode::eAdvanceByLengthExactly);
CheckData(iterator.Data(), offset++, iterator.Length());
CheckedAdvanceIteratorStateOnly(iterator, 0, 2, totalLength,
AdvanceMode::eAdvanceByLengthExactly);
// Read the rest of the second chunk. The chunk count should not increase. As
// before, we do a zero-length advance after each normal advance.
while (offset < totalLength) {
CheckedAdvanceIteratorStateOnly(iterator, 1, 2, totalLength,
AdvanceMode::eAdvanceByLengthExactly);
CheckData(iterator.Data(), offset++, iterator.Length());
CheckedAdvanceIteratorStateOnly(iterator, 0, 2, totalLength,
AdvanceMode::eAdvanceByLengthExactly);
}
// Make sure we reached the end.
CheckIteratorIsComplete(iterator, 2, totalLength);
}
TEST_F(ImageSourceBuffer, SubchunkZeroByteAdvanceWithNoData)
{
SourceBufferIterator iterator = mSourceBuffer->Iterator();
// Check that advancing by zero bytes still makes us enter the WAITING state.
// This is because if we entered the READY state before reading any data at
// all, we'd break the invariant that SourceBufferIterator::Data() always
// returns a non-null pointer in the READY state.
auto state = iterator.AdvanceOrScheduleResume(0, mCountResumes);
EXPECT_EQ(SourceBufferIterator::WAITING, state);
// Call Complete(). This should trigger a resume.
CheckedCompleteBuffer();
EXPECT_EQ(1u, mCountResumes->Count());
}
TEST_F(ImageSourceBuffer, NullIResumable)
{
SourceBufferIterator iterator = mSourceBuffer->Iterator();
// Check that we can't advance.
CheckIteratorMustWait(iterator, nullptr);
// Append to the buffer, which would cause a resume if we had passed a
// non-null IResumable.
CheckedAppendToBuffer(mData, sizeof(mData));
CheckedCompleteBuffer(iterator, sizeof(mData));
}
TEST_F(ImageSourceBuffer, AppendTriggersResume)
{
SourceBufferIterator iterator = mSourceBuffer->Iterator();
// Check that we can't advance.
CheckIteratorMustWait(iterator, mCountResumes);
// Call Append(). This should trigger a resume.
mSourceBuffer->Append(mData, sizeof(mData));
EXPECT_EQ(1u, mCountResumes->Count());
}
TEST_F(ImageSourceBuffer, OnlyOneResumeTriggeredPerAppend)
{
SourceBufferIterator iterator = mSourceBuffer->Iterator();
// Check that we can't advance.
CheckIteratorMustWait(iterator, mCountResumes);
// Allocate some data we'll use below.
constexpr size_t firstWriteLength = SourceBuffer::MIN_CHUNK_CAPACITY / 2;
constexpr size_t secondWriteLength = 3 * SourceBuffer::MIN_CHUNK_CAPACITY;
constexpr size_t totalLength = firstWriteLength + secondWriteLength;
char data[totalLength];
GenerateData(data, sizeof(data));
// Write half of SourceBuffer::MIN_CHUNK_CAPACITY bytes of test data to the
// buffer in a single Append() call. This should fill half of the first chunk.
// This should trigger a resume.
CheckedAppendToBuffer(data, firstWriteLength);
EXPECT_EQ(1u, mCountResumes->Count());
// Advance past the new data and wait again.
CheckedAdvanceIterator(iterator, firstWriteLength);
CheckIteratorMustWait(iterator, mCountResumes);
// Write three times SourceBuffer::MIN_CHUNK_CAPACITY bytes of test data to the
// buffer in a single Append() call. We expect this to result in the first of
// the first chunk being filled and a new chunk being allocated for the
// remainder. Even though two chunks are getting written to here, only *one*
// resume should get triggered, for a total of two in this test.
CheckedAppendToBuffer(data + firstWriteLength, secondWriteLength);
EXPECT_EQ(2u, mCountResumes->Count());
}
TEST_F(ImageSourceBuffer, CompleteTriggersResume)
{
SourceBufferIterator iterator = mSourceBuffer->Iterator();
// Check that we can't advance.
CheckIteratorMustWait(iterator, mCountResumes);
// Call Complete(). This should trigger a resume.
CheckedCompleteBuffer();
EXPECT_EQ(1u, mCountResumes->Count());
}
TEST_F(ImageSourceBuffer, ExpectLengthDoesNotTriggerResume)
{
SourceBufferIterator iterator = mSourceBuffer->Iterator();
// Check that we can't advance.
CheckIteratorMustWait(iterator, mExpectNoResume);
// Call ExpectLength(). If this triggers a resume, |mExpectNoResume| will
// ensure that the test fails.
mSourceBuffer->ExpectLength(1000);
}

View file

@ -0,0 +1,973 @@
/* 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/. */
#include "gtest/gtest.h"
#include "mozilla/Vector.h"
#include "StreamingLexer.h"
using namespace mozilla;
using namespace mozilla::image;
enum class TestState
{
ONE,
TWO,
THREE,
UNBUFFERED,
TRUNCATED_SUCCESS,
TRUNCATED_FAILURE
};
void
CheckLexedData(const char* aData,
size_t aLength,
size_t aOffset,
size_t aExpectedLength)
{
EXPECT_TRUE(aLength == aExpectedLength);
for (size_t i = 0; i < aLength; ++i) {
EXPECT_EQ(aData[i], char(aOffset + i + 1));
}
}
LexerTransition<TestState>
DoLex(TestState aState, const char* aData, size_t aLength)
{
switch (aState) {
case TestState::ONE:
CheckLexedData(aData, aLength, 0, 3);
return Transition::To(TestState::TWO, 3);
case TestState::TWO:
CheckLexedData(aData, aLength, 3, 3);
return Transition::To(TestState::THREE, 3);
case TestState::THREE:
CheckLexedData(aData, aLength, 6, 3);
return Transition::TerminateSuccess();
case TestState::TRUNCATED_SUCCESS:
return Transition::TerminateSuccess();
case TestState::TRUNCATED_FAILURE:
return Transition::TerminateFailure();
default:
MOZ_CRASH("Unexpected or unhandled TestState");
}
}
LexerTransition<TestState>
DoLexWithUnbuffered(TestState aState, const char* aData, size_t aLength,
Vector<char>& aUnbufferedVector)
{
switch (aState) {
case TestState::ONE:
CheckLexedData(aData, aLength, 0, 3);
return Transition::ToUnbuffered(TestState::TWO, TestState::UNBUFFERED, 3);
case TestState::TWO:
CheckLexedData(aUnbufferedVector.begin(), aUnbufferedVector.length(), 3, 3);
return Transition::To(TestState::THREE, 3);
case TestState::THREE:
CheckLexedData(aData, aLength, 6, 3);
return Transition::TerminateSuccess();
case TestState::UNBUFFERED:
EXPECT_TRUE(aLength <= 3);
EXPECT_TRUE(aUnbufferedVector.append(aData, aLength));
return Transition::ContinueUnbuffered(TestState::UNBUFFERED);
default:
MOZ_CRASH("Unexpected or unhandled TestState");
}
}
LexerTransition<TestState>
DoLexWithUnbufferedTerminate(TestState aState, const char* aData, size_t aLength)
{
switch (aState) {
case TestState::ONE:
CheckLexedData(aData, aLength, 0, 3);
return Transition::ToUnbuffered(TestState::TWO, TestState::UNBUFFERED, 3);
case TestState::UNBUFFERED:
return Transition::TerminateSuccess();
default:
MOZ_CRASH("Unexpected or unhandled TestState");
}
}
LexerTransition<TestState>
DoLexWithYield(TestState aState, const char* aData, size_t aLength)
{
switch (aState) {
case TestState::ONE:
CheckLexedData(aData, aLength, 0, 3);
return Transition::ToAfterYield(TestState::TWO);
case TestState::TWO:
CheckLexedData(aData, aLength, 0, 3);
return Transition::To(TestState::THREE, 6);
case TestState::THREE:
CheckLexedData(aData, aLength, 3, 6);
return Transition::TerminateSuccess();
default:
MOZ_CRASH("Unexpected or unhandled TestState");
}
}
LexerTransition<TestState>
DoLexWithTerminateAfterYield(TestState aState, const char* aData, size_t aLength)
{
switch (aState) {
case TestState::ONE:
CheckLexedData(aData, aLength, 0, 3);
return Transition::ToAfterYield(TestState::TWO);
case TestState::TWO:
return Transition::TerminateSuccess();
default:
MOZ_CRASH("Unexpected or unhandled TestState");
}
}
LexerTransition<TestState>
DoLexWithZeroLengthStates(TestState aState, const char* aData, size_t aLength)
{
switch (aState) {
case TestState::ONE:
EXPECT_TRUE(aLength == 0);
return Transition::To(TestState::TWO, 0);
case TestState::TWO:
EXPECT_TRUE(aLength == 0);
return Transition::To(TestState::THREE, 9);
case TestState::THREE:
CheckLexedData(aData, aLength, 0, 9);
return Transition::TerminateSuccess();
default:
MOZ_CRASH("Unexpected or unhandled TestState");
}
}
LexerTransition<TestState>
DoLexWithZeroLengthStatesAtEnd(TestState aState, const char* aData, size_t aLength)
{
switch (aState) {
case TestState::ONE:
CheckLexedData(aData, aLength, 0, 9);
return Transition::To(TestState::TWO, 0);
case TestState::TWO:
EXPECT_TRUE(aLength == 0);
return Transition::To(TestState::THREE, 0);
case TestState::THREE:
EXPECT_TRUE(aLength == 0);
return Transition::TerminateSuccess();
default:
MOZ_CRASH("Unexpected or unhandled TestState");
}
}
LexerTransition<TestState>
DoLexWithZeroLengthYield(TestState aState, const char* aData, size_t aLength)
{
switch (aState) {
case TestState::ONE:
EXPECT_EQ(0u, aLength);
return Transition::ToAfterYield(TestState::TWO);
case TestState::TWO:
EXPECT_EQ(0u, aLength);
return Transition::To(TestState::THREE, 9);
case TestState::THREE:
CheckLexedData(aData, aLength, 0, 9);
return Transition::TerminateSuccess();
default:
MOZ_CRASH("Unexpected or unhandled TestState");
}
}
LexerTransition<TestState>
DoLexWithZeroLengthStatesUnbuffered(TestState aState,
const char* aData,
size_t aLength)
{
switch (aState) {
case TestState::ONE:
EXPECT_TRUE(aLength == 0);
return Transition::ToUnbuffered(TestState::TWO, TestState::UNBUFFERED, 0);
case TestState::TWO:
EXPECT_TRUE(aLength == 0);
return Transition::To(TestState::THREE, 9);
case TestState::THREE:
CheckLexedData(aData, aLength, 0, 9);
return Transition::TerminateSuccess();
case TestState::UNBUFFERED:
ADD_FAILURE() << "Should not enter zero-length unbuffered state";
return Transition::TerminateFailure();
default:
MOZ_CRASH("Unexpected or unhandled TestState");
}
}
LexerTransition<TestState>
DoLexWithZeroLengthStatesAfterUnbuffered(TestState aState,
const char* aData,
size_t aLength)
{
switch (aState) {
case TestState::ONE:
EXPECT_TRUE(aLength == 0);
return Transition::ToUnbuffered(TestState::TWO, TestState::UNBUFFERED, 9);
case TestState::TWO:
EXPECT_TRUE(aLength == 0);
return Transition::To(TestState::THREE, 0);
case TestState::THREE:
EXPECT_TRUE(aLength == 0);
return Transition::TerminateSuccess();
case TestState::UNBUFFERED:
CheckLexedData(aData, aLength, 0, 9);
return Transition::ContinueUnbuffered(TestState::UNBUFFERED);
default:
MOZ_CRASH("Unexpected or unhandled TestState");
}
}
class ImageStreamingLexer : public ::testing::Test
{
public:
// Note that mLexer is configured to enter TerminalState::FAILURE immediately
// if the input data is truncated. We don't expect that to happen in most
// tests, so we want to detect that issue. If a test needs a different
// behavior, we create a special StreamingLexer just for that test.
ImageStreamingLexer()
: mLexer(Transition::To(TestState::ONE, 3), Transition::TerminateFailure())
, mSourceBuffer(new SourceBuffer)
, mIterator(mSourceBuffer->Iterator())
, mExpectNoResume(new ExpectNoResume)
, mCountResumes(new CountResumes)
{ }
protected:
void CheckTruncatedState(StreamingLexer<TestState>& aLexer,
TerminalState aExpectedTerminalState,
nsresult aCompletionStatus = NS_OK)
{
for (unsigned i = 0; i < 9; ++i) {
if (i < 2) {
mSourceBuffer->Append(mData + i, 1);
} else if (i == 2) {
mSourceBuffer->Complete(aCompletionStatus);
}
LexerResult result = aLexer.Lex(mIterator, mCountResumes, DoLex);
if (i >= 2) {
EXPECT_TRUE(result.is<TerminalState>());
EXPECT_EQ(aExpectedTerminalState, result.as<TerminalState>());
} else {
EXPECT_TRUE(result.is<Yield>());
EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>());
}
}
EXPECT_EQ(2u, mCountResumes->Count());
}
AutoInitializeImageLib mInit;
const char mData[9] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
StreamingLexer<TestState> mLexer;
RefPtr<SourceBuffer> mSourceBuffer;
SourceBufferIterator mIterator;
RefPtr<ExpectNoResume> mExpectNoResume;
RefPtr<CountResumes> mCountResumes;
};
TEST_F(ImageStreamingLexer, ZeroLengthData)
{
// Test a zero-length input.
mSourceBuffer->Complete(NS_OK);
LexerResult result = mLexer.Lex(mIterator, mExpectNoResume, DoLex);
EXPECT_TRUE(result.is<TerminalState>());
EXPECT_EQ(TerminalState::FAILURE, result.as<TerminalState>());
}
TEST_F(ImageStreamingLexer, ZeroLengthDataUnbuffered)
{
// Test a zero-length input.
mSourceBuffer->Complete(NS_OK);
// Create a special StreamingLexer for this test because we want the first
// state to be unbuffered.
StreamingLexer<TestState> lexer(Transition::ToUnbuffered(TestState::ONE,
TestState::UNBUFFERED,
sizeof(mData)),
Transition::TerminateFailure());
LexerResult result = lexer.Lex(mIterator, mExpectNoResume, DoLex);
EXPECT_TRUE(result.is<TerminalState>());
EXPECT_EQ(TerminalState::FAILURE, result.as<TerminalState>());
}
TEST_F(ImageStreamingLexer, StartWithTerminal)
{
// Create a special StreamingLexer for this test because we want the first
// state to be a terminal state. This doesn't really make sense, but we should
// handle it.
StreamingLexer<TestState> lexer(Transition::TerminateSuccess(),
Transition::TerminateFailure());
LexerResult result = lexer.Lex(mIterator, mExpectNoResume, DoLex);
EXPECT_TRUE(result.is<TerminalState>());
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
mSourceBuffer->Complete(NS_OK);
}
TEST_F(ImageStreamingLexer, SingleChunk)
{
// Test delivering all the data at once.
mSourceBuffer->Append(mData, sizeof(mData));
mSourceBuffer->Complete(NS_OK);
LexerResult result = mLexer.Lex(mIterator, mExpectNoResume, DoLex);
EXPECT_TRUE(result.is<TerminalState>());
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
}
TEST_F(ImageStreamingLexer, SingleChunkWithUnbuffered)
{
Vector<char> unbufferedVector;
// Test delivering all the data at once.
mSourceBuffer->Append(mData, sizeof(mData));
mSourceBuffer->Complete(NS_OK);
LexerResult result =
mLexer.Lex(mIterator, mExpectNoResume,
[&](TestState aState, const char* aData, size_t aLength) {
return DoLexWithUnbuffered(aState, aData, aLength, unbufferedVector);
});
EXPECT_TRUE(result.is<TerminalState>());
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
}
TEST_F(ImageStreamingLexer, SingleChunkWithYield)
{
// Test delivering all the data at once.
mSourceBuffer->Append(mData, sizeof(mData));
mSourceBuffer->Complete(NS_OK);
LexerResult result = mLexer.Lex(mIterator, mExpectNoResume, DoLexWithYield);
ASSERT_TRUE(result.is<Yield>());
EXPECT_EQ(Yield::OUTPUT_AVAILABLE, result.as<Yield>());
result = mLexer.Lex(mIterator, mExpectNoResume, DoLexWithYield);
ASSERT_TRUE(result.is<TerminalState>());
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
}
TEST_F(ImageStreamingLexer, ChunkPerState)
{
// Test delivering in perfectly-sized chunks, one per state.
for (unsigned i = 0; i < 3; ++i) {
mSourceBuffer->Append(mData + 3 * i, 3);
LexerResult result = mLexer.Lex(mIterator, mCountResumes, DoLex);
if (i == 2) {
EXPECT_TRUE(result.is<TerminalState>());
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
} else {
EXPECT_TRUE(result.is<Yield>());
EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>());
}
}
EXPECT_EQ(2u, mCountResumes->Count());
mSourceBuffer->Complete(NS_OK);
}
TEST_F(ImageStreamingLexer, ChunkPerStateWithUnbuffered)
{
Vector<char> unbufferedVector;
// Test delivering in perfectly-sized chunks, one per state.
for (unsigned i = 0; i < 3; ++i) {
mSourceBuffer->Append(mData + 3 * i, 3);
LexerResult result =
mLexer.Lex(mIterator, mCountResumes,
[&](TestState aState, const char* aData, size_t aLength) {
return DoLexWithUnbuffered(aState, aData, aLength, unbufferedVector);
});
if (i == 2) {
EXPECT_TRUE(result.is<TerminalState>());
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
} else {
EXPECT_TRUE(result.is<Yield>());
EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>());
}
}
EXPECT_EQ(2u, mCountResumes->Count());
mSourceBuffer->Complete(NS_OK);
}
TEST_F(ImageStreamingLexer, ChunkPerStateWithYield)
{
// Test delivering in perfectly-sized chunks, one per state.
mSourceBuffer->Append(mData, 3);
LexerResult result = mLexer.Lex(mIterator, mCountResumes, DoLexWithYield);
EXPECT_TRUE(result.is<Yield>());
EXPECT_EQ(Yield::OUTPUT_AVAILABLE, result.as<Yield>());
result = mLexer.Lex(mIterator, mCountResumes, DoLexWithYield);
EXPECT_TRUE(result.is<Yield>());
EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>());
mSourceBuffer->Append(mData + 3, 6);
result = mLexer.Lex(mIterator, mCountResumes, DoLexWithYield);
EXPECT_TRUE(result.is<TerminalState>());
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
EXPECT_EQ(1u, mCountResumes->Count());
mSourceBuffer->Complete(NS_OK);
}
TEST_F(ImageStreamingLexer, ChunkPerStateWithUnbufferedYield)
{
size_t unbufferedCallCount = 0;
Vector<char> unbufferedVector;
auto lexerFunc = [&](TestState aState, const char* aData, size_t aLength)
-> LexerTransition<TestState> {
switch (aState) {
case TestState::ONE:
CheckLexedData(aData, aLength, 0, 3);
return Transition::ToUnbuffered(TestState::TWO, TestState::UNBUFFERED, 3);
case TestState::TWO:
CheckLexedData(unbufferedVector.begin(), unbufferedVector.length(), 3, 3);
return Transition::To(TestState::THREE, 3);
case TestState::THREE:
CheckLexedData(aData, aLength, 6, 3);
return Transition::TerminateSuccess();
case TestState::UNBUFFERED:
switch (unbufferedCallCount) {
case 0:
CheckLexedData(aData, aLength, 3, 3);
EXPECT_TRUE(unbufferedVector.append(aData, 2));
unbufferedCallCount++;
// Continue after yield, telling StreamingLexer we consumed 2 bytes.
return Transition::ContinueUnbufferedAfterYield(TestState::UNBUFFERED, 2);
case 1:
CheckLexedData(aData, aLength, 5, 1);
EXPECT_TRUE(unbufferedVector.append(aData, 1));
unbufferedCallCount++;
// Continue after yield, telling StreamingLexer we consumed 1 byte.
// We should end up in the TWO state.
return Transition::ContinueUnbuffered(TestState::UNBUFFERED);
}
ADD_FAILURE() << "Too many invocations of TestState::UNBUFFERED";
return Transition::TerminateFailure();
default:
MOZ_CRASH("Unexpected or unhandled TestState");
}
};
// Test delivering in perfectly-sized chunks, one per state.
for (unsigned i = 0; i < 3; ++i) {
mSourceBuffer->Append(mData + 3 * i, 3);
LexerResult result = mLexer.Lex(mIterator, mCountResumes, lexerFunc);
switch (i) {
case 0:
EXPECT_TRUE(result.is<Yield>());
EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>());
EXPECT_EQ(0u, unbufferedCallCount);
break;
case 1:
EXPECT_TRUE(result.is<Yield>());
EXPECT_EQ(Yield::OUTPUT_AVAILABLE, result.as<Yield>());
EXPECT_EQ(1u, unbufferedCallCount);
result = mLexer.Lex(mIterator, mCountResumes, lexerFunc);
EXPECT_TRUE(result.is<Yield>());
EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>());
EXPECT_EQ(2u, unbufferedCallCount);
break;
case 2:
EXPECT_TRUE(result.is<TerminalState>());
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
break;
}
}
EXPECT_EQ(2u, mCountResumes->Count());
mSourceBuffer->Complete(NS_OK);
LexerResult result = mLexer.Lex(mIterator, mCountResumes, lexerFunc);
EXPECT_TRUE(result.is<TerminalState>());
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
}
TEST_F(ImageStreamingLexer, OneByteChunks)
{
// Test delivering in one byte chunks.
for (unsigned i = 0; i < 9; ++i) {
mSourceBuffer->Append(mData + i, 1);
LexerResult result = mLexer.Lex(mIterator, mCountResumes, DoLex);
if (i == 8) {
EXPECT_TRUE(result.is<TerminalState>());
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
} else {
EXPECT_TRUE(result.is<Yield>());
EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>());
}
}
EXPECT_EQ(8u, mCountResumes->Count());
mSourceBuffer->Complete(NS_OK);
}
TEST_F(ImageStreamingLexer, OneByteChunksWithUnbuffered)
{
Vector<char> unbufferedVector;
// Test delivering in one byte chunks.
for (unsigned i = 0; i < 9; ++i) {
mSourceBuffer->Append(mData + i, 1);
LexerResult result =
mLexer.Lex(mIterator, mCountResumes,
[&](TestState aState, const char* aData, size_t aLength) {
return DoLexWithUnbuffered(aState, aData, aLength, unbufferedVector);
});
if (i == 8) {
EXPECT_TRUE(result.is<TerminalState>());
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
} else {
EXPECT_TRUE(result.is<Yield>());
EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>());
}
}
EXPECT_EQ(8u, mCountResumes->Count());
mSourceBuffer->Complete(NS_OK);
}
TEST_F(ImageStreamingLexer, OneByteChunksWithYield)
{
// Test delivering in one byte chunks.
for (unsigned i = 0; i < 9; ++i) {
mSourceBuffer->Append(mData + i, 1);
LexerResult result = mLexer.Lex(mIterator, mCountResumes, DoLexWithYield);
switch (i) {
case 2:
EXPECT_TRUE(result.is<Yield>());
EXPECT_EQ(Yield::OUTPUT_AVAILABLE, result.as<Yield>());
result = mLexer.Lex(mIterator, mCountResumes, DoLexWithYield);
EXPECT_TRUE(result.is<Yield>());
EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>());
break;
case 8:
EXPECT_TRUE(result.is<TerminalState>());
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
break;
default:
EXPECT_TRUE(i < 9);
EXPECT_TRUE(result.is<Yield>());
EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>());
}
}
EXPECT_EQ(8u, mCountResumes->Count());
mSourceBuffer->Complete(NS_OK);
}
TEST_F(ImageStreamingLexer, ZeroLengthState)
{
mSourceBuffer->Append(mData, sizeof(mData));
mSourceBuffer->Complete(NS_OK);
// Create a special StreamingLexer for this test because we want the first
// state to be zero length.
StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 0),
Transition::TerminateFailure());
LexerResult result =
lexer.Lex(mIterator, mExpectNoResume, DoLexWithZeroLengthStates);
EXPECT_TRUE(result.is<TerminalState>());
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
}
TEST_F(ImageStreamingLexer, ZeroLengthStatesAtEnd)
{
mSourceBuffer->Append(mData, sizeof(mData));
mSourceBuffer->Complete(NS_OK);
// Create a special StreamingLexer for this test because we want the first
// state to consume the full input.
StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 9),
Transition::TerminateFailure());
LexerResult result =
lexer.Lex(mIterator, mExpectNoResume, DoLexWithZeroLengthStatesAtEnd);
EXPECT_TRUE(result.is<TerminalState>());
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
}
TEST_F(ImageStreamingLexer, ZeroLengthStateWithYield)
{
// Create a special StreamingLexer for this test because we want the first
// state to be zero length.
StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 0),
Transition::TerminateFailure());
mSourceBuffer->Append(mData, 3);
LexerResult result =
lexer.Lex(mIterator, mExpectNoResume, DoLexWithZeroLengthYield);
ASSERT_TRUE(result.is<Yield>());
EXPECT_EQ(Yield::OUTPUT_AVAILABLE, result.as<Yield>());
result = lexer.Lex(mIterator, mCountResumes, DoLexWithZeroLengthYield);
ASSERT_TRUE(result.is<Yield>());
EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>());
mSourceBuffer->Append(mData + 3, sizeof(mData) - 3);
mSourceBuffer->Complete(NS_OK);
result = lexer.Lex(mIterator, mExpectNoResume, DoLexWithZeroLengthYield);
ASSERT_TRUE(result.is<TerminalState>());
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
EXPECT_EQ(1u, mCountResumes->Count());
}
TEST_F(ImageStreamingLexer, ZeroLengthStateWithUnbuffered)
{
mSourceBuffer->Append(mData, sizeof(mData));
mSourceBuffer->Complete(NS_OK);
// Create a special StreamingLexer for this test because we want the first
// state to be both zero length and unbuffered.
StreamingLexer<TestState> lexer(Transition::ToUnbuffered(TestState::ONE,
TestState::UNBUFFERED,
0),
Transition::TerminateFailure());
LexerResult result =
lexer.Lex(mIterator, mExpectNoResume, DoLexWithZeroLengthStatesUnbuffered);
EXPECT_TRUE(result.is<TerminalState>());
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
}
TEST_F(ImageStreamingLexer, ZeroLengthStateAfterUnbuffered)
{
mSourceBuffer->Append(mData, sizeof(mData));
mSourceBuffer->Complete(NS_OK);
// Create a special StreamingLexer for this test because we want the first
// state to be zero length.
StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 0),
Transition::TerminateFailure());
LexerResult result =
lexer.Lex(mIterator, mExpectNoResume, DoLexWithZeroLengthStatesAfterUnbuffered);
EXPECT_TRUE(result.is<TerminalState>());
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
}
TEST_F(ImageStreamingLexer, ZeroLengthStateWithUnbufferedYield)
{
size_t unbufferedCallCount = 0;
auto lexerFunc = [&](TestState aState, const char* aData, size_t aLength)
-> LexerTransition<TestState> {
switch (aState) {
case TestState::ONE:
EXPECT_EQ(0u, aLength);
return Transition::TerminateSuccess();
case TestState::UNBUFFERED:
switch (unbufferedCallCount) {
case 0:
CheckLexedData(aData, aLength, 0, 3);
unbufferedCallCount++;
// Continue after yield, telling StreamingLexer we consumed 0 bytes.
return Transition::ContinueUnbufferedAfterYield(TestState::UNBUFFERED, 0);
case 1:
CheckLexedData(aData, aLength, 0, 3);
unbufferedCallCount++;
// Continue after yield, telling StreamingLexer we consumed 2 bytes.
return Transition::ContinueUnbufferedAfterYield(TestState::UNBUFFERED, 2);
case 2:
EXPECT_EQ(1u, aLength);
CheckLexedData(aData, aLength, 2, 1);
unbufferedCallCount++;
// Continue after yield, telling StreamingLexer we consumed 1 bytes.
return Transition::ContinueUnbufferedAfterYield(TestState::UNBUFFERED, 1);
case 3:
CheckLexedData(aData, aLength, 3, 6);
unbufferedCallCount++;
// Continue after yield, telling StreamingLexer we consumed 6 bytes.
// We should transition to TestState::ONE when we return from the
// yield.
return Transition::ContinueUnbufferedAfterYield(TestState::UNBUFFERED, 6);
}
ADD_FAILURE() << "Too many invocations of TestState::UNBUFFERED";
return Transition::TerminateFailure();
default:
MOZ_CRASH("Unexpected or unhandled TestState");
}
};
// Create a special StreamingLexer for this test because we want the first
// state to be unbuffered.
StreamingLexer<TestState> lexer(Transition::ToUnbuffered(TestState::ONE,
TestState::UNBUFFERED,
sizeof(mData)),
Transition::TerminateFailure());
mSourceBuffer->Append(mData, 3);
LexerResult result = lexer.Lex(mIterator, mExpectNoResume, lexerFunc);
ASSERT_TRUE(result.is<Yield>());
EXPECT_EQ(Yield::OUTPUT_AVAILABLE, result.as<Yield>());
EXPECT_EQ(1u, unbufferedCallCount);
result = lexer.Lex(mIterator, mExpectNoResume, lexerFunc);
ASSERT_TRUE(result.is<Yield>());
EXPECT_EQ(Yield::OUTPUT_AVAILABLE, result.as<Yield>());
EXPECT_EQ(2u, unbufferedCallCount);
result = lexer.Lex(mIterator, mExpectNoResume, lexerFunc);
ASSERT_TRUE(result.is<Yield>());
EXPECT_EQ(Yield::OUTPUT_AVAILABLE, result.as<Yield>());
EXPECT_EQ(3u, unbufferedCallCount);
result = lexer.Lex(mIterator, mCountResumes, lexerFunc);
ASSERT_TRUE(result.is<Yield>());
EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>());
EXPECT_EQ(3u, unbufferedCallCount);
mSourceBuffer->Append(mData + 3, 6);
mSourceBuffer->Complete(NS_OK);
EXPECT_EQ(1u, mCountResumes->Count());
result = lexer.Lex(mIterator, mExpectNoResume, lexerFunc);
ASSERT_TRUE(result.is<Yield>());
EXPECT_EQ(Yield::OUTPUT_AVAILABLE, result.as<Yield>());
EXPECT_EQ(4u, unbufferedCallCount);
result = lexer.Lex(mIterator, mExpectNoResume, lexerFunc);
ASSERT_TRUE(result.is<TerminalState>());
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
}
TEST_F(ImageStreamingLexer, TerminateSuccess)
{
mSourceBuffer->Append(mData, sizeof(mData));
mSourceBuffer->Complete(NS_OK);
// Test that Terminate is "sticky".
SourceBufferIterator iterator = mSourceBuffer->Iterator();
LexerResult result =
mLexer.Lex(iterator, mExpectNoResume,
[&](TestState aState, const char* aData, size_t aLength) {
EXPECT_TRUE(aState == TestState::ONE);
return Transition::TerminateSuccess();
});
EXPECT_TRUE(result.is<TerminalState>());
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
SourceBufferIterator iterator2 = mSourceBuffer->Iterator();
result =
mLexer.Lex(iterator2, mExpectNoResume,
[&](TestState aState, const char* aData, size_t aLength) {
EXPECT_TRUE(false); // Shouldn't get here.
return Transition::TerminateFailure();
});
EXPECT_TRUE(result.is<TerminalState>());
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
}
TEST_F(ImageStreamingLexer, TerminateFailure)
{
mSourceBuffer->Append(mData, sizeof(mData));
mSourceBuffer->Complete(NS_OK);
// Test that Terminate is "sticky".
SourceBufferIterator iterator = mSourceBuffer->Iterator();
LexerResult result =
mLexer.Lex(iterator, mExpectNoResume,
[&](TestState aState, const char* aData, size_t aLength) {
EXPECT_TRUE(aState == TestState::ONE);
return Transition::TerminateFailure();
});
EXPECT_TRUE(result.is<TerminalState>());
EXPECT_EQ(TerminalState::FAILURE, result.as<TerminalState>());
SourceBufferIterator iterator2 = mSourceBuffer->Iterator();
result =
mLexer.Lex(iterator2, mExpectNoResume,
[&](TestState aState, const char* aData, size_t aLength) {
EXPECT_TRUE(false); // Shouldn't get here.
return Transition::TerminateFailure();
});
EXPECT_TRUE(result.is<TerminalState>());
EXPECT_EQ(TerminalState::FAILURE, result.as<TerminalState>());
}
TEST_F(ImageStreamingLexer, TerminateUnbuffered)
{
// Test that Terminate works during an unbuffered read.
for (unsigned i = 0; i < 9; ++i) {
mSourceBuffer->Append(mData + i, 1);
LexerResult result =
mLexer.Lex(mIterator, mCountResumes, DoLexWithUnbufferedTerminate);
if (i > 2) {
EXPECT_TRUE(result.is<TerminalState>());
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
} else {
EXPECT_TRUE(result.is<Yield>());
EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>());
}
}
// We expect 3 resumes because TestState::ONE consumes 3 bytes and then
// transitions to TestState::UNBUFFERED, which calls TerminateSuccess() as
// soon as it receives a single byte. That's four bytes total, which are
// delivered one at a time, requiring 3 resumes.
EXPECT_EQ(3u, mCountResumes->Count());
mSourceBuffer->Complete(NS_OK);
}
TEST_F(ImageStreamingLexer, TerminateAfterYield)
{
// Test that Terminate works after yielding.
for (unsigned i = 0; i < 9; ++i) {
mSourceBuffer->Append(mData + i, 1);
LexerResult result =
mLexer.Lex(mIterator, mCountResumes, DoLexWithTerminateAfterYield);
if (i > 2) {
EXPECT_TRUE(result.is<TerminalState>());
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
} else if (i == 2) {
EXPECT_TRUE(result.is<Yield>());
EXPECT_EQ(Yield::OUTPUT_AVAILABLE, result.as<Yield>());
} else {
EXPECT_TRUE(result.is<Yield>());
EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>());
}
}
// We expect 2 resumes because TestState::ONE consumes 3 bytes and then
// yields. When the lexer resumes at TestState::TWO, which receives the same 3
// bytes, TerminateSuccess() gets called immediately. That's three bytes
// total, which are delivered one at a time, requiring 2 resumes.
EXPECT_EQ(2u, mCountResumes->Count());
mSourceBuffer->Complete(NS_OK);
}
TEST_F(ImageStreamingLexer, SourceBufferImmediateComplete)
{
// Test calling SourceBuffer::Complete() without appending any data. This
// causes the SourceBuffer to automatically have a failing completion status,
// no matter what you pass, so we expect TerminalState::FAILURE below.
mSourceBuffer->Complete(NS_OK);
LexerResult result = mLexer.Lex(mIterator, mExpectNoResume, DoLex);
EXPECT_TRUE(result.is<TerminalState>());
EXPECT_EQ(TerminalState::FAILURE, result.as<TerminalState>());
}
TEST_F(ImageStreamingLexer, SourceBufferTruncatedTerminalStateSuccess)
{
// Test that using a terminal state (in this case TerminalState::SUCCESS) as a
// truncated state works.
StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 3),
Transition::TerminateSuccess());
CheckTruncatedState(lexer, TerminalState::SUCCESS);
}
TEST_F(ImageStreamingLexer, SourceBufferTruncatedTerminalStateFailure)
{
// Test that using a terminal state (in this case TerminalState::FAILURE) as a
// truncated state works.
StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 3),
Transition::TerminateFailure());
CheckTruncatedState(lexer, TerminalState::FAILURE);
}
TEST_F(ImageStreamingLexer, SourceBufferTruncatedStateReturningSuccess)
{
// Test that a truncated state that returns TerminalState::SUCCESS works. When
// |lexer| discovers that the data is truncated, it invokes the
// TRUNCATED_SUCCESS state, which returns TerminalState::SUCCESS.
// CheckTruncatedState() verifies that this happens.
StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 3),
Transition::To(TestState::TRUNCATED_SUCCESS, 0));
CheckTruncatedState(lexer, TerminalState::SUCCESS);
}
TEST_F(ImageStreamingLexer, SourceBufferTruncatedStateReturningFailure)
{
// Test that a truncated state that returns TerminalState::FAILURE works. When
// |lexer| discovers that the data is truncated, it invokes the
// TRUNCATED_FAILURE state, which returns TerminalState::FAILURE.
// CheckTruncatedState() verifies that this happens.
StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 3),
Transition::To(TestState::TRUNCATED_FAILURE, 0));
CheckTruncatedState(lexer, TerminalState::FAILURE);
}
TEST_F(ImageStreamingLexer, SourceBufferTruncatedFailingCompleteStatus)
{
// Test that calling SourceBuffer::Complete() with a failing status results in
// an immediate TerminalState::FAILURE result. (Note that |lexer|'s truncated
// state is TerminalState::SUCCESS, so if we ignore the failing status, the
// test will fail.)
StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 3),
Transition::TerminateSuccess());
CheckTruncatedState(lexer, TerminalState::FAILURE, NS_ERROR_FAILURE);
}
TEST_F(ImageStreamingLexer, NoSourceBufferResumable)
{
// Test delivering in one byte chunks with no IResumable.
for (unsigned i = 0; i < 9; ++i) {
mSourceBuffer->Append(mData + i, 1);
LexerResult result = mLexer.Lex(mIterator, nullptr, DoLex);
if (i == 8) {
EXPECT_TRUE(result.is<TerminalState>());
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
} else {
EXPECT_TRUE(result.is<Yield>());
EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>());
}
}
mSourceBuffer->Complete(NS_OK);
}

View file

@ -0,0 +1,508 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */
#include "gtest/gtest.h"
#include "mozilla/gfx/2D.h"
#include "Common.h"
#include "Decoder.h"
#include "DecoderFactory.h"
#include "SourceBuffer.h"
#include "SurfacePipe.h"
using namespace mozilla;
using namespace mozilla::gfx;
using namespace mozilla::image;
namespace mozilla {
namespace image {
class TestSurfacePipeFactory
{
public:
static SurfacePipe SimpleSurfacePipe()
{
SurfacePipe pipe;
return Move(pipe);
}
template <typename T>
static SurfacePipe SurfacePipeFromPipeline(T&& aPipeline)
{
return SurfacePipe { Move(aPipeline) };
}
private:
TestSurfacePipeFactory() { }
};
} // namespace image
} // namespace mozilla
void
CheckSurfacePipeMethodResults(SurfacePipe* aPipe,
Decoder* aDecoder,
const IntRect& aRect = IntRect(0, 0, 100, 100))
{
// Check that the pipeline ended up in the state we expect. Note that we're
// explicitly testing the SurfacePipe versions of these methods, so we don't
// want to use AssertCorrectPipelineFinalState() here.
EXPECT_TRUE(aPipe->IsSurfaceFinished());
Maybe<SurfaceInvalidRect> invalidRect = aPipe->TakeInvalidRect();
EXPECT_TRUE(invalidRect.isSome());
EXPECT_EQ(IntRect(0, 0, 100, 100), invalidRect->mInputSpaceRect);
EXPECT_EQ(IntRect(0, 0, 100, 100), invalidRect->mOutputSpaceRect);
// Check the generated image.
CheckGeneratedImage(aDecoder, aRect);
// Reset and clear the image before the next test.
aPipe->ResetToFirstRow();
EXPECT_FALSE(aPipe->IsSurfaceFinished());
invalidRect = aPipe->TakeInvalidRect();
EXPECT_TRUE(invalidRect.isNothing());
uint32_t count = 0;
auto result = aPipe->WritePixels<uint32_t>([&]() {
++count;
return AsVariant(BGRAColor::Transparent().AsPixel());
});
EXPECT_EQ(WriteState::FINISHED, result);
EXPECT_EQ(100u * 100u, count);
EXPECT_TRUE(aPipe->IsSurfaceFinished());
invalidRect = aPipe->TakeInvalidRect();
EXPECT_TRUE(invalidRect.isSome());
EXPECT_EQ(IntRect(0, 0, 100, 100), invalidRect->mInputSpaceRect);
EXPECT_EQ(IntRect(0, 0, 100, 100), invalidRect->mOutputSpaceRect);
aPipe->ResetToFirstRow();
EXPECT_FALSE(aPipe->IsSurfaceFinished());
invalidRect = aPipe->TakeInvalidRect();
EXPECT_TRUE(invalidRect.isNothing());
}
void
CheckPalettedSurfacePipeMethodResults(SurfacePipe* aPipe,
Decoder* aDecoder,
const IntRect& aRect
= IntRect(0, 0, 100, 100))
{
// Check that the pipeline ended up in the state we expect. Note that we're
// explicitly testing the SurfacePipe versions of these methods, so we don't
// want to use AssertCorrectPipelineFinalState() here.
EXPECT_TRUE(aPipe->IsSurfaceFinished());
Maybe<SurfaceInvalidRect> invalidRect = aPipe->TakeInvalidRect();
EXPECT_TRUE(invalidRect.isSome());
EXPECT_EQ(IntRect(0, 0, 100, 100), invalidRect->mInputSpaceRect);
EXPECT_EQ(IntRect(0, 0, 100, 100), invalidRect->mOutputSpaceRect);
// Check the generated image.
CheckGeneratedPalettedImage(aDecoder, aRect);
// Reset and clear the image before the next test.
aPipe->ResetToFirstRow();
EXPECT_FALSE(aPipe->IsSurfaceFinished());
invalidRect = aPipe->TakeInvalidRect();
EXPECT_TRUE(invalidRect.isNothing());
uint32_t count = 0;
auto result = aPipe->WritePixels<uint8_t>([&]() {
++count;
return AsVariant(uint8_t(0));
});
EXPECT_EQ(WriteState::FINISHED, result);
EXPECT_EQ(100u * 100u, count);
EXPECT_TRUE(aPipe->IsSurfaceFinished());
invalidRect = aPipe->TakeInvalidRect();
EXPECT_TRUE(invalidRect.isSome());
EXPECT_EQ(IntRect(0, 0, 100, 100), invalidRect->mInputSpaceRect);
EXPECT_EQ(IntRect(0, 0, 100, 100), invalidRect->mOutputSpaceRect);
aPipe->ResetToFirstRow();
EXPECT_FALSE(aPipe->IsSurfaceFinished());
invalidRect = aPipe->TakeInvalidRect();
EXPECT_TRUE(invalidRect.isNothing());
}
class ImageSurfacePipeIntegration : public ::testing::Test
{
protected:
AutoInitializeImageLib mInit;
};
TEST_F(ImageSurfacePipeIntegration, SurfacePipe)
{
// Test that SurfacePipe objects can be initialized and move constructed.
SurfacePipe pipe = TestSurfacePipeFactory::SimpleSurfacePipe();
// Test that SurfacePipe objects can be move assigned.
pipe = TestSurfacePipeFactory::SimpleSurfacePipe();
// Test that SurfacePipe objects can be initialized with a pipeline.
RefPtr<Decoder> decoder = CreateTrivialDecoder();
ASSERT_TRUE(decoder != nullptr);
auto sink = MakeUnique<SurfaceSink>();
nsresult rv =
sink->Configure(SurfaceConfig { decoder, 0, IntSize(100, 100),
SurfaceFormat::B8G8R8A8, false });
ASSERT_TRUE(NS_SUCCEEDED(rv));
pipe = TestSurfacePipeFactory::SurfacePipeFromPipeline(sink);
// Test that WritePixels() gets passed through to the underlying pipeline.
{
uint32_t count = 0;
auto result = pipe.WritePixels<uint32_t>([&]() {
++count;
return AsVariant(BGRAColor::Green().AsPixel());
});
EXPECT_EQ(WriteState::FINISHED, result);
EXPECT_EQ(100u * 100u, count);
CheckSurfacePipeMethodResults(&pipe, decoder);
}
// Create a buffer the same size as one row of the surface, containing all
// green pixels. We'll use this for the WriteBuffer() tests.
uint32_t buffer[100];
for (int i = 0; i < 100; ++i) {
buffer[i] = BGRAColor::Green().AsPixel();
}
// Test that WriteBuffer() gets passed through to the underlying pipeline.
{
uint32_t count = 0;
WriteState result = WriteState::NEED_MORE_DATA;
while (result == WriteState::NEED_MORE_DATA) {
result = pipe.WriteBuffer(buffer);
++count;
}
EXPECT_EQ(WriteState::FINISHED, result);
EXPECT_EQ(100u, count);
CheckSurfacePipeMethodResults(&pipe, decoder);
}
// Test that the 3 argument version of WriteBuffer() gets passed through to
// the underlying pipeline.
{
uint32_t count = 0;
WriteState result = WriteState::NEED_MORE_DATA;
while (result == WriteState::NEED_MORE_DATA) {
result = pipe.WriteBuffer(buffer, 0, 100);
++count;
}
EXPECT_EQ(WriteState::FINISHED, result);
EXPECT_EQ(100u, count);
CheckSurfacePipeMethodResults(&pipe, decoder);
}
// Test that WriteEmptyRow() gets passed through to the underlying pipeline.
{
uint32_t count = 0;
WriteState result = WriteState::NEED_MORE_DATA;
while (result == WriteState::NEED_MORE_DATA) {
result = pipe.WriteEmptyRow();
++count;
}
EXPECT_EQ(WriteState::FINISHED, result);
EXPECT_EQ(100u, count);
CheckSurfacePipeMethodResults(&pipe, decoder, IntRect(0, 0, 0, 0));
}
// Mark the frame as finished so we don't get an assertion.
RawAccessFrameRef currentFrame = decoder->GetCurrentFrameRef();
currentFrame->Finish();
}
TEST_F(ImageSurfacePipeIntegration, PalettedSurfacePipe)
{
// Create a SurfacePipe containing a PalettedSurfaceSink.
RefPtr<Decoder> decoder = CreateTrivialDecoder();
ASSERT_TRUE(decoder != nullptr);
auto sink = MakeUnique<PalettedSurfaceSink>();
nsresult rv =
sink->Configure(PalettedSurfaceConfig { decoder, 0, IntSize(100, 100),
IntRect(0, 0, 100, 100),
SurfaceFormat::B8G8R8A8,
8, false });
ASSERT_TRUE(NS_SUCCEEDED(rv));
SurfacePipe pipe = TestSurfacePipeFactory::SurfacePipeFromPipeline(sink);
// Test that WritePixels() gets passed through to the underlying pipeline.
{
uint32_t count = 0;
auto result = pipe.WritePixels<uint8_t>([&]() {
++count;
return AsVariant(uint8_t(255));
});
EXPECT_EQ(WriteState::FINISHED, result);
EXPECT_EQ(100u * 100u, count);
CheckPalettedSurfacePipeMethodResults(&pipe, decoder);
}
// Create a buffer the same size as one row of the surface, containing all
// 255 pixels. We'll use this for the WriteBuffer() tests.
uint8_t buffer[100];
for (int i = 0; i < 100; ++i) {
buffer[i] = 255;
}
// Test that WriteBuffer() gets passed through to the underlying pipeline.
{
uint32_t count = 0;
WriteState result = WriteState::NEED_MORE_DATA;
while (result == WriteState::NEED_MORE_DATA) {
result = pipe.WriteBuffer(buffer);
++count;
}
EXPECT_EQ(WriteState::FINISHED, result);
EXPECT_EQ(100u, count);
CheckPalettedSurfacePipeMethodResults(&pipe, decoder);
}
// Test that the 3 argument version of WriteBuffer() gets passed through to
// the underlying pipeline.
{
uint32_t count = 0;
WriteState result = WriteState::NEED_MORE_DATA;
while (result == WriteState::NEED_MORE_DATA) {
result = pipe.WriteBuffer(buffer, 0, 100);
++count;
}
EXPECT_EQ(WriteState::FINISHED, result);
EXPECT_EQ(100u, count);
CheckPalettedSurfacePipeMethodResults(&pipe, decoder);
}
// Test that WriteEmptyRow() gets passed through to the underlying pipeline.
{
uint32_t count = 0;
WriteState result = WriteState::NEED_MORE_DATA;
while (result == WriteState::NEED_MORE_DATA) {
result = pipe.WriteEmptyRow();
++count;
}
EXPECT_EQ(WriteState::FINISHED, result);
EXPECT_EQ(100u, count);
CheckPalettedSurfacePipeMethodResults(&pipe, decoder, IntRect(0, 0, 0, 0));
}
// Mark the frame as finished so we don't get an assertion.
RawAccessFrameRef currentFrame = decoder->GetCurrentFrameRef();
currentFrame->Finish();
}
TEST_F(ImageSurfacePipeIntegration, DeinterlaceDownscaleWritePixels)
{
RefPtr<Decoder> decoder = CreateTrivialDecoder();
ASSERT_TRUE(decoder != nullptr);
auto test = [](Decoder* aDecoder, SurfaceFilter* aFilter) {
CheckWritePixels(aDecoder, aFilter,
/* aOutputRect = */ Some(IntRect(0, 0, 25, 25)));
};
WithFilterPipeline(decoder, test,
DeinterlacingConfig<uint32_t> { /* mProgressiveDisplay = */ true },
DownscalingConfig { IntSize(100, 100),
SurfaceFormat::B8G8R8A8 },
SurfaceConfig { decoder, 0, IntSize(25, 25),
SurfaceFormat::B8G8R8A8, false });
}
TEST_F(ImageSurfacePipeIntegration, RemoveFrameRectBottomRightDownscaleWritePixels)
{
// This test case uses a frame rect that extends beyond the borders of the
// image to the bottom and to the right. It looks roughly like this (with the
// box made of '#'s representing the frame rect):
//
// +------------+
// + +
// + +------------+
// + +############+
// +------+############+
// +############+
// +------------+
RefPtr<Decoder> decoder = CreateTrivialDecoder();
ASSERT_TRUE(decoder != nullptr);
// Note that aInputWriteRect is 100x50 because RemoveFrameRectFilter ignores
// trailing rows that don't show up in the output. (Leading rows unfortunately
// can't be ignored.) So the action of the pipeline is as follows:
//
// (1) RemoveFrameRectFilter reads a 100x50 region of the input.
// (aInputWriteRect captures this fact.) The remaining 50 rows are ignored
// because they extend off the bottom of the image due to the frame rect's
// (50, 50) offset. The 50 columns on the right also don't end up in the
// output, so ultimately only a 50x50 region in the output contains data
// from the input. The filter's output is not 50x50, though, but 100x100,
// because what RemoveFrameRectFilter does is introduce blank rows or
// columns as necessary to transform an image that needs a frame rect into
// an image that doesn't.
//
// (2) DownscalingFilter reads the output of RemoveFrameRectFilter (100x100)
// and downscales it to 20x20.
//
// (3) The surface owned by SurfaceSink logically has only a 10x10 region
// region in it that's non-blank; this is the downscaled version of the
// 50x50 region discussed in (1). (aOutputWriteRect captures this fact.)
// Some fuzz, as usual, is necessary when dealing with Lanczos downscaling.
auto test = [](Decoder* aDecoder, SurfaceFilter* aFilter) {
CheckWritePixels(aDecoder, aFilter,
/* aOutputRect = */ Some(IntRect(0, 0, 20, 20)),
/* aInputRect = */ Some(IntRect(0, 0, 100, 100)),
/* aInputWriteRect = */ Some(IntRect(50, 50, 100, 50)),
/* aOutputWriteRect = */ Some(IntRect(10, 10, 10, 10)),
/* aFuzz = */ 0x33);
};
WithFilterPipeline(decoder, test,
RemoveFrameRectConfig { IntRect(50, 50, 100, 100) },
DownscalingConfig { IntSize(100, 100),
SurfaceFormat::B8G8R8A8 },
SurfaceConfig { decoder, 0, IntSize(20, 20),
SurfaceFormat::B8G8R8A8, false });
}
TEST_F(ImageSurfacePipeIntegration, RemoveFrameRectTopLeftDownscaleWritePixels)
{
// This test case uses a frame rect that extends beyond the borders of the
// image to the top and to the left. It looks roughly like this (with the
// box made of '#'s representing the frame rect):
//
// +------------+
// +############+
// +############+------+
// +############+ +
// +------------+ +
// + +
// +------------+
RefPtr<Decoder> decoder = CreateTrivialDecoder();
ASSERT_TRUE(decoder != nullptr);
auto test = [](Decoder* aDecoder, SurfaceFilter* aFilter) {
CheckWritePixels(aDecoder, aFilter,
/* aOutputRect = */ Some(IntRect(0, 0, 20, 20)),
/* aInputRect = */ Some(IntRect(0, 0, 100, 100)),
/* aInputWriteRect = */ Some(IntRect(0, 0, 100, 100)),
/* aOutputWriteRect = */ Some(IntRect(0, 0, 10, 10)),
/* aFuzz = */ 0x21);
};
WithFilterPipeline(decoder, test,
RemoveFrameRectConfig { IntRect(-50, -50, 100, 100) },
DownscalingConfig { IntSize(100, 100),
SurfaceFormat::B8G8R8A8 },
SurfaceConfig { decoder, 0, IntSize(20, 20),
SurfaceFormat::B8G8R8A8, false });
}
TEST_F(ImageSurfacePipeIntegration, DeinterlaceRemoveFrameRectWritePixels)
{
RefPtr<Decoder> decoder = CreateTrivialDecoder();
ASSERT_TRUE(decoder != nullptr);
// Note that aInputRect is the full 100x100 size even though
// RemoveFrameRectFilter is part of this pipeline, because deinterlacing
// requires reading every row.
auto test = [](Decoder* aDecoder, SurfaceFilter* aFilter) {
CheckWritePixels(aDecoder, aFilter,
/* aOutputRect = */ Some(IntRect(0, 0, 100, 100)),
/* aInputRect = */ Some(IntRect(0, 0, 100, 100)),
/* aInputWriteRect = */ Some(IntRect(50, 50, 100, 100)),
/* aOutputWriteRect = */ Some(IntRect(50, 50, 50, 50)));
};
WithFilterPipeline(decoder, test,
DeinterlacingConfig<uint32_t> { /* mProgressiveDisplay = */ true },
RemoveFrameRectConfig { IntRect(50, 50, 100, 100) },
SurfaceConfig { decoder, 0, IntSize(100, 100),
SurfaceFormat::B8G8R8A8, false });
}
TEST_F(ImageSurfacePipeIntegration, DeinterlaceRemoveFrameRectDownscaleWritePixels)
{
RefPtr<Decoder> decoder = CreateTrivialDecoder();
ASSERT_TRUE(decoder != nullptr);
auto test = [](Decoder* aDecoder, SurfaceFilter* aFilter) {
CheckWritePixels(aDecoder, aFilter,
/* aOutputRect = */ Some(IntRect(0, 0, 20, 20)),
/* aInputRect = */ Some(IntRect(0, 0, 100, 100)),
/* aInputWriteRect = */ Some(IntRect(50, 50, 100, 100)),
/* aOutputWriteRect = */ Some(IntRect(10, 10, 10, 10)),
/* aFuzz = */ 33);
};
WithFilterPipeline(decoder, test,
DeinterlacingConfig<uint32_t> { /* mProgressiveDisplay = */ true },
RemoveFrameRectConfig { IntRect(50, 50, 100, 100) },
DownscalingConfig { IntSize(100, 100),
SurfaceFormat::B8G8R8A8 },
SurfaceConfig { decoder, 0, IntSize(20, 20),
SurfaceFormat::B8G8R8A8, false });
}
TEST_F(ImageSurfacePipeIntegration, ConfiguringPalettedRemoveFrameRectDownscaleFails)
{
RefPtr<Decoder> decoder = CreateTrivialDecoder();
ASSERT_TRUE(decoder != nullptr);
// This is an invalid pipeline for paletted images, so configuration should
// fail.
AssertConfiguringPipelineFails(decoder,
RemoveFrameRectConfig { IntRect(0, 0, 50, 50) },
DownscalingConfig { IntSize(100, 100),
SurfaceFormat::B8G8R8A8 },
PalettedSurfaceConfig { decoder, 0, IntSize(100, 100),
IntRect(0, 0, 50, 50),
SurfaceFormat::B8G8R8A8, 8,
false });
}
TEST_F(ImageSurfacePipeIntegration, ConfiguringPalettedDeinterlaceDownscaleFails)
{
RefPtr<Decoder> decoder = CreateTrivialDecoder();
ASSERT_TRUE(decoder != nullptr);
// This is an invalid pipeline for paletted images, so configuration should
// fail.
AssertConfiguringPipelineFails(decoder,
DeinterlacingConfig<uint8_t> { /* mProgressiveDisplay = */ true},
DownscalingConfig { IntSize(100, 100),
SurfaceFormat::B8G8R8A8 },
PalettedSurfaceConfig { decoder, 0, IntSize(100, 100),
IntRect(0, 0, 20, 20),
SurfaceFormat::B8G8R8A8, 8,
false });
}
TEST_F(ImageSurfacePipeIntegration, ConfiguringHugeDeinterlacingBufferFails)
{
RefPtr<Decoder> decoder = CreateTrivialDecoder();
ASSERT_TRUE(decoder != nullptr);
// When DownscalingFilter is used, we may succeed in allocating an output
// surface for huge images, because we only need to store the scaled-down
// version of the image. However, regardless of downscaling,
// DeinterlacingFilter needs to allocate a buffer as large as the size of the
// input. This can cause OOMs on operating systems that allow overcommit. This
// test makes sure that we reject such allocations.
AssertConfiguringPipelineFails(decoder,
DeinterlacingConfig<uint32_t> { /* mProgressiveDisplay = */ true},
DownscalingConfig { IntSize(60000, 60000),
SurfaceFormat::B8G8R8A8 },
SurfaceConfig { decoder, 0, IntSize(600, 600),
SurfaceFormat::B8G8R8A8, false });
}

File diff suppressed because it is too large Load diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 434 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,015 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 317 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 B

BIN
image/test/gtest/green.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
image/test/gtest/green.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 B

BIN
image/test/gtest/green.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

BIN
image/test/gtest/green.icon Normal file

Binary file not shown.

BIN
image/test/gtest/green.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 B

BIN
image/test/gtest/green.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 B

View file

@ -0,0 +1,78 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
Library('imagetest')
UNIFIED_SOURCES = [
'Common.cpp',
'TestADAM7InterpolatingFilter.cpp',
'TestCopyOnWrite.cpp',
'TestDecoders.cpp',
'TestDecodeToSurface.cpp',
'TestDeinterlacingFilter.cpp',
'TestMetadata.cpp',
'TestRemoveFrameRectFilter.cpp',
'TestSourceBuffer.cpp',
'TestStreamingLexer.cpp',
'TestSurfaceSink.cpp',
]
if CONFIG['MOZ_ENABLE_SKIA']:
UNIFIED_SOURCES += [
'TestDownscalingFilter.cpp',
'TestSurfacePipeIntegration.cpp',
]
SOURCES += [
# Can't be unified because it manipulates the preprocessor environment.
'TestDownscalingFilterNoSkia.cpp',
]
TEST_HARNESS_FILES.gtest += [
'animated-with-extra-image-sub-blocks.gif',
'corrupt-with-bad-bmp-height.ico',
'corrupt-with-bad-bmp-width.ico',
'corrupt.jpg',
'downscaled.bmp',
'downscaled.gif',
'downscaled.ico',
'downscaled.icon',
'downscaled.jpg',
'downscaled.png',
'first-frame-green.gif',
'first-frame-green.png',
'first-frame-padding.gif',
'green-1x1-truncated.gif',
'green.bmp',
'green.gif',
'green.ico',
'green.icon',
'green.jpg',
'green.png',
'invalid-truncated-metadata.bmp',
'no-frame-delay.gif',
'rle4.bmp',
'rle8.bmp',
'transparent-ico-with-and-mask.ico',
'transparent-if-within-ico.bmp',
'transparent.gif',
'transparent.png',
]
include('/ipc/chromium/chromium-config.mozbuild')
LOCAL_INCLUDES += [
'/dom/base',
'/gfx/2d',
'/image',
]
LOCAL_INCLUDES += CONFIG['SKIA_INCLUDES']
FINAL_LIBRARY = 'xul-gtest'
if CONFIG['GNU_CXX']:
CXXFLAGS += ['-Wno-error=shadow']

Binary file not shown.

After

Width:  |  Height:  |  Size: 317 B

BIN
image/test/gtest/rle4.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

BIN
image/test/gtest/rle8.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 355 B

Some files were not shown because too many files have changed in this diff Show more