mirror of
https://github.com/roytam1/palemoon27.git
synced 2026-05-26 13:34:03 +00:00
import change from rmottola/Arctic-Fox:
- Bug 1135100 - Don't update GC thing pointers that haven't changed after marking r=terrence (0df3ea820) - Bug 1135857 - Remove ContentClientIncremental. r=mattwoodrow (059587352) - Bug 1135809 - add apz. prefs to about:support. r=kats (6439aaf6b) - Bug 1135361 - Fix position of ruby annotation in vertical-rl mode when justification is applied to the base. r=jfkthame (a00bb53be) - Bug 1133288 - Remove nonstandard expression closures from editor. r=ehsan (605992184) - Bug 1135361 - Reftest for ruby positioning in justified vertical text. r=xidorn (60fe87ae3) - Bug 1135984 - Fix typo which made Context.__init__ set the unused exe (312c35ef2) - Bug 1077864, Part 1: Check consistency of certificates' signature and signatureAlgorithm fields, r=keeler (9a11f90c3) - Bug 1077864, Part 2: Override the trust level for OCSP response signer certs so that they are never considered trust anchors, r=keeler (c46772e6d) - Bug 1077864, Part 3: update nsserrors.properties so error message gets localized. (935233549) - Bug 1135407: Factor out duplicate logic in tests, r=keeler (383ff80c5) - Bug 1131767: Prune away paths using unacceptable algorithms earlier, r=keeler (55182b7e2) - Followup to Bug 1135563 - uiUnsupportedAlreadyNotified.js doesn't use httpd.js. r=me (cef9dbdcd) - Bug 1135563 - Fix several javascript warnings for xpcshell app update tests and cleanup style. r=spohl (6330eb78c) - Bug 1123019 - In DrawTargetTiled::StrokeRect and StrokeLine, skip tiles that don't intersect the stroke. r=jrmuizel (71afc7653) - Bug 1123019 - Shrink clipped stroked rectangles and stroked lines. r=jrmuizel (17e93d70f) - Bug 1123019 - Actually use the clipped rect variable. r=jrmuizel (29c96ab43)
This commit is contained in:
@@ -19,8 +19,8 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=519928
|
||||
|
||||
var iframe = document.getElementById("load-frame");
|
||||
|
||||
function enableJS() allowJS(true, iframe);
|
||||
function disableJS() allowJS(false, iframe);
|
||||
function enableJS() { allowJS(true, iframe); }
|
||||
function disableJS() { allowJS(false, iframe); }
|
||||
function allowJS(allow, frame) {
|
||||
SpecialPowers.wrap(frame.contentWindow)
|
||||
.QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
|
||||
@@ -28,6 +28,7 @@ function allowJS(allow, frame) {
|
||||
.QueryInterface(SpecialPowers.Ci.nsIDocShell)
|
||||
.allowJavascript = allow;
|
||||
}
|
||||
|
||||
function expectJSAllowed(allowed, testCondition, callback) {
|
||||
window.ICanRunMyJS = false;
|
||||
var self_ = window;
|
||||
@@ -49,8 +50,8 @@ function expectJSAllowed(allowed, testCondition, callback) {
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
addLoadEvent(function() {
|
||||
var enterDesignMode = function() document.designMode = "on";
|
||||
var leaveDesignMode = function() document.designMode = "off";
|
||||
var enterDesignMode = function() { document.designMode = "on"; };
|
||||
var leaveDesignMode = function() { document.designMode = "off"; };
|
||||
expectJSAllowed(false, disableJS, function() {
|
||||
expectJSAllowed(true, enableJS, function() {
|
||||
expectJSAllowed(true, enterDesignMode, function() {
|
||||
@@ -59,8 +60,8 @@ addLoadEvent(function() {
|
||||
expectJSAllowed(false, enterDesignMode, function() {
|
||||
expectJSAllowed(false, leaveDesignMode, function() {
|
||||
expectJSAllowed(true, enableJS, function() {
|
||||
enterDesignMode = function() iframe.contentDocument.designMode = "on";
|
||||
leaveDesignMode = function() iframe.contentDocument.designMode = "off";
|
||||
enterDesignMode = function() { iframe.contentDocument.designMode = "on"; };
|
||||
leaveDesignMode = function() { iframe.contentDocument.designMode = "off"; };
|
||||
expectJSAllowed(false, disableJS, function() {
|
||||
expectJSAllowed(true, enableJS, function() {
|
||||
expectJSAllowed(true, enterDesignMode, function() {
|
||||
@@ -120,4 +121,3 @@ function testDocumentDisabledJS() {
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ function copyCF_HTML(cfhtml, success, failure) {
|
||||
|
||||
var flavors = [CF_HTML];
|
||||
if (!cb.hasDataMatchingFlavors(flavors, flavors.length, cb.kGlobalClipboard)) {
|
||||
setTimeout(function() copyCF_HTML_worker(success, failure), 100);
|
||||
setTimeout(function() { copyCF_HTML_worker(success, failure); }, 100);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ function copyCF_HTML(cfhtml, success, failure) {
|
||||
trans.getTransferData(CF_HTML, data, {});
|
||||
data = SpecialPowers.wrap(data).value.QueryInterface(Ci.nsISupportsCString).data;
|
||||
} catch (e) {
|
||||
setTimeout(function() copyCF_HTML_worker(success, failure), 100);
|
||||
setTimeout(function() { copyCF_HTML_worker(success, failure); }, 100);
|
||||
return;
|
||||
}
|
||||
success();
|
||||
|
||||
@@ -119,175 +119,175 @@ var tests = [
|
||||
isIFrame: true,
|
||||
payload: dataPayload,
|
||||
iframeCount: 0,
|
||||
rootElement: function() document.getElementById("a").contentDocument.documentElement
|
||||
rootElement() { return document.getElementById("a").contentDocument.documentElement; },
|
||||
},
|
||||
{
|
||||
id: "b",
|
||||
isIFrame: true,
|
||||
payload: jsPayload,
|
||||
iframeCount: 0,
|
||||
rootElement: function() document.getElementById("b").contentDocument.documentElement
|
||||
rootElement() { return document.getElementById("b").contentDocument.documentElement; },
|
||||
},
|
||||
{
|
||||
id: "c",
|
||||
isIFrame: true,
|
||||
payload: httpPayload,
|
||||
iframeCount: 0,
|
||||
rootElement: function() document.getElementById("c").contentDocument.documentElement
|
||||
rootElement() { return document.getElementById("c").contentDocument.documentElement; },
|
||||
},
|
||||
{
|
||||
id: "g",
|
||||
isIFrame: true,
|
||||
payload: scriptPayload,
|
||||
rootElement: function() document.getElementById("g").contentDocument.documentElement,
|
||||
rootElement() { return document.getElementById("g").contentDocument.documentElement; },
|
||||
iframeCount: 0
|
||||
},
|
||||
{
|
||||
id: "h",
|
||||
isIFrame: true,
|
||||
payload: scriptExternalPayload,
|
||||
rootElement: function() document.getElementById("h").contentDocument.documentElement,
|
||||
rootElement() { return document.getElementById("h").contentDocument.documentElement; },
|
||||
iframeCount: 0
|
||||
},
|
||||
{
|
||||
id: "d",
|
||||
payload: dataPayload,
|
||||
iframeCount: 0,
|
||||
rootElement: function() document.getElementById("d")
|
||||
rootElement() { return document.getElementById("d"); },
|
||||
},
|
||||
{
|
||||
id: "e",
|
||||
payload: jsPayload,
|
||||
iframeCount: 0,
|
||||
rootElement: function() document.getElementById("e")
|
||||
rootElement() { return document.getElementById("e"); },
|
||||
},
|
||||
{
|
||||
id: "f",
|
||||
payload: httpPayload,
|
||||
iframeCount: 0,
|
||||
rootElement: function() document.getElementById("f")
|
||||
rootElement() { return document.getElementById("f"); },
|
||||
},
|
||||
{
|
||||
id: "i",
|
||||
payload: scriptPayload,
|
||||
rootElement: function() document.getElementById("i"),
|
||||
rootElement() { return document.getElementById("i"); },
|
||||
iframeCount: 0
|
||||
},
|
||||
{
|
||||
id: "j",
|
||||
payload: scriptExternalPayload,
|
||||
rootElement: function() document.getElementById("j"),
|
||||
rootElement() { return document.getElementById("j"); },
|
||||
iframeCount: 0
|
||||
},
|
||||
{
|
||||
id: "k",
|
||||
isIFrame: true,
|
||||
payload: validStyle1Payload,
|
||||
rootElement: function() document.getElementById("k").contentDocument.documentElement,
|
||||
checkResult: function(html) isnot(html.indexOf("style"), -1, "Should have retained style")
|
||||
rootElement() { return document.getElementById("k").contentDocument.documentElement; },
|
||||
checkResult(html) { isnot(html.indexOf("style"), -1, "Should have retained style"); },
|
||||
},
|
||||
{
|
||||
id: "l",
|
||||
payload: validStyle1Payload,
|
||||
rootElement: function() document.getElementById("l"),
|
||||
checkResult: function(html) isnot(html.indexOf("style"), -1, "Should have retained style")
|
||||
rootElement() { return document.getElementById("l"); },
|
||||
checkResult(html) { isnot(html.indexOf("style"), -1, "Should have retained style"); },
|
||||
},
|
||||
{
|
||||
id: "m",
|
||||
isIFrame: true,
|
||||
payload: validStyle2Payload,
|
||||
rootElement: function() document.getElementById("m").contentDocument.documentElement,
|
||||
checkResult: function(html) isnot(html.indexOf("style"), -1, "Should have retained style")
|
||||
rootElement() { return document.getElementById("m").contentDocument.documentElement; },
|
||||
checkResult(html) { isnot(html.indexOf("style"), -1, "Should have retained style"); },
|
||||
},
|
||||
{
|
||||
id: "n",
|
||||
payload: validStyle2Payload,
|
||||
rootElement: function() document.getElementById("n"),
|
||||
checkResult: function(html) isnot(html.indexOf("style"), -1, "Should have retained style")
|
||||
rootElement() { return document.getElementById("n"); },
|
||||
checkResult(html) { isnot(html.indexOf("style"), -1, "Should have retained style"); },
|
||||
},
|
||||
{
|
||||
id: "o",
|
||||
isIFrame: true,
|
||||
payload: invalidStyle1Payload,
|
||||
rootElement: function() document.getElementById("o").contentDocument.documentElement,
|
||||
checkResult: function(html) is(html.indexOf("binding"), -1, "Should not have retained the binding style")
|
||||
rootElement() { return document.getElementById("o").contentDocument.documentElement; },
|
||||
checkResult(html) { is(html.indexOf("binding"), -1, "Should not have retained the binding style"); },
|
||||
},
|
||||
{
|
||||
id: "p",
|
||||
payload: invalidStyle1Payload,
|
||||
rootElement: function() document.getElementById("p"),
|
||||
checkResult: function(html) is(html.indexOf("binding"), -1, "Should not have retained the binding style")
|
||||
rootElement() { return document.getElementById("p"); },
|
||||
checkResult(html) { is(html.indexOf("binding"), -1, "Should not have retained the binding style"); },
|
||||
},
|
||||
{
|
||||
id: "q",
|
||||
isIFrame: true,
|
||||
payload: invalidStyle2Payload,
|
||||
rootElement: function() document.getElementById("q").contentDocument.documentElement,
|
||||
checkResult: function(html) is(html.indexOf("binding"), -1, "Should not have retained the binding style")
|
||||
rootElement() { return document.getElementById("q").contentDocument.documentElement; },
|
||||
checkResult(html) { is(html.indexOf("binding"), -1, "Should not have retained the binding style"); },
|
||||
},
|
||||
{
|
||||
id: "r",
|
||||
payload: invalidStyle2Payload,
|
||||
rootElement: function() document.getElementById("r"),
|
||||
checkResult: function(html) is(html.indexOf("binding"), -1, "Should not have retained the binding style")
|
||||
rootElement() { return document.getElementById("r"); },
|
||||
checkResult(html) { is(html.indexOf("binding"), -1, "Should not have retained the binding style"); },
|
||||
},
|
||||
{
|
||||
id: "s",
|
||||
isIFrame: true,
|
||||
payload: invalidStyle1Payload,
|
||||
rootElement: function() document.getElementById("s").contentDocument.documentElement,
|
||||
checkResult: function(html) is(html.indexOf("xxx"), -1, "Should not have retained the import style")
|
||||
rootElement() { return document.getElementById("s").contentDocument.documentElement; },
|
||||
checkResult(html) { is(html.indexOf("xxx"), -1, "Should not have retained the import style"); },
|
||||
},
|
||||
{
|
||||
id: "t",
|
||||
payload: invalidStyle1Payload,
|
||||
rootElement: function() document.getElementById("t"),
|
||||
checkResult: function(html) is(html.indexOf("xxx"), -1, "Should not have retained the import style")
|
||||
rootElement() { return document.getElementById("t"); },
|
||||
checkResult(html) { is(html.indexOf("xxx"), -1, "Should not have retained the import style"); },
|
||||
},
|
||||
{
|
||||
id: "u",
|
||||
isIFrame: true,
|
||||
payload: invalidStyle2Payload,
|
||||
rootElement: function() document.getElementById("u").contentDocument.documentElement,
|
||||
checkResult: function(html) is(html.indexOf("xxx"), -1, "Should not have retained the import style")
|
||||
rootElement() { return document.getElementById("u").contentDocument.documentElement; },
|
||||
checkResult(html) { is(html.indexOf("xxx"), -1, "Should not have retained the import style"); },
|
||||
},
|
||||
{
|
||||
id: "v",
|
||||
payload: invalidStyle2Payload,
|
||||
rootElement: function() document.getElementById("v"),
|
||||
checkResult: function(html) is(html.indexOf("xxx"), -1, "Should not have retained the import style")
|
||||
rootElement() { return document.getElementById("v"); },
|
||||
checkResult(html) { is(html.indexOf("xxx"), -1, "Should not have retained the import style"); },
|
||||
},
|
||||
{
|
||||
id: "w",
|
||||
isIFrame: true,
|
||||
payload: validStyle3Payload,
|
||||
rootElement: function() document.getElementById("w").contentDocument.documentElement,
|
||||
checkResult: function(html) isnot(html.indexOf("xxx"), -1, "Should have retained the font-face style")
|
||||
rootElement() { return document.getElementById("w").contentDocument.documentElement; },
|
||||
checkResult(html) { isnot(html.indexOf("xxx"), -1, "Should have retained the font-face style"); },
|
||||
},
|
||||
{
|
||||
id: "x",
|
||||
payload: validStyle3Payload,
|
||||
rootElement: function() document.getElementById("x"),
|
||||
checkResult: function(html) isnot(html.indexOf("xxx"), -1, "Should have retained the font-face style")
|
||||
rootElement() { return document.getElementById("x"); },
|
||||
checkResult(html) { isnot(html.indexOf("xxx"), -1, "Should have retained the font-face style"); },
|
||||
},
|
||||
{
|
||||
id: "y",
|
||||
isIFrame: true,
|
||||
payload: invalidStyle5Payload,
|
||||
rootElement: function() document.getElementById("y").contentDocument.documentElement,
|
||||
checkResult: function(html) isnot(html.indexOf("xxx"), -1, "Should not have retained the font-face style")
|
||||
rootElement() { return document.getElementById("y").contentDocument.documentElement; },
|
||||
checkResult(html) { isnot(html.indexOf("xxx"), -1, "Should not have retained the font-face style"); },
|
||||
},
|
||||
{
|
||||
id: "z",
|
||||
payload: invalidStyle5Payload,
|
||||
rootElement: function() document.getElementById("z"),
|
||||
checkResult: function(html) isnot(html.indexOf("xxx"), -1, "Should not have retained the font-face style")
|
||||
rootElement() { return document.getElementById("z"); },
|
||||
checkResult(html) { isnot(html.indexOf("xxx"), -1, "Should not have retained the font-face style"); },
|
||||
},
|
||||
{
|
||||
id: "aa",
|
||||
isIFrame: true,
|
||||
payload: nestedStylePayload,
|
||||
rootElement: function() document.getElementById("aa").contentDocument.documentElement,
|
||||
rootElement() { return document.getElementById("aa").contentDocument.documentElement; },
|
||||
checkResult: function(html, text) {
|
||||
is(html.indexOf("binding-1"), -1, "Should not have retained the binding-1 style");
|
||||
isnot(text.indexOf("#bar2"), -1, "Should have retained binding-2 as text content");
|
||||
@@ -297,7 +297,7 @@ var tests = [
|
||||
{
|
||||
id: "bb",
|
||||
payload: nestedStylePayload,
|
||||
rootElement: function() document.getElementById("bb"),
|
||||
rootElement() { return document.getElementById("bb"); },
|
||||
checkResult: function(html, text) {
|
||||
is(html.indexOf("binding-1"), -1, "Should not have retained the binding-1 style");
|
||||
isnot(text.indexOf("#bar2"), -1, "Should have retained binding-2 as text content");
|
||||
@@ -308,73 +308,73 @@ var tests = [
|
||||
id: "cc",
|
||||
isIFrame: true,
|
||||
payload: validStyle4Payload,
|
||||
rootElement: function() document.getElementById("cc").contentDocument.documentElement,
|
||||
checkResult: function(html) isnot(html.indexOf("xxx"), -1, "Should have retained the namespace style")
|
||||
rootElement() { return document.getElementById("cc").contentDocument.documentElement; },
|
||||
checkResult(html) { isnot(html.indexOf("xxx"), -1, "Should have retained the namespace style"); },
|
||||
},
|
||||
{
|
||||
id: "dd",
|
||||
payload: validStyle4Payload,
|
||||
rootElement: function() document.getElementById("dd"),
|
||||
checkResult: function(html) isnot(html.indexOf("xxx"), -1, "Should have retained the namespace style")
|
||||
rootElement() { return document.getElementById("dd"); },
|
||||
checkResult(html) { isnot(html.indexOf("xxx"), -1, "Should have retained the namespace style"); },
|
||||
},
|
||||
{
|
||||
id: "ee",
|
||||
isIFrame: true,
|
||||
payload: invalidStyle6Payload,
|
||||
rootElement: function() document.getElementById("ee").contentDocument.documentElement,
|
||||
checkResult: function(html) isnot(html.indexOf("xxx"), -1, "Should not have retained the namespace style")
|
||||
rootElement() { return document.getElementById("ee").contentDocument.documentElement; },
|
||||
checkResult(html) { isnot(html.indexOf("xxx"), -1, "Should not have retained the namespace style"); },
|
||||
},
|
||||
{
|
||||
id: "ff",
|
||||
payload: invalidStyle6Payload,
|
||||
rootElement: function() document.getElementById("ff"),
|
||||
checkResult: function(html) isnot(html.indexOf("xxx"), -1, "Should not have retained the namespace style")
|
||||
rootElement() { return document.getElementById("ff"); },
|
||||
checkResult(html) { isnot(html.indexOf("xxx"), -1, "Should not have retained the namespace style"); },
|
||||
},
|
||||
{
|
||||
id: "gg",
|
||||
isIFrame: true,
|
||||
payload: invalidStyle6Payload,
|
||||
rootElement: function() document.getElementById("gg").contentDocument.documentElement,
|
||||
checkResult: function(html) isnot(html.indexOf("bar"), -1, "Should have retained the src attribute for the image")
|
||||
rootElement() { return document.getElementById("gg").contentDocument.documentElement; },
|
||||
checkResult(html) { isnot(html.indexOf("bar"), -1, "Should have retained the src attribute for the image"); },
|
||||
},
|
||||
{
|
||||
id: "hh",
|
||||
payload: invalidStyle6Payload,
|
||||
rootElement: function() document.getElementById("hh"),
|
||||
checkResult: function(html) isnot(html.indexOf("bar"), -1, "Should have retained the src attribute for the image")
|
||||
rootElement() { return document.getElementById("hh"); },
|
||||
checkResult(html) { isnot(html.indexOf("bar"), -1, "Should have retained the src attribute for the image"); },
|
||||
},
|
||||
{
|
||||
id: "ii",
|
||||
isIFrame: true,
|
||||
payload: invalidStyle6Payload,
|
||||
rootElement: function() document.getElementById("ii").contentDocument.documentElement,
|
||||
checkResult: function(html) isnot(html.indexOf("bar"), -1, "Should have retained the src attribute for the image")
|
||||
rootElement() { return document.getElementById("ii").contentDocument.documentElement; },
|
||||
checkResult(html) { isnot(html.indexOf("bar"), -1, "Should have retained the src attribute for the image"); },
|
||||
},
|
||||
{
|
||||
id: "jj",
|
||||
payload: invalidStyle6Payload,
|
||||
rootElement: function() document.getElementById("jj"),
|
||||
checkResult: function(html) isnot(html.indexOf("bar"), -1, "Should have retained the src attribute for the image")
|
||||
rootElement() { return document.getElementById("jj"); },
|
||||
checkResult(html) { isnot(html.indexOf("bar"), -1, "Should have retained the src attribute for the image"); },
|
||||
},
|
||||
{
|
||||
id: "kk",
|
||||
isIFrame: true,
|
||||
payload: invalidStyle6Payload,
|
||||
rootElement: function() document.getElementById("kk").contentDocument.documentElement,
|
||||
checkResult: function(html) isnot(html.indexOf("bar"), -1, "Should have retained the src attribute for the image")
|
||||
rootElement() { return document.getElementById("kk").contentDocument.documentElement; },
|
||||
checkResult(html) { isnot(html.indexOf("bar"), -1, "Should have retained the src attribute for the image"); },
|
||||
},
|
||||
{
|
||||
id: "ll",
|
||||
payload: invalidStyle6Payload,
|
||||
rootElement: function() document.getElementById("ll"),
|
||||
checkResult: function(html) isnot(html.indexOf("bar"), -1, "Should have retained the src attribute for the image")
|
||||
rootElement() { return document.getElementById("ll"); },
|
||||
checkResult(html) { isnot(html.indexOf("bar"), -1, "Should have retained the src attribute for the image"); },
|
||||
},
|
||||
{
|
||||
id: "mm",
|
||||
isIFrame: true,
|
||||
indirectPaste: true,
|
||||
payload: invalidStyle7Payload,
|
||||
rootElement: function() document.getElementById("mm").contentDocument.documentElement,
|
||||
rootElement() { return document.getElementById("mm").contentDocument.documentElement; },
|
||||
checkResult: function(html) {
|
||||
is(html.indexOf("xxx"), -1, "Should not have retained the title text");
|
||||
isnot(html.indexOf("foo"), -1, "Should have retained the body text");
|
||||
@@ -384,7 +384,7 @@ var tests = [
|
||||
id: "nn",
|
||||
indirectPaste: true,
|
||||
payload: invalidStyle7Payload,
|
||||
rootElement: function() document.getElementById("nn"),
|
||||
rootElement() { return document.getElementById("nn"); },
|
||||
checkResult: function(html) {
|
||||
is(html.indexOf("xxx"), -1, "Should not have retained the title text");
|
||||
isnot(html.indexOf("foo"), -1, "Should have retained the body text");
|
||||
@@ -394,143 +394,143 @@ var tests = [
|
||||
id: "oo",
|
||||
isIFrame: true,
|
||||
payload: validDataFooPayload,
|
||||
rootElement: function() document.getElementById("oo").contentDocument.documentElement,
|
||||
checkResult: function(html) isnot(html.indexOf("bar"), -1, "Should have retained the data-bar attribute")
|
||||
rootElement() { return document.getElementById("oo").contentDocument.documentElement; },
|
||||
checkResult(html) { isnot(html.indexOf("bar"), -1, "Should have retained the data-bar attribute"); },
|
||||
},
|
||||
{
|
||||
id: "pp",
|
||||
payload: validDataFooPayload,
|
||||
rootElement: function() document.getElementById("pp"),
|
||||
checkResult: function(html) isnot(html.indexOf("bar"), -1, "Should have retained the data-bar attribute")
|
||||
rootElement() { return document.getElementById("pp"); },
|
||||
checkResult(html) { isnot(html.indexOf("bar"), -1, "Should have retained the data-bar attribute"); },
|
||||
},
|
||||
{
|
||||
id: "qq",
|
||||
isIFrame: true,
|
||||
payload: validDataFoo2Payload,
|
||||
rootElement: function() document.getElementById("qq").contentDocument.documentElement,
|
||||
checkResult: function(html) isnot(html.indexOf("bar"), -1, "Should have retained the _bar attribute")
|
||||
rootElement() { return document.getElementById("qq").contentDocument.documentElement; },
|
||||
checkResult(html) { isnot(html.indexOf("bar"), -1, "Should have retained the _bar attribute"); },
|
||||
},
|
||||
{
|
||||
id: "rr",
|
||||
payload: validDataFoo2Payload,
|
||||
rootElement: function() document.getElementById("rr"),
|
||||
checkResult: function(html) isnot(html.indexOf("bar"), -1, "Should have retained the _bar attribute")
|
||||
rootElement() { return document.getElementById("rr"); },
|
||||
checkResult(html) { isnot(html.indexOf("bar"), -1, "Should have retained the _bar attribute"); },
|
||||
},
|
||||
{
|
||||
id: "ss",
|
||||
isIFrame: true,
|
||||
payload: invalidStyle8Payload,
|
||||
rootElement: function() document.getElementById("ss").contentDocument.documentElement,
|
||||
checkResult: function(html) is(html.indexOf("@-moz-document"), -1, "Should not have retained the @-moz-document rule")
|
||||
rootElement() { return document.getElementById("ss").contentDocument.documentElement; },
|
||||
checkResult(html) { is(html.indexOf("@-moz-document"), -1, "Should not have retained the @-moz-document rule"); },
|
||||
},
|
||||
{
|
||||
id: "tt",
|
||||
payload: invalidStyle8Payload,
|
||||
rootElement: function() document.getElementById("tt"),
|
||||
checkResult: function(html) is(html.indexOf("@-moz-document"), -1, "Should not have retained the @-moz-document rule")
|
||||
rootElement() { return document.getElementById("tt"); },
|
||||
checkResult(html) { is(html.indexOf("@-moz-document"), -1, "Should not have retained the @-moz-document rule"); },
|
||||
},
|
||||
{
|
||||
id: "uu",
|
||||
isIFrame: true,
|
||||
payload: invalidStyle9Payload,
|
||||
rootElement: function() document.getElementById("uu").contentDocument.documentElement,
|
||||
checkResult: function(html) is(html.indexOf("@-moz-keyframes"), -1, "Should not have retained the @-moz-keyframes rule")
|
||||
rootElement() { return document.getElementById("uu").contentDocument.documentElement; },
|
||||
checkResult(html) { is(html.indexOf("@-moz-keyframes"), -1, "Should not have retained the @-moz-keyframes rule"); },
|
||||
},
|
||||
{
|
||||
id: "vv",
|
||||
payload: invalidStyle9Payload,
|
||||
rootElement: function() document.getElementById("vv"),
|
||||
checkResult: function(html) is(html.indexOf("@-moz-keyframes"), -1, "Should not have retained the @-moz-keyframes rule")
|
||||
rootElement() { return document.getElementById("vv"); },
|
||||
checkResult(html) { is(html.indexOf("@-moz-keyframes"), -1, "Should not have retained the @-moz-keyframes rule"); },
|
||||
},
|
||||
{
|
||||
id: "sss",
|
||||
payload: svgPayload,
|
||||
rootElement: function() document.getElementById("sss"),
|
||||
checkResult: function(html) isnot(html.indexOf("svgtitle"), -1, "Should have retained SVG title")
|
||||
rootElement() { return document.getElementById("sss"); },
|
||||
checkResult(html) { isnot(html.indexOf("svgtitle"), -1, "Should have retained SVG title"); },
|
||||
},
|
||||
{
|
||||
id: "ssss",
|
||||
isIFrame: true,
|
||||
payload: svgPayload,
|
||||
rootElement: function() document.getElementById("ssss").contentDocument.documentElement,
|
||||
checkResult: function(html) isnot(html.indexOf("svgtitle"), -1, "Should have retained SVG title")
|
||||
rootElement() { return document.getElementById("ssss").contentDocument.documentElement; },
|
||||
checkResult(html) { isnot(html.indexOf("svgtitle"), -1, "Should have retained SVG title"); },
|
||||
},
|
||||
{
|
||||
id: "ttt",
|
||||
payload: svg2Payload,
|
||||
rootElement: function() document.getElementById("ttt"),
|
||||
checkResult: function(html) is(html.indexOf("bogussvg"), -1, "Should have dropped bogussvg element")
|
||||
rootElement() { return document.getElementById("ttt"); },
|
||||
checkResult(html) { is(html.indexOf("bogussvg"), -1, "Should have dropped bogussvg element"); },
|
||||
},
|
||||
{
|
||||
id: "tttt",
|
||||
isIFrame: true,
|
||||
payload: svg2Payload,
|
||||
rootElement: function() document.getElementById("tttt").contentDocument.documentElement,
|
||||
checkResult: function(html) is(html.indexOf("bogussvg"), -1, "Should have dropped bogussvg element")
|
||||
rootElement() { return document.getElementById("tttt").contentDocument.documentElement; },
|
||||
checkResult(html) { is(html.indexOf("bogussvg"), -1, "Should have dropped bogussvg element"); },
|
||||
},
|
||||
{
|
||||
id: "uuu",
|
||||
payload: mathPayload,
|
||||
rootElement: function() document.getElementById("uuu"),
|
||||
checkResult: function(html) is(html.indexOf("bogusmath"), -1, "Should have dropped bogusmath element")
|
||||
rootElement() { return document.getElementById("uuu"); },
|
||||
checkResult(html) { is(html.indexOf("bogusmath"), -1, "Should have dropped bogusmath element"); },
|
||||
},
|
||||
{
|
||||
id: "uuuu",
|
||||
isIFrame: true,
|
||||
payload: mathPayload,
|
||||
rootElement: function() document.getElementById("uuuu").contentDocument.documentElement,
|
||||
checkResult: function(html) is(html.indexOf("bogusmath"), -1, "Should have dropped bogusmath element")
|
||||
rootElement() { return document.getElementById("uuuu").contentDocument.documentElement; },
|
||||
checkResult(html) { is(html.indexOf("bogusmath"), -1, "Should have dropped bogusmath element"); },
|
||||
},
|
||||
{
|
||||
id: "vvv",
|
||||
payload: math2Payload,
|
||||
rootElement: function() document.getElementById("vvv"),
|
||||
checkResult: function(html) is(html.indexOf("yyy.css"), -1, "Should have dropped MathML style element")
|
||||
rootElement() { return document.getElementById("vvv"); },
|
||||
checkResult(html) { is(html.indexOf("yyy.css"), -1, "Should have dropped MathML style element"); },
|
||||
},
|
||||
{
|
||||
id: "vvvv",
|
||||
isIFrame: true,
|
||||
payload: math2Payload,
|
||||
rootElement: function() document.getElementById("vvvv").contentDocument.documentElement,
|
||||
checkResult: function(html) is(html.indexOf("yyy.css"), -1, "Should have dropped MathML style element")
|
||||
rootElement() { return document.getElementById("vvvv").contentDocument.documentElement; },
|
||||
checkResult(html) { is(html.indexOf("yyy.css"), -1, "Should have dropped MathML style element"); },
|
||||
},
|
||||
{
|
||||
id: "www",
|
||||
payload: math3Payload,
|
||||
rootElement: function() document.getElementById("www"),
|
||||
checkResult: function(html) isnot(html.indexOf("<mi"), -1, "Should not have dropped MathML mi element")
|
||||
rootElement() { return document.getElementById("www"); },
|
||||
checkResult(html) { isnot(html.indexOf("<mi"), -1, "Should not have dropped MathML mi element"); },
|
||||
},
|
||||
{
|
||||
id: "wwww",
|
||||
isIFrame: true,
|
||||
payload: math3Payload,
|
||||
rootElement: function() document.getElementById("wwww").contentDocument.documentElement,
|
||||
checkResult: function(html) isnot(html.indexOf("<mi"), -1, "Should not have dropped MathML mi element")
|
||||
rootElement() { return document.getElementById("wwww").contentDocument.documentElement; },
|
||||
checkResult(html) { isnot(html.indexOf("<mi"), -1, "Should not have dropped MathML mi element"); },
|
||||
},
|
||||
{
|
||||
id: "xxx",
|
||||
payload: videoPayload,
|
||||
rootElement: function() document.getElementById("xxx"),
|
||||
checkResult: function(html) isnot(html.indexOf("controls="), -1, "Should have added the controls attribute")
|
||||
rootElement() { return document.getElementById("xxx"); },
|
||||
checkResult(html) { isnot(html.indexOf("controls="), -1, "Should have added the controls attribute"); },
|
||||
},
|
||||
{
|
||||
id: "xxxx",
|
||||
isIFrame: true,
|
||||
payload: videoPayload,
|
||||
rootElement: function() document.getElementById("xxxx").contentDocument.documentElement,
|
||||
checkResult: function(html) isnot(html.indexOf("controls="), -1, "Should have added the controls attribute")
|
||||
rootElement() { return document.getElementById("xxxx").contentDocument.documentElement; },
|
||||
checkResult(html) { isnot(html.indexOf("controls="), -1, "Should have added the controls attribute"); },
|
||||
},
|
||||
{
|
||||
id: "yyy",
|
||||
payload: microdataPayload,
|
||||
rootElement: function() document.getElementById("yyy"),
|
||||
rootElement() { return document.getElementById("yyy"); },
|
||||
checkResult: function(html) { is(html.indexOf("name"), -1, "Should have dropped name."); is(html.indexOf("rel"), -1, "Should have dropped rel."); isnot(html.indexOf("itemprop"), -1, "Should not have dropped itemprop."); }
|
||||
},
|
||||
{
|
||||
id: "yyyy",
|
||||
isIFrame: true,
|
||||
payload: microdataPayload,
|
||||
rootElement: function() document.getElementById("yyyy").contentDocument.documentElement,
|
||||
rootElement() { return document.getElementById("yyyy").contentDocument.documentElement; },
|
||||
checkResult: function(html) { is(html.indexOf("name"), -1, "Should have dropped name."); is(html.indexOf("rel"), -1, "Should have dropped rel."); isnot(html.indexOf("itemprop"), -1, "Should not have dropped itemprop."); }
|
||||
}
|
||||
];
|
||||
|
||||
@@ -34,7 +34,7 @@ function testTab(prefix, callback) {
|
||||
function() {
|
||||
dst.focus();
|
||||
var inputReceived = false;
|
||||
dst.addEventListener("input", function() inputReceived = true, false);
|
||||
dst.addEventListener("input", function() { inputReceived = true; }, false);
|
||||
synthesizeKey("v", {accelKey: true});
|
||||
ok(inputReceived, "An input event should be raised");
|
||||
is(dst.value, prefix + src.value, "The value should be pasted verbatim");
|
||||
|
||||
@@ -23,7 +23,7 @@ SimpleTest.waitForExplicitFinish();
|
||||
addLoadEvent(function() {
|
||||
var i = document.querySelector("input");
|
||||
var inputCount = 0;
|
||||
i.addEventListener("input", function() inputCount++, false);
|
||||
i.addEventListener("input", function() { inputCount++; }, false);
|
||||
|
||||
// test cut
|
||||
i.focus();
|
||||
|
||||
+185
-14
@@ -11,6 +11,7 @@
|
||||
#include "Rect.h"
|
||||
#include "ScaledFontMac.h"
|
||||
#include "Tools.h"
|
||||
#include "PathHelpers.h"
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include "MacIOSurface.h"
|
||||
@@ -992,13 +993,162 @@ DrawTargetCG::FillRect(const Rect &aRect,
|
||||
CGContextRestoreGState(mCg);
|
||||
}
|
||||
|
||||
void
|
||||
DrawTargetCG::StrokeLine(const Point &p1, const Point &p2, const Pattern &aPattern, const StrokeOptions &aStrokeOptions, const DrawOptions &aDrawOptions)
|
||||
static Float
|
||||
DashPeriodLength(const StrokeOptions& aStrokeOptions)
|
||||
{
|
||||
if (!std::isfinite(p1.x) ||
|
||||
!std::isfinite(p1.y) ||
|
||||
!std::isfinite(p2.x) ||
|
||||
!std::isfinite(p2.y)) {
|
||||
Float length = 0;
|
||||
for (size_t i = 0; i < aStrokeOptions.mDashLength; i++) {
|
||||
length += aStrokeOptions.mDashPattern[i];
|
||||
}
|
||||
if (aStrokeOptions.mDashLength & 1) {
|
||||
// "If an odd number of values is provided, then the list of values is
|
||||
// repeated to yield an even number of values."
|
||||
// Double the length.
|
||||
length += length;
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
inline Float
|
||||
RoundDownToMultiple(Float aValue, Float aFactor)
|
||||
{
|
||||
return floorf(aValue / aFactor) * aFactor;
|
||||
}
|
||||
|
||||
static Rect
|
||||
UserSpaceStrokeClip(const Rect &aDeviceClip,
|
||||
const Matrix &aTransform,
|
||||
const StrokeOptions &aStrokeOptions)
|
||||
{
|
||||
Matrix inverse = aTransform;
|
||||
if (!inverse.Invert()) {
|
||||
return Rect();
|
||||
}
|
||||
Rect deviceClip = aDeviceClip;
|
||||
deviceClip.Inflate(MaxStrokeExtents(aStrokeOptions, aTransform));
|
||||
return inverse.TransformBounds(deviceClip);
|
||||
}
|
||||
|
||||
static Rect
|
||||
ShrinkClippedStrokedRect(const Rect &aStrokedRect, const Rect &aDeviceClip,
|
||||
const Matrix &aTransform,
|
||||
const StrokeOptions &aStrokeOptions)
|
||||
{
|
||||
Rect userSpaceStrokeClip =
|
||||
UserSpaceStrokeClip(aDeviceClip, aTransform, aStrokeOptions);
|
||||
|
||||
Rect intersection = aStrokedRect.Intersect(userSpaceStrokeClip);
|
||||
Float dashPeriodLength = DashPeriodLength(aStrokeOptions);
|
||||
if (intersection.IsEmpty() || dashPeriodLength == 0.0f) {
|
||||
return intersection;
|
||||
}
|
||||
|
||||
// Reduce the rectangle side lengths in multiples of the dash period length
|
||||
// so that the visible dashes stay in the same place.
|
||||
Margin insetBy = aStrokedRect - intersection;
|
||||
insetBy.top = RoundDownToMultiple(insetBy.top, dashPeriodLength);
|
||||
insetBy.right = RoundDownToMultiple(insetBy.right, dashPeriodLength);
|
||||
insetBy.bottom = RoundDownToMultiple(insetBy.bottom, dashPeriodLength);
|
||||
insetBy.left = RoundDownToMultiple(insetBy.left, dashPeriodLength);
|
||||
|
||||
Rect shrunkRect = aStrokedRect;
|
||||
shrunkRect.Deflate(insetBy);
|
||||
return shrunkRect;
|
||||
}
|
||||
|
||||
// Liang-Barsky
|
||||
// This algorithm was chosen for its code brevity, with the hope that its
|
||||
// performance is good enough.
|
||||
// Sets aStart and aEnd to floats between 0 and the line length, or returns
|
||||
// false if the line is completely outside the rect.
|
||||
static bool
|
||||
IntersectLineWithRect(const Point& aP1, const Point& aP2, const Rect& aClip,
|
||||
Float* aStart, Float* aEnd)
|
||||
{
|
||||
Float t0 = 0.0f;
|
||||
Float t1 = 1.0f;
|
||||
Point vector = aP2 - aP1;
|
||||
for (uint32_t edge = 0; edge < 4; edge++) {
|
||||
Float p, q;
|
||||
switch (edge) {
|
||||
case 0: p = -vector.x; q = aP1.x - aClip.x; break;
|
||||
case 1: p = vector.x; q = aClip.XMost() - aP1.x; break;
|
||||
case 2: p = -vector.y; q = aP1.y - aClip.y; break;
|
||||
case 3: p = vector.y; q = aClip.YMost() - aP1.y; break;
|
||||
}
|
||||
|
||||
if (p == 0.0f) {
|
||||
// Line is parallel to the edge.
|
||||
if (q < 0.0f) {
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
Float r = q / p;
|
||||
if (p < 0) {
|
||||
t0 = std::max(t0, r);
|
||||
} else {
|
||||
t1 = std::min(t1, r);
|
||||
}
|
||||
|
||||
if (t0 > t1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Float length = vector.Length();
|
||||
*aStart = t0 * length;
|
||||
*aEnd = t1 * length;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Adjusts aP1 and aP2 to a shrunk line, or returns false if the line is
|
||||
// completely outside the clip.
|
||||
static bool
|
||||
ShrinkClippedStrokedLine(Point &aP1, Point& aP2, const Rect &aDeviceClip,
|
||||
const Matrix &aTransform,
|
||||
const StrokeOptions &aStrokeOptions)
|
||||
{
|
||||
Rect userSpaceStrokeClip =
|
||||
UserSpaceStrokeClip(aDeviceClip, aTransform, aStrokeOptions);
|
||||
|
||||
Point vector = aP2 - aP1;
|
||||
Float length = vector.Length();
|
||||
|
||||
if (length == 0.0f) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Float start = 0;
|
||||
Float end = length;
|
||||
if (!IntersectLineWithRect(aP1, aP2, userSpaceStrokeClip, &start, &end)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Float dashPeriodLength = DashPeriodLength(aStrokeOptions);
|
||||
if (dashPeriodLength > 0.0f) {
|
||||
// Shift the line points by multiples of dashPeriodLength so that the
|
||||
// dashes stay in the same place.
|
||||
start = RoundDownToMultiple(start, dashPeriodLength);
|
||||
end = length - RoundDownToMultiple(length - end, dashPeriodLength);
|
||||
}
|
||||
|
||||
Point startPoint = aP1;
|
||||
aP1 = Point(startPoint.x + start * vector.x / length,
|
||||
startPoint.y + start * vector.y / length);
|
||||
aP2 = Point(startPoint.x + end * vector.x / length,
|
||||
startPoint.y + end * vector.y / length);
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
DrawTargetCG::StrokeLine(const Point &aP1, const Point &aP2, const Pattern &aPattern, const StrokeOptions &aStrokeOptions, const DrawOptions &aDrawOptions)
|
||||
{
|
||||
if (!std::isfinite(aP1.x) ||
|
||||
!std::isfinite(aP1.y) ||
|
||||
!std::isfinite(aP2.x) ||
|
||||
!std::isfinite(aP2.y)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1006,6 +1156,14 @@ DrawTargetCG::StrokeLine(const Point &p1, const Point &p2, const Pattern &aPatte
|
||||
return;
|
||||
}
|
||||
|
||||
Point p1 = aP1;
|
||||
Point p2 = aP2;
|
||||
|
||||
Rect deviceClip(0, 0, mSize.width, mSize.height);
|
||||
if (!ShrinkClippedStrokedLine(p1, p2, deviceClip, mTransform, aStrokeOptions)) {
|
||||
return;
|
||||
}
|
||||
|
||||
MarkChanged();
|
||||
|
||||
CGContextSaveGState(mCg);
|
||||
@@ -1080,6 +1238,20 @@ DrawTargetCG::StrokeRect(const Rect &aRect,
|
||||
if (MOZ2D_ERROR_IF(!cg)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Stroking large rectangles with dashes is expensive with CG (fixed
|
||||
// overhead based on the number of dashes, regardless of whether the dashes
|
||||
// are visible), so we try to reduce the size of the stroked rectangle as
|
||||
// much as possible before passing it on to CG.
|
||||
Rect rect = aRect;
|
||||
if (!rect.IsEmpty()) {
|
||||
Rect deviceClip(0, 0, mSize.width, mSize.height);
|
||||
rect = ShrinkClippedStrokedRect(rect, deviceClip, mTransform, aStrokeOptions);
|
||||
if (rect.IsEmpty()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
CGContextSetAlpha(mCg, aDrawOptions.mAlpha);
|
||||
CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp));
|
||||
|
||||
@@ -1091,7 +1263,7 @@ DrawTargetCG::StrokeRect(const Rect &aRect,
|
||||
bool pixelAlignedStroke = mTransform.IsAllIntegers() &&
|
||||
mTransform.PreservesAxisAlignedRectangles() &&
|
||||
aPattern.GetType() == PatternType::COLOR &&
|
||||
IsPixelAlignedStroke(aRect, aStrokeOptions.mLineWidth);
|
||||
IsPixelAlignedStroke(rect, aStrokeOptions.mLineWidth);
|
||||
CGContextSetShouldAntialias(cg,
|
||||
aDrawOptions.mAntialiasMode != AntialiasMode::NONE && !pixelAlignedStroke);
|
||||
|
||||
@@ -1102,7 +1274,7 @@ DrawTargetCG::StrokeRect(const Rect &aRect,
|
||||
if (isGradient(aPattern)) {
|
||||
// There's no CGContextClipStrokeRect so we do it by hand
|
||||
CGContextBeginPath(cg);
|
||||
CGContextAddRect(cg, RectToCGRect(aRect));
|
||||
CGContextAddRect(cg, RectToCGRect(rect));
|
||||
CGContextReplacePathWithStrokedPath(cg);
|
||||
CGRect extents = CGContextGetPathBoundingBox(cg);
|
||||
//XXX: should we use EO clip here?
|
||||
@@ -1110,17 +1282,16 @@ DrawTargetCG::StrokeRect(const Rect &aRect,
|
||||
DrawGradient(mColorSpace, cg, aPattern, extents);
|
||||
} else {
|
||||
SetStrokeFromPattern(cg, mColorSpace, aPattern);
|
||||
// We'd like to use CGContextStrokeRect(cg, RectToCGRect(aRect));
|
||||
// We'd like to use CGContextStrokeRect(cg, RectToCGRect(rect));
|
||||
// Unfortunately, newer versions of OS X no longer start at the top-left
|
||||
// corner and stroke clockwise as older OS X versions and all the other
|
||||
// Moz2D backends do. (Newer versions start at the top right-hand corner
|
||||
// and stroke counter-clockwise.) For consistency we draw the rect by hand.
|
||||
CGRect rect = RectToCGRect(aRect);
|
||||
CGContextBeginPath(cg);
|
||||
CGContextMoveToPoint(cg, CGRectGetMinX(rect), CGRectGetMinY(rect));
|
||||
CGContextAddLineToPoint(cg, CGRectGetMaxX(rect), CGRectGetMinY(rect));
|
||||
CGContextAddLineToPoint(cg, CGRectGetMaxX(rect), CGRectGetMaxY(rect));
|
||||
CGContextAddLineToPoint(cg, CGRectGetMinX(rect), CGRectGetMaxY(rect));
|
||||
CGContextMoveToPoint(cg, rect.x, rect.y);
|
||||
CGContextAddLineToPoint(cg, rect.XMost(), rect.y);
|
||||
CGContextAddLineToPoint(cg, rect.XMost(), rect.YMost());
|
||||
CGContextAddLineToPoint(cg, rect.x, rect.YMost());
|
||||
CGContextClosePath(cg);
|
||||
CGContextStrokePath(cg);
|
||||
}
|
||||
|
||||
+48
-35
@@ -3,11 +3,9 @@
|
||||
* 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/. */
|
||||
|
||||
#define _USE_MATH_DEFINES
|
||||
#include <cmath>
|
||||
|
||||
#include "DrawTargetTiled.h"
|
||||
#include "Logging.h"
|
||||
#include "PathHelpers.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
@@ -107,8 +105,6 @@ TILED_COMMAND(Flush)
|
||||
TILED_COMMAND4(DrawFilter, FilterNode*, const Rect&, const Point&, const DrawOptions&)
|
||||
TILED_COMMAND1(ClearRect, const Rect&)
|
||||
TILED_COMMAND4(MaskSurface, const Pattern&, SourceSurface*, Point, const DrawOptions&)
|
||||
TILED_COMMAND4(StrokeRect, const Rect&, const Pattern&, const StrokeOptions&, const DrawOptions&)
|
||||
TILED_COMMAND5(StrokeLine, const Point&, const Point&, const Pattern&, const StrokeOptions&, const DrawOptions&)
|
||||
TILED_COMMAND5(FillGlyphs, ScaledFont*, const GlyphBuffer&, const Pattern&, const DrawOptions&, const GlyphRenderingOptions*)
|
||||
TILED_COMMAND3(Mask, const Pattern&, const Pattern&, const DrawOptions&)
|
||||
|
||||
@@ -232,40 +228,12 @@ DrawTargetTiled::FillRect(const Rect& aRect, const Pattern& aPattern, const Draw
|
||||
}
|
||||
}
|
||||
|
||||
// The logic for this comes from _cairo_stroke_style_max_distance_from_path
|
||||
static Rect
|
||||
PathExtentsToMaxStrokeExtents(const StrokeOptions &aStrokeOptions,
|
||||
const Rect &aRect,
|
||||
const Matrix &aTransform)
|
||||
{
|
||||
double styleExpansionFactor = 0.5f;
|
||||
|
||||
if (aStrokeOptions.mLineCap == CapStyle::SQUARE) {
|
||||
styleExpansionFactor = M_SQRT1_2;
|
||||
}
|
||||
|
||||
if (aStrokeOptions.mLineJoin == JoinStyle::MITER &&
|
||||
styleExpansionFactor < M_SQRT2 * aStrokeOptions.mMiterLimit) {
|
||||
styleExpansionFactor = M_SQRT2 * aStrokeOptions.mMiterLimit;
|
||||
}
|
||||
|
||||
styleExpansionFactor *= aStrokeOptions.mLineWidth;
|
||||
|
||||
double dx = styleExpansionFactor * hypot(aTransform._11, aTransform._21);
|
||||
double dy = styleExpansionFactor * hypot(aTransform._22, aTransform._12);
|
||||
|
||||
Rect result = aRect;
|
||||
result.Inflate(dx, dy);
|
||||
return result;
|
||||
}
|
||||
|
||||
void
|
||||
DrawTargetTiled::Stroke(const Path* aPath, const Pattern& aPattern, const StrokeOptions& aStrokeOptions, const DrawOptions& aDrawOptions)
|
||||
{
|
||||
// Approximate the stroke extents, since Path::GetStrokeExtents can be slow
|
||||
Rect deviceRect = PathExtentsToMaxStrokeExtents(aStrokeOptions,
|
||||
aPath->GetBounds(mTransform),
|
||||
mTransform);
|
||||
Rect deviceRect = aPath->GetBounds(mTransform);
|
||||
deviceRect.Inflate(MaxStrokeExtents(aStrokeOptions, mTransform));
|
||||
for (size_t i = 0; i < mTiles.size(); i++) {
|
||||
if (!mTiles[i].mClippedOut &&
|
||||
deviceRect.Intersects(Rect(mTiles[i].mTileOrigin.x,
|
||||
@@ -277,6 +245,51 @@ DrawTargetTiled::Stroke(const Path* aPath, const Pattern& aPattern, const Stroke
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
DrawTargetTiled::StrokeRect(const Rect& aRect, const Pattern& aPattern, const StrokeOptions &aStrokeOptions, const DrawOptions& aDrawOptions)
|
||||
{
|
||||
Rect deviceRect = mTransform.TransformBounds(aRect);
|
||||
Margin strokeMargin = MaxStrokeExtents(aStrokeOptions, mTransform);
|
||||
Rect outerRect = deviceRect;
|
||||
outerRect.Inflate(strokeMargin);
|
||||
Rect innerRect;
|
||||
if (mTransform.IsRectilinear()) {
|
||||
// If rects are mapped to rects, we can compute the inner rect
|
||||
// of the stroked rect.
|
||||
innerRect = deviceRect;
|
||||
innerRect.Deflate(strokeMargin);
|
||||
}
|
||||
for (size_t i = 0; i < mTiles.size(); i++) {
|
||||
if (mTiles[i].mClippedOut) {
|
||||
continue;
|
||||
}
|
||||
Rect tileRect(mTiles[i].mTileOrigin.x,
|
||||
mTiles[i].mTileOrigin.y,
|
||||
mTiles[i].mDrawTarget->GetSize().width,
|
||||
mTiles[i].mDrawTarget->GetSize().height);
|
||||
if (outerRect.Intersects(tileRect) && !innerRect.Contains(tileRect)) {
|
||||
mTiles[i].mDrawTarget->StrokeRect(aRect, aPattern, aStrokeOptions, aDrawOptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
DrawTargetTiled::StrokeLine(const Point& aStart, const Point& aEnd, const Pattern& aPattern, const StrokeOptions &aStrokeOptions, const DrawOptions& aDrawOptions)
|
||||
{
|
||||
Rect lineBounds = Rect(aStart, Size()).UnionEdges(Rect(aEnd, Size()));
|
||||
Rect deviceRect = mTransform.TransformBounds(lineBounds);
|
||||
deviceRect.Inflate(MaxStrokeExtents(aStrokeOptions, mTransform));
|
||||
for (size_t i = 0; i < mTiles.size(); i++) {
|
||||
if (!mTiles[i].mClippedOut &&
|
||||
deviceRect.Intersects(Rect(mTiles[i].mTileOrigin.x,
|
||||
mTiles[i].mTileOrigin.y,
|
||||
mTiles[i].mDrawTarget->GetSize().width,
|
||||
mTiles[i].mDrawTarget->GetSize().height))) {
|
||||
mTiles[i].mDrawTarget->StrokeLine(aStart, aEnd, aPattern, aStrokeOptions, aDrawOptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
DrawTargetTiled::Fill(const Path* aPath, const Pattern& aPattern, const DrawOptions& aDrawOptions)
|
||||
{
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
* 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/. */
|
||||
|
||||
#define _USE_MATH_DEFINES
|
||||
#include <cmath>
|
||||
|
||||
#include "PathHelpers.h"
|
||||
|
||||
namespace mozilla {
|
||||
@@ -238,6 +241,29 @@ StrokeSnappedEdgesOfRect(const Rect& aRect, DrawTarget& aDrawTarget,
|
||||
aDrawTarget.StrokeLine(p1, p2, aColor, aStrokeOptions);
|
||||
}
|
||||
|
||||
// The logic for this comes from _cairo_stroke_style_max_distance_from_path
|
||||
Margin
|
||||
MaxStrokeExtents(const StrokeOptions& aStrokeOptions,
|
||||
const Matrix& aTransform)
|
||||
{
|
||||
double styleExpansionFactor = 0.5f;
|
||||
|
||||
if (aStrokeOptions.mLineCap == CapStyle::SQUARE) {
|
||||
styleExpansionFactor = M_SQRT1_2;
|
||||
}
|
||||
|
||||
if (aStrokeOptions.mLineJoin == JoinStyle::MITER &&
|
||||
styleExpansionFactor < M_SQRT2 * aStrokeOptions.mMiterLimit) {
|
||||
styleExpansionFactor = M_SQRT2 * aStrokeOptions.mMiterLimit;
|
||||
}
|
||||
|
||||
styleExpansionFactor *= aStrokeOptions.mLineWidth;
|
||||
|
||||
double dx = styleExpansionFactor * hypot(aTransform._11, aTransform._21);
|
||||
double dy = styleExpansionFactor * hypot(aTransform._22, aTransform._12);
|
||||
return Margin(dy, dx, dy, dx);
|
||||
}
|
||||
|
||||
} // namespace gfx
|
||||
} // namespace mozilla
|
||||
|
||||
|
||||
@@ -307,6 +307,16 @@ GFX2D_API void StrokeSnappedEdgesOfRect(const Rect& aRect,
|
||||
const ColorPattern& aColor,
|
||||
const StrokeOptions& aStrokeOptions);
|
||||
|
||||
/**
|
||||
* Return the margin, in device space, by which a stroke can extend beyond the
|
||||
* rendered shape.
|
||||
* @param aStrokeOptions The stroke options that the stroke is drawn with.
|
||||
* @param aTransform The user space to device space transform.
|
||||
* @return The stroke margin.
|
||||
*/
|
||||
GFX2D_API Margin MaxStrokeExtents(const StrokeOptions& aStrokeOptions,
|
||||
const Matrix& aTransform);
|
||||
|
||||
extern UserDataKey sDisablePixelSnapping;
|
||||
|
||||
/**
|
||||
|
||||
+2
-2
@@ -115,6 +115,7 @@ UNIFIED_SOURCES += [
|
||||
'DrawTargetCapture.cpp',
|
||||
'DrawTargetDual.cpp',
|
||||
'DrawTargetRecording.cpp',
|
||||
'DrawTargetTiled.cpp',
|
||||
'Factory.cpp',
|
||||
'FilterNodeSoftware.cpp',
|
||||
'FilterProcessing.cpp',
|
||||
@@ -123,7 +124,6 @@ UNIFIED_SOURCES += [
|
||||
'Matrix.cpp',
|
||||
'Path.cpp',
|
||||
'PathCairo.cpp',
|
||||
'PathHelpers.cpp',
|
||||
'PathRecording.cpp',
|
||||
'RecordedEvent.cpp',
|
||||
'Scale.cpp',
|
||||
@@ -134,7 +134,7 @@ UNIFIED_SOURCES += [
|
||||
]
|
||||
|
||||
SOURCES += [
|
||||
'DrawTargetTiled.cpp',
|
||||
'PathHelpers.cpp', # Uses _USE_MATH_DEFINES
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
|
||||
|
||||
@@ -277,21 +277,6 @@ struct ParamTraits<mozilla::layers::TextureFlags>
|
||||
mozilla::layers::TextureFlags::ALL_BITS>
|
||||
{};
|
||||
|
||||
template <>
|
||||
struct ParamTraits<mozilla::layers::TextureIdentifier>
|
||||
: public ContiguousEnumSerializer<
|
||||
mozilla::layers::TextureIdentifier,
|
||||
mozilla::layers::TextureIdentifier::Front,
|
||||
mozilla::layers::TextureIdentifier::HighBound>
|
||||
{};
|
||||
|
||||
template <>
|
||||
struct ParamTraits<mozilla::layers::DeprecatedTextureHostFlags>
|
||||
: public BitFlagsEnumSerializer<
|
||||
mozilla::layers::DeprecatedTextureHostFlags,
|
||||
mozilla::layers::DeprecatedTextureHostFlags::ALL_BITS>
|
||||
{};
|
||||
|
||||
template <>
|
||||
struct ParamTraits<mozilla::layers::DiagnosticTypes>
|
||||
: public BitFlagsEnumSerializer<
|
||||
@@ -850,14 +835,12 @@ struct ParamTraits<mozilla::layers::TextureInfo>
|
||||
static void Write(Message* aMsg, const paramType& aParam)
|
||||
{
|
||||
WriteParam(aMsg, aParam.mCompositableType);
|
||||
WriteParam(aMsg, aParam.mDeprecatedTextureHostFlags);
|
||||
WriteParam(aMsg, aParam.mTextureFlags);
|
||||
}
|
||||
|
||||
static bool Read(const Message* aMsg, void** aIter, paramType* aResult)
|
||||
{
|
||||
return ReadParam(aMsg, aIter, &aResult->mCompositableType) &&
|
||||
ReadParam(aMsg, aIter, &aResult->mDeprecatedTextureHostFlags) &&
|
||||
ReadParam(aMsg, aIter, &aResult->mTextureFlags);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -133,8 +133,6 @@ enum class EffectTypes : uint8_t {
|
||||
*/
|
||||
enum class CompositableType : uint8_t {
|
||||
UNKNOWN,
|
||||
CONTENT_INC, // painted layer interface, only sends incremental
|
||||
// updates to a texture on the compositor side.
|
||||
CONTENT_TILED, // tiled painted layer
|
||||
IMAGE, // image with single buffering
|
||||
IMAGE_OVERLAY, // image without buffer
|
||||
@@ -144,19 +142,6 @@ enum class CompositableType : uint8_t {
|
||||
COUNT
|
||||
};
|
||||
|
||||
/**
|
||||
* How the texture host is used for composition,
|
||||
* XXX - Only used by ContentClientIncremental
|
||||
*/
|
||||
enum class DeprecatedTextureHostFlags : uint8_t {
|
||||
DEFAULT = 0, // The default texture host for the given SurfaceDescriptor
|
||||
TILED = 1 << 0, // A texture host that supports tiling
|
||||
COPY_PREVIOUS = 1 << 1, // Texture contents should be initialized
|
||||
// from the previous texture.
|
||||
ALL_BITS = (1 << 2) - 1
|
||||
};
|
||||
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(DeprecatedTextureHostFlags)
|
||||
|
||||
#ifdef XP_WIN
|
||||
typedef void* SyncHandle;
|
||||
#else
|
||||
@@ -194,20 +179,6 @@ struct TextureFactoryIdentifier
|
||||
{}
|
||||
};
|
||||
|
||||
/**
|
||||
* Identify a texture to a compositable. Many textures can have the same id, but
|
||||
* the id is unique for any texture owned by a particular compositable.
|
||||
* XXX - We don't really need this, it will be removed along with the incremental
|
||||
* ContentClient/Host.
|
||||
*/
|
||||
enum class TextureIdentifier : uint8_t {
|
||||
Front = 1,
|
||||
Back = 2,
|
||||
OnWhiteFront = 3,
|
||||
OnWhiteBack = 4,
|
||||
HighBound
|
||||
};
|
||||
|
||||
/**
|
||||
* Information required by the compositor from the content-side for creating or
|
||||
* using compositables and textures.
|
||||
@@ -218,27 +189,22 @@ enum class TextureIdentifier : uint8_t {
|
||||
struct TextureInfo
|
||||
{
|
||||
CompositableType mCompositableType;
|
||||
// XXX - only used by ContentClientIncremental
|
||||
DeprecatedTextureHostFlags mDeprecatedTextureHostFlags;
|
||||
TextureFlags mTextureFlags;
|
||||
|
||||
TextureInfo()
|
||||
: mCompositableType(CompositableType::UNKNOWN)
|
||||
, mDeprecatedTextureHostFlags(DeprecatedTextureHostFlags::DEFAULT)
|
||||
, mTextureFlags(TextureFlags::NO_FLAGS)
|
||||
{}
|
||||
|
||||
explicit TextureInfo(CompositableType aType,
|
||||
TextureFlags aTextureFlags = TextureFlags::DEFAULT)
|
||||
: mCompositableType(aType)
|
||||
, mDeprecatedTextureHostFlags(DeprecatedTextureHostFlags::DEFAULT)
|
||||
, mTextureFlags(aTextureFlags)
|
||||
{}
|
||||
|
||||
bool operator==(const TextureInfo& aOther) const
|
||||
{
|
||||
return mCompositableType == aOther.mCompositableType &&
|
||||
mDeprecatedTextureHostFlags == aOther.mDeprecatedTextureHostFlags &&
|
||||
mTextureFlags == aOther.mTextureFlags;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -272,7 +272,6 @@ public:
|
||||
|
||||
struct DrawIterator {
|
||||
friend class RotatedContentBuffer;
|
||||
friend class ContentClientIncremental;
|
||||
DrawIterator()
|
||||
: mCount(0)
|
||||
{}
|
||||
|
||||
@@ -53,8 +53,6 @@ public:
|
||||
nsIntRegion* aDestRegion = nullptr,
|
||||
gfx::IntPoint* aSrcOffset = nullptr) override
|
||||
{
|
||||
// XXX - For this to work with IncrementalContentHost we will need to support
|
||||
// the aDestRegion and aSrcOffset parameters properly;
|
||||
mSurface = aSurface;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -594,17 +594,6 @@ ClientLayerManager::ForwardTransaction(bool aScheduleComposite)
|
||||
|
||||
break;
|
||||
}
|
||||
case EditReply::TOpTextureSwap: {
|
||||
MOZ_LAYERS_LOG(("[LayersForwarder] TextureSwap"));
|
||||
|
||||
const OpTextureSwap& ots = reply.get_OpTextureSwap();
|
||||
|
||||
CompositableClient* compositable =
|
||||
CompositableClient::FromIPDLActor(ots.compositableChild());
|
||||
MOZ_ASSERT(compositable);
|
||||
compositable->SetDescriptorFromReply(ots.textureId(), ots.image());
|
||||
break;
|
||||
}
|
||||
case EditReply::TReturnReleaseFence: {
|
||||
const ReturnReleaseFence& rep = reply.get_ReturnReleaseFence();
|
||||
FenceHandle fence = rep.fence();
|
||||
|
||||
@@ -143,12 +143,6 @@ public:
|
||||
TextureFlags aTextureFlags,
|
||||
TextureAllocationFlags aAllocFlags = ALLOC_DEFAULT);
|
||||
|
||||
virtual void SetDescriptorFromReply(TextureIdentifier aTextureId,
|
||||
const SurfaceDescriptor& aDescriptor)
|
||||
{
|
||||
MOZ_CRASH("If you want to call this, you should have implemented it");
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the connection with compositor side through IPDL
|
||||
*/
|
||||
|
||||
@@ -93,11 +93,6 @@ ContentClient::CreateContentClient(CompositableForwarder* aForwarder)
|
||||
if (useDoubleBuffering || PR_GetEnv("MOZ_FORCE_DOUBLE_BUFFERING")) {
|
||||
return MakeAndAddRef<ContentClientDoubleBuffered>(aForwarder);
|
||||
}
|
||||
#ifdef XP_MACOSX
|
||||
if (backend == LayersBackend::LAYERS_OPENGL) {
|
||||
return MakeAndAddRef<ContentClientIncremental>(aForwarder);
|
||||
}
|
||||
#endif
|
||||
return MakeAndAddRef<ContentClientSingleBuffered>(aForwarder);
|
||||
}
|
||||
|
||||
@@ -673,362 +668,5 @@ ContentClientSingleBuffered::FinalizeFrame(const nsIntRegion& aRegionToDraw)
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
WrapRotationAxis(int32_t* aRotationPoint, int32_t aSize)
|
||||
{
|
||||
if (*aRotationPoint < 0) {
|
||||
*aRotationPoint += aSize;
|
||||
} else if (*aRotationPoint >= aSize) {
|
||||
*aRotationPoint -= aSize;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
FillSurface(DrawTarget* aDT, const nsIntRegion& aRegion,
|
||||
const nsIntPoint& aOffset, const gfxRGBA& aColor)
|
||||
{
|
||||
nsIntRegionRectIterator iter(aRegion);
|
||||
const nsIntRect* r;
|
||||
while ((r = iter.Next()) != nullptr) {
|
||||
aDT->FillRect(Rect(r->x - aOffset.x, r->y - aOffset.y,
|
||||
r->width, r->height),
|
||||
ColorPattern(ToColor(aColor)));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ContentClientIncremental::NotifyBufferCreated(ContentType aType, TextureFlags aFlags)
|
||||
{
|
||||
mTextureInfo.mTextureFlags = aFlags;
|
||||
mContentType = aType;
|
||||
|
||||
mForwarder->CreatedIncrementalBuffer(this,
|
||||
mTextureInfo,
|
||||
mBufferRect);
|
||||
|
||||
}
|
||||
|
||||
RotatedContentBuffer::PaintState
|
||||
ContentClientIncremental::BeginPaintBuffer(PaintedLayer* aLayer,
|
||||
uint32_t aFlags)
|
||||
{
|
||||
mTextureInfo.mDeprecatedTextureHostFlags = DeprecatedTextureHostFlags::DEFAULT;
|
||||
PaintState result;
|
||||
// We need to disable rotation if we're going to be resampled when
|
||||
// drawing, because we might sample across the rotation boundary.
|
||||
bool canHaveRotation = !(aFlags & RotatedContentBuffer::PAINT_WILL_RESAMPLE);
|
||||
|
||||
nsIntRegion validRegion = aLayer->GetValidRegion();
|
||||
|
||||
bool canUseOpaqueSurface = aLayer->CanUseOpaqueSurface();
|
||||
ContentType contentType =
|
||||
canUseOpaqueSurface ? gfxContentType::COLOR :
|
||||
gfxContentType::COLOR_ALPHA;
|
||||
|
||||
SurfaceMode mode;
|
||||
nsIntRegion neededRegion;
|
||||
bool canReuseBuffer;
|
||||
nsIntRect destBufferRect;
|
||||
|
||||
while (true) {
|
||||
mode = aLayer->GetSurfaceMode();
|
||||
neededRegion = aLayer->GetVisibleRegion();
|
||||
// If we're going to resample, we need a buffer that's in clamp mode.
|
||||
canReuseBuffer = neededRegion.GetBounds().Size() <= mBufferRect.Size() &&
|
||||
mHasBuffer && !(aFlags & RotatedContentBuffer::PAINT_WILL_RESAMPLE);
|
||||
|
||||
if (canReuseBuffer) {
|
||||
if (mBufferRect.Contains(neededRegion.GetBounds())) {
|
||||
// We don't need to adjust mBufferRect.
|
||||
destBufferRect = mBufferRect;
|
||||
} else {
|
||||
// The buffer's big enough but doesn't contain everything that's
|
||||
// going to be visible. We'll move it.
|
||||
destBufferRect = nsIntRect(neededRegion.GetBounds().TopLeft(), mBufferRect.Size());
|
||||
}
|
||||
} else {
|
||||
destBufferRect = neededRegion.GetBounds();
|
||||
}
|
||||
|
||||
if (mode == SurfaceMode::SURFACE_COMPONENT_ALPHA) {
|
||||
if (!gfxPrefs::ComponentAlphaEnabled() ||
|
||||
!aLayer->GetParent() ||
|
||||
!aLayer->GetParent()->SupportsComponentAlphaChildren()) {
|
||||
mode = SurfaceMode::SURFACE_SINGLE_CHANNEL_ALPHA;
|
||||
} else {
|
||||
contentType = gfxContentType::COLOR;
|
||||
}
|
||||
}
|
||||
|
||||
if ((aFlags & RotatedContentBuffer::PAINT_WILL_RESAMPLE) &&
|
||||
(!neededRegion.GetBounds().IsEqualInterior(destBufferRect) ||
|
||||
neededRegion.GetNumRects() > 1)) {
|
||||
// The area we add to neededRegion might not be painted opaquely
|
||||
if (mode == SurfaceMode::SURFACE_OPAQUE) {
|
||||
contentType = gfxContentType::COLOR_ALPHA;
|
||||
mode = SurfaceMode::SURFACE_SINGLE_CHANNEL_ALPHA;
|
||||
}
|
||||
// For component alpha layers, we leave contentType as gfxContentType::COLOR.
|
||||
|
||||
// We need to validate the entire buffer, to make sure that only valid
|
||||
// pixels are sampled
|
||||
neededRegion = destBufferRect;
|
||||
}
|
||||
|
||||
if (mHasBuffer &&
|
||||
(mContentType != contentType ||
|
||||
(mode == SurfaceMode::SURFACE_COMPONENT_ALPHA) != mHasBufferOnWhite)) {
|
||||
#ifdef MOZ_DUMP_PAINTING
|
||||
if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
|
||||
if (mContentType != contentType) {
|
||||
printf_stderr("Layer's content type has changed\n");
|
||||
}
|
||||
if ((mode == SurfaceMode::SURFACE_COMPONENT_ALPHA) != mHasBufferOnWhite) {
|
||||
printf_stderr("Layer's component alpha status has changed\n");
|
||||
}
|
||||
printf_stderr("Invalidating entire layer %p: no buffer, or content type or component alpha changed\n", aLayer);
|
||||
}
|
||||
#endif
|
||||
// We're effectively clearing the valid region, so we need to draw
|
||||
// the entire needed region now.
|
||||
result.mRegionToInvalidate = aLayer->GetValidRegion();
|
||||
validRegion.SetEmpty();
|
||||
mHasBuffer = false;
|
||||
mHasBufferOnWhite = false;
|
||||
mBufferRect.SetRect(0, 0, 0, 0);
|
||||
mBufferRotation.MoveTo(0, 0);
|
||||
// Restart decision process with the cleared buffer. We can only go
|
||||
// around the loop one more iteration, since mTexImage is null now.
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
result.mRegionToDraw.Sub(neededRegion, validRegion);
|
||||
if (result.mRegionToDraw.IsEmpty())
|
||||
return result;
|
||||
|
||||
if (destBufferRect.width > mForwarder->GetMaxTextureSize() ||
|
||||
destBufferRect.height > mForwarder->GetMaxTextureSize()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// BlitTextureImage depends on the FBO texture target being
|
||||
// TEXTURE_2D. This isn't the case on some older X1600-era Radeons.
|
||||
if (!mForwarder->SupportsTextureBlitting() ||
|
||||
!mForwarder->SupportsPartialUploads()) {
|
||||
result.mRegionToDraw = neededRegion;
|
||||
validRegion.SetEmpty();
|
||||
mHasBuffer = false;
|
||||
mHasBufferOnWhite = false;
|
||||
mBufferRect.SetRect(0, 0, 0, 0);
|
||||
mBufferRotation.MoveTo(0, 0);
|
||||
canReuseBuffer = false;
|
||||
}
|
||||
|
||||
nsIntRect drawBounds = result.mRegionToDraw.GetBounds();
|
||||
bool createdBuffer = false;
|
||||
|
||||
TextureFlags bufferFlags = TextureFlags::NO_FLAGS;
|
||||
if (mode == SurfaceMode::SURFACE_COMPONENT_ALPHA) {
|
||||
bufferFlags |= TextureFlags::COMPONENT_ALPHA;
|
||||
}
|
||||
if (canReuseBuffer) {
|
||||
nsIntRect keepArea;
|
||||
if (keepArea.IntersectRect(destBufferRect, mBufferRect)) {
|
||||
// Set mBufferRotation so that the pixels currently in mBuffer
|
||||
// will still be rendered in the right place when mBufferRect
|
||||
// changes to destBufferRect.
|
||||
nsIntPoint newRotation = mBufferRotation +
|
||||
(destBufferRect.TopLeft() - mBufferRect.TopLeft());
|
||||
WrapRotationAxis(&newRotation.x, mBufferRect.width);
|
||||
WrapRotationAxis(&newRotation.y, mBufferRect.height);
|
||||
NS_ASSERTION(nsIntRect(nsIntPoint(0,0), mBufferRect.Size()).Contains(newRotation),
|
||||
"newRotation out of bounds");
|
||||
int32_t xBoundary = destBufferRect.XMost() - newRotation.x;
|
||||
int32_t yBoundary = destBufferRect.YMost() - newRotation.y;
|
||||
if ((drawBounds.x < xBoundary && xBoundary < drawBounds.XMost()) ||
|
||||
(drawBounds.y < yBoundary && yBoundary < drawBounds.YMost()) ||
|
||||
(newRotation != nsIntPoint(0,0) && !canHaveRotation)) {
|
||||
// The stuff we need to redraw will wrap around an edge of the
|
||||
// buffer, so we will need to do a self-copy
|
||||
// If mBufferRotation == nsIntPoint(0,0) we could do a real
|
||||
// self-copy but we're not going to do that in GL yet.
|
||||
// We can't do a real self-copy because the buffer is rotated.
|
||||
// So allocate a new buffer for the destination.
|
||||
destBufferRect = neededRegion.GetBounds();
|
||||
createdBuffer = true;
|
||||
} else {
|
||||
mBufferRect = destBufferRect;
|
||||
mBufferRotation = newRotation;
|
||||
}
|
||||
} else {
|
||||
// No pixels are going to be kept. The whole visible region
|
||||
// will be redrawn, so we don't need to copy anything, so we don't
|
||||
// set destBuffer.
|
||||
mBufferRect = destBufferRect;
|
||||
mBufferRotation = nsIntPoint(0,0);
|
||||
}
|
||||
} else {
|
||||
// The buffer's not big enough, so allocate a new one
|
||||
createdBuffer = true;
|
||||
}
|
||||
NS_ASSERTION(!(aFlags & RotatedContentBuffer::PAINT_WILL_RESAMPLE) ||
|
||||
destBufferRect == neededRegion.GetBounds(),
|
||||
"If we're resampling, we need to validate the entire buffer");
|
||||
|
||||
if (!createdBuffer && !mHasBuffer) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (createdBuffer) {
|
||||
if (mHasBuffer &&
|
||||
(mode != SurfaceMode::SURFACE_COMPONENT_ALPHA || mHasBufferOnWhite)) {
|
||||
mTextureInfo.mDeprecatedTextureHostFlags = DeprecatedTextureHostFlags::COPY_PREVIOUS;
|
||||
}
|
||||
|
||||
mHasBuffer = true;
|
||||
if (mode == SurfaceMode::SURFACE_COMPONENT_ALPHA) {
|
||||
mHasBufferOnWhite = true;
|
||||
}
|
||||
mBufferRect = destBufferRect;
|
||||
mBufferRotation = nsIntPoint(0,0);
|
||||
NotifyBufferCreated(contentType, bufferFlags);
|
||||
}
|
||||
|
||||
NS_ASSERTION(canHaveRotation || mBufferRotation == nsIntPoint(0,0),
|
||||
"Rotation disabled, but we have nonzero rotation?");
|
||||
|
||||
nsIntRegion invalidate;
|
||||
invalidate.Sub(aLayer->GetValidRegion(), destBufferRect);
|
||||
result.mRegionToInvalidate.Or(result.mRegionToInvalidate, invalidate);
|
||||
|
||||
// If we do partial updates, we have to clip drawing to the regionToDraw.
|
||||
// If we don't clip, background images will be fillrect'd to the region correctly,
|
||||
// while text or lines will paint outside of the regionToDraw. This becomes apparent
|
||||
// with concave regions. Right now the scrollbars invalidate a narrow strip of the bar
|
||||
// although they never cover it. This leads to two draw rects, the narow strip and the actually
|
||||
// newly exposed area. It would be wise to fix this glitch in any way to have simpler
|
||||
// clip and draw regions.
|
||||
result.mClip = DrawRegionClip::DRAW;
|
||||
result.mMode = mode;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
DrawTarget*
|
||||
ContentClientIncremental::BorrowDrawTargetForPainting(PaintState& aPaintState,
|
||||
RotatedContentBuffer::DrawIterator* aIter)
|
||||
{
|
||||
if (aPaintState.mMode == SurfaceMode::SURFACE_NONE) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (aIter) {
|
||||
if (aIter->mCount++ > 0) {
|
||||
return nullptr;
|
||||
}
|
||||
aIter->mDrawRegion = aPaintState.mRegionToDraw;
|
||||
}
|
||||
|
||||
DrawTarget* result = nullptr;
|
||||
|
||||
nsIntRect drawBounds = aPaintState.mRegionToDraw.GetBounds();
|
||||
MOZ_ASSERT(!mLoanedDrawTarget);
|
||||
|
||||
// BeginUpdate is allowed to modify the given region,
|
||||
// if it wants more to be repainted than we request.
|
||||
if (aPaintState.mMode == SurfaceMode::SURFACE_COMPONENT_ALPHA) {
|
||||
nsIntRegion drawRegionCopy = aPaintState.mRegionToDraw;
|
||||
RefPtr<DrawTarget> onBlack = GetUpdateSurface(BUFFER_BLACK, drawRegionCopy);
|
||||
RefPtr<DrawTarget> onWhite = GetUpdateSurface(BUFFER_WHITE, aPaintState.mRegionToDraw);
|
||||
if (onBlack && onWhite) {
|
||||
NS_ASSERTION(aPaintState.mRegionToDraw == drawRegionCopy,
|
||||
"BeginUpdate should always modify the draw region in the same way!");
|
||||
FillSurface(onBlack, aPaintState.mRegionToDraw, nsIntPoint(drawBounds.x, drawBounds.y), gfxRGBA(0.0, 0.0, 0.0, 1.0));
|
||||
FillSurface(onWhite, aPaintState.mRegionToDraw, nsIntPoint(drawBounds.x, drawBounds.y), gfxRGBA(1.0, 1.0, 1.0, 1.0));
|
||||
mLoanedDrawTarget = Factory::CreateDualDrawTarget(onBlack, onWhite);
|
||||
} else {
|
||||
mLoanedDrawTarget = nullptr;
|
||||
}
|
||||
} else {
|
||||
mLoanedDrawTarget = GetUpdateSurface(BUFFER_BLACK, aPaintState.mRegionToDraw);
|
||||
}
|
||||
if (!mLoanedDrawTarget) {
|
||||
NS_WARNING("unable to get context for update");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
result = mLoanedDrawTarget;
|
||||
mLoanedTransform = mLoanedDrawTarget->GetTransform();
|
||||
result->SetTransform(Matrix(mLoanedTransform).
|
||||
PreTranslate(-drawBounds.x, -drawBounds.y));
|
||||
|
||||
if (mContentType == gfxContentType::COLOR_ALPHA) {
|
||||
gfxUtils::ClipToRegion(result, aPaintState.mRegionToDraw);
|
||||
nsIntRect bounds = aPaintState.mRegionToDraw.GetBounds();
|
||||
result->ClearRect(Rect(bounds.x, bounds.y, bounds.width, bounds.height));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void
|
||||
ContentClientIncremental::Updated(const nsIntRegion& aRegionToDraw,
|
||||
const nsIntRegion& aVisibleRegion,
|
||||
bool aDidSelfCopy)
|
||||
{
|
||||
if (IsSurfaceDescriptorValid(mUpdateDescriptor)) {
|
||||
mForwarder->UpdateTextureIncremental(this,
|
||||
TextureIdentifier::Front,
|
||||
mUpdateDescriptor,
|
||||
aRegionToDraw,
|
||||
mBufferRect,
|
||||
mBufferRotation);
|
||||
mUpdateDescriptor = SurfaceDescriptor();
|
||||
}
|
||||
if (IsSurfaceDescriptorValid(mUpdateDescriptorOnWhite)) {
|
||||
mForwarder->UpdateTextureIncremental(this,
|
||||
TextureIdentifier::OnWhiteFront,
|
||||
mUpdateDescriptorOnWhite,
|
||||
aRegionToDraw,
|
||||
mBufferRect,
|
||||
mBufferRotation);
|
||||
mUpdateDescriptorOnWhite = SurfaceDescriptor();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
already_AddRefed<DrawTarget>
|
||||
ContentClientIncremental::GetUpdateSurface(BufferType aType,
|
||||
const nsIntRegion& aUpdateRegion)
|
||||
{
|
||||
nsIntRect rgnSize = aUpdateRegion.GetBounds();
|
||||
if (!mBufferRect.Contains(rgnSize)) {
|
||||
NS_ERROR("update outside of image");
|
||||
return nullptr;
|
||||
}
|
||||
SurfaceDescriptor desc;
|
||||
if (!mForwarder->AllocSurfaceDescriptor(rgnSize.Size().ToIntSize(),
|
||||
mContentType,
|
||||
&desc)) {
|
||||
NS_WARNING("creating SurfaceDescriptor failed!");
|
||||
Clear();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (aType == BUFFER_BLACK) {
|
||||
MOZ_ASSERT(!IsSurfaceDescriptorValid(mUpdateDescriptor));
|
||||
mUpdateDescriptor = desc;
|
||||
} else {
|
||||
MOZ_ASSERT(!IsSurfaceDescriptorValid(mUpdateDescriptorOnWhite));
|
||||
MOZ_ASSERT(aType == BUFFER_WHITE);
|
||||
mUpdateDescriptorOnWhite = desc;
|
||||
}
|
||||
|
||||
return GetDrawTargetForDescriptor(desc, gfx::BackendType::COREGRAPHICS);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -397,89 +397,6 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A single buffered ContentClient that creates temporary buffers which are
|
||||
* used to update the host-side texture. The ownership of the buffers is
|
||||
* passed to the host side during the transaction, and we need to create
|
||||
* new ones each frame.
|
||||
*/
|
||||
class ContentClientIncremental : public ContentClientRemote
|
||||
, public BorrowDrawTarget
|
||||
{
|
||||
public:
|
||||
explicit ContentClientIncremental(CompositableForwarder* aFwd)
|
||||
: ContentClientRemote(aFwd)
|
||||
, mContentType(gfxContentType::COLOR_ALPHA)
|
||||
, mHasBuffer(false)
|
||||
, mHasBufferOnWhite(false)
|
||||
{
|
||||
mTextureInfo.mCompositableType = CompositableType::CONTENT_INC;
|
||||
}
|
||||
|
||||
typedef RotatedContentBuffer::PaintState PaintState;
|
||||
typedef RotatedContentBuffer::ContentType ContentType;
|
||||
|
||||
virtual TextureInfo GetTextureInfo() const override
|
||||
{
|
||||
return mTextureInfo;
|
||||
}
|
||||
|
||||
virtual void Clear() override
|
||||
{
|
||||
mBufferRect.SetEmpty();
|
||||
mHasBuffer = false;
|
||||
mHasBufferOnWhite = false;
|
||||
}
|
||||
|
||||
virtual PaintState BeginPaintBuffer(PaintedLayer* aLayer,
|
||||
uint32_t aFlags) override;
|
||||
virtual gfx::DrawTarget* BorrowDrawTargetForPainting(PaintState& aPaintState,
|
||||
RotatedContentBuffer::DrawIterator* aIter = nullptr) override;
|
||||
virtual void ReturnDrawTargetToBuffer(gfx::DrawTarget*& aReturned) override
|
||||
{
|
||||
BorrowDrawTarget::ReturnDrawTarget(aReturned);
|
||||
}
|
||||
|
||||
virtual void Updated(const nsIntRegion& aRegionToDraw,
|
||||
const nsIntRegion& aVisibleRegion,
|
||||
bool aDidSelfCopy) override;
|
||||
|
||||
virtual void EndPaint(nsTArray<ReadbackProcessor::Update>* aReadbackUpdates = nullptr) override
|
||||
{
|
||||
if (IsSurfaceDescriptorValid(mUpdateDescriptor)) {
|
||||
mForwarder->DestroySharedSurface(&mUpdateDescriptor);
|
||||
}
|
||||
if (IsSurfaceDescriptorValid(mUpdateDescriptorOnWhite)) {
|
||||
mForwarder->DestroySharedSurface(&mUpdateDescriptorOnWhite);
|
||||
}
|
||||
ContentClientRemote::EndPaint(aReadbackUpdates);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
enum BufferType{
|
||||
BUFFER_BLACK,
|
||||
BUFFER_WHITE
|
||||
};
|
||||
|
||||
void NotifyBufferCreated(ContentType aType, TextureFlags aFlags);
|
||||
|
||||
already_AddRefed<gfx::DrawTarget> GetUpdateSurface(BufferType aType,
|
||||
const nsIntRegion& aUpdateRegion);
|
||||
|
||||
TextureInfo mTextureInfo;
|
||||
nsIntRect mBufferRect;
|
||||
nsIntPoint mBufferRotation;
|
||||
|
||||
SurfaceDescriptor mUpdateDescriptor;
|
||||
SurfaceDescriptor mUpdateDescriptorOnWhite;
|
||||
|
||||
ContentType mContentType;
|
||||
|
||||
bool mHasBuffer;
|
||||
bool mHasBufferOnWhite;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -183,9 +183,6 @@ CompositableHost::Create(const TextureInfo& aTextureInfo)
|
||||
case CompositableType::IMAGE_BRIDGE:
|
||||
NS_ERROR("Cannot create an image bridge compositable this way");
|
||||
break;
|
||||
case CompositableType::CONTENT_INC:
|
||||
result = new ContentHostIncremental(aTextureInfo);
|
||||
break;
|
||||
case CompositableType::CONTENT_TILED:
|
||||
result = new TiledContentHost(aTextureInfo);
|
||||
break;
|
||||
|
||||
@@ -101,45 +101,6 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the content host using a surface that only contains the updated
|
||||
* region.
|
||||
*
|
||||
* Takes ownership of aSurface, and is responsible for freeing it.
|
||||
*
|
||||
* @param aTextureId Texture to update.
|
||||
* @param aSurface Surface containing the update area. Its contents are relative
|
||||
* to aUpdated.TopLeft()
|
||||
* @param aUpdated Area of the content host to update.
|
||||
* @param aBufferRect New area covered by the content host.
|
||||
* @param aBufferRotation New buffer rotation.
|
||||
*/
|
||||
virtual void UpdateIncremental(TextureIdentifier aTextureId,
|
||||
SurfaceDescriptor& aSurface,
|
||||
const nsIntRegion& aUpdated,
|
||||
const nsIntRect& aBufferRect,
|
||||
const nsIntPoint& aBufferRotation)
|
||||
{
|
||||
MOZ_ASSERT(false, "should be implemented or not used");
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that a suitable texture host exists in this compsitable.
|
||||
*
|
||||
* Only used with ContentHostIncremental.
|
||||
*
|
||||
* No SurfaceDescriptor or TextureIdentifier is provider as we
|
||||
* don't have a single surface for the texture contents, and we
|
||||
* need to allocate our own one to be updated later.
|
||||
*/
|
||||
virtual bool CreatedIncrementalTexture(ISurfaceAllocator* aAllocator,
|
||||
const TextureInfo& aTextureInfo,
|
||||
const nsIntRect& aBufferRect)
|
||||
{
|
||||
NS_ERROR("should be implemented or not used");
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the front buffer.
|
||||
*/
|
||||
|
||||
@@ -388,429 +388,6 @@ ContentHostDoubleBuffered::UpdateThebes(const ThebesBufferData& aData,
|
||||
return true;
|
||||
}
|
||||
|
||||
ContentHostIncremental::ContentHostIncremental(const TextureInfo& aTextureInfo)
|
||||
: ContentHostBase(aTextureInfo)
|
||||
, mDeAllocator(nullptr)
|
||||
, mLocked(false)
|
||||
{
|
||||
}
|
||||
|
||||
ContentHostIncremental::~ContentHostIncremental()
|
||||
{
|
||||
}
|
||||
|
||||
bool
|
||||
ContentHostIncremental::CreatedIncrementalTexture(ISurfaceAllocator* aAllocator,
|
||||
const TextureInfo& aTextureInfo,
|
||||
const nsIntRect& aBufferRect)
|
||||
{
|
||||
mUpdateList.AppendElement(new TextureCreationRequest(aTextureInfo,
|
||||
aBufferRect));
|
||||
mDeAllocator = aAllocator;
|
||||
FlushUpdateQueue();
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
ContentHostIncremental::UpdateIncremental(TextureIdentifier aTextureId,
|
||||
SurfaceDescriptor& aSurface,
|
||||
const nsIntRegion& aUpdated,
|
||||
const nsIntRect& aBufferRect,
|
||||
const nsIntPoint& aBufferRotation)
|
||||
{
|
||||
mUpdateList.AppendElement(new TextureUpdateRequest(mDeAllocator,
|
||||
aTextureId,
|
||||
aSurface,
|
||||
aUpdated,
|
||||
aBufferRect,
|
||||
aBufferRotation));
|
||||
FlushUpdateQueue();
|
||||
}
|
||||
|
||||
void
|
||||
ContentHostIncremental::Composite(EffectChain& aEffectChain,
|
||||
float aOpacity,
|
||||
const gfx::Matrix4x4& aTransform,
|
||||
const Filter& aFilter,
|
||||
const Rect& aClipRect,
|
||||
const nsIntRegion* aVisibleRegion)
|
||||
{
|
||||
NS_ASSERTION(aVisibleRegion, "Requires a visible region");
|
||||
|
||||
AutoLockCompositableHost lock(this);
|
||||
if (lock.Failed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mSource) {
|
||||
return;
|
||||
}
|
||||
|
||||
RefPtr<TexturedEffect> effect = CreateTexturedEffect(mSource.get(),
|
||||
mSourceOnWhite.get(),
|
||||
aFilter, true);
|
||||
if (!effect) {
|
||||
return;
|
||||
}
|
||||
|
||||
aEffectChain.mPrimaryEffect = effect;
|
||||
|
||||
nsIntRegion tmpRegion;
|
||||
const nsIntRegion* renderRegion;
|
||||
if (PaintWillResample()) {
|
||||
// If we're resampling, then the texture image will contain exactly the
|
||||
// entire visible region's bounds, and we should draw it all in one quad
|
||||
// to avoid unexpected aliasing.
|
||||
tmpRegion = aVisibleRegion->GetBounds();
|
||||
renderRegion = &tmpRegion;
|
||||
} else {
|
||||
renderRegion = aVisibleRegion;
|
||||
}
|
||||
|
||||
nsIntRegion region(*renderRegion);
|
||||
nsIntPoint origin = GetOriginOffset();
|
||||
// translate into TexImage space, buffer origin might not be at texture (0,0)
|
||||
region.MoveBy(-origin);
|
||||
|
||||
// Figure out the intersecting draw region
|
||||
gfx::IntSize texSize = mSource->GetSize();
|
||||
nsIntRect textureRect = nsIntRect(0, 0, texSize.width, texSize.height);
|
||||
textureRect.MoveBy(region.GetBounds().TopLeft());
|
||||
nsIntRegion subregion;
|
||||
subregion.And(region, textureRect);
|
||||
if (subregion.IsEmpty()) {
|
||||
// Region is empty, nothing to draw
|
||||
return;
|
||||
}
|
||||
|
||||
nsIntRegion screenRects;
|
||||
nsIntRegion regionRects;
|
||||
|
||||
// Collect texture/screen coordinates for drawing
|
||||
nsIntRegionRectIterator iter(subregion);
|
||||
while (const nsIntRect* iterRect = iter.Next()) {
|
||||
nsIntRect regionRect = *iterRect;
|
||||
nsIntRect screenRect = regionRect;
|
||||
screenRect.MoveBy(origin);
|
||||
|
||||
screenRects.Or(screenRects, screenRect);
|
||||
regionRects.Or(regionRects, regionRect);
|
||||
}
|
||||
|
||||
BigImageIterator* bigImgIter = mSource->AsBigImageIterator();
|
||||
BigImageIterator* iterOnWhite = nullptr;
|
||||
if (bigImgIter) {
|
||||
bigImgIter->BeginBigImageIteration();
|
||||
}
|
||||
|
||||
if (mSourceOnWhite) {
|
||||
iterOnWhite = mSourceOnWhite->AsBigImageIterator();
|
||||
MOZ_ASSERT(!bigImgIter || bigImgIter->GetTileCount() == iterOnWhite->GetTileCount(),
|
||||
"Tile count mismatch on component alpha texture");
|
||||
if (iterOnWhite) {
|
||||
iterOnWhite->BeginBigImageIteration();
|
||||
}
|
||||
}
|
||||
|
||||
bool usingTiles = (bigImgIter && bigImgIter->GetTileCount() > 1);
|
||||
do {
|
||||
if (iterOnWhite) {
|
||||
MOZ_ASSERT(iterOnWhite->GetTileRect() == bigImgIter->GetTileRect(),
|
||||
"component alpha textures should be the same size.");
|
||||
}
|
||||
|
||||
nsIntRect texRect = bigImgIter ? bigImgIter->GetTileRect()
|
||||
: nsIntRect(0, 0,
|
||||
texSize.width,
|
||||
texSize.height);
|
||||
|
||||
// Draw texture. If we're using tiles, we do repeating manually, as texture
|
||||
// repeat would cause each individual tile to repeat instead of the
|
||||
// compound texture as a whole. This involves drawing at most 4 sections,
|
||||
// 2 for each axis that has texture repeat.
|
||||
for (int y = 0; y < (usingTiles ? 2 : 1); y++) {
|
||||
for (int x = 0; x < (usingTiles ? 2 : 1); x++) {
|
||||
nsIntRect currentTileRect(texRect);
|
||||
currentTileRect.MoveBy(x * texSize.width, y * texSize.height);
|
||||
|
||||
nsIntRegionRectIterator screenIter(screenRects);
|
||||
nsIntRegionRectIterator regionIter(regionRects);
|
||||
|
||||
const nsIntRect* screenRect;
|
||||
const nsIntRect* regionRect;
|
||||
while ((screenRect = screenIter.Next()) &&
|
||||
(regionRect = regionIter.Next())) {
|
||||
nsIntRect tileScreenRect(*screenRect);
|
||||
nsIntRect tileRegionRect(*regionRect);
|
||||
|
||||
// When we're using tiles, find the intersection between the tile
|
||||
// rect and this region rect. Tiling is then handled by the
|
||||
// outer for-loops and modifying the tile rect.
|
||||
if (usingTiles) {
|
||||
tileScreenRect.MoveBy(-origin);
|
||||
tileScreenRect = tileScreenRect.Intersect(currentTileRect);
|
||||
tileScreenRect.MoveBy(origin);
|
||||
|
||||
if (tileScreenRect.IsEmpty())
|
||||
continue;
|
||||
|
||||
tileRegionRect = regionRect->Intersect(currentTileRect);
|
||||
tileRegionRect.MoveBy(-currentTileRect.TopLeft());
|
||||
}
|
||||
gfx::Rect rect(tileScreenRect.x, tileScreenRect.y,
|
||||
tileScreenRect.width, tileScreenRect.height);
|
||||
|
||||
effect->mTextureCoords = Rect(Float(tileRegionRect.x) / texRect.width,
|
||||
Float(tileRegionRect.y) / texRect.height,
|
||||
Float(tileRegionRect.width) / texRect.width,
|
||||
Float(tileRegionRect.height) / texRect.height);
|
||||
GetCompositor()->DrawQuad(rect, aClipRect, aEffectChain, aOpacity, aTransform);
|
||||
if (usingTiles) {
|
||||
DiagnosticFlags diagnostics = DiagnosticFlags::CONTENT | DiagnosticFlags::BIGIMAGE;
|
||||
if (iterOnWhite) {
|
||||
diagnostics |= DiagnosticFlags::COMPONENT_ALPHA;
|
||||
}
|
||||
GetCompositor()->DrawDiagnostics(diagnostics, rect, aClipRect,
|
||||
aTransform, mFlashCounter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (iterOnWhite) {
|
||||
iterOnWhite->NextTile();
|
||||
}
|
||||
} while (usingTiles && bigImgIter->NextTile());
|
||||
|
||||
if (bigImgIter) {
|
||||
bigImgIter->EndBigImageIteration();
|
||||
}
|
||||
if (iterOnWhite) {
|
||||
iterOnWhite->EndBigImageIteration();
|
||||
}
|
||||
|
||||
DiagnosticFlags diagnostics = DiagnosticFlags::CONTENT;
|
||||
if (iterOnWhite) {
|
||||
diagnostics |= DiagnosticFlags::COMPONENT_ALPHA;
|
||||
}
|
||||
GetCompositor()->DrawDiagnostics(diagnostics, nsIntRegion(mBufferRect), aClipRect,
|
||||
aTransform, mFlashCounter);
|
||||
}
|
||||
|
||||
void
|
||||
ContentHostIncremental::FlushUpdateQueue()
|
||||
{
|
||||
// If we're not compositing for some reason (the window being minimized
|
||||
// is one example), then we never process these updates and it can consume
|
||||
// huge amounts of memory. Instead we forcibly process the updates (during the
|
||||
// transaction) if the list gets too long.
|
||||
static const uint32_t kMaxUpdateCount = 6;
|
||||
if (mUpdateList.Length() >= kMaxUpdateCount) {
|
||||
ProcessTextureUpdates();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ContentHostIncremental::ProcessTextureUpdates()
|
||||
{
|
||||
for (uint32_t i = 0; i < mUpdateList.Length(); i++) {
|
||||
mUpdateList[i]->Execute(this);
|
||||
}
|
||||
mUpdateList.Clear();
|
||||
}
|
||||
|
||||
void
|
||||
ContentHostIncremental::TextureCreationRequest::Execute(ContentHostIncremental* aHost)
|
||||
{
|
||||
Compositor* compositor = aHost->GetCompositor();
|
||||
MOZ_ASSERT(compositor);
|
||||
|
||||
RefPtr<DataTextureSource> temp =
|
||||
compositor->CreateDataTextureSource(mTextureInfo.mTextureFlags);
|
||||
MOZ_ASSERT(temp->AsSourceOGL() &&
|
||||
temp->AsSourceOGL()->AsTextureImageTextureSource());
|
||||
RefPtr<TextureImageTextureSourceOGL> newSource =
|
||||
temp->AsSourceOGL()->AsTextureImageTextureSource();
|
||||
|
||||
RefPtr<TextureImageTextureSourceOGL> newSourceOnWhite;
|
||||
if (mTextureInfo.mTextureFlags & TextureFlags::COMPONENT_ALPHA) {
|
||||
temp =
|
||||
compositor->CreateDataTextureSource(mTextureInfo.mTextureFlags);
|
||||
MOZ_ASSERT(temp->AsSourceOGL() &&
|
||||
temp->AsSourceOGL()->AsTextureImageTextureSource());
|
||||
newSourceOnWhite = temp->AsSourceOGL()->AsTextureImageTextureSource();
|
||||
}
|
||||
|
||||
if (mTextureInfo.mDeprecatedTextureHostFlags & DeprecatedTextureHostFlags::COPY_PREVIOUS) {
|
||||
MOZ_ASSERT(aHost->mSource);
|
||||
MOZ_ASSERT(aHost->mSource->IsValid());
|
||||
nsIntRect bufferRect = aHost->mBufferRect;
|
||||
nsIntPoint bufferRotation = aHost->mBufferRotation;
|
||||
nsIntRect overlap;
|
||||
|
||||
// The buffer looks like:
|
||||
// ______
|
||||
// |1 |2 | Where the center point is offset by mBufferRotation from the top-left corner.
|
||||
// |___|__|
|
||||
// |3 |4 |
|
||||
// |___|__|
|
||||
//
|
||||
// This is drawn to the screen as:
|
||||
// ______
|
||||
// |4 |3 | Where the center point is { width - mBufferRotation.x, height - mBufferRotation.y } from
|
||||
// |___|__| from the top left corner - rotationPoint.
|
||||
// |2 |1 |
|
||||
// |___|__|
|
||||
//
|
||||
|
||||
// The basic idea below is to take all quadrant rectangles from the src and transform them into rectangles
|
||||
// in the destination. Unfortunately, it seems it is overly complex and could perhaps be simplified.
|
||||
|
||||
nsIntRect srcBufferSpaceBottomRight(bufferRotation.x, bufferRotation.y, bufferRect.width - bufferRotation.x, bufferRect.height - bufferRotation.y);
|
||||
nsIntRect srcBufferSpaceTopRight(bufferRotation.x, 0, bufferRect.width - bufferRotation.x, bufferRotation.y);
|
||||
nsIntRect srcBufferSpaceTopLeft(0, 0, bufferRotation.x, bufferRotation.y);
|
||||
nsIntRect srcBufferSpaceBottomLeft(0, bufferRotation.y, bufferRotation.x, bufferRect.height - bufferRotation.y);
|
||||
|
||||
overlap.IntersectRect(bufferRect, mBufferRect);
|
||||
|
||||
nsIntRect srcRect(overlap), dstRect(overlap);
|
||||
srcRect.MoveBy(- bufferRect.TopLeft() + bufferRotation);
|
||||
|
||||
nsIntRect srcRectDrawTopRight(srcRect);
|
||||
nsIntRect srcRectDrawTopLeft(srcRect);
|
||||
nsIntRect srcRectDrawBottomLeft(srcRect);
|
||||
// transform into the different quadrants
|
||||
srcRectDrawTopRight .MoveBy(-nsIntPoint(0, bufferRect.height));
|
||||
srcRectDrawTopLeft .MoveBy(-nsIntPoint(bufferRect.width, bufferRect.height));
|
||||
srcRectDrawBottomLeft.MoveBy(-nsIntPoint(bufferRect.width, 0));
|
||||
|
||||
// Intersect with the quadrant
|
||||
srcRect = srcRect .Intersect(srcBufferSpaceBottomRight);
|
||||
srcRectDrawTopRight = srcRectDrawTopRight .Intersect(srcBufferSpaceTopRight);
|
||||
srcRectDrawTopLeft = srcRectDrawTopLeft .Intersect(srcBufferSpaceTopLeft);
|
||||
srcRectDrawBottomLeft = srcRectDrawBottomLeft.Intersect(srcBufferSpaceBottomLeft);
|
||||
|
||||
dstRect = srcRect;
|
||||
nsIntRect dstRectDrawTopRight(srcRectDrawTopRight);
|
||||
nsIntRect dstRectDrawTopLeft(srcRectDrawTopLeft);
|
||||
nsIntRect dstRectDrawBottomLeft(srcRectDrawBottomLeft);
|
||||
|
||||
// transform back to src buffer space
|
||||
dstRect .MoveBy(-bufferRotation);
|
||||
dstRectDrawTopRight .MoveBy(-bufferRotation + nsIntPoint(0, bufferRect.height));
|
||||
dstRectDrawTopLeft .MoveBy(-bufferRotation + nsIntPoint(bufferRect.width, bufferRect.height));
|
||||
dstRectDrawBottomLeft.MoveBy(-bufferRotation + nsIntPoint(bufferRect.width, 0));
|
||||
|
||||
// transform back to draw coordinates
|
||||
dstRect .MoveBy(bufferRect.TopLeft());
|
||||
dstRectDrawTopRight .MoveBy(bufferRect.TopLeft());
|
||||
dstRectDrawTopLeft .MoveBy(bufferRect.TopLeft());
|
||||
dstRectDrawBottomLeft.MoveBy(bufferRect.TopLeft());
|
||||
|
||||
// transform to destBuffer space
|
||||
dstRect .MoveBy(-mBufferRect.TopLeft());
|
||||
dstRectDrawTopRight .MoveBy(-mBufferRect.TopLeft());
|
||||
dstRectDrawTopLeft .MoveBy(-mBufferRect.TopLeft());
|
||||
dstRectDrawBottomLeft.MoveBy(-mBufferRect.TopLeft());
|
||||
|
||||
newSource->EnsureBuffer(mBufferRect.Size(),
|
||||
ContentForFormat(aHost->mSource->GetFormat()));
|
||||
|
||||
aHost->mSource->CopyTo(srcRect, newSource, dstRect);
|
||||
if (bufferRotation != nsIntPoint(0, 0)) {
|
||||
// Draw the remaining quadrants. We call BlitTextureImage 3 extra
|
||||
// times instead of doing a single draw call because supporting that
|
||||
// with a tiled source is quite tricky.
|
||||
|
||||
if (!srcRectDrawTopRight.IsEmpty())
|
||||
aHost->mSource->CopyTo(srcRectDrawTopRight,
|
||||
newSource, dstRectDrawTopRight);
|
||||
if (!srcRectDrawTopLeft.IsEmpty())
|
||||
aHost->mSource->CopyTo(srcRectDrawTopLeft,
|
||||
newSource, dstRectDrawTopLeft);
|
||||
if (!srcRectDrawBottomLeft.IsEmpty())
|
||||
aHost->mSource->CopyTo(srcRectDrawBottomLeft,
|
||||
newSource, dstRectDrawBottomLeft);
|
||||
}
|
||||
|
||||
if (newSourceOnWhite) {
|
||||
newSourceOnWhite->EnsureBuffer(mBufferRect.Size(),
|
||||
ContentForFormat(aHost->mSourceOnWhite->GetFormat()));
|
||||
aHost->mSourceOnWhite->CopyTo(srcRect, newSourceOnWhite, dstRect);
|
||||
if (bufferRotation != nsIntPoint(0, 0)) {
|
||||
// draw the remaining quadrants
|
||||
if (!srcRectDrawTopRight.IsEmpty())
|
||||
aHost->mSourceOnWhite->CopyTo(srcRectDrawTopRight,
|
||||
newSourceOnWhite, dstRectDrawTopRight);
|
||||
if (!srcRectDrawTopLeft.IsEmpty())
|
||||
aHost->mSourceOnWhite->CopyTo(srcRectDrawTopLeft,
|
||||
newSourceOnWhite, dstRectDrawTopLeft);
|
||||
if (!srcRectDrawBottomLeft.IsEmpty())
|
||||
aHost->mSourceOnWhite->CopyTo(srcRectDrawBottomLeft,
|
||||
newSourceOnWhite, dstRectDrawBottomLeft);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
aHost->mSource = newSource;
|
||||
aHost->mSourceOnWhite = newSourceOnWhite;
|
||||
|
||||
aHost->mBufferRect = mBufferRect;
|
||||
aHost->mBufferRotation = nsIntPoint();
|
||||
}
|
||||
|
||||
nsIntRect
|
||||
ContentHostIncremental::TextureUpdateRequest::GetQuadrantRectangle(XSide aXSide,
|
||||
YSide aYSide) const
|
||||
{
|
||||
// quadrantTranslation is the amount we translate the top-left
|
||||
// of the quadrant by to get coordinates relative to the layer
|
||||
nsIntPoint quadrantTranslation = -mBufferRotation;
|
||||
quadrantTranslation.x += aXSide == LEFT ? mBufferRect.width : 0;
|
||||
quadrantTranslation.y += aYSide == TOP ? mBufferRect.height : 0;
|
||||
return mBufferRect + quadrantTranslation;
|
||||
}
|
||||
|
||||
void
|
||||
ContentHostIncremental::TextureUpdateRequest::Execute(ContentHostIncremental* aHost)
|
||||
{
|
||||
nsIntRect drawBounds = mUpdated.GetBounds();
|
||||
|
||||
aHost->mBufferRect = mBufferRect;
|
||||
aHost->mBufferRotation = mBufferRotation;
|
||||
|
||||
// Figure out which quadrant to draw in
|
||||
int32_t xBoundary = mBufferRect.XMost() - mBufferRotation.x;
|
||||
int32_t yBoundary = mBufferRect.YMost() - mBufferRotation.y;
|
||||
XSide sideX = drawBounds.XMost() <= xBoundary ? RIGHT : LEFT;
|
||||
YSide sideY = drawBounds.YMost() <= yBoundary ? BOTTOM : TOP;
|
||||
nsIntRect quadrantRect = GetQuadrantRectangle(sideX, sideY);
|
||||
NS_ASSERTION(quadrantRect.Contains(drawBounds), "Messed up quadrants");
|
||||
|
||||
mUpdated.MoveBy(-nsIntPoint(quadrantRect.x, quadrantRect.y));
|
||||
|
||||
IntPoint offset = ToIntPoint(-mUpdated.GetBounds().TopLeft());
|
||||
|
||||
RefPtr<DataSourceSurface> surf = GetSurfaceForDescriptor(mDescriptor);
|
||||
|
||||
if (mTextureId == TextureIdentifier::Front) {
|
||||
aHost->mSource->Update(surf, &mUpdated, &offset);
|
||||
} else {
|
||||
aHost->mSourceOnWhite->Update(surf, &mUpdated, &offset);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ContentHostIncremental::PrintInfo(std::stringstream& aStream, const char* aPrefix)
|
||||
{
|
||||
aStream << aPrefix;
|
||||
aStream << nsPrintfCString("ContentHostIncremental (0x%p)", this).get();
|
||||
|
||||
if (PaintWillResample()) {
|
||||
aStream << " [paint-will-resample]";
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ContentHostTexture::PrintInfo(std::stringstream& aStream, const char* aPrefix)
|
||||
{
|
||||
@@ -869,15 +446,6 @@ ContentHostTexture::GenEffect(const gfx::Filter& aFilter)
|
||||
aFilter, true);
|
||||
}
|
||||
|
||||
already_AddRefed<TexturedEffect>
|
||||
ContentHostIncremental::GenEffect(const gfx::Filter& aFilter)
|
||||
{
|
||||
if (!mSource) {
|
||||
return nullptr;
|
||||
}
|
||||
return CreateTexturedEffect(mSource, mSourceOnWhite, aFilter, true);
|
||||
}
|
||||
|
||||
already_AddRefed<gfx::DataSourceSurface>
|
||||
ContentHostTexture::GetAsSurface()
|
||||
{
|
||||
|
||||
@@ -223,160 +223,6 @@ public:
|
||||
nsIntRegion* aUpdatedRegionBack);
|
||||
};
|
||||
|
||||
/**
|
||||
* Maintains a host-side only texture, and gets provided with
|
||||
* surfaces that only cover the changed pixels during an update.
|
||||
*
|
||||
* Takes ownership of the passed in update surfaces, and must
|
||||
* free them once texture upload is complete.
|
||||
*
|
||||
* Delays texture uploads until the next composite to
|
||||
* avoid blocking the main thread.
|
||||
*/
|
||||
class ContentHostIncremental : public ContentHostBase
|
||||
{
|
||||
public:
|
||||
explicit ContentHostIncremental(const TextureInfo& aTextureInfo);
|
||||
~ContentHostIncremental();
|
||||
|
||||
virtual CompositableType GetType() override { return CompositableType::CONTENT_INC; }
|
||||
|
||||
virtual LayerRenderState GetRenderState() override { return LayerRenderState(); }
|
||||
|
||||
virtual bool CreatedIncrementalTexture(ISurfaceAllocator* aAllocator,
|
||||
const TextureInfo& aTextureInfo,
|
||||
const nsIntRect& aBufferRect) override;
|
||||
|
||||
virtual void UpdateIncremental(TextureIdentifier aTextureId,
|
||||
SurfaceDescriptor& aSurface,
|
||||
const nsIntRegion& aUpdated,
|
||||
const nsIntRect& aBufferRect,
|
||||
const nsIntPoint& aBufferRotation) override;
|
||||
|
||||
virtual bool UpdateThebes(const ThebesBufferData& aData,
|
||||
const nsIntRegion& aUpdated,
|
||||
const nsIntRegion& aOldValidRegionBack,
|
||||
nsIntRegion* aUpdatedRegionBack) override
|
||||
{
|
||||
NS_ERROR("Shouldn't call this");
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual void Composite(EffectChain& aEffectChain,
|
||||
float aOpacity,
|
||||
const gfx::Matrix4x4& aTransform,
|
||||
const gfx::Filter& aFilter,
|
||||
const gfx::Rect& aClipRect,
|
||||
const nsIntRegion* aVisibleRegion = nullptr) override;
|
||||
|
||||
virtual void PrintInfo(std::stringstream& aStream, const char* aPrefix) override;
|
||||
|
||||
virtual bool Lock() override {
|
||||
MOZ_ASSERT(!mLocked);
|
||||
ProcessTextureUpdates();
|
||||
mLocked = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void Unlock() override {
|
||||
MOZ_ASSERT(mLocked);
|
||||
mLocked = false;
|
||||
}
|
||||
|
||||
virtual already_AddRefed<TexturedEffect>
|
||||
GenEffect(const gfx::Filter& aFilter) override;
|
||||
|
||||
private:
|
||||
|
||||
void FlushUpdateQueue();
|
||||
void ProcessTextureUpdates();
|
||||
|
||||
class Request
|
||||
{
|
||||
public:
|
||||
Request()
|
||||
{
|
||||
MOZ_COUNT_CTOR(ContentHostIncremental::Request);
|
||||
}
|
||||
|
||||
virtual ~Request()
|
||||
{
|
||||
MOZ_COUNT_DTOR(ContentHostIncremental::Request);
|
||||
}
|
||||
|
||||
virtual void Execute(ContentHostIncremental *aHost) = 0;
|
||||
};
|
||||
|
||||
class TextureCreationRequest : public Request
|
||||
{
|
||||
public:
|
||||
TextureCreationRequest(const TextureInfo& aTextureInfo,
|
||||
const nsIntRect& aBufferRect)
|
||||
: mTextureInfo(aTextureInfo)
|
||||
, mBufferRect(aBufferRect)
|
||||
{}
|
||||
|
||||
virtual void Execute(ContentHostIncremental *aHost);
|
||||
|
||||
private:
|
||||
TextureInfo mTextureInfo;
|
||||
nsIntRect mBufferRect;
|
||||
};
|
||||
|
||||
class TextureUpdateRequest : public Request
|
||||
{
|
||||
public:
|
||||
TextureUpdateRequest(ISurfaceAllocator* aDeAllocator,
|
||||
TextureIdentifier aTextureId,
|
||||
SurfaceDescriptor& aDescriptor,
|
||||
const nsIntRegion& aUpdated,
|
||||
const nsIntRect& aBufferRect,
|
||||
const nsIntPoint& aBufferRotation)
|
||||
: mDeAllocator(aDeAllocator)
|
||||
, mTextureId(aTextureId)
|
||||
, mDescriptor(aDescriptor)
|
||||
, mUpdated(aUpdated)
|
||||
, mBufferRect(aBufferRect)
|
||||
, mBufferRotation(aBufferRotation)
|
||||
{}
|
||||
|
||||
~TextureUpdateRequest()
|
||||
{
|
||||
//TODO: Recycle these?
|
||||
mDeAllocator->DestroySharedSurface(&mDescriptor);
|
||||
}
|
||||
|
||||
virtual void Execute(ContentHostIncremental *aHost);
|
||||
|
||||
private:
|
||||
enum XSide {
|
||||
LEFT, RIGHT
|
||||
};
|
||||
enum YSide {
|
||||
TOP, BOTTOM
|
||||
};
|
||||
|
||||
nsIntRect GetQuadrantRectangle(XSide aXSide, YSide aYSide) const;
|
||||
|
||||
RefPtr<ISurfaceAllocator> mDeAllocator;
|
||||
TextureIdentifier mTextureId;
|
||||
SurfaceDescriptor mDescriptor;
|
||||
nsIntRegion mUpdated;
|
||||
nsIntRect mBufferRect;
|
||||
nsIntPoint mBufferRotation;
|
||||
};
|
||||
|
||||
nsTArray<UniquePtr<Request> > mUpdateList;
|
||||
|
||||
// Specific to OGL to avoid exposing methods on TextureSource that only
|
||||
// have one implementation.
|
||||
RefPtr<TextureImageTextureSourceOGL> mSource;
|
||||
RefPtr<TextureImageTextureSourceOGL> mSourceOnWhite;
|
||||
|
||||
RefPtr<ISurfaceAllocator> mDeAllocator;
|
||||
bool mLocked;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +53,6 @@ bool
|
||||
PaintedLayerComposite::SetCompositableHost(CompositableHost* aHost)
|
||||
{
|
||||
switch (aHost->GetType()) {
|
||||
case CompositableType::CONTENT_INC:
|
||||
case CompositableType::CONTENT_TILED:
|
||||
case CompositableType::CONTENT_SINGLE:
|
||||
case CompositableType::CONTENT_DOUBLE:
|
||||
|
||||
@@ -55,14 +55,6 @@ public:
|
||||
*/
|
||||
virtual void Connect(CompositableClient* aCompositable) = 0;
|
||||
|
||||
/**
|
||||
* Notify the CompositableHost that it should create host-side-only
|
||||
* texture(s), that we will update incrementally using UpdateTextureIncremental.
|
||||
*/
|
||||
virtual void CreatedIncrementalBuffer(CompositableClient* aCompositable,
|
||||
const TextureInfo& aTextureInfo,
|
||||
const nsIntRect& aBufferRect) = 0;
|
||||
|
||||
/**
|
||||
* Tell the CompositableHost on the compositor side what TiledLayerBuffer to
|
||||
* use for the next composition.
|
||||
@@ -83,23 +75,6 @@ public:
|
||||
const ThebesBufferData& aThebesBufferData,
|
||||
const nsIntRegion& aUpdatedRegion) = 0;
|
||||
|
||||
/**
|
||||
* Notify the compositor to update aTextureId using aDescriptor, and take
|
||||
* ownership of aDescriptor.
|
||||
*
|
||||
* aDescriptor only contains the pixels for aUpdatedRegion, and is relative
|
||||
* to aUpdatedRegion.TopLeft().
|
||||
*
|
||||
* aBufferRect/aBufferRotation define the new valid region contained
|
||||
* within the texture after the update has been applied.
|
||||
*/
|
||||
virtual void UpdateTextureIncremental(CompositableClient* aCompositable,
|
||||
TextureIdentifier aTextureId,
|
||||
SurfaceDescriptor& aDescriptor,
|
||||
const nsIntRegion& aUpdatedRegion,
|
||||
const nsIntRect& aBufferRect,
|
||||
const nsIntPoint& aBufferRotation) = 0;
|
||||
|
||||
/**
|
||||
* Communicate the picture rect of a YUV image in aLayer to the compositor
|
||||
*/
|
||||
|
||||
@@ -73,20 +73,6 @@ CompositableParentManager::ReceiveCompositableUpdate(const CompositableOperation
|
||||
EditReplyVector& replyv)
|
||||
{
|
||||
switch (aEdit.type()) {
|
||||
case CompositableOperation::TOpCreatedIncrementalTexture: {
|
||||
MOZ_LAYERS_LOG(("[ParentSide] Created texture"));
|
||||
const OpCreatedIncrementalTexture& op = aEdit.get_OpCreatedIncrementalTexture();
|
||||
CompositableHost* compositable = AsCompositable(op);
|
||||
|
||||
bool success =
|
||||
compositable->CreatedIncrementalTexture(this,
|
||||
op.textureInfo(),
|
||||
op.bufferRect());
|
||||
if (!success) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CompositableOperation::TOpPaintTextureRegion: {
|
||||
MOZ_LAYERS_LOG(("[ParentSide] Paint PaintedLayer"));
|
||||
|
||||
@@ -116,22 +102,6 @@ CompositableParentManager::ReceiveCompositableUpdate(const CompositableOperation
|
||||
RenderTraceInvalidateEnd(thebes, "FF00FF");
|
||||
break;
|
||||
}
|
||||
case CompositableOperation::TOpPaintTextureIncremental: {
|
||||
MOZ_LAYERS_LOG(("[ParentSide] Paint PaintedLayer"));
|
||||
|
||||
const OpPaintTextureIncremental& op = aEdit.get_OpPaintTextureIncremental();
|
||||
|
||||
CompositableHost* compositable = AsCompositable(op);
|
||||
|
||||
SurfaceDescriptor desc = op.image();
|
||||
|
||||
compositable->UpdateIncremental(op.textureId(),
|
||||
desc,
|
||||
op.updatedRegion(),
|
||||
op.bufferRect(),
|
||||
op.bufferRotation());
|
||||
break;
|
||||
}
|
||||
case CompositableOperation::TOpUpdatePictureRect: {
|
||||
const OpUpdatePictureRect& op = aEdit.get_OpUpdatePictureRect();
|
||||
CompositableHost* compositable = AsCompositable(op);
|
||||
|
||||
@@ -541,17 +541,6 @@ ImageBridgeChild::EndTransaction()
|
||||
for (nsTArray<EditReply>::size_type i = 0; i < replies.Length(); ++i) {
|
||||
const EditReply& reply = replies[i];
|
||||
switch (reply.type()) {
|
||||
case EditReply::TOpTextureSwap: {
|
||||
const OpTextureSwap& ots = reply.get_OpTextureSwap();
|
||||
|
||||
CompositableClient* compositable =
|
||||
CompositableClient::FromIPDLActor(ots.compositableChild());
|
||||
|
||||
MOZ_ASSERT(compositable);
|
||||
|
||||
compositable->SetDescriptorFromReply(ots.textureId(), ots.image());
|
||||
break;
|
||||
}
|
||||
case EditReply::TReturnReleaseFence: {
|
||||
const ReturnReleaseFence& rep = reply.get_ReturnReleaseFence();
|
||||
FenceHandle fence = rep.fence();
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
#include "mozilla/ipc/SharedMemory.h" // for SharedMemory, etc
|
||||
#include "mozilla/layers/AsyncTransactionTracker.h" // for AsyncTransactionTrackerHolder
|
||||
#include "mozilla/layers/CompositableForwarder.h"
|
||||
#include "mozilla/layers/CompositorTypes.h" // for TextureIdentifier, etc
|
||||
#include "mozilla/layers/CompositorTypes.h"
|
||||
#include "mozilla/layers/PImageBridgeChild.h"
|
||||
#include "nsDebug.h" // for NS_RUNTIMEABORT
|
||||
#include "nsRegion.h" // for nsIntRegion
|
||||
@@ -248,16 +248,6 @@ public:
|
||||
NS_RUNTIMEABORT("should not be called");
|
||||
}
|
||||
|
||||
virtual void UpdateTextureIncremental(CompositableClient* aCompositable,
|
||||
TextureIdentifier aTextureId,
|
||||
SurfaceDescriptor& aDescriptor,
|
||||
const nsIntRegion& aUpdatedRegion,
|
||||
const nsIntRect& aBufferRect,
|
||||
const nsIntPoint& aBufferRotation) override
|
||||
{
|
||||
NS_RUNTIMEABORT("should not be called");
|
||||
}
|
||||
|
||||
/**
|
||||
* Communicate the picture rect of a YUV image in aLayer to the compositor
|
||||
*/
|
||||
@@ -265,12 +255,6 @@ public:
|
||||
const nsIntRect& aRect) override;
|
||||
|
||||
|
||||
virtual void CreatedIncrementalBuffer(CompositableClient* aCompositable,
|
||||
const TextureInfo& aTextureInfo,
|
||||
const nsIntRect& aBufferRect) override
|
||||
{
|
||||
NS_RUNTIMEABORT("should not be called");
|
||||
}
|
||||
virtual void UpdateTextureRegion(CompositableClient* aCompositable,
|
||||
const ThebesBufferData& aThebesBufferData,
|
||||
const nsIntRegion& aUpdatedRegion) override {
|
||||
|
||||
@@ -41,7 +41,6 @@ using struct mozilla::layers::FrameMetrics from "FrameMetrics.h";
|
||||
using mozilla::layers::FrameMetrics::ViewID from "FrameMetrics.h";
|
||||
using struct mozilla::layers::FenceHandle from "mozilla/layers/FenceUtils.h";
|
||||
using struct mozilla::layers::FenceHandleFromChild from "mozilla/layers/FenceUtils.h";
|
||||
using mozilla::layers::TextureIdentifier from "mozilla/layers/CompositorTypes.h";
|
||||
using std::string from "string";
|
||||
|
||||
namespace mozilla {
|
||||
@@ -340,27 +339,12 @@ struct OpUseOverlaySource {
|
||||
OverlaySource overlay;
|
||||
};
|
||||
|
||||
struct OpCreatedIncrementalTexture {
|
||||
PCompositable compositable;
|
||||
TextureInfo textureInfo;
|
||||
nsIntRect bufferRect;
|
||||
};
|
||||
|
||||
struct OpPaintTextureRegion {
|
||||
PCompositable compositable;
|
||||
ThebesBufferData bufferData;
|
||||
nsIntRegion updatedRegion;
|
||||
};
|
||||
|
||||
struct OpPaintTextureIncremental {
|
||||
PCompositable compositable;
|
||||
TextureIdentifier textureId;
|
||||
SurfaceDescriptor image;
|
||||
nsIntRegion updatedRegion;
|
||||
nsIntRect bufferRect;
|
||||
nsIntPoint bufferRotation;
|
||||
};
|
||||
|
||||
struct OpUpdatePictureRect {
|
||||
PCompositable compositable;
|
||||
nsIntRect picture;
|
||||
@@ -439,10 +423,7 @@ struct OpReplyDeliverFence {
|
||||
union CompositableOperation {
|
||||
OpUpdatePictureRect;
|
||||
|
||||
OpCreatedIncrementalTexture;
|
||||
|
||||
OpPaintTextureRegion;
|
||||
OpPaintTextureIncremental;
|
||||
|
||||
OpUseTiledLayerBuffer;
|
||||
|
||||
@@ -488,12 +469,6 @@ struct OpContentBufferSwap {
|
||||
nsIntRegion frontUpdatedRegion;
|
||||
};
|
||||
|
||||
struct OpTextureSwap {
|
||||
PCompositable compositable;
|
||||
TextureIdentifier textureId;
|
||||
SurfaceDescriptor image;
|
||||
};
|
||||
|
||||
struct ReturnReleaseFence {
|
||||
PCompositable compositable;
|
||||
PTexture texture;
|
||||
@@ -504,7 +479,6 @@ struct ReturnReleaseFence {
|
||||
// only to be used for buffer swapping.
|
||||
union EditReply {
|
||||
OpContentBufferSwap;
|
||||
OpTextureSwap;
|
||||
|
||||
ReturnReleaseFence;
|
||||
};
|
||||
|
||||
@@ -352,26 +352,6 @@ ShadowLayerForwarder::UpdateTextureRegion(CompositableClient* aCompositable,
|
||||
aUpdatedRegion));
|
||||
}
|
||||
|
||||
void
|
||||
ShadowLayerForwarder::UpdateTextureIncremental(CompositableClient* aCompositable,
|
||||
TextureIdentifier aTextureId,
|
||||
SurfaceDescriptor& aDescriptor,
|
||||
const nsIntRegion& aUpdatedRegion,
|
||||
const nsIntRect& aBufferRect,
|
||||
const nsIntPoint& aBufferRotation)
|
||||
{
|
||||
CheckSurfaceDescriptor(&aDescriptor);
|
||||
MOZ_ASSERT(aCompositable);
|
||||
MOZ_ASSERT(aCompositable->GetIPDLActor());
|
||||
mTxn->AddNoSwapPaint(OpPaintTextureIncremental(nullptr, aCompositable->GetIPDLActor(),
|
||||
aTextureId,
|
||||
aDescriptor,
|
||||
aUpdatedRegion,
|
||||
aBufferRect,
|
||||
aBufferRotation));
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ShadowLayerForwarder::UpdatePictureRect(CompositableClient* aCompositable,
|
||||
const nsIntRect& aRect)
|
||||
@@ -815,16 +795,6 @@ ShadowLayerForwarder::Connect(CompositableClient* aCompositable)
|
||||
aCompositable->InitIPDLActor(actor);
|
||||
}
|
||||
|
||||
void
|
||||
ShadowLayerForwarder::CreatedIncrementalBuffer(CompositableClient* aCompositable,
|
||||
const TextureInfo& aTextureInfo,
|
||||
const nsIntRect& aBufferRect)
|
||||
{
|
||||
MOZ_ASSERT(aCompositable);
|
||||
mTxn->AddNoSwapPaint(OpCreatedIncrementalTexture(nullptr, aCompositable->GetIPDLActor(),
|
||||
aTextureInfo, aBufferRect));
|
||||
}
|
||||
|
||||
void ShadowLayerForwarder::Attach(CompositableClient* aCompositable,
|
||||
ShadowableLayer* aLayer)
|
||||
{
|
||||
|
||||
@@ -133,7 +133,6 @@ class Transaction;
|
||||
|
||||
class ShadowLayerForwarder : public CompositableForwarder
|
||||
{
|
||||
friend class ContentClientIncremental;
|
||||
friend class ClientLayerManager;
|
||||
|
||||
public:
|
||||
@@ -148,10 +147,6 @@ public:
|
||||
virtual PTextureChild* CreateTexture(const SurfaceDescriptor& aSharedData,
|
||||
TextureFlags aFlags) override;
|
||||
|
||||
virtual void CreatedIncrementalBuffer(CompositableClient* aCompositable,
|
||||
const TextureInfo& aTextureInfo,
|
||||
const nsIntRect& aBufferRect) override;
|
||||
|
||||
/**
|
||||
* Adds an edit in the layers transaction in order to attach
|
||||
* the corresponding compositable and layer on the compositor side.
|
||||
@@ -259,13 +254,6 @@ public:
|
||||
const ThebesBufferData& aThebesBufferData,
|
||||
const nsIntRegion& aUpdatedRegion) override;
|
||||
|
||||
virtual void UpdateTextureIncremental(CompositableClient* aCompositable,
|
||||
TextureIdentifier aTextureId,
|
||||
SurfaceDescriptor& aDescriptor,
|
||||
const nsIntRegion& aUpdatedRegion,
|
||||
const nsIntRect& aBufferRect,
|
||||
const nsIntPoint& aBufferRotation) override;
|
||||
|
||||
/**
|
||||
* Communicate the picture rect of an image to the compositor
|
||||
*/
|
||||
|
||||
@@ -1021,8 +1021,6 @@ nsLineLayout::ReflowFrame(nsIFrame* aFrame,
|
||||
} else {
|
||||
if (nsGkAtoms::letterFrame==frameType) {
|
||||
pfd->mIsLetterFrame = true;
|
||||
} else if (nsGkAtoms::rubyFrame == frameType) {
|
||||
SyncAnnotationBounds(pfd);
|
||||
}
|
||||
if (pfd->mSpan) {
|
||||
isEmpty = !pfd->mSpan->mHasNonemptyContent && pfd->mFrame->IsSelfEmpty();
|
||||
@@ -1129,6 +1127,7 @@ nsLineLayout::ReflowFrame(nsIFrame* aFrame,
|
||||
}
|
||||
if (nsGkAtoms::rubyFrame == frameType) {
|
||||
mHasRuby = true;
|
||||
SyncAnnotationBounds(pfd);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2799,17 +2798,15 @@ nsLineLayout::AdvanceAnnotationInlineBounds(PerFrameData* aPFD,
|
||||
/**
|
||||
* This function applies the changes of icoord and isize caused by
|
||||
* justification to annotations of the given frame.
|
||||
* aPFD must be one of the frames in aContainingSpan.
|
||||
*/
|
||||
void
|
||||
nsLineLayout::ApplyLineJustificationToAnnotations(PerFrameData* aPFD,
|
||||
PerSpanData* aContainingSpan,
|
||||
nscoord aDeltaICoord,
|
||||
nscoord aDeltaISize)
|
||||
{
|
||||
PerFrameData* pfd = aPFD->mNextAnnotation;
|
||||
nscoord containerWidth = ContainerWidthForSpan(aContainingSpan);
|
||||
while (pfd) {
|
||||
nscoord containerWidth = pfd->mFrame->GetParent()->GetRect().Width();
|
||||
AdvanceAnnotationInlineBounds(pfd, containerWidth,
|
||||
aDeltaICoord, aDeltaISize);
|
||||
|
||||
@@ -2878,8 +2875,7 @@ nsLineLayout::ApplyFrameJustification(PerSpanData* aPSD,
|
||||
|
||||
// The gaps added to the end of the frame should also be
|
||||
// excluded from the isize added to the annotation.
|
||||
ApplyLineJustificationToAnnotations(pfd, aPSD,
|
||||
deltaICoord, dw - gapsAtEnd);
|
||||
ApplyLineJustificationToAnnotations(pfd, deltaICoord, dw - gapsAtEnd);
|
||||
deltaICoord += dw;
|
||||
pfd->mFrame->SetRect(lineWM, pfd->mBounds, ContainerWidthForSpan(aPSD));
|
||||
}
|
||||
|
||||
@@ -686,7 +686,6 @@ protected:
|
||||
nscoord aDeltaISize);
|
||||
|
||||
void ApplyLineJustificationToAnnotations(PerFrameData* aPFD,
|
||||
PerSpanData* aContainingSpan,
|
||||
nscoord aDeltaICoord,
|
||||
nscoord aDeltaISize);
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Test for bug 1135361</title>
|
||||
<style>
|
||||
body {
|
||||
font: 48px sans-serif;
|
||||
}
|
||||
div {
|
||||
display: inline-block;
|
||||
width: 3em;
|
||||
border: 1px solid silver;
|
||||
padding: .5em;
|
||||
}
|
||||
p {
|
||||
writing-mode: vertical-rl;
|
||||
-webkit-writing-mode: vertical-rl;
|
||||
-ms-writing-mode: tb-rl; /* old syntax. IE */
|
||||
text-orientation: upright;
|
||||
-webkit-text-orientation: upright;
|
||||
height: 4ch;
|
||||
}
|
||||
rt {
|
||||
font-size: 20%; /* ensure ruby is small enough that it won't affect inline spacing */
|
||||
}
|
||||
span {
|
||||
display: inline-block;
|
||||
height: .5ch; /* shim for fake justification */
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<p>
|
||||
<span></span><ruby>東<rt>to</rt></ruby>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p style="text-align:right;">
|
||||
<ruby>京<rt>kyo</rt></ruby><span></span>
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,46 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Test for bug 1135361</title>
|
||||
<style>
|
||||
body {
|
||||
font: 48px sans-serif;
|
||||
}
|
||||
div {
|
||||
display: inline-block;
|
||||
width: 3em;
|
||||
border: 1px solid silver;
|
||||
padding: .5em;
|
||||
}
|
||||
p {
|
||||
writing-mode: vertical-rl;
|
||||
-webkit-writing-mode: vertical-rl;
|
||||
-ms-writing-mode: tb-rl; /* old syntax. IE */
|
||||
text-orientation: upright;
|
||||
-webkit-text-orientation: upright;
|
||||
height: 4ch;
|
||||
text-align: justify;
|
||||
-moz-text-align-last: justify;
|
||||
}
|
||||
rt {
|
||||
font-size: 20%; /* ensure ruby is small enough that it won't affect inline spacing */
|
||||
}
|
||||
.t {
|
||||
color: transparent;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<p>
|
||||
<ruby>東<rt>to</rt></ruby><ruby class="t">京<rt>kyo</rt></ruby>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p>
|
||||
<ruby class="t">東<rt>to</rt></ruby><ruby>京<rt>kyo</rt></ruby>
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -104,3 +104,4 @@ HTTP(..) == 1127488-align-end-vertical-lr-ltr.html 1127488-align-bottom-left-ref
|
||||
HTTP(..) == 1127488-align-left-vertical-lr-ltr.html 1127488-align-top-left-ref.html
|
||||
HTTP(..) == 1127488-align-right-vertical-lr-ltr.html 1127488-align-bottom-left-ref.html
|
||||
== 1131013-vertical-bidi.html 1131013-vertical-bidi-ref.html
|
||||
fails-if(B2G) == 1135361-ruby-justify-1.html 1135361-ruby-justify-1-ref.html # bug 1136067
|
||||
|
||||
@@ -83,7 +83,7 @@ class Context(KeyedDefaultDict):
|
||||
# a list to be a problem.
|
||||
self._all_paths = []
|
||||
self.config = config
|
||||
self.executed_time = 0
|
||||
self.execution_time = 0
|
||||
self._sandbox = None
|
||||
KeyedDefaultDict.__init__(self, self._factory)
|
||||
|
||||
|
||||
@@ -243,6 +243,13 @@ AppTrustDomain::IsChainValid(const DERArray& certChain, Time time)
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result
|
||||
AppTrustDomain::CheckSignatureDigestAlgorithm(DigestAlgorithm)
|
||||
{
|
||||
// TODO: We should restrict signatures to SHA-256 or better.
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result
|
||||
AppTrustDomain::CheckRSAPublicKeyModulusSizeInBits(
|
||||
EndEntityOrCA /*endEntityOrCA*/, unsigned int modulusSizeInBits)
|
||||
@@ -257,6 +264,7 @@ Result
|
||||
AppTrustDomain::VerifyRSAPKCS1SignedDigest(const SignedDigest& signedDigest,
|
||||
Input subjectPublicKeyInfo)
|
||||
{
|
||||
// TODO: We should restrict signatures to SHA-256 or better.
|
||||
return VerifyRSAPKCS1SignedDigestNSS(signedDigest, subjectPublicKeyInfo,
|
||||
mPinArg);
|
||||
}
|
||||
|
||||
@@ -38,6 +38,8 @@ public:
|
||||
/*optional*/ const mozilla::pkix::Input* aiaExtension) override;
|
||||
virtual Result IsChainValid(const mozilla::pkix::DERArray& certChain,
|
||||
mozilla::pkix::Time time) override;
|
||||
virtual Result CheckSignatureDigestAlgorithm(
|
||||
mozilla::pkix::DigestAlgorithm digestAlg) override;
|
||||
virtual Result CheckRSAPublicKeyModulusSizeInBits(
|
||||
mozilla::pkix::EndEntityOrCA endEntityOrCA,
|
||||
unsigned int modulusSizeInBits) override;
|
||||
|
||||
@@ -710,6 +710,12 @@ NSSCertDBTrustDomain::IsChainValid(const DERArray& certArray, Time time)
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result
|
||||
NSSCertDBTrustDomain::CheckSignatureDigestAlgorithm(DigestAlgorithm)
|
||||
{
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result
|
||||
NSSCertDBTrustDomain::CheckRSAPublicKeyModulusSizeInBits(
|
||||
EndEntityOrCA /*endEntityOrCA*/, unsigned int modulusSizeInBits)
|
||||
|
||||
@@ -70,6 +70,9 @@ public:
|
||||
/*out*/ mozilla::pkix::TrustLevel& trustLevel)
|
||||
override;
|
||||
|
||||
virtual Result CheckSignatureDigestAlgorithm(
|
||||
mozilla::pkix::DigestAlgorithm digestAlg) override;
|
||||
|
||||
virtual Result CheckRSAPublicKeyModulusSizeInBits(
|
||||
mozilla::pkix::EndEntityOrCA endEntityOrCA,
|
||||
unsigned int modulusSizeInBits) override;
|
||||
|
||||
@@ -317,3 +317,4 @@ MOZILLA_PKIX_ERROR_INADEQUATE_KEY_SIZE=The server presented a certificate with a
|
||||
MOZILLA_PKIX_ERROR_V1_CERT_USED_AS_CA=An X.509 version 1 certificate that is not a trust anchor was used to issue the server's certificate. X.509 version 1 certificates are deprecated and should not be used to sign other certificates.
|
||||
MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE=The server presented a certificate that is not yet valid.
|
||||
MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE=A certificate that is not yet valid was used to issue the server's certificate.
|
||||
MOZILLA_PKIX_ERROR_SIGNATURE_ALGORITHM_MISMATCH=The signature algorithm in the signature field of the certificate does not match the algorithm in its signatureAlgorithm field.
|
||||
|
||||
@@ -179,6 +179,8 @@ static const unsigned int FATAL_ERROR_FLAG = 0x800;
|
||||
MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE) \
|
||||
MOZILLA_PKIX_MAP(ERROR_UNSUPPORTED_EC_POINT_FORM, 47, \
|
||||
SEC_ERROR_UNSUPPORTED_EC_POINT_FORM) \
|
||||
MOZILLA_PKIX_MAP(ERROR_SIGNATURE_ALGORITHM_MISMATCH, 48, \
|
||||
MOZILLA_PKIX_ERROR_SIGNATURE_ALGORITHM_MISMATCH) \
|
||||
MOZILLA_PKIX_MAP(FATAL_ERROR_INVALID_ARGS, FATAL_ERROR_FLAG | 1, \
|
||||
SEC_ERROR_INVALID_ARGS) \
|
||||
MOZILLA_PKIX_MAP(FATAL_ERROR_INVALID_STATE, FATAL_ERROR_FLAG | 2, \
|
||||
|
||||
@@ -81,6 +81,7 @@ enum ErrorCode
|
||||
MOZILLA_PKIX_ERROR_NO_RFC822NAME_MATCH = ERROR_BASE + 4,
|
||||
MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE = ERROR_BASE + 5,
|
||||
MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE = ERROR_BASE + 6,
|
||||
MOZILLA_PKIX_ERROR_SIGNATURE_ALGORITHM_MISMATCH = ERROR_BASE + 7,
|
||||
};
|
||||
|
||||
void RegisterErrorTable();
|
||||
|
||||
@@ -271,6 +271,13 @@ public:
|
||||
/*optional*/ const Input* stapledOCSPresponse,
|
||||
/*optional*/ const Input* aiaExtension) = 0;
|
||||
|
||||
// Check that the given digest algorithm is acceptable for use in signatures.
|
||||
//
|
||||
// Return Success if the algorithm is acceptable,
|
||||
// Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED if the algorithm is not
|
||||
// acceptable, or another error code if another error occurred.
|
||||
virtual Result CheckSignatureDigestAlgorithm(DigestAlgorithm digestAlg) = 0;
|
||||
|
||||
// Check that the RSA public key size is acceptable.
|
||||
//
|
||||
// Return Success if the key size is acceptable,
|
||||
|
||||
@@ -79,9 +79,6 @@ BackCert::Init()
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
// XXX: Ignored. What are we supposed to check? This seems totally redundant
|
||||
// with Certificate.signatureAlgorithm. Is it important to check that they
|
||||
// are consistent with each other? It doesn't seem to matter!
|
||||
rv = der::ExpectTagAndGetValue(tbsCertificate, der::SEQUENCE, signature);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
|
||||
@@ -29,6 +29,99 @@
|
||||
|
||||
namespace mozilla { namespace pkix {
|
||||
|
||||
// 4.1.1.2 signatureAlgorithm
|
||||
// 4.1.2.3 signature
|
||||
|
||||
Result
|
||||
CheckSignatureAlgorithm(TrustDomain& trustDomain,
|
||||
EndEntityOrCA endEntityOrCA,
|
||||
const der::SignedDataWithSignature& signedData,
|
||||
Input signatureValue)
|
||||
{
|
||||
// 4.1.1.2. signatureAlgorithm
|
||||
der::PublicKeyAlgorithm publicKeyAlg;
|
||||
DigestAlgorithm digestAlg;
|
||||
Reader signatureAlgorithmReader(signedData.algorithm);
|
||||
Result rv = der::SignatureAlgorithmIdentifierValue(signatureAlgorithmReader,
|
||||
publicKeyAlg, digestAlg);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
rv = der::End(signatureAlgorithmReader);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
// 4.1.2.3. Signature
|
||||
der::PublicKeyAlgorithm signedPublicKeyAlg;
|
||||
DigestAlgorithm signedDigestAlg;
|
||||
Reader signedSignatureAlgorithmReader(signatureValue);
|
||||
rv = der::SignatureAlgorithmIdentifierValue(signedSignatureAlgorithmReader,
|
||||
signedPublicKeyAlg,
|
||||
signedDigestAlg);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
rv = der::End(signedSignatureAlgorithmReader);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
// "This field MUST contain the same algorithm identifier as the
|
||||
// signatureAlgorithm field in the sequence Certificate." However, it may
|
||||
// be encoded differently. In particular, one of the fields may have a NULL
|
||||
// parameter while the other one may omit the parameter field altogether, and
|
||||
// these are considered equivalent. Some certificates generation software
|
||||
// actually generates certificates like that, so we compare the parsed values
|
||||
// instead of comparing the encoded values byte-for-byte.
|
||||
//
|
||||
// Along the same lines, we accept two different OIDs for RSA-with-SHA1, and
|
||||
// we consider those OIDs to be equivalent here.
|
||||
if (publicKeyAlg != signedPublicKeyAlg || digestAlg != signedDigestAlg) {
|
||||
return Result::ERROR_SIGNATURE_ALGORITHM_MISMATCH;
|
||||
}
|
||||
|
||||
// During the time of the deprecation of SHA-1 and the deprecation of RSA
|
||||
// keys of less than 2048 bits, we will encounter many certs signed using
|
||||
// SHA-1 and/or too-small RSA keys. With this in mind, we ask the trust
|
||||
// domain early on if it knows it will reject the signature purely based on
|
||||
// the digest algorithm and/or the RSA key size (if an RSA signature). This
|
||||
// is a good optimization because it completely avoids calling
|
||||
// trustDomain.FindIssuers (which may be slow) for such rejected certs, and
|
||||
// more generally it short-circuits any path building with them (which, of
|
||||
// course, is even slower).
|
||||
|
||||
rv = trustDomain.CheckSignatureDigestAlgorithm(digestAlg);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
switch (publicKeyAlg) {
|
||||
case der::PublicKeyAlgorithm::RSA_PKCS1:
|
||||
{
|
||||
// The RSA computation may give a result that requires fewer bytes to
|
||||
// encode than the public key (since it is modular arithmetic). However,
|
||||
// the last step of generating a PKCS#1.5 signature is the I2OSP
|
||||
// procedure, which pads any such shorter result with zeros so that it
|
||||
// is exactly the same length as the public key.
|
||||
unsigned int signatureSizeInBits = signedData.signature.GetLength() * 8u;
|
||||
return trustDomain.CheckRSAPublicKeyModulusSizeInBits(
|
||||
endEntityOrCA, signatureSizeInBits);
|
||||
}
|
||||
|
||||
case der::PublicKeyAlgorithm::ECDSA:
|
||||
// In theory, we could implement a similar early-pruning optimization for
|
||||
// ECDSA curves. However, since there has been no similar deprecation for
|
||||
// for any curve that we support, the chances of us encountering a curve
|
||||
// during path building is too low to be worth bothering with.
|
||||
break;
|
||||
|
||||
MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM
|
||||
}
|
||||
|
||||
return Success;
|
||||
}
|
||||
|
||||
// 4.1.2.5 Validity
|
||||
|
||||
Result
|
||||
@@ -735,21 +828,46 @@ CheckIssuerIndependentProperties(TrustDomain& trustDomain,
|
||||
|
||||
const EndEntityOrCA endEntityOrCA = cert.endEntityOrCA;
|
||||
|
||||
// Check the cert's trust first, because we want to minimize the amount of
|
||||
// processing we do on a distrusted cert, in case it is trying to exploit
|
||||
// some bug in our processing.
|
||||
rv = trustDomain.GetCertTrust(endEntityOrCA, requiredPolicy, cert.GetDER(),
|
||||
trustLevel);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
if (trustLevel == TrustLevel::ActivelyDistrusted) {
|
||||
return Result::ERROR_UNTRUSTED_CERT;
|
||||
}
|
||||
if (trustLevel != TrustLevel::TrustAnchor &&
|
||||
trustLevel != TrustLevel::InheritsTrust) {
|
||||
// The TrustDomain returned a trust level that we weren't expecting.
|
||||
return Result::FATAL_ERROR_INVALID_STATE;
|
||||
|
||||
if (trustLevel == TrustLevel::TrustAnchor &&
|
||||
endEntityOrCA == EndEntityOrCA::MustBeEndEntity &&
|
||||
requiredEKUIfPresent == KeyPurposeId::id_kp_OCSPSigning) {
|
||||
// OCSP signer certificates can never be trust anchors, especially
|
||||
// since we don't support designated OCSP responders. All of the checks
|
||||
// below that are dependent on trustLevel rely on this overriding of the
|
||||
// trust level for OCSP signers.
|
||||
trustLevel = TrustLevel::InheritsTrust;
|
||||
}
|
||||
|
||||
// Check the SPKI first, because it is one of the most selective properties
|
||||
switch (trustLevel) {
|
||||
case TrustLevel::InheritsTrust:
|
||||
rv = CheckSignatureAlgorithm(trustDomain, endEntityOrCA,
|
||||
cert.GetSignedData(), cert.GetSignature());
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
break;
|
||||
|
||||
case TrustLevel::TrustAnchor:
|
||||
// We don't even bother checking signatureAlgorithm or signature for
|
||||
// syntactic validity for trust anchors, because we don't use those
|
||||
// fields for anything, and because the trust anchor might be signed
|
||||
// with a signature algorithm we don't actually support.
|
||||
break;
|
||||
|
||||
case TrustLevel::ActivelyDistrusted:
|
||||
return Result::ERROR_UNTRUSTED_CERT;
|
||||
}
|
||||
|
||||
// Check the SPKI early, because it is one of the most selective properties
|
||||
// of the certificate due to SHA-1 deprecation and the deprecation of
|
||||
// certificates with keys weaker than RSA 2048.
|
||||
Reader spki(cert.GetSubjectPublicKeyInfo());
|
||||
|
||||
@@ -111,6 +111,13 @@ SignatureAlgorithmIdentifierValue(Reader& input,
|
||||
/*out*/ PublicKeyAlgorithm& publicKeyAlgorithm,
|
||||
/*out*/ DigestAlgorithm& digestAlgorithm)
|
||||
{
|
||||
// RFC 5758 Section 3.2 (ECDSA with SHA-2), and RFC 3279 Section 2.2.3
|
||||
// (ECDSA with SHA-1) say that parameters must be omitted.
|
||||
//
|
||||
// RFC 4055 Section 5 and RFC 3279 Section 2.2.1 both say that parameters for
|
||||
// RSA must be encoded as NULL; we relax that requirement by allowing the
|
||||
// NULL to be omitted, to match all the other signature algorithms we support
|
||||
// and for compatibility.
|
||||
Reader algorithmID;
|
||||
Result rv = AlgorithmIdentifierValue(input, algorithmID);
|
||||
if (rv != Success) {
|
||||
@@ -166,15 +173,6 @@ SignatureAlgorithmIdentifierValue(Reader& input,
|
||||
0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x01
|
||||
};
|
||||
|
||||
// RFC 5758 Section 3.1 (DSA with SHA-2), RFC 3279 Section 2.2.2 (DSA with
|
||||
// SHA-1), RFC 5758 Section 3.2 (ECDSA with SHA-2), and RFC 3279
|
||||
// Section 2.2.3 (ECDSA with SHA-1) all say that parameters must be omitted.
|
||||
//
|
||||
// RFC 4055 Section 5 and RFC 3279 Section 2.2.1 both say that parameters for
|
||||
// RSA must be encoded as NULL; we relax that requirement by allowing the
|
||||
// NULL to be omitted, to match all the other signature algorithms we support
|
||||
// and for compatibility.
|
||||
|
||||
// Matching is attempted based on a rough estimate of the commonality of the
|
||||
// algorithm, to minimize the number of MatchRest calls.
|
||||
if (algorithmID.MatchRest(sha256WithRSAEncryption)) {
|
||||
|
||||
@@ -194,6 +194,9 @@ RegisterErrorTable()
|
||||
{ "MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE",
|
||||
"A certificate that is not yet valid was used to issue the server's "
|
||||
"certificate." },
|
||||
{ "MOZILLA_PKIX_ERROR_SIGNATURE_ALGORITHM_MISMATCH",
|
||||
"The signature algorithm in the signature field of the certificate does "
|
||||
"not match the algorithm in its signatureAlgorithm field." },
|
||||
};
|
||||
// Note that these error strings are not localizable.
|
||||
// When these strings change, update the localization information too.
|
||||
|
||||
@@ -53,16 +53,18 @@ public:
|
||||
Result Init();
|
||||
|
||||
const Input GetDER() const { return der; }
|
||||
der::Version GetVersion() const { return version; }
|
||||
const der::SignedDataWithSignature& GetSignedData() const {
|
||||
return signedData;
|
||||
}
|
||||
|
||||
der::Version GetVersion() const { return version; }
|
||||
const Input GetSerialNumber() const { return serialNumber; }
|
||||
const Input GetSignature() const { return signature; }
|
||||
const Input GetIssuer() const { return issuer; }
|
||||
// XXX: "validity" is a horrible name for the structure that holds
|
||||
// notBefore & notAfter, but that is the name used in RFC 5280 and we use the
|
||||
// RFC 5280 names for everything.
|
||||
const Input GetValidity() const { return validity; }
|
||||
const Input GetSerialNumber() const { return serialNumber; }
|
||||
const Input GetSubject() const { return subject; }
|
||||
const Input GetSubjectPublicKeyInfo() const
|
||||
{
|
||||
|
||||
@@ -9,6 +9,7 @@ SOURCES += [
|
||||
'pkixcert_extension_tests.cpp',
|
||||
'pkixcert_signature_algorithm_tests.cpp',
|
||||
'pkixcheck_CheckKeyUsage_tests.cpp',
|
||||
'pkixcheck_CheckSignatureAlgorithm_tests.cpp',
|
||||
'pkixcheck_CheckValidity_tests.cpp',
|
||||
|
||||
# The naming conventions are described in ./README.txt.
|
||||
|
||||
@@ -36,9 +36,7 @@
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
#include "pkix/pkix.h"
|
||||
#include "pkixgtest.h"
|
||||
#include "pkixtestutil.h"
|
||||
|
||||
using namespace mozilla::pkix;
|
||||
using namespace mozilla::pkix::test;
|
||||
@@ -80,7 +78,7 @@ CreateCert(const char* issuerCN, // null means "empty name"
|
||||
return certDER;
|
||||
}
|
||||
|
||||
class TestTrustDomain final : public TrustDomain
|
||||
class TestTrustDomain final : public DefaultCryptoTrustDomain
|
||||
{
|
||||
public:
|
||||
// The "cert chain tail" is a longish chain of certificates that is used by
|
||||
@@ -153,36 +151,6 @@ private:
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result DigestBuf(Input input, DigestAlgorithm digestAlg,
|
||||
/*out*/ uint8_t* digestBuf, size_t digestLen) override
|
||||
{
|
||||
return TestDigestBuf(input, digestAlg, digestBuf, digestLen);
|
||||
}
|
||||
|
||||
Result CheckRSAPublicKeyModulusSizeInBits(EndEntityOrCA, unsigned int)
|
||||
override
|
||||
{
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result VerifyRSAPKCS1SignedDigest(const SignedDigest& signedDigest,
|
||||
Input subjectPublicKeyInfo) override
|
||||
{
|
||||
return TestVerifyRSAPKCS1SignedDigest(signedDigest, subjectPublicKeyInfo);
|
||||
}
|
||||
|
||||
Result CheckECDSACurveIsAcceptable(EndEntityOrCA, NamedCurve) override
|
||||
{
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result VerifyECDSASignedDigest(const SignedDigest& signedDigest,
|
||||
Input subjectPublicKeyInfo) override
|
||||
{
|
||||
return TestVerifyECDSASignedDigest(signedDigest, subjectPublicKeyInfo);
|
||||
}
|
||||
|
||||
|
||||
std::map<ByteString, ByteString> subjectDERToCertDER;
|
||||
ByteString leafCACertDER;
|
||||
ByteString rootCACertDER;
|
||||
@@ -276,7 +244,7 @@ TEST_F(pkixbuild, BeyondMaxAcceptableCertChainLength)
|
||||
// is treated as a trust anchor and is assumed to have issued all certificates
|
||||
// (i.e. FindIssuer always attempts to build the next step in the chain with
|
||||
// it).
|
||||
class ExpiredCertTrustDomain final : public TrustDomain
|
||||
class ExpiredCertTrustDomain final : public DefaultCryptoTrustDomain
|
||||
{
|
||||
public:
|
||||
explicit ExpiredCertTrustDomain(ByteString rootDER)
|
||||
@@ -315,48 +283,11 @@ public:
|
||||
return checker.Check(rootCert, nullptr, keepGoing);
|
||||
}
|
||||
|
||||
Result CheckRevocation(EndEntityOrCA, const CertID&, Time,
|
||||
/*optional*/ const Input*,
|
||||
/*optional*/ const Input*) override
|
||||
{
|
||||
ADD_FAILURE();
|
||||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
}
|
||||
|
||||
Result IsChainValid(const DERArray&, Time) override
|
||||
{
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result DigestBuf(Input input, DigestAlgorithm digestAlg,
|
||||
/*out*/ uint8_t* digestBuf, size_t digestLen) override
|
||||
{
|
||||
return TestDigestBuf(input, digestAlg, digestBuf, digestLen);
|
||||
}
|
||||
|
||||
Result CheckRSAPublicKeyModulusSizeInBits(EndEntityOrCA, unsigned int)
|
||||
override
|
||||
{
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result VerifyRSAPKCS1SignedDigest(const SignedDigest& signedDigest,
|
||||
Input subjectPublicKeyInfo) override
|
||||
{
|
||||
return TestVerifyRSAPKCS1SignedDigest(signedDigest, subjectPublicKeyInfo);
|
||||
}
|
||||
|
||||
Result CheckECDSACurveIsAcceptable(EndEntityOrCA, NamedCurve) override
|
||||
{
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result VerifyECDSASignedDigest(const SignedDigest& signedDigest,
|
||||
Input subjectPublicKeyInfo) override
|
||||
{
|
||||
return TestVerifyECDSASignedDigest(signedDigest, subjectPublicKeyInfo);
|
||||
}
|
||||
|
||||
private:
|
||||
ByteString rootDER;
|
||||
};
|
||||
@@ -394,7 +325,7 @@ TEST_F(pkixbuild, NoRevocationCheckingForExpiredCert)
|
||||
nullptr));
|
||||
}
|
||||
|
||||
class DSSTrustDomain final : public TrustDomain
|
||||
class DSSTrustDomain final : public EverythingFailsByDefaultTrustDomain
|
||||
{
|
||||
public:
|
||||
Result GetCertTrust(EndEntityOrCA, const CertPolicyId&,
|
||||
@@ -403,56 +334,6 @@ public:
|
||||
trustLevel = TrustLevel::TrustAnchor;
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result FindIssuer(Input, IssuerChecker&, Time) override
|
||||
{
|
||||
ADD_FAILURE();
|
||||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
}
|
||||
|
||||
Result CheckRevocation(EndEntityOrCA, const CertID&, Time,
|
||||
/*optional*/ const Input*,
|
||||
/*optional*/ const Input*) override
|
||||
{
|
||||
ADD_FAILURE();
|
||||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
}
|
||||
|
||||
Result IsChainValid(const DERArray&, Time) override
|
||||
{
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result DigestBuf(Input, DigestAlgorithm, /*out*/uint8_t*, size_t) override
|
||||
{
|
||||
ADD_FAILURE();
|
||||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
}
|
||||
|
||||
Result CheckRSAPublicKeyModulusSizeInBits(EndEntityOrCA, unsigned int)
|
||||
override
|
||||
{
|
||||
ADD_FAILURE();
|
||||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
}
|
||||
|
||||
Result VerifyRSAPKCS1SignedDigest(const SignedDigest&, Input) override
|
||||
{
|
||||
ADD_FAILURE();
|
||||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
}
|
||||
|
||||
Result CheckECDSACurveIsAcceptable(EndEntityOrCA, NamedCurve) override
|
||||
{
|
||||
ADD_FAILURE();
|
||||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
}
|
||||
|
||||
Result VerifyECDSASignedDigest(const SignedDigest&, Input) override
|
||||
{
|
||||
ADD_FAILURE();
|
||||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
}
|
||||
};
|
||||
|
||||
class pkixbuild_DSS : public ::testing::Test { };
|
||||
@@ -492,7 +373,7 @@ TEST_F(pkixbuild_DSS, DSSEndEntityKeyNotAccepted)
|
||||
nullptr/*stapledOCSPResponse*/));
|
||||
}
|
||||
|
||||
class IssuerNameCheckTrustDomain final : public TrustDomain
|
||||
class IssuerNameCheckTrustDomain final : public DefaultCryptoTrustDomain
|
||||
{
|
||||
public:
|
||||
IssuerNameCheckTrustDomain(const ByteString& issuer, bool expectedKeepGoing)
|
||||
@@ -534,35 +415,6 @@ public:
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result DigestBuf(Input input, DigestAlgorithm digestAlg,
|
||||
/*out*/ uint8_t* digestBuf, size_t digestLen) override
|
||||
{
|
||||
return TestDigestBuf(input, digestAlg, digestBuf, digestLen);
|
||||
}
|
||||
|
||||
Result CheckRSAPublicKeyModulusSizeInBits(EndEntityOrCA, unsigned int)
|
||||
override
|
||||
{
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result VerifyRSAPKCS1SignedDigest(const SignedDigest& signedDigest,
|
||||
Input subjectPublicKeyInfo) override
|
||||
{
|
||||
return TestVerifyRSAPKCS1SignedDigest(signedDigest, subjectPublicKeyInfo);
|
||||
}
|
||||
|
||||
Result CheckECDSACurveIsAcceptable(EndEntityOrCA, NamedCurve) override
|
||||
{
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result VerifyECDSASignedDigest(const SignedDigest& signedDigest,
|
||||
Input subjectPublicKeyInfo) override
|
||||
{
|
||||
return TestVerifyECDSASignedDigest(signedDigest, subjectPublicKeyInfo);
|
||||
}
|
||||
|
||||
private:
|
||||
const ByteString issuer;
|
||||
const bool expectedKeepGoing;
|
||||
|
||||
@@ -22,10 +22,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "pkix/pkix.h"
|
||||
#include "pkixder.h"
|
||||
#include "pkixgtest.h"
|
||||
#include "pkixtestutil.h"
|
||||
|
||||
using namespace mozilla::pkix;
|
||||
using namespace mozilla::pkix::test;
|
||||
@@ -60,7 +58,7 @@ CreateCertWithOneExtension(const char* subjectStr, const ByteString& extension)
|
||||
return CreateCertWithExtensions(subjectStr, extensions);
|
||||
}
|
||||
|
||||
class TrustEverythingTrustDomain final : public TrustDomain
|
||||
class TrustEverythingTrustDomain final : public DefaultCryptoTrustDomain
|
||||
{
|
||||
private:
|
||||
Result GetCertTrust(EndEntityOrCA, const CertPolicyId&, Input,
|
||||
@@ -70,13 +68,6 @@ private:
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result FindIssuer(Input /*encodedIssuerName*/, IssuerChecker& /*checker*/,
|
||||
Time /*time*/) override
|
||||
{
|
||||
ADD_FAILURE();
|
||||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
}
|
||||
|
||||
Result CheckRevocation(EndEntityOrCA, const CertID&, Time,
|
||||
/*optional*/ const Input*, /*optional*/ const Input*)
|
||||
override
|
||||
@@ -88,36 +79,6 @@ private:
|
||||
{
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result DigestBuf(Input input, DigestAlgorithm digestAlg,
|
||||
/*out*/ uint8_t* digestBuf, size_t digestLen) override
|
||||
{
|
||||
return TestDigestBuf(input, digestAlg, digestBuf, digestLen);
|
||||
}
|
||||
|
||||
Result CheckRSAPublicKeyModulusSizeInBits(EndEntityOrCA, unsigned int)
|
||||
override
|
||||
{
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result VerifyRSAPKCS1SignedDigest(const SignedDigest& signedDigest,
|
||||
Input subjectPublicKeyInfo) override
|
||||
{
|
||||
return TestVerifyRSAPKCS1SignedDigest(signedDigest, subjectPublicKeyInfo);
|
||||
}
|
||||
|
||||
Result CheckECDSACurveIsAcceptable(EndEntityOrCA, NamedCurve) override
|
||||
{
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result VerifyECDSASignedDigest(const SignedDigest& signedDigest,
|
||||
Input subjectPublicKeyInfo) override
|
||||
{
|
||||
return TestVerifyECDSASignedDigest(signedDigest, subjectPublicKeyInfo);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// python DottedOIDToCode.py --tlv unknownExtensionOID 1.3.6.1.4.1.13769.666.666.666.1.500.9.3
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
#include "pkix/pkix.h"
|
||||
#include "pkixgtest.h"
|
||||
#include "pkixtestutil.h"
|
||||
|
||||
using namespace mozilla::pkix;
|
||||
using namespace mozilla::pkix::test;
|
||||
@@ -45,7 +43,7 @@ CreateCert(const char* issuerCN,
|
||||
return certDER;
|
||||
}
|
||||
|
||||
class AlgorithmTestsTrustDomain final : public TrustDomain
|
||||
class AlgorithmTestsTrustDomain final : public DefaultCryptoTrustDomain
|
||||
{
|
||||
public:
|
||||
AlgorithmTestsTrustDomain(const ByteString& rootDER,
|
||||
@@ -103,35 +101,6 @@ private:
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result DigestBuf(Input input, DigestAlgorithm digestAlg,
|
||||
/*out*/ uint8_t* digestBuf, size_t digestLen) override
|
||||
{
|
||||
return TestDigestBuf(input, digestAlg, digestBuf, digestLen);
|
||||
}
|
||||
|
||||
Result CheckRSAPublicKeyModulusSizeInBits(EndEntityOrCA, unsigned int)
|
||||
override
|
||||
{
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result VerifyRSAPKCS1SignedDigest(const SignedDigest& signedDigest,
|
||||
Input subjectPublicKeyInfo) override
|
||||
{
|
||||
return TestVerifyRSAPKCS1SignedDigest(signedDigest, subjectPublicKeyInfo);
|
||||
}
|
||||
|
||||
Result CheckECDSACurveIsAcceptable(EndEntityOrCA, NamedCurve) override
|
||||
{
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result VerifyECDSASignedDigest(const SignedDigest& signedDigest,
|
||||
Input subjectPublicKeyInfo) override
|
||||
{
|
||||
return TestVerifyECDSASignedDigest(signedDigest, subjectPublicKeyInfo);
|
||||
}
|
||||
|
||||
ByteString rootDER;
|
||||
ByteString rootSubjectDER;
|
||||
ByteString intDER;
|
||||
|
||||
@@ -23,8 +23,6 @@
|
||||
*/
|
||||
|
||||
#include "pkixgtest.h"
|
||||
#include "pkix/pkixtypes.h"
|
||||
#include "pkixtestutil.h"
|
||||
|
||||
using namespace mozilla::pkix;
|
||||
using namespace mozilla::pkix::test;
|
||||
|
||||
@@ -0,0 +1,358 @@
|
||||
/* -*- 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 code is made available to you under your choice of the following sets
|
||||
* of licensing terms:
|
||||
*/
|
||||
/* 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/.
|
||||
*/
|
||||
/* Copyright 2015 Mozilla Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "pkixder.h"
|
||||
#include "pkixgtest.h"
|
||||
|
||||
using namespace mozilla::pkix;
|
||||
using namespace mozilla::pkix::test;
|
||||
|
||||
namespace mozilla { namespace pkix {
|
||||
|
||||
extern Result CheckSignatureAlgorithm(
|
||||
TrustDomain& trustDomain, EndEntityOrCA endEntityOrCA,
|
||||
const der::SignedDataWithSignature& signedData,
|
||||
Input signatureValue);
|
||||
|
||||
} } // namespace mozilla::pkix
|
||||
|
||||
struct CheckSignatureAlgorithmTestParams
|
||||
{
|
||||
ByteString signatureAlgorithmValue;
|
||||
ByteString signatureValue;
|
||||
unsigned int signatureLengthInBytes;
|
||||
Result expectedResult;
|
||||
};
|
||||
|
||||
#define BS(s) ByteString(s, MOZILLA_PKIX_ARRAY_LENGTH(s))
|
||||
|
||||
// python DottedOIDToCode.py --tlv sha256WithRSAEncryption 1.2.840.113549.1.1.11
|
||||
static const uint8_t tlv_sha256WithRSAEncryption[] = {
|
||||
0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b
|
||||
};
|
||||
|
||||
// Same as tlv_sha256WithRSAEncryption, except one without the "0x0b" and with
|
||||
// the DER length decreased accordingly.
|
||||
static const uint8_t tlv_sha256WithRSAEncryption_truncated[] = {
|
||||
0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01
|
||||
};
|
||||
|
||||
// python DottedOIDToCode.py --tlv sha-1WithRSAEncryption 1.2.840.113549.1.1.5
|
||||
static const uint8_t tlv_sha_1WithRSAEncryption[] = {
|
||||
0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05
|
||||
};
|
||||
|
||||
// python DottedOIDToCode.py --tlv sha1WithRSASignature 1.3.14.3.2.29
|
||||
static const uint8_t tlv_sha1WithRSASignature[] = {
|
||||
0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1d
|
||||
};
|
||||
|
||||
// python DottedOIDToCode.py --tlv md5WithRSAEncryption 1.2.840.113549.1.1.4
|
||||
static const uint8_t tlv_md5WithRSAEncryption[] = {
|
||||
0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x04
|
||||
};
|
||||
|
||||
static const CheckSignatureAlgorithmTestParams
|
||||
CHECKSIGNATUREALGORITHM_TEST_PARAMS[] =
|
||||
{
|
||||
{ // Both algorithm IDs are empty
|
||||
ByteString(),
|
||||
ByteString(),
|
||||
2048 / 8,
|
||||
Result::ERROR_BAD_DER,
|
||||
},
|
||||
{ // signatureAlgorithm is empty, signature is supported.
|
||||
ByteString(),
|
||||
BS(tlv_sha256WithRSAEncryption),
|
||||
2048 / 8,
|
||||
Result::ERROR_BAD_DER,
|
||||
},
|
||||
{ // signatureAlgorithm is supported, signature is empty.
|
||||
BS(tlv_sha256WithRSAEncryption),
|
||||
ByteString(),
|
||||
2048 / 8,
|
||||
Result::ERROR_BAD_DER,
|
||||
},
|
||||
{ // Algorithms match, both are supported.
|
||||
BS(tlv_sha256WithRSAEncryption),
|
||||
BS(tlv_sha256WithRSAEncryption),
|
||||
2048 / 8,
|
||||
Success
|
||||
},
|
||||
{ // Algorithms do not match because signatureAlgorithm is truncated.
|
||||
BS(tlv_sha256WithRSAEncryption_truncated),
|
||||
BS(tlv_sha256WithRSAEncryption),
|
||||
2048 / 8,
|
||||
Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED
|
||||
},
|
||||
{ // Algorithms do not match because signature is truncated.
|
||||
BS(tlv_sha256WithRSAEncryption),
|
||||
BS(tlv_sha256WithRSAEncryption_truncated),
|
||||
2048 / 8,
|
||||
Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED
|
||||
},
|
||||
{ // Algorithms do not match, both are supported.
|
||||
BS(tlv_sha_1WithRSAEncryption),
|
||||
BS(tlv_sha256WithRSAEncryption),
|
||||
2048 / 8,
|
||||
Result::ERROR_SIGNATURE_ALGORITHM_MISMATCH,
|
||||
},
|
||||
{ // Algorithms do not match, both are supported.
|
||||
BS(tlv_sha256WithRSAEncryption),
|
||||
BS(tlv_sha_1WithRSAEncryption),
|
||||
2048 / 8,
|
||||
Result::ERROR_SIGNATURE_ALGORITHM_MISMATCH,
|
||||
},
|
||||
{ // Algorithms match, both are unsupported.
|
||||
BS(tlv_md5WithRSAEncryption),
|
||||
BS(tlv_md5WithRSAEncryption),
|
||||
2048 / 8,
|
||||
Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED
|
||||
},
|
||||
{ // signatureAlgorithm is unsupported, signature is supported.
|
||||
BS(tlv_md5WithRSAEncryption),
|
||||
BS(tlv_sha256WithRSAEncryption),
|
||||
2048 / 8,
|
||||
Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED
|
||||
},
|
||||
{ // signatureAlgorithm is supported, signature is unsupported.
|
||||
BS(tlv_sha256WithRSAEncryption),
|
||||
BS(tlv_md5WithRSAEncryption),
|
||||
2048 / 8,
|
||||
Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED
|
||||
},
|
||||
{ // Both have the optional NULL parameter.
|
||||
BS(tlv_sha256WithRSAEncryption) + TLV(der::NULLTag, ByteString()),
|
||||
BS(tlv_sha256WithRSAEncryption) + TLV(der::NULLTag, ByteString()),
|
||||
2048 / 8,
|
||||
Success
|
||||
},
|
||||
{ // signatureAlgorithm has the optional NULL parameter, signature doesn't.
|
||||
BS(tlv_sha256WithRSAEncryption) + TLV(der::NULLTag, ByteString()),
|
||||
BS(tlv_sha256WithRSAEncryption),
|
||||
2048 / 8,
|
||||
Success
|
||||
},
|
||||
{ // signatureAlgorithm does not have the optional NULL parameter, signature
|
||||
// does.
|
||||
BS(tlv_sha256WithRSAEncryption),
|
||||
BS(tlv_sha256WithRSAEncryption) + TLV(der::NULLTag, ByteString()),
|
||||
2048 / 8,
|
||||
Success
|
||||
},
|
||||
{ // The different OIDs for RSA-with-SHA1 we support are semantically
|
||||
// equivalent.
|
||||
BS(tlv_sha1WithRSASignature),
|
||||
BS(tlv_sha_1WithRSAEncryption),
|
||||
2048 / 8,
|
||||
Success,
|
||||
},
|
||||
{ // The different OIDs for RSA-with-SHA1 we support are semantically
|
||||
// equivalent (opposite order).
|
||||
BS(tlv_sha_1WithRSAEncryption),
|
||||
BS(tlv_sha1WithRSASignature),
|
||||
2048 / 8,
|
||||
Success,
|
||||
},
|
||||
{ // Algorithms match, both are supported, key size is not a multile of 128
|
||||
// bits. This test verifies that we're not wrongly rounding up the
|
||||
// signature size like we did in the original patch for bug 1131767.
|
||||
BS(tlv_sha256WithRSAEncryption),
|
||||
BS(tlv_sha256WithRSAEncryption),
|
||||
(2048 / 8) - 1,
|
||||
Success
|
||||
},
|
||||
};
|
||||
|
||||
class pkixcheck_CheckSignatureAlgorithm
|
||||
: public ::testing::Test
|
||||
, public ::testing::WithParamInterface<CheckSignatureAlgorithmTestParams>
|
||||
{
|
||||
};
|
||||
|
||||
class pkixcheck_CheckSignatureAlgorithm_TrustDomain final
|
||||
: public EverythingFailsByDefaultTrustDomain
|
||||
{
|
||||
public:
|
||||
explicit pkixcheck_CheckSignatureAlgorithm_TrustDomain(
|
||||
unsigned int publicKeySizeInBits)
|
||||
: publicKeySizeInBits(publicKeySizeInBits)
|
||||
, checkedDigestAlgorithm(false)
|
||||
, checkedModulusSizeInBits(false)
|
||||
{
|
||||
}
|
||||
|
||||
Result CheckSignatureDigestAlgorithm(DigestAlgorithm) override
|
||||
{
|
||||
checkedDigestAlgorithm = true;
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result CheckRSAPublicKeyModulusSizeInBits(EndEntityOrCA endEntityOrCA,
|
||||
unsigned int modulusSizeInBits)
|
||||
override
|
||||
{
|
||||
EXPECT_EQ(EndEntityOrCA::MustBeEndEntity, endEntityOrCA);
|
||||
EXPECT_EQ(publicKeySizeInBits, modulusSizeInBits);
|
||||
checkedModulusSizeInBits = true;
|
||||
return Success;
|
||||
}
|
||||
|
||||
const unsigned int publicKeySizeInBits;
|
||||
bool checkedDigestAlgorithm;
|
||||
bool checkedModulusSizeInBits;
|
||||
};
|
||||
|
||||
TEST_P(pkixcheck_CheckSignatureAlgorithm, CheckSignatureAlgorithm)
|
||||
{
|
||||
const CheckSignatureAlgorithmTestParams& params(GetParam());
|
||||
|
||||
Input signatureValueInput;
|
||||
ASSERT_EQ(Success,
|
||||
signatureValueInput.Init(params.signatureValue.data(),
|
||||
params.signatureValue.length()));
|
||||
|
||||
pkixcheck_CheckSignatureAlgorithm_TrustDomain
|
||||
trustDomain(params.signatureLengthInBytes * 8);
|
||||
|
||||
der::SignedDataWithSignature signedData;
|
||||
ASSERT_EQ(Success,
|
||||
signedData.algorithm.Init(params.signatureAlgorithmValue.data(),
|
||||
params.signatureAlgorithmValue.length()));
|
||||
|
||||
ByteString dummySignature(params.signatureLengthInBytes, 0xDE);
|
||||
ASSERT_EQ(Success,
|
||||
signedData.signature.Init(dummySignature.data(),
|
||||
dummySignature.length()));
|
||||
|
||||
ASSERT_EQ(params.expectedResult,
|
||||
CheckSignatureAlgorithm(trustDomain, EndEntityOrCA::MustBeEndEntity,
|
||||
signedData, signatureValueInput));
|
||||
ASSERT_EQ(params.expectedResult == Success,
|
||||
trustDomain.checkedDigestAlgorithm);
|
||||
ASSERT_EQ(params.expectedResult == Success,
|
||||
trustDomain.checkedModulusSizeInBits);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
pkixcheck_CheckSignatureAlgorithm, pkixcheck_CheckSignatureAlgorithm,
|
||||
testing::ValuesIn(CHECKSIGNATUREALGORITHM_TEST_PARAMS));
|
||||
|
||||
class pkixcheck_CheckSignatureAlgorithm_BuildCertChain_TrustDomain
|
||||
: public DefaultCryptoTrustDomain
|
||||
{
|
||||
public:
|
||||
explicit pkixcheck_CheckSignatureAlgorithm_BuildCertChain_TrustDomain(
|
||||
const ByteString& issuer)
|
||||
: issuer(issuer)
|
||||
{
|
||||
}
|
||||
|
||||
Result GetCertTrust(EndEntityOrCA, const CertPolicyId&,
|
||||
Input cert, /*out*/ TrustLevel& trustLevel) override
|
||||
{
|
||||
trustLevel = InputEqualsByteString(cert, issuer)
|
||||
? TrustLevel::TrustAnchor
|
||||
: TrustLevel::InheritsTrust;
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result FindIssuer(Input, IssuerChecker& checker, Time) override
|
||||
{
|
||||
EXPECT_FALSE(ENCODING_FAILED(issuer));
|
||||
|
||||
Input issuerInput;
|
||||
EXPECT_EQ(Success, issuerInput.Init(issuer.data(), issuer.length()));
|
||||
|
||||
bool keepGoing;
|
||||
EXPECT_EQ(Success, checker.Check(issuerInput, nullptr, keepGoing));
|
||||
EXPECT_FALSE(keepGoing);
|
||||
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result CheckRevocation(EndEntityOrCA, const CertID&, Time,
|
||||
/*optional*/ const Input*,
|
||||
/*optional*/ const Input*) override
|
||||
{
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result IsChainValid(const DERArray&, Time) override
|
||||
{
|
||||
return Success;
|
||||
}
|
||||
|
||||
ByteString issuer;
|
||||
};
|
||||
|
||||
// Test that CheckSignatureAlgorithm actually gets called at some point when
|
||||
// BuildCertChain is called.
|
||||
TEST_F(pkixcheck_CheckSignatureAlgorithm, BuildCertChain)
|
||||
{
|
||||
ScopedTestKeyPair keyPair(CloneReusedKeyPair());
|
||||
ASSERT_TRUE(keyPair);
|
||||
|
||||
ByteString issuerExtensions[2];
|
||||
issuerExtensions[0] = CreateEncodedBasicConstraints(true, nullptr,
|
||||
Critical::No);
|
||||
ASSERT_FALSE(ENCODING_FAILED(issuerExtensions[0]));
|
||||
|
||||
ByteString issuer(CreateEncodedCertificate(3,
|
||||
sha256WithRSAEncryption,
|
||||
CreateEncodedSerialNumber(1),
|
||||
CNToDERName("issuer"),
|
||||
oneDayBeforeNow, oneDayAfterNow,
|
||||
CNToDERName("issuer"),
|
||||
*keyPair,
|
||||
issuerExtensions,
|
||||
*keyPair,
|
||||
sha256WithRSAEncryption));
|
||||
ASSERT_FALSE(ENCODING_FAILED(issuer));
|
||||
|
||||
ByteString subject(CreateEncodedCertificate(3,
|
||||
TLV(der::SEQUENCE,
|
||||
BS(tlv_sha_1WithRSAEncryption)),
|
||||
CreateEncodedSerialNumber(2),
|
||||
CNToDERName("issuer"),
|
||||
oneDayBeforeNow, oneDayAfterNow,
|
||||
CNToDERName("subject"),
|
||||
*keyPair,
|
||||
nullptr,
|
||||
*keyPair,
|
||||
sha256WithRSAEncryption));
|
||||
ASSERT_FALSE(ENCODING_FAILED(subject));
|
||||
|
||||
Input subjectInput;
|
||||
ASSERT_EQ(Success, subjectInput.Init(subject.data(), subject.length()));
|
||||
pkixcheck_CheckSignatureAlgorithm_BuildCertChain_TrustDomain
|
||||
trustDomain(issuer);
|
||||
Result rv = BuildCertChain(trustDomain, subjectInput, Now(),
|
||||
EndEntityOrCA::MustBeEndEntity,
|
||||
KeyUsage::noParticularKeyUsageRequired,
|
||||
KeyPurposeId::anyExtendedKeyUsage,
|
||||
CertPolicyId::anyPolicy,
|
||||
nullptr);
|
||||
ASSERT_EQ(Result::ERROR_SIGNATURE_ALGORITHM_MISMATCH, rv);
|
||||
}
|
||||
@@ -23,8 +23,6 @@
|
||||
*/
|
||||
|
||||
#include "pkixgtest.h"
|
||||
#include "pkix/pkixtypes.h"
|
||||
#include "pkixtestutil.h"
|
||||
|
||||
using namespace mozilla::pkix;
|
||||
using namespace mozilla::pkix::test;
|
||||
|
||||
@@ -23,12 +23,11 @@
|
||||
*/
|
||||
|
||||
#include <limits>
|
||||
#include <stdint.h>
|
||||
#include <vector>
|
||||
#include "pkixgtest.h"
|
||||
|
||||
#include "pkixder.h"
|
||||
#include "pkixtestutil.h"
|
||||
#include "stdint.h"
|
||||
#include "pkixgtest.h"
|
||||
|
||||
using namespace mozilla::pkix;
|
||||
using namespace mozilla::pkix::der;
|
||||
|
||||
@@ -56,7 +56,8 @@
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
#include "pkix/Result.h"
|
||||
#include "pkix/pkix.h"
|
||||
#include "pkixtestutil.h"
|
||||
|
||||
// PrintTo must be in the same namespace as the type we're overloading it for.
|
||||
namespace mozilla { namespace pkix {
|
||||
@@ -82,6 +83,122 @@ extern const std::time_t now;
|
||||
extern const std::time_t oneDayBeforeNow;
|
||||
extern const std::time_t oneDayAfterNow;
|
||||
|
||||
|
||||
class EverythingFailsByDefaultTrustDomain : public TrustDomain
|
||||
{
|
||||
public:
|
||||
Result GetCertTrust(EndEntityOrCA, const CertPolicyId&,
|
||||
Input, /*out*/ TrustLevel&) override
|
||||
{
|
||||
ADD_FAILURE();
|
||||
return NotReached("GetCertTrust should not be called",
|
||||
Result::FATAL_ERROR_LIBRARY_FAILURE);
|
||||
}
|
||||
|
||||
Result FindIssuer(Input, IssuerChecker&, Time) override
|
||||
{
|
||||
ADD_FAILURE();
|
||||
return NotReached("FindIssuer should not be called",
|
||||
Result::FATAL_ERROR_LIBRARY_FAILURE);
|
||||
}
|
||||
|
||||
Result CheckRevocation(EndEntityOrCA, const CertID&, Time,
|
||||
/*optional*/ const Input*,
|
||||
/*optional*/ const Input*) override
|
||||
{
|
||||
ADD_FAILURE();
|
||||
return NotReached("CheckRevocation should not be called",
|
||||
Result::FATAL_ERROR_LIBRARY_FAILURE);
|
||||
}
|
||||
|
||||
Result IsChainValid(const DERArray&, Time) override
|
||||
{
|
||||
ADD_FAILURE();
|
||||
return NotReached("IsChainValid should not be called",
|
||||
Result::FATAL_ERROR_LIBRARY_FAILURE);
|
||||
}
|
||||
|
||||
Result DigestBuf(Input, DigestAlgorithm, /*out*/ uint8_t*, size_t) override
|
||||
{
|
||||
ADD_FAILURE();
|
||||
return NotReached("DigestBuf should not be called",
|
||||
Result::FATAL_ERROR_LIBRARY_FAILURE);
|
||||
}
|
||||
|
||||
Result CheckSignatureDigestAlgorithm(DigestAlgorithm) override
|
||||
{
|
||||
ADD_FAILURE();
|
||||
return NotReached("CheckSignatureDigestAlgorithm should not be called",
|
||||
Result::FATAL_ERROR_LIBRARY_FAILURE);
|
||||
}
|
||||
|
||||
Result CheckECDSACurveIsAcceptable(EndEntityOrCA, NamedCurve) override
|
||||
{
|
||||
ADD_FAILURE();
|
||||
return NotReached("CheckECDSACurveIsAcceptable should not be called",
|
||||
Result::FATAL_ERROR_LIBRARY_FAILURE);
|
||||
}
|
||||
|
||||
Result VerifyECDSASignedDigest(const SignedDigest&, Input) override
|
||||
{
|
||||
ADD_FAILURE();
|
||||
return NotReached("VerifyECDSASignedDigest should not be called",
|
||||
Result::FATAL_ERROR_LIBRARY_FAILURE);
|
||||
}
|
||||
|
||||
Result CheckRSAPublicKeyModulusSizeInBits(EndEntityOrCA, unsigned int)
|
||||
override
|
||||
{
|
||||
ADD_FAILURE();
|
||||
return NotReached("CheckRSAPublicKeyModulusSizeInBits should not be called",
|
||||
Result::FATAL_ERROR_LIBRARY_FAILURE);
|
||||
}
|
||||
|
||||
Result VerifyRSAPKCS1SignedDigest(const SignedDigest&, Input) override
|
||||
{
|
||||
ADD_FAILURE();
|
||||
return NotReached("VerifyRSAPKCS1SignedDigest should not be called",
|
||||
Result::FATAL_ERROR_LIBRARY_FAILURE);
|
||||
}
|
||||
};
|
||||
|
||||
class DefaultCryptoTrustDomain : public EverythingFailsByDefaultTrustDomain
|
||||
{
|
||||
Result DigestBuf(Input item, DigestAlgorithm digestAlg,
|
||||
/*out*/ uint8_t* digestBuf, size_t digestBufLen) override
|
||||
{
|
||||
return TestDigestBuf(item, digestAlg, digestBuf, digestBufLen);
|
||||
}
|
||||
|
||||
Result CheckSignatureDigestAlgorithm(DigestAlgorithm) override
|
||||
{
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result CheckECDSACurveIsAcceptable(EndEntityOrCA, NamedCurve) override
|
||||
{
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result VerifyECDSASignedDigest(const SignedDigest& signedDigest,
|
||||
Input subjectPublicKeyInfo) override
|
||||
{
|
||||
return TestVerifyECDSASignedDigest(signedDigest, subjectPublicKeyInfo);
|
||||
}
|
||||
|
||||
Result CheckRSAPublicKeyModulusSizeInBits(EndEntityOrCA, unsigned int)
|
||||
override
|
||||
{
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result VerifyRSAPKCS1SignedDigest(const SignedDigest& signedDigest,
|
||||
Input subjectPublicKeyInfo) override
|
||||
{
|
||||
return TestVerifyRSAPKCS1SignedDigest(signedDigest, subjectPublicKeyInfo);
|
||||
}
|
||||
};
|
||||
|
||||
} } } // namespace mozilla::pkix::test
|
||||
|
||||
#endif // mozilla_pkix_pkixgtest_h
|
||||
|
||||
@@ -21,11 +21,9 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#include "pkix/pkix.h"
|
||||
#include "pkixcheck.h"
|
||||
#include "pkixder.h"
|
||||
#include "pkixgtest.h"
|
||||
#include "pkixtestutil.h"
|
||||
#include "pkixutil.h"
|
||||
|
||||
namespace mozilla { namespace pkix {
|
||||
|
||||
@@ -23,42 +23,15 @@
|
||||
*/
|
||||
|
||||
#include "pkixgtest.h"
|
||||
#include "pkix/pkix.h"
|
||||
#include "pkixder.h"
|
||||
#include "pkixtestutil.h"
|
||||
|
||||
using namespace mozilla::pkix;
|
||||
using namespace mozilla::pkix::test;
|
||||
|
||||
class CreateEncodedOCSPRequestTrustDomain final : public TrustDomain
|
||||
class CreateEncodedOCSPRequestTrustDomain final
|
||||
: public EverythingFailsByDefaultTrustDomain
|
||||
{
|
||||
private:
|
||||
Result GetCertTrust(EndEntityOrCA, const CertPolicyId&, Input,
|
||||
/*out*/ TrustLevel&) override
|
||||
{
|
||||
ADD_FAILURE();
|
||||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
}
|
||||
|
||||
Result FindIssuer(Input, IssuerChecker&, Time) override
|
||||
{
|
||||
ADD_FAILURE();
|
||||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
}
|
||||
|
||||
Result CheckRevocation(EndEntityOrCA, const CertID&, Time, const Input*,
|
||||
const Input*) override
|
||||
{
|
||||
ADD_FAILURE();
|
||||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
}
|
||||
|
||||
Result IsChainValid(const DERArray&, Time) override
|
||||
{
|
||||
ADD_FAILURE();
|
||||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
}
|
||||
|
||||
Result DigestBuf(Input item, DigestAlgorithm digestAlg,
|
||||
/*out*/ uint8_t *digestBuf, size_t digestBufLen)
|
||||
override
|
||||
@@ -67,28 +40,9 @@ private:
|
||||
}
|
||||
|
||||
Result CheckRSAPublicKeyModulusSizeInBits(EndEntityOrCA, unsigned int)
|
||||
final override
|
||||
override
|
||||
{
|
||||
ADD_FAILURE();
|
||||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
}
|
||||
|
||||
Result VerifyRSAPKCS1SignedDigest(const SignedDigest&, Input) override
|
||||
{
|
||||
ADD_FAILURE();
|
||||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
}
|
||||
|
||||
Result CheckECDSACurveIsAcceptable(EndEntityOrCA, NamedCurve) final override
|
||||
{
|
||||
ADD_FAILURE();
|
||||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
}
|
||||
|
||||
Result VerifyECDSASignedDigest(const SignedDigest&, Input) override
|
||||
{
|
||||
ADD_FAILURE();
|
||||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
return Success;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -22,21 +22,18 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "pkix/pkix.h"
|
||||
#include "pkixgtest.h"
|
||||
#include "pkixtestutil.h"
|
||||
|
||||
using namespace mozilla::pkix;
|
||||
using namespace mozilla::pkix::test;
|
||||
|
||||
const uint16_t END_ENTITY_MAX_LIFETIME_IN_DAYS = 10;
|
||||
|
||||
class OCSPTestTrustDomain : public TrustDomain
|
||||
// Note that CheckRevocation is never called for OCSP signing certificates.
|
||||
class OCSPTestTrustDomain : public DefaultCryptoTrustDomain
|
||||
{
|
||||
public:
|
||||
OCSPTestTrustDomain()
|
||||
{
|
||||
}
|
||||
OCSPTestTrustDomain() { }
|
||||
|
||||
Result GetCertTrust(EndEntityOrCA endEntityOrCA, const CertPolicyId&,
|
||||
Input, /*out*/ TrustLevel& trustLevel)
|
||||
@@ -46,62 +43,6 @@ public:
|
||||
trustLevel = TrustLevel::InheritsTrust;
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result FindIssuer(Input, IssuerChecker&, Time) final override
|
||||
{
|
||||
ADD_FAILURE();
|
||||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
}
|
||||
|
||||
Result CheckRevocation(EndEntityOrCA, const CertID&, Time,
|
||||
/*optional*/ const Input*, /*optional*/ const Input*)
|
||||
final override
|
||||
{
|
||||
// TODO: I guess mozilla::pkix should support revocation of designated
|
||||
// OCSP responder eventually, but we don't now, so this function should
|
||||
// never get called.
|
||||
ADD_FAILURE();
|
||||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
}
|
||||
|
||||
Result IsChainValid(const DERArray&, Time) final override
|
||||
{
|
||||
ADD_FAILURE();
|
||||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
}
|
||||
|
||||
Result DigestBuf(Input item, DigestAlgorithm digestAlg,
|
||||
/*out*/ uint8_t* digestBuf, size_t digestBufLen)
|
||||
final override
|
||||
{
|
||||
return TestDigestBuf(item, digestAlg, digestBuf, digestBufLen);
|
||||
}
|
||||
|
||||
Result CheckRSAPublicKeyModulusSizeInBits(EndEntityOrCA, unsigned int)
|
||||
final override
|
||||
{
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result VerifyRSAPKCS1SignedDigest(const SignedDigest& signedDigest,
|
||||
Input subjectPublicKeyInfo) override
|
||||
{
|
||||
return TestVerifyRSAPKCS1SignedDigest(signedDigest, subjectPublicKeyInfo);
|
||||
}
|
||||
|
||||
Result CheckECDSACurveIsAcceptable(EndEntityOrCA, NamedCurve) final override
|
||||
{
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result VerifyECDSASignedDigest(const SignedDigest& signedDigest,
|
||||
Input subjectPublicKeyInfo) override
|
||||
{
|
||||
return TestVerifyECDSASignedDigest(signedDigest, subjectPublicKeyInfo);
|
||||
}
|
||||
|
||||
OCSPTestTrustDomain(const OCSPTestTrustDomain&) = delete;
|
||||
void operator=(const OCSPTestTrustDomain&) = delete;
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -24,6 +24,7 @@ catch (e) {
|
||||
// under the "accessibility.*" branch.
|
||||
const PREFS_WHITELIST = [
|
||||
"accessibility.",
|
||||
"apz.",
|
||||
"browser.cache.",
|
||||
"browser.display.",
|
||||
"browser.download.folderList",
|
||||
|
||||
@@ -1365,8 +1365,9 @@ function readStringFromInputStream(inputStream) {
|
||||
sis.init(inputStream);
|
||||
var text = sis.read(sis.available());
|
||||
sis.close();
|
||||
if (text[text.length - 1] == "\n")
|
||||
if (text && text[text.length - 1] == "\n") {
|
||||
text = text.slice(0, -1);
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
|
||||
@@ -77,9 +77,8 @@ function runTest() {
|
||||
removeDirRecursive(addonPrepDir);
|
||||
}
|
||||
catch (e) {
|
||||
dump("Unable to remove directory\n" +
|
||||
"path: " + addonPrepDir.path + "\n" +
|
||||
"Exception: " + e + "\n");
|
||||
logTestInfo("Unable to remove directory. Path: " + addonPrepDir.path +
|
||||
", Exception: " + e);
|
||||
}
|
||||
|
||||
resetAddons(finishTest);
|
||||
|
||||
@@ -166,6 +166,8 @@ const TEST_ADDONS = [ "appdisabled_1", "appdisabled_2",
|
||||
"updateversion_1", "updateversion_2",
|
||||
"userdisabled_1", "userdisabled_2" ];
|
||||
|
||||
const LOG_FUNCTION = info;
|
||||
|
||||
var gURLData = URL_HOST + "/" + REL_PATH_DATA + "/";
|
||||
|
||||
var gTestTimeout = 240000; // 4 minutes
|
||||
@@ -658,10 +660,11 @@ function waitForRemoteContentLoaded(aEvent) {
|
||||
// expected or isn't the event's originalTarget.
|
||||
if (gRemoteContentState != gTest.expectedRemoteContentState ||
|
||||
aEvent.originalTarget != gRemoteContent) {
|
||||
debugDump("returning early\n" +
|
||||
"gRemoteContentState: " + gRemoteContentState + "\n" +
|
||||
debugDump("returning early. " +
|
||||
"gRemoteContentState: " +
|
||||
gRemoteContentState + ", " +
|
||||
"expectedRemoteContentState: " +
|
||||
gTest.expectedRemoteContentState + "\n" +
|
||||
gTest.expectedRemoteContentState + ", " +
|
||||
"aEvent.originalTarget.nodeName: " +
|
||||
aEvent.originalTarget.nodeName);
|
||||
return true;
|
||||
@@ -947,9 +950,8 @@ function resetFiles() {
|
||||
removeDirRecursive(updatedDir);
|
||||
}
|
||||
catch (e) {
|
||||
dump("Unable to remove directory\n" +
|
||||
"path: " + updatedDir.path + "\n" +
|
||||
"Exception: " + e + "\n");
|
||||
logTestInfo("Unable to remove directory. Path: " + updatedDir.path +
|
||||
", Exception: " + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -430,8 +430,8 @@ function removeUpdateDirsAndFiles() {
|
||||
if (file.exists())
|
||||
file.remove(false);
|
||||
} catch (e) {
|
||||
dump("Unable to remove file\nPath: " + file.path +
|
||||
"\nException: " + e + "\n");
|
||||
logTestInfo("Unable to remove file. Path: " + file.path +
|
||||
", Exception: " + e);
|
||||
}
|
||||
|
||||
file = getUpdatesXMLFile(false);
|
||||
@@ -439,8 +439,8 @@ function removeUpdateDirsAndFiles() {
|
||||
if (file.exists())
|
||||
file.remove(false);
|
||||
} catch (e) {
|
||||
dump("Unable to remove file\nPath: " + file.path +
|
||||
"\nException: " + e + "\n");
|
||||
logTestInfo("Unable to remove file. Path: " + file.path +
|
||||
", Exception: " + e);
|
||||
}
|
||||
|
||||
// This fails sporadically on Mac OS X so wrap it in a try catch
|
||||
@@ -448,8 +448,8 @@ function removeUpdateDirsAndFiles() {
|
||||
try {
|
||||
cleanUpdatesDir(updatesDir);
|
||||
} catch (e) {
|
||||
dump("Unable to remove files / directories from directory\nPath: " +
|
||||
updatesDir.path + "\nException: " + e + "\n");
|
||||
logTestInfo("Unable to remove files / directories from directory. Path: " +
|
||||
updatesDir.path + ", Exception: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -483,8 +483,8 @@ function cleanUpdatesDir(aDir) {
|
||||
try {
|
||||
entry.remove(true);
|
||||
} catch (e) {
|
||||
dump("cleanUpdatesDir: unable to remove directory\nPath: " +
|
||||
entry.path + "\nException: " + e + "\n");
|
||||
logTestInfo("cleanUpdatesDir: unable to remove directory. Path: " +
|
||||
entry.path + ", Exception: " + e);
|
||||
throw(e);
|
||||
}
|
||||
}
|
||||
@@ -493,8 +493,8 @@ function cleanUpdatesDir(aDir) {
|
||||
try {
|
||||
entry.remove(false);
|
||||
} catch (e) {
|
||||
dump("cleanUpdatesDir: unable to remove file\nPath: " + entry.path +
|
||||
"\nException: " + e + "\n");
|
||||
logTestInfo("cleanUpdatesDir: unable to remove file. Path: " +
|
||||
entry.path + ", Exception: " + e);
|
||||
throw(e);
|
||||
}
|
||||
}
|
||||
@@ -614,8 +614,9 @@ function logTestInfo(aText, aCaller) {
|
||||
(mm < 10 ? "0" + mm : mm) + ":" +
|
||||
(ss < 10 ? "0" + ss : ss) + ":" +
|
||||
(ms < 10 ? "00" + ms : ms < 100 ? "0" + ms : ms);
|
||||
dump(time + " | TEST-INFO | " + caller.filename + " | [" + caller.name +
|
||||
" : " + caller.lineNumber + "] " + aText + "\n");
|
||||
let msg = time + " | TEST-INFO | " + caller.filename + " | [" + caller.name +
|
||||
" : " + caller.lineNumber + "] " + aText;
|
||||
LOG_FUNCTION(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -61,74 +61,84 @@ function run_test() {
|
||||
doTestFinish();
|
||||
}
|
||||
|
||||
if (IS_WIN) {
|
||||
/**
|
||||
* Determines a unique mutex name for the installation.
|
||||
*
|
||||
* @return Global mutex path.
|
||||
*/
|
||||
function getPerInstallationMutexName() {
|
||||
let hasher = AUS_Cc["@mozilla.org/security/hash;1"].
|
||||
createInstance(AUS_Ci.nsICryptoHash);
|
||||
hasher.init(hasher.SHA1);
|
||||
|
||||
let exeFile = Services.dirsvc.get(XRE_EXECUTABLE_FILE, AUS_Ci.nsILocalFile);
|
||||
|
||||
let converter = AUS_Cc["@mozilla.org/intl/scriptableunicodeconverter"].
|
||||
createInstance(AUS_Ci.nsIScriptableUnicodeConverter);
|
||||
converter.charset = "UTF-8";
|
||||
let data = converter.convertToByteArray(exeFile.path.toLowerCase());
|
||||
|
||||
hasher.update(data, data.length);
|
||||
return "Global\\MozillaUpdateMutex-" + hasher.finish(true);
|
||||
/**
|
||||
* Determines a unique mutex name for the installation.
|
||||
*
|
||||
* @return Global mutex path.
|
||||
*/
|
||||
function getPerInstallationMutexName() {
|
||||
if (!IS_WIN) {
|
||||
do_throw("Windows only function called by a different platform!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes a Win32 handle.
|
||||
*
|
||||
* @param aHandle
|
||||
* The handle to close.
|
||||
*/
|
||||
function closeHandle(aHandle) {
|
||||
let lib = ctypes.open("kernel32.dll");
|
||||
let CloseHandle = lib.declare("CloseHandle",
|
||||
ctypes.winapi_abi,
|
||||
ctypes.int32_t, /* success */
|
||||
ctypes.void_t.ptr); /* handle */
|
||||
CloseHandle(aHandle);
|
||||
lib.close();
|
||||
}
|
||||
let hasher = AUS_Cc["@mozilla.org/security/hash;1"].
|
||||
createInstance(AUS_Ci.nsICryptoHash);
|
||||
hasher.init(hasher.SHA1);
|
||||
|
||||
/**
|
||||
* Creates a mutex.
|
||||
*
|
||||
* @param aName
|
||||
* The name for the mutex.
|
||||
* @return The Win32 handle to the mutex.
|
||||
*/
|
||||
function createMutex(aName) {
|
||||
const INITIAL_OWN = 1;
|
||||
const ERROR_ALREADY_EXISTS = 0xB7;
|
||||
let lib = ctypes.open("kernel32.dll");
|
||||
let CreateMutexW = lib.declare("CreateMutexW",
|
||||
ctypes.winapi_abi,
|
||||
ctypes.void_t.ptr, /* return handle */
|
||||
ctypes.void_t.ptr, /* security attributes */
|
||||
ctypes.int32_t, /* initial owner */
|
||||
ctypes.char16_t.ptr); /* name */
|
||||
let exeFile = Services.dirsvc.get(XRE_EXECUTABLE_FILE, AUS_Ci.nsILocalFile);
|
||||
|
||||
let handle = CreateMutexW(null, INITIAL_OWN, aName);
|
||||
lib.close();
|
||||
let alreadyExists = ctypes.winLastError == ERROR_ALREADY_EXISTS;
|
||||
if (handle && !handle.isNull() && alreadyExists) {
|
||||
closeHandle(handle);
|
||||
handle = null;
|
||||
}
|
||||
let converter = AUS_Cc["@mozilla.org/intl/scriptableunicodeconverter"].
|
||||
createInstance(AUS_Ci.nsIScriptableUnicodeConverter);
|
||||
converter.charset = "UTF-8";
|
||||
let data = converter.convertToByteArray(exeFile.path.toLowerCase());
|
||||
|
||||
if (handle && handle.isNull()) {
|
||||
handle = null;
|
||||
}
|
||||
|
||||
return handle;
|
||||
}
|
||||
hasher.update(data, data.length);
|
||||
return "Global\\MozillaUpdateMutex-" + hasher.finish(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes a Win32 handle.
|
||||
*
|
||||
* @param aHandle
|
||||
* The handle to close.
|
||||
*/
|
||||
function closeHandle(aHandle) {
|
||||
if (!IS_WIN) {
|
||||
do_throw("Windows only function called by a different platform!");
|
||||
}
|
||||
|
||||
let lib = ctypes.open("kernel32.dll");
|
||||
let CloseHandle = lib.declare("CloseHandle",
|
||||
ctypes.winapi_abi,
|
||||
ctypes.int32_t, /* success */
|
||||
ctypes.void_t.ptr); /* handle */
|
||||
CloseHandle(aHandle);
|
||||
lib.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a mutex.
|
||||
*
|
||||
* @param aName
|
||||
* The name for the mutex.
|
||||
* @return The Win32 handle to the mutex.
|
||||
*/
|
||||
function createMutex(aName) {
|
||||
if (!IS_WIN) {
|
||||
do_throw("Windows only function called by a different platform!");
|
||||
}
|
||||
|
||||
const INITIAL_OWN = 1;
|
||||
const ERROR_ALREADY_EXISTS = 0xB7;
|
||||
let lib = ctypes.open("kernel32.dll");
|
||||
let CreateMutexW = lib.declare("CreateMutexW",
|
||||
ctypes.winapi_abi,
|
||||
ctypes.void_t.ptr, /* return handle */
|
||||
ctypes.void_t.ptr, /* security attributes */
|
||||
ctypes.int32_t, /* initial owner */
|
||||
ctypes.char16_t.ptr); /* name */
|
||||
|
||||
let handle = CreateMutexW(null, INITIAL_OWN, aName);
|
||||
lib.close();
|
||||
let alreadyExists = ctypes.winLastError == ERROR_ALREADY_EXISTS;
|
||||
if (handle && !handle.isNull() && alreadyExists) {
|
||||
closeHandle(handle);
|
||||
handle = null;
|
||||
}
|
||||
|
||||
if (handle && handle.isNull()) {
|
||||
handle = null;
|
||||
}
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@ function run_test() {
|
||||
do_check_eq(gUpdateManager.activeUpdate, null);
|
||||
// Verify that the active-update.xml file has had the update from the old
|
||||
// channel removed.
|
||||
file = getUpdatesXMLFile(true);
|
||||
let file = getUpdatesXMLFile(true);
|
||||
logTestInfo("verifying contents of " + FILE_UPDATE_ACTIVE);
|
||||
do_check_eq(readFile(file), getLocalUpdatesXMLString(""));
|
||||
|
||||
|
||||
@@ -5,7 +5,10 @@
|
||||
|
||||
const KEY_UPDATE_ARCHIVE_DIR = "UpdArchD"
|
||||
|
||||
let gActiveUpdate = null;
|
||||
let gActiveUpdate;
|
||||
let gDirService;
|
||||
let gDirProvider;
|
||||
let gOldProviders;
|
||||
|
||||
function FakeDirProvider() {}
|
||||
FakeDirProvider.prototype = {
|
||||
|
||||
@@ -152,7 +152,7 @@ IncrementalDownload.prototype = {
|
||||
tm.mainThread.dispatch(function() {
|
||||
this._observer = observer.QueryInterface(AUS_Ci.nsIRequestObserver);
|
||||
this._ctxt = ctxt;
|
||||
this._observer.onStartRequest(this, this.ctxt);
|
||||
this._observer.onStartRequest(this, this._ctxt);
|
||||
let mar = getTestDirFile(FILE_SIMPLE_MAR);
|
||||
mar.copyTo(this._destination.parent, this._destination.leafName);
|
||||
var status = AUS_Cr.NS_OK
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
'use strict';
|
||||
|
||||
const INSTALL_LOCALE = "@AB_CD@";
|
||||
const MOZ_APP_NAME = "@MOZ_APP_NAME@";
|
||||
const BIN_SUFFIX = "@BIN_SUFFIX@";
|
||||
@@ -140,6 +142,8 @@ const PIPE_TO_NULL = ">nul";
|
||||
const PIPE_TO_NULL = "> /dev/null 2>&1";
|
||||
#endif
|
||||
|
||||
const LOG_FUNCTION = do_print;
|
||||
|
||||
// This default value will be overridden when using the http server.
|
||||
var gURLData = URL_HOST + "/";
|
||||
|
||||
@@ -1451,41 +1455,46 @@ function getMockUpdRootD() {
|
||||
}
|
||||
#endif
|
||||
|
||||
if (IS_WIN) {
|
||||
const kLockFileName = "updated.update_in_progress.lock";
|
||||
/**
|
||||
* Helper function for locking a directory on Windows.
|
||||
*
|
||||
* @param aDir
|
||||
* The nsIFile for the directory to lock.
|
||||
*/
|
||||
function lockDirectory(aDir) {
|
||||
var file = aDir.clone();
|
||||
file.append(kLockFileName);
|
||||
file.create(file.NORMAL_FILE_TYPE, 0o444);
|
||||
file.QueryInterface(AUS_Ci.nsILocalFileWin);
|
||||
file.fileAttributesWin |= file.WFA_READONLY;
|
||||
file.fileAttributesWin &= ~file.WFA_READWRITE;
|
||||
logTestInfo("testing the successful creation of the lock file");
|
||||
do_check_true(file.exists());
|
||||
do_check_false(file.isWritable());
|
||||
const kLockFileName = "updated.update_in_progress.lock";
|
||||
/**
|
||||
* Helper function for locking a directory on Windows.
|
||||
*
|
||||
* @param aDir
|
||||
* The nsIFile for the directory to lock.
|
||||
*/
|
||||
function lockDirectory(aDir) {
|
||||
if (!IS_WIN) {
|
||||
do_throw("Windows only function called by a different platform!");
|
||||
}
|
||||
/**
|
||||
* Helper function for unlocking a directory on Windows.
|
||||
*
|
||||
* @param aDir
|
||||
* The nsIFile for the directory to unlock.
|
||||
*/
|
||||
function unlockDirectory(aDir) {
|
||||
var file = aDir.clone();
|
||||
file.append(kLockFileName);
|
||||
file.QueryInterface(AUS_Ci.nsILocalFileWin);
|
||||
file.fileAttributesWin |= file.WFA_READWRITE;
|
||||
file.fileAttributesWin &= ~file.WFA_READONLY;
|
||||
logTestInfo("removing and testing the successful removal of the lock file");
|
||||
file.remove(false);
|
||||
do_check_false(file.exists());
|
||||
|
||||
let file = aDir.clone();
|
||||
file.append(kLockFileName);
|
||||
file.create(file.NORMAL_FILE_TYPE, 0o444);
|
||||
file.QueryInterface(AUS_Ci.nsILocalFileWin);
|
||||
file.fileAttributesWin |= file.WFA_READONLY;
|
||||
file.fileAttributesWin &= ~file.WFA_READWRITE;
|
||||
logTestInfo("testing the successful creation of the lock file");
|
||||
do_check_true(file.exists());
|
||||
do_check_false(file.isWritable());
|
||||
}
|
||||
/**
|
||||
* Helper function for unlocking a directory on Windows.
|
||||
*
|
||||
* @param aDir
|
||||
* The nsIFile for the directory to unlock.
|
||||
*/
|
||||
function unlockDirectory(aDir) {
|
||||
if (!IS_WIN) {
|
||||
do_throw("Windows only function called by a different platform!");
|
||||
}
|
||||
let file = aDir.clone();
|
||||
file.append(kLockFileName);
|
||||
file.QueryInterface(AUS_Ci.nsILocalFileWin);
|
||||
file.fileAttributesWin |= file.WFA_READWRITE;
|
||||
file.fileAttributesWin &= ~file.WFA_READONLY;
|
||||
logTestInfo("removing and testing the successful removal of the lock file");
|
||||
file.remove(false);
|
||||
do_check_false(file.exists());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3253,7 +3262,7 @@ function start_httpserver() {
|
||||
|
||||
if (!dir.isDirectory()) {
|
||||
do_throw("A file instead of a directory was specified for HttpServer " +
|
||||
"registerDirectory! Path: " + dir.path + "\n");
|
||||
"registerDirectory! Path: " + dir.path);
|
||||
}
|
||||
|
||||
AUS_Cu.import("resource://testing-common/httpd.js");
|
||||
@@ -3663,7 +3672,7 @@ function setEnvironment() {
|
||||
env.set("XPCOM_DEBUG_BREAK", "warn");
|
||||
|
||||
if (gStageUpdate) {
|
||||
logTestInfo("setting the MOZ_UPDATE_STAGING environment variable to 1\n");
|
||||
logTestInfo("setting the MOZ_UPDATE_STAGING environment variable to 1");
|
||||
env.set("MOZ_UPDATE_STAGING", "1");
|
||||
}
|
||||
|
||||
@@ -3729,7 +3738,7 @@ function resetEnvironment() {
|
||||
}
|
||||
|
||||
if (gStageUpdate) {
|
||||
logTestInfo("removing the MOZ_UPDATE_STAGING environment variable\n");
|
||||
logTestInfo("removing the MOZ_UPDATE_STAGING environment variable");
|
||||
env.set("MOZ_UPDATE_STAGING", "");
|
||||
}
|
||||
|
||||
|
||||
@@ -14,8 +14,6 @@ function run_test() {
|
||||
// The mock XMLHttpRequest is MUCH faster
|
||||
overrideXHR(callHandleEvent);
|
||||
standardInit();
|
||||
// The HTTP server is only used for the mar file downloads which is slow
|
||||
start_httpserver();
|
||||
|
||||
let registrar = Components.manager.QueryInterface(AUS_Ci.nsIComponentRegistrar);
|
||||
registrar.registerFactory(Components.ID("{1dfeb90a-2193-45d5-9cb8-864928b2af55}"),
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
/* General Update Timer Manager Tests */
|
||||
|
||||
'use strict';
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cm = Components.manager;
|
||||
@@ -103,8 +105,8 @@ const TESTS = [ {
|
||||
lastUpdateTime : 0
|
||||
} ];
|
||||
|
||||
var gUTM;
|
||||
var gNextFunc;
|
||||
let gUTM;
|
||||
let gNextFunc;
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "gPref",
|
||||
"@mozilla.org/preferences-service;1",
|
||||
@@ -127,7 +129,7 @@ function run_test() {
|
||||
gPref.setBoolPref(PREF_APP_UPDATE_LOG_ALL, true);
|
||||
|
||||
// Remove existing update timers to prevent them from being notified
|
||||
var entries = gCatMan.enumerateCategory(CATEGORY_UPDATE_TIMER);
|
||||
let entries = gCatMan.enumerateCategory(CATEGORY_UPDATE_TIMER);
|
||||
while (entries.hasMoreElements()) {
|
||||
let entry = entries.getNext().QueryInterface(Ci.nsISupportsCString).data;
|
||||
gCatMan.deleteCategoryEntry(CATEGORY_UPDATE_TIMER, entry, false);
|
||||
@@ -165,7 +167,7 @@ function run_test1thru7() {
|
||||
TESTS[1].defaultInterval].join(","), false, true);
|
||||
|
||||
// has a last update time of now - 43200 which is half of its interval
|
||||
var lastUpdateTime = Math.round(Date.now() / 1000) - 43200;
|
||||
let lastUpdateTime = Math.round(Date.now() / 1000) - 43200;
|
||||
gPref.setIntPref(PREF_BRANCH_LAST_UPDATE_TIME + TESTS[2].timerID, lastUpdateTime);
|
||||
gCompReg.registerFactory(TESTS[2].classID, TESTS[2].desc,
|
||||
TESTS[2].contractID, gTest3Factory);
|
||||
@@ -201,7 +203,7 @@ function run_test1thru7() {
|
||||
TESTS[5].defaultInterval].join(","), false, true);
|
||||
|
||||
// has a next update time 24 hours from now
|
||||
var nextUpdateTime = Math.round(Date.now() / 1000) + 86400;
|
||||
let nextUpdateTime = Math.round(Date.now() / 1000) + 86400;
|
||||
gPref.setIntPref(PREF_BRANCH_LAST_UPDATE_TIME + TESTS[6].timerID, nextUpdateTime);
|
||||
gCompReg.registerFactory(TESTS[6].classID, TESTS[6].desc,
|
||||
TESTS[6].contractID, gTest7Factory);
|
||||
@@ -212,40 +214,41 @@ function run_test1thru7() {
|
||||
}
|
||||
|
||||
function finished_test1thru7() {
|
||||
if (TESTS[4].notified && TESTS[5].notified && TESTS[6].notified)
|
||||
if (TESTS[4].notified && TESTS[5].notified && TESTS[6].notified) {
|
||||
do_timeout(0, gNextFunc);
|
||||
}
|
||||
}
|
||||
|
||||
function check_test1thru7() {
|
||||
dump("Testing: a category registered timer didn't fire due to an invalid " +
|
||||
"default interval\n");
|
||||
do_print("Testing: a category registered timer didn't fire due to an " +
|
||||
"invalid default interval");
|
||||
do_check_false(TESTS[0].notified);
|
||||
|
||||
dump("Testing: a category registered timer didn't fire due to not " +
|
||||
"implementing nsITimerCallback\n");
|
||||
do_print("Testing: a category registered timer didn't fire due to not " +
|
||||
"implementing nsITimerCallback");
|
||||
do_check_false(TESTS[1].notified);
|
||||
|
||||
dump("Testing: a category registered timer didn't fire due to the next " +
|
||||
"update time being in the future\n");
|
||||
do_print("Testing: a category registered timer didn't fire due to the next " +
|
||||
"update time being in the future");
|
||||
do_check_false(TESTS[2].notified);
|
||||
|
||||
dump("Testing: a category registered timer didn't fire due to not " +
|
||||
"having a notify method\n");
|
||||
do_print("Testing: a category registered timer didn't fire due to not " +
|
||||
"having a notify method");
|
||||
do_check_false(TESTS[3].notified);
|
||||
|
||||
dump("Testing: a category registered timer has fired\n");
|
||||
do_print("Testing: a category registered timer has fired");
|
||||
do_check_true(TESTS[4].notified);
|
||||
|
||||
dump("Testing: a category registered timer fired that has an interval " +
|
||||
"preference that overrides a default that wouldn't have fired yet\n");
|
||||
do_print("Testing: a category registered timer fired that has an interval " +
|
||||
"preference that overrides a default that wouldn't have fired yet");
|
||||
do_check_true(TESTS[5].notified);
|
||||
|
||||
dump("Testing: a category registered timer has fired due to the next " +
|
||||
"update time being reset due to a future last update time\n");
|
||||
do_print("Testing: a category registered timer has fired due to the next " +
|
||||
"update time being reset due to a future last update time");
|
||||
do_check_true(TESTS[6].notified);
|
||||
|
||||
dump("Testing: two category registered timers last update time has " +
|
||||
"user values\n");
|
||||
do_print("Testing: two category registered timers last update time has " +
|
||||
"user values");
|
||||
do_check_true(gPref.prefHasUserValue(PREF_BRANCH_LAST_UPDATE_TIME +
|
||||
TESTS[4].timerID));
|
||||
do_check_true(gPref.prefHasUserValue(PREF_BRANCH_LAST_UPDATE_TIME +
|
||||
@@ -256,23 +259,22 @@ function check_test1thru7() {
|
||||
gCatMan.deleteCategoryEntry(CATEGORY_UPDATE_TIMER, TESTS[1].desc, true);
|
||||
gCatMan.deleteCategoryEntry(CATEGORY_UPDATE_TIMER, TESTS[2].desc, true);
|
||||
gCatMan.deleteCategoryEntry(CATEGORY_UPDATE_TIMER, TESTS[3].desc, true);
|
||||
var count = 0;
|
||||
var entries = gCatMan.enumerateCategory(CATEGORY_UPDATE_TIMER);
|
||||
let count = 0;
|
||||
let entries = gCatMan.enumerateCategory(CATEGORY_UPDATE_TIMER);
|
||||
while (entries.hasMoreElements()) {
|
||||
let entry = entries.getNext().QueryInterface(Ci.nsISupportsCString).data;
|
||||
gCatMan.deleteCategoryEntry(CATEGORY_UPDATE_TIMER, entry, false);
|
||||
count++;
|
||||
}
|
||||
dump("Testing: no " + CATEGORY_UPDATE_TIMER + " categories are still " +
|
||||
"registered\n");
|
||||
do_print("Testing: no " + CATEGORY_UPDATE_TIMER + " categories are still " +
|
||||
"registered");
|
||||
do_check_eq(count, 0);
|
||||
|
||||
do_timeout(0, run_test8);
|
||||
}
|
||||
|
||||
function run_test8() {
|
||||
gNextFunc = check_test8;
|
||||
for (var i = 0; i < 2; i++) {
|
||||
for (let i = 0; i < 2; i++) {
|
||||
gPref.setIntPref(PREF_BRANCH_LAST_UPDATE_TIME + TESTS[7 + i].timerID, 1);
|
||||
gCompReg.registerFactory(TESTS[7 + i].classID, TESTS[7 + i].desc,
|
||||
TESTS[7 + i].contractID, eval("gTest" + (8 + i) + "Factory"));
|
||||
@@ -281,15 +283,16 @@ function run_test8() {
|
||||
}
|
||||
}
|
||||
|
||||
function check_test8() {
|
||||
var self = arguments.callee;
|
||||
self.timesCalled = (self.timesCalled || 0) + 1;
|
||||
if (self.timesCalled < 2)
|
||||
function check_test8(aTestTimerCallback) {
|
||||
aTestTimerCallback.timesCalled = (aTestTimerCallback.timesCalled || 0) + 1;
|
||||
if (aTestTimerCallback.timesCalled < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
dump("Testing: two registerTimer registered timers have fired\n");
|
||||
for (var i = 0; i < 2; i++)
|
||||
do_print("Testing: two registerTimer registered timers have fired");
|
||||
for (let i = 0; i < 2; i++) {
|
||||
do_check_true(TESTS[7 + i].notified);
|
||||
}
|
||||
|
||||
// Check that 'staggering' has happened: even though the two events wanted to fire at
|
||||
// the same time, we waited a full MAIN_TIMER_INTERVAL between them.
|
||||
@@ -297,71 +300,76 @@ function check_test8() {
|
||||
do_check_true(Math.abs(TESTS[7].notifyTime - TESTS[8].notifyTime) >=
|
||||
MAIN_TIMER_INTERVAL * 0.5);
|
||||
|
||||
dump("Testing: two registerTimer registered timers last update time have " +
|
||||
"been updated\n");
|
||||
for (var i = 0; i < 2; i++)
|
||||
do_print("Testing: two registerTimer registered timers last update time have " +
|
||||
"been updated");
|
||||
for (let i = 0; i < 2; i++) {
|
||||
do_check_neq(gPref.getIntPref(PREF_BRANCH_LAST_UPDATE_TIME + TESTS[7 + i].timerID), 1);
|
||||
}
|
||||
end_test();
|
||||
}
|
||||
|
||||
var gTest1TimerCallback = {
|
||||
const gTest1TimerCallback = {
|
||||
notify: function T1CB_notify(aTimer) {
|
||||
do_throw("gTest1TimerCallback notify method should not have been called");
|
||||
},
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsITimerCallback])
|
||||
};
|
||||
|
||||
var gTest1Factory = {
|
||||
const gTest1Factory = {
|
||||
createInstance: function (outer, iid) {
|
||||
if (outer == null)
|
||||
if (outer == null) {
|
||||
return gTest1TimerCallback.QueryInterface(iid);
|
||||
}
|
||||
throw Cr.NS_ERROR_NO_AGGREGATION;
|
||||
}
|
||||
};
|
||||
|
||||
var gTest2TimerCallback = {
|
||||
const gTest2TimerCallback = {
|
||||
notify: function T2CB_notify(aTimer) {
|
||||
do_throw("gTest2TimerCallback notify method should not have been called");
|
||||
},
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsITimer])
|
||||
};
|
||||
|
||||
var gTest2Factory = {
|
||||
const gTest2Factory = {
|
||||
createInstance: function (outer, iid) {
|
||||
if (outer == null)
|
||||
if (outer == null) {
|
||||
return gTest2TimerCallback.QueryInterface(iid);
|
||||
}
|
||||
throw Cr.NS_ERROR_NO_AGGREGATION;
|
||||
}
|
||||
};
|
||||
|
||||
var gTest3TimerCallback = {
|
||||
const gTest3TimerCallback = {
|
||||
notify: function T3CB_notify(aTimer) {
|
||||
do_throw("gTest3TimerCallback notify method should not have been called");
|
||||
},
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsITimerCallback])
|
||||
};
|
||||
|
||||
var gTest3Factory = {
|
||||
const gTest3Factory = {
|
||||
createInstance: function (outer, iid) {
|
||||
if (outer == null)
|
||||
if (outer == null) {
|
||||
return gTest3TimerCallback.QueryInterface(iid);
|
||||
}
|
||||
throw Cr.NS_ERROR_NO_AGGREGATION;
|
||||
}
|
||||
};
|
||||
|
||||
var gTest4TimerCallback = {
|
||||
const gTest4TimerCallback = {
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsITimerCallback])
|
||||
};
|
||||
|
||||
var gTest4Factory = {
|
||||
const gTest4Factory = {
|
||||
createInstance: function (outer, iid) {
|
||||
if (outer == null)
|
||||
if (outer == null) {
|
||||
return gTest4TimerCallback.QueryInterface(iid);
|
||||
}
|
||||
throw Cr.NS_ERROR_NO_AGGREGATION;
|
||||
}
|
||||
};
|
||||
|
||||
var gTest5TimerCallback = {
|
||||
const gTest5TimerCallback = {
|
||||
notify: function T5CB_notify(aTimer) {
|
||||
gCatMan.deleteCategoryEntry(CATEGORY_UPDATE_TIMER, TESTS[4].desc, true);
|
||||
TESTS[4].notified = true;
|
||||
@@ -370,15 +378,16 @@ var gTest5TimerCallback = {
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsITimerCallback])
|
||||
};
|
||||
|
||||
var gTest5Factory = {
|
||||
const gTest5Factory = {
|
||||
createInstance: function (outer, iid) {
|
||||
if (outer == null)
|
||||
if (outer == null) {
|
||||
return gTest5TimerCallback.QueryInterface(iid);
|
||||
}
|
||||
throw Cr.NS_ERROR_NO_AGGREGATION;
|
||||
}
|
||||
};
|
||||
|
||||
var gTest6TimerCallback = {
|
||||
const gTest6TimerCallback = {
|
||||
notify: function T6CB_notify(aTimer) {
|
||||
gCatMan.deleteCategoryEntry(CATEGORY_UPDATE_TIMER, TESTS[5].desc, true);
|
||||
TESTS[5].notified = true;
|
||||
@@ -387,15 +396,16 @@ var gTest6TimerCallback = {
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsITimerCallback])
|
||||
};
|
||||
|
||||
var gTest6Factory = {
|
||||
const gTest6Factory = {
|
||||
createInstance: function (outer, iid) {
|
||||
if (outer == null)
|
||||
if (outer == null) {
|
||||
return gTest6TimerCallback.QueryInterface(iid);
|
||||
}
|
||||
throw Cr.NS_ERROR_NO_AGGREGATION;
|
||||
}
|
||||
};
|
||||
|
||||
var gTest7TimerCallback = {
|
||||
const gTest7TimerCallback = {
|
||||
notify: function T7CB_notify(aTimer) {
|
||||
gCatMan.deleteCategoryEntry(CATEGORY_UPDATE_TIMER, TESTS[6].desc, true);
|
||||
TESTS[6].notified = true;
|
||||
@@ -404,44 +414,51 @@ var gTest7TimerCallback = {
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsITimerCallback])
|
||||
};
|
||||
|
||||
var gTest7Factory = {
|
||||
const gTest7Factory = {
|
||||
createInstance: function (outer, iid) {
|
||||
if (outer == null)
|
||||
if (outer == null) {
|
||||
return gTest7TimerCallback.QueryInterface(iid);
|
||||
}
|
||||
throw Cr.NS_ERROR_NO_AGGREGATION;
|
||||
}
|
||||
};
|
||||
|
||||
var gTest8TimerCallback = {
|
||||
const gTest8TimerCallback = {
|
||||
notify: function T8CB_notify(aTimer) {
|
||||
TESTS[7].notified = true;
|
||||
TESTS[7].notifyTime = Date.now();
|
||||
do_timeout(0, check_test8);
|
||||
do_timeout(0, function() {
|
||||
check_test8(gTest8TimerCallback);
|
||||
});
|
||||
},
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsITimerCallback])
|
||||
};
|
||||
|
||||
var gTest8Factory = {
|
||||
const gTest8Factory = {
|
||||
createInstance: function (outer, iid) {
|
||||
if (outer == null)
|
||||
if (outer == null) {
|
||||
return gTest8TimerCallback.QueryInterface(iid);
|
||||
}
|
||||
throw Cr.NS_ERROR_NO_AGGREGATION;
|
||||
}
|
||||
};
|
||||
|
||||
var gTest9TimerCallback = {
|
||||
const gTest9TimerCallback = {
|
||||
notify: function T9CB_notify(aTimer) {
|
||||
TESTS[8].notified = true;
|
||||
TESTS[8].notifyTime = Date.now();
|
||||
do_timeout(0, check_test8);
|
||||
do_timeout(0, function() {
|
||||
check_test8(gTest9TimerCallback);
|
||||
});
|
||||
},
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsITimerCallback])
|
||||
};
|
||||
|
||||
var gTest9Factory = {
|
||||
const gTest9Factory = {
|
||||
createInstance: function (outer, iid) {
|
||||
if (outer == null)
|
||||
if (outer == null) {
|
||||
return gTest9TimerCallback.QueryInterface(iid);
|
||||
}
|
||||
throw Cr.NS_ERROR_NO_AGGREGATION;
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user