diff options
Diffstat (limited to 'dom/smil/test')
55 files changed, 9006 insertions, 0 deletions
diff --git a/dom/smil/test/db_smilAnimateMotion.js b/dom/smil/test/db_smilAnimateMotion.js new file mode 100644 index 000000000..c4dfb4e24 --- /dev/null +++ b/dom/smil/test/db_smilAnimateMotion.js @@ -0,0 +1,253 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ts=2 sw=2 sts=2 et: */ +/* 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/. */ + +/* testcase data for <animateMotion> */ + +// Fake motion 'attribute', to satisfy testing code that expects an attribute. +var gMotionAttr = new AdditiveAttribute(SMILUtil.getMotionFakeAttributeName(), + "XML", "rect"); + +// CTM-summary-definitions, for re-use by multiple testcase bundles below. +var _reusedCTMLists = { + pacedBasic: { ctm0: [100, 200, 0], + ctm1_6: [105, 205, 0], + ctm1_3: [110, 210, 0], + ctm2_3: [120, 220, 0], + ctm1: [130, 210, 0] + }, + pacedR60: { ctm0: [100, 200, Math.PI/3], + ctm1_6: [105, 205, Math.PI/3], + ctm1_3: [110, 210, Math.PI/3], + ctm2_3: [120, 220, Math.PI/3], + ctm1: [130, 210, Math.PI/3] + }, + pacedRAuto: { ctm0: [100, 200, Math.PI/4], + ctm1_6: [105, 205, Math.PI/4], + ctm1_3: [110, 210, Math.PI/4], + ctm2_3: [120, 220, -Math.PI/4], + ctm1: [130, 210, -Math.PI/4] + }, + pacedRAutoReverse : { ctm0: [100, 200, 5*Math.PI/4], + ctm1_6: [105, 205, 5*Math.PI/4], + ctm1_3: [110, 210, 5*Math.PI/4], + ctm2_3: [120, 220, 3*Math.PI/4], + ctm1: [130, 210, 3*Math.PI/4] + }, + + discreteBasic : { ctm0: [100, 200, 0], + ctm1_6: [100, 200, 0], + ctm1_3: [120, 220, 0], + ctm2_3: [130, 210, 0], + ctm1: [130, 210, 0] + }, + discreteRAuto : { ctm0: [100, 200, Math.PI/4], + ctm1_6: [100, 200, Math.PI/4], + ctm1_3: [120, 220, -Math.PI/4], + ctm2_3: [130, 210, -Math.PI/4], + ctm1: [130, 210, -Math.PI/4] + }, + justMoveBasic : { ctm0: [40, 80, 0], + ctm1_6: [40, 80, 0], + ctm1_3: [40, 80, 0], + ctm2_3: [40, 80, 0], + ctm1: [40, 80, 0] + }, + justMoveR60 : { ctm0: [40, 80, Math.PI/3], + ctm1_6: [40, 80, Math.PI/3], + ctm1_3: [40, 80, Math.PI/3], + ctm2_3: [40, 80, Math.PI/3], + ctm1: [40, 80, Math.PI/3] + }, + justMoveRAuto : { ctm0: [40, 80, Math.atan(2)], + ctm1_6: [40, 80, Math.atan(2)], + ctm1_3: [40, 80, Math.atan(2)], + ctm2_3: [40, 80, Math.atan(2)], + ctm1: [40, 80, Math.atan(2)] + }, + justMoveRAutoReverse : { ctm0: [40, 80, Math.PI + Math.atan(2)], + ctm1_6: [40, 80, Math.PI + Math.atan(2)], + ctm1_3: [40, 80, Math.PI + Math.atan(2)], + ctm2_3: [40, 80, Math.PI + Math.atan(2)], + ctm1: [40, 80, Math.PI + Math.atan(2)] + }, + nullMoveBasic : { ctm0: [0, 0, 0], + ctm1_6: [0, 0, 0], + ctm1_3: [0, 0, 0], + ctm2_3: [0, 0, 0], + ctm1: [0, 0, 0] + }, + nullMoveRAutoReverse : { ctm0: [0, 0, Math.PI], + ctm1_6: [0, 0, Math.PI], + ctm1_3: [0, 0, Math.PI], + ctm2_3: [0, 0, Math.PI], + ctm1: [0, 0, Math.PI] + }, +}; + +var gMotionBundles = +[ + // Bundle to test basic functionality (using default calcMode='paced') + new TestcaseBundle(gMotionAttr, [ + // Basic paced-mode (default) test, with values/mpath/path + new AnimMotionTestcase({ "values": "100, 200; 120, 220; 130, 210" }, + _reusedCTMLists.pacedBasic), + new AnimMotionTestcase({ "path": "M100 200 L120 220 L130 210" }, + _reusedCTMLists.pacedBasic), + new AnimMotionTestcase({ "mpath": "M100 200 L120 220 L130 210" }, + _reusedCTMLists.pacedBasic), + + // ..and now with rotate=constant value in degrees + new AnimMotionTestcase({ "values": "100,200; 120,220; 130, 210", + "rotate": "60" }, + _reusedCTMLists.pacedR60), + new AnimMotionTestcase({ "path": "M100 200 L120 220 L130 210", + "rotate": "60" }, + _reusedCTMLists.pacedR60), + new AnimMotionTestcase({ "mpath": "M100 200 L120 220 L130 210", + "rotate": "60" }, + _reusedCTMLists.pacedR60), + + // ..and now with rotate=constant value in radians + new AnimMotionTestcase({ "path": "M100 200 L120 220 L130 210", + "rotate": "1.0471975512rad" }, // pi/3 + _reusedCTMLists.pacedR60), + + // ..and now with rotate=auto + new AnimMotionTestcase({ "values": "100,200; 120,220; 130, 210", + "rotate": "auto" }, + _reusedCTMLists.pacedRAuto), + new AnimMotionTestcase({ "path": "M100 200 L120 220 L130 210", + "rotate": "auto" }, + _reusedCTMLists.pacedRAuto), + new AnimMotionTestcase({ "mpath": "M100 200 L120 220 L130 210", + "rotate": "auto" }, + _reusedCTMLists.pacedRAuto), + + // ..and now with rotate=auto-reverse + new AnimMotionTestcase({ "values": "100,200; 120,220; 130, 210", + "rotate": "auto-reverse" }, + _reusedCTMLists.pacedRAutoReverse), + new AnimMotionTestcase({ "path": "M100 200 L120 220 L130 210", + "rotate": "auto-reverse" }, + _reusedCTMLists.pacedRAutoReverse), + new AnimMotionTestcase({ "mpath": "M100 200 L120 220 L130 210", + "rotate": "auto-reverse" }, + _reusedCTMLists.pacedRAutoReverse), + + ]), + + // Bundle to test calcMode='discrete' + new TestcaseBundle(gMotionAttr, [ + new AnimMotionTestcase({ "values": "100, 200; 120, 220; 130, 210", + "calcMode": "discrete" }, + _reusedCTMLists.discreteBasic), + new AnimMotionTestcase({ "path": "M100 200 L120 220 L130 210", + "calcMode": "discrete" }, + _reusedCTMLists.discreteBasic), + new AnimMotionTestcase({ "mpath": "M100 200 L120 220 L130 210", + "calcMode": "discrete" }, + _reusedCTMLists.discreteBasic), + // ..and now with rotate=auto + new AnimMotionTestcase({ "values": "100, 200; 120, 220; 130, 210", + "calcMode": "discrete", + "rotate": "auto" }, + _reusedCTMLists.discreteRAuto), + new AnimMotionTestcase({ "path": "M100 200 L120 220 L130 210", + "calcMode": "discrete", + "rotate": "auto" }, + _reusedCTMLists.discreteRAuto), + new AnimMotionTestcase({ "mpath": "M100 200 L120 220 L130 210", + "calcMode": "discrete", + "rotate": "auto" }, + _reusedCTMLists.discreteRAuto), + ]), + + // Bundle to test relative units ('em') + new TestcaseBundle(gMotionAttr, [ + // First with unitless values from->by... + new AnimMotionTestcase({ "from": "10, 10", + "by": "30, 60" }, + { ctm0: [10, 10, 0], + ctm1_6: [15, 20, 0], + ctm1_3: [20, 30, 0], + ctm2_3: [30, 50, 0], + ctm1: [40, 70, 0] + }), + // ... then add 'em' units (with 1em=10px) on half the values + new AnimMotionTestcase({ "from": "1em, 10", + "by": "30, 6em" }, + { ctm0: [10, 10, 0], + ctm1_6: [15, 20, 0], + ctm1_3: [20, 30, 0], + ctm2_3: [30, 50, 0], + ctm1: [40, 70, 0] + }), + ]), + + // Bundle to test a path with just a "move" command and nothing else + new TestcaseBundle(gMotionAttr, [ + new AnimMotionTestcase({ "values": "40, 80" }, + _reusedCTMLists.justMoveBasic), + new AnimMotionTestcase({ "path": "M40 80" }, + _reusedCTMLists.justMoveBasic), + new AnimMotionTestcase({ "mpath": "m40 80" }, + _reusedCTMLists.justMoveBasic), + ]), + // ... and now with a fixed rotate-angle + new TestcaseBundle(gMotionAttr, [ + new AnimMotionTestcase({ "values": "40, 80", + "rotate": "60" }, + _reusedCTMLists.justMoveR60), + new AnimMotionTestcase({ "path": "M40 80", + "rotate": "60" }, + _reusedCTMLists.justMoveR60), + new AnimMotionTestcase({ "mpath": "m40 80", + "rotate": "60" }, + _reusedCTMLists.justMoveR60), + ]), + // ... and now with 'auto' (should use the move itself as + // our tangent angle, I think) + new TestcaseBundle(gMotionAttr, [ + new AnimMotionTestcase({ "values": "40, 80", + "rotate": "auto" }, + _reusedCTMLists.justMoveRAuto), + new AnimMotionTestcase({ "path": "M40 80", + "rotate": "auto" }, + _reusedCTMLists.justMoveRAuto), + new AnimMotionTestcase({ "mpath": "m40 80", + "rotate": "auto" }, + _reusedCTMLists.justMoveRAuto), + ]), + // ... and now with 'auto-reverse' + new TestcaseBundle(gMotionAttr, [ + new AnimMotionTestcase({ "values": "40, 80", + "rotate": "auto-reverse" }, + _reusedCTMLists.justMoveRAutoReverse), + new AnimMotionTestcase({ "path": "M40 80", + "rotate": "auto-reverse" }, + _reusedCTMLists.justMoveRAutoReverse), + new AnimMotionTestcase({ "mpath": "m40 80", + "rotate": "auto-reverse" }, + _reusedCTMLists.justMoveRAutoReverse), + ]), + // ... and now with a null move to make sure 'auto'/'auto-reverse' don't + // blow up + new TestcaseBundle(gMotionAttr, [ + new AnimMotionTestcase({ "values": "0, 0", + "rotate": "auto" }, + _reusedCTMLists.nullMoveBasic), + ]), + new TestcaseBundle(gMotionAttr, [ + new AnimMotionTestcase({ "values": "0, 0", + "rotate": "auto-reverse" }, + _reusedCTMLists.nullMoveRAutoReverse), + ]), +]; + +// XXXdholbert Add more tests: +// - keyPoints/keyTimes +// - paths with curves +// - Control path with from/by/to diff --git a/dom/smil/test/db_smilCSSFromBy.js b/dom/smil/test/db_smilCSSFromBy.js new file mode 100644 index 000000000..f8b36e70a --- /dev/null +++ b/dom/smil/test/db_smilCSSFromBy.js @@ -0,0 +1,166 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ts=2 sw=2 sts=2 et: */ +/* 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/. */ + +/* testcase data for simple "from-by" animations of CSS properties */ + +// NOTE: This js file requires db_smilCSSPropertyList.js + +// Lists of testcases for re-use across multiple properties of the same type +var _fromByTestLists = +{ + color: [ + new AnimTestcaseFromBy("rgb(10, 20, 30)", "currentColor", + { midComp: "rgb(35, 45, 55)", + toComp: "rgb(60, 70, 80)"}), + new AnimTestcaseFromBy("currentColor", "rgb(30, 20, 10)", + { fromComp: "rgb(50, 50, 50)", + midComp: "rgb(65, 60, 55)", + toComp: "rgb(80, 70, 60)"}), + new AnimTestcaseFromBy("rgba(10, 20, 30, 0.2)", "rgba(50, 50, 50, 1)", + // (rgb(10, 20, 30) * 0.2 * 0.5 + rgb(52, 54, 56) * 1.0 * 0.5) * (1 / 0.6) + { midComp: "rgba(45, 48, 52, 0.6)", + // (rgb(10, 20, 30) * 0.2 + rgb(50, 50, 50) * 1) / 1.0 + toComp: "rgb(52, 54, 56)"}), + // Note: technically, the "from" and "by" values in the test case below + // would overflow the maxium color-channel values when added together. + // (e.g. for red [ignoring alpha for now], 100 + 240 = 340 which is > 255) + // The SVG Animation spec says we should clamp color values "as late as + // possible," i.e. allow the channel overflow and clamp at paint-time. + // But for now, we instead clamp the implicit "to" value for the animation + // and interpolate up to that clamped result. + new AnimTestcaseFromBy("rgba(100, 100, 100, 0.6)", "rgba(240, 240, 240, 1)", + // (rgb(100, 100, 100) * 0.6 * 0.5 + rgb(255, 255, 255) * 1.0 * 0.5) * (1 / 0.8) + { midComp: "rgba(197, 197, 197, 0.8)", + // (rgb(100, 100, 100) * 0.6 + rgb(240, 240, 240) is overflowed + toComp: "rgb(255, 255, 255)"}), + ], + lengthNoUnits: [ + new AnimTestcaseFromBy("0", "50", { fromComp: "0px", // 0 acts like 0px + midComp: "25px", + toComp: "50px"}), + new AnimTestcaseFromBy("30", "10", { fromComp: "30px", + midComp: "35px", + toComp: "40px"}), + ], + lengthNoUnitsSVG: [ + new AnimTestcaseFromBy("0", "50", { fromComp: "0", + midComp: "25", + toComp: "50"}), + new AnimTestcaseFromBy("30", "10", { fromComp: "30", + midComp: "35", + toComp: "40"}), + ], + lengthPx: [ + new AnimTestcaseFromBy("0px", "8px", { fromComp: "0px", + midComp: "4px", + toComp: "8px"}), + new AnimTestcaseFromBy("1px", "10px", { midComp: "6px", toComp: "11px"}), + ], + lengthPxSVG: [ + new AnimTestcaseFromBy("0px", "8px", { fromComp: "0", + midComp: "4", + toComp: "8"}), + new AnimTestcaseFromBy("1px", "10px", { fromComp: "1", + midComp: "6", + toComp: "11"}), + ], + opacity: [ + new AnimTestcaseFromBy("1", "-1", { midComp: "0.5", toComp: "0"}), + new AnimTestcaseFromBy("0.4", "-0.6", { midComp: "0.1", toComp: "0"}), + new AnimTestcaseFromBy("0.8", "-1.4", { midComp: "0.1", toComp: "0"}, + "opacities with abs val >1 get clamped too early"), + new AnimTestcaseFromBy("1.2", "-0.6", { midComp: "0.9", toComp: "0.6"}, + "opacities with abs val >1 get clamped too early"), + ], + paint: [ + // The "none" keyword & URI values aren't addiditve, so the animations in + // these testcases are expected to have no effect. + new AnimTestcaseFromBy("none", "none", { noEffect: 1 }), + new AnimTestcaseFromBy("url(#gradA)", "url(#gradB)", { noEffect: 1 }), + new AnimTestcaseFromBy("url(#gradA)", "url(#gradB) red", { noEffect: 1 }), + new AnimTestcaseFromBy("url(#gradA)", "none", { noEffect: 1 }), + new AnimTestcaseFromBy("red", "url(#gradA)", { noEffect: 1 }), + ], + URIsAndNone: [ + // No need to specify { noEffect: 1 }, since plain URI-valued properties + // aren't additive + new AnimTestcaseFromBy("url(#idA)", "url(#idB)"), + new AnimTestcaseFromBy("none", "url(#idB)"), + new AnimTestcaseFromBy("url(#idB)", "inherit"), + ], +}; + +// List of attribute/testcase-list bundles to be tested +var gFromByBundles = +[ + new TestcaseBundle(gPropList.clip, [ + new AnimTestcaseFromBy("rect(1px, 2px, 3px, 4px)", + "rect(10px, 20px, 30px, 40px)", + { midComp: "rect(6px, 12px, 18px, 24px)", + toComp: "rect(11px, 22px, 33px, 44px)"}), + // Adding "auto" (either as a standalone value or a subcomponent value) + // should cause animation to fail. + new AnimTestcaseFromBy("auto", "auto", { noEffect: 1 }), + new AnimTestcaseFromBy("auto", + "rect(auto, auto, auto, auto)", { noEffect: 1 }), + new AnimTestcaseFromBy("rect(auto, auto, auto, auto)", + "rect(auto, auto, auto, auto)", { noEffect: 1 }), + new AnimTestcaseFromBy("rect(1px, 2px, 3px, 4px)", "auto", { noEffect: 1 }), + new AnimTestcaseFromBy("auto", "rect(1px, 2px, 3px, 4px)", { noEffect: 1 }), + new AnimTestcaseFromBy("rect(1px, 2px, 3px, auto)", + "rect(10px, 20px, 30px, 40px)", { noEffect: 1 }), + new AnimTestcaseFromBy("rect(1px, auto, 3px, 4px)", + "rect(10px, auto, 30px, 40px)", { noEffect: 1 }), + new AnimTestcaseFromBy("rect(1px, 2px, 3px, 4px)", + "rect(10px, auto, 30px, 40px)", { noEffect: 1 }), + ]), + // Check that 'by' animations for 'cursor' has no effect + new TestcaseBundle(gPropList.cursor, [ + new AnimTestcaseFromBy("crosshair", "move"), + ]), + new TestcaseBundle(gPropList.fill, [].concat(_fromByTestLists.color, + _fromByTestLists.paint)), + // Check that 'by' animations involving URIs have no effect + new TestcaseBundle(gPropList.filter, _fromByTestLists.URIsAndNone), + new TestcaseBundle(gPropList.font, [ + new AnimTestcaseFromBy("10px serif", + "normal normal 400 100px / 10px monospace"), + ]), + new TestcaseBundle(gPropList.font_size, + [].concat(_fromByTestLists.lengthNoUnits, + _fromByTestLists.lengthPx)), + new TestcaseBundle(gPropList.font_size_adjust, [ + // These testcases implicitly have no effect, because font-size-adjust is + // non-additive (and is declared as such in db_smilCSSPropertyList.js) + new AnimTestcaseFromBy("0.5", "0.1"), + new AnimTestcaseFromBy("none", "0.1"), + new AnimTestcaseFromBy("0.1", "none") + ]), + new TestcaseBundle(gPropList.lighting_color, _fromByTestLists.color), + new TestcaseBundle(gPropList.marker, _fromByTestLists.URIsAndNone), + new TestcaseBundle(gPropList.marker_end, _fromByTestLists.URIsAndNone), + new TestcaseBundle(gPropList.marker_mid, _fromByTestLists.URIsAndNone), + new TestcaseBundle(gPropList.marker_start, _fromByTestLists.URIsAndNone), + new TestcaseBundle(gPropList.overflow, [ + new AnimTestcaseFromBy("inherit", "auto"), + new AnimTestcaseFromBy("scroll", "hidden") + ]), + new TestcaseBundle(gPropList.opacity, _fromByTestLists.opacity), + new TestcaseBundle(gPropList.stroke_miterlimit, [ + new AnimTestcaseFromBy("1", "1", { midComp: "1.5", toComp: "2" }), + new AnimTestcaseFromBy("20.1", "-10", { midComp: "15.1", toComp: "10.1" }), + ]), + new TestcaseBundle(gPropList.stroke_dasharray, [ + // These testcases implicitly have no effect, because stroke-dasharray is + // non-additive (and is declared as such in db_smilCSSPropertyList.js) + new AnimTestcaseFromBy("none", "5"), + new AnimTestcaseFromBy("10", "5"), + new AnimTestcaseFromBy("1", "2, 3"), + ]), + new TestcaseBundle(gPropList.stroke_width, + [].concat(_fromByTestLists.lengthNoUnitsSVG, + _fromByTestLists.lengthPxSVG)) +]; diff --git a/dom/smil/test/db_smilCSSFromTo.js b/dom/smil/test/db_smilCSSFromTo.js new file mode 100644 index 000000000..fe9cecd6c --- /dev/null +++ b/dom/smil/test/db_smilCSSFromTo.js @@ -0,0 +1,483 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ts=2 sw=2 sts=2 et: */ +/* 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/. */ + +/* testcase data for simple "from-to" animations of CSS properties */ + +// NOTE: This js file requires db_smilCSSPropertyList.js + +// NOTE: I'm Including 'inherit' and 'currentColor' as interpolatable values. +// According to SVG Mobile 1.2 section 16.2.9, "keywords such as inherit which +// yield a numeric computed value may be included in the values list for an +// interpolated animation". + +// Path of test URL (stripping off final slash + filename), for use in +// generating computed value of 'cursor' property +var _testPath = document.URL.substring(0, document.URL.lastIndexOf('/')); + +// Lists of testcases for re-use across multiple properties of the same type +var _fromToTestLists = { + color: [ + new AnimTestcaseFromTo("rgb(100, 100, 100)", "rgb(200, 200, 200)", + { midComp: "rgb(150, 150, 150)" }), + new AnimTestcaseFromTo("#F02000", "#0080A0", + { fromComp: "rgb(240, 32, 0)", + midComp: "rgb(120, 80, 80)", + toComp: "rgb(0, 128, 160)" }), + new AnimTestcaseFromTo("crimson", "lawngreen", + { fromComp: "rgb(220, 20, 60)", + midComp: "rgb(172, 136, 30)", + toComp: "rgb(124, 252, 0)" }), + new AnimTestcaseFromTo("currentColor", "rgb(100, 100, 100)", + { fromComp: "rgb(50, 50, 50)", + midComp: "rgb(75, 75, 75)" }), + new AnimTestcaseFromTo("rgba(10, 20, 30, 0.2)", "rgba(50, 50, 50, 1)", + // (rgb(10, 20, 30) * 0.2 * 0.5 + rgb(50, 50, 50) * 1.0 * 0.5) * (1 / 0.6) + { midComp: "rgba(43, 45, 47, 0.6)", + toComp: "rgb(50, 50, 50)"}), + ], + colorFromInheritBlack: [ + new AnimTestcaseFromTo("inherit", "rgb(200, 200, 200)", + { fromComp: "rgb(0, 0, 0)", + midComp: "rgb(100, 100, 100)" }), + ], + colorFromInheritWhite: [ + new AnimTestcaseFromTo("inherit", "rgb(205, 205, 205)", + { fromComp: "rgb(255, 255, 255)", + midComp: "rgb(230, 230, 230)" }), + ], + paintServer: [ + new AnimTestcaseFromTo("none", "none"), + new AnimTestcaseFromTo("none", "blue", { toComp : "rgb(0, 0, 255)" }), + new AnimTestcaseFromTo("rgb(50, 50, 50)", "none"), + new AnimTestcaseFromTo("url(#gradA)", "url(#gradB) currentColor", + { fromComp: "url(\"" + document.URL + + "#gradA\") rgb(0, 0, 0)", + toComp: "url(\"" + document.URL + + "#gradB\") rgb(50, 50, 50)" }, + "need support for URI-based paints"), + new AnimTestcaseFromTo("url(#gradA) orange", "url(#gradB)", + { fromComp: "url(\"" + document.URL + + "#gradA\") rgb(255, 165, 0)", + toComp: "url(\"" + document.URL + + "#gradB\") rgb(0, 0, 0)" }, + "need support for URI-based paints"), + new AnimTestcaseFromTo("url(#no_grad)", "url(#gradB)", + { fromComp: "url(\"" + document.URL + + "#no_grad\") " + "rgb(0, 0, 0)", + toComp: "url(\"" + document.URL + + "#gradB\") rgb(0, 0, 0)" }, + "need support for URI-based paints"), + new AnimTestcaseFromTo("url(#no_grad) rgb(1,2,3)", "url(#gradB) blue", + { fromComp: "url(\"" + document.URL + + "#no_grad\") " + "rgb(1, 2, 3)", + toComp: "url(\"" + document.URL + + "#gradB\") rgb(0, 0, 255)" }, + "need support for URI-based paints"), + ], + lengthNoUnits: [ + new AnimTestcaseFromTo("0", "20", { fromComp: "0px", + midComp: "10px", + toComp: "20px"}), + new AnimTestcaseFromTo("50", "0", { fromComp: "50px", + midComp: "25px", + toComp: "0px"}), + new AnimTestcaseFromTo("30", "80", { fromComp: "30px", + midComp: "55px", + toComp: "80px"}), + ], + lengthNoUnitsSVG: [ + new AnimTestcaseFromTo("0", "20", { fromComp: "0", + midComp: "10", + toComp: "20"}), + new AnimTestcaseFromTo("50", "0", { fromComp: "50", + midComp: "25", + toComp: "0"}), + new AnimTestcaseFromTo("30", "80", { fromComp: "30", + midComp: "55", + toComp: "80"}), + ], + lengthPx: [ + new AnimTestcaseFromTo("0px", "12px", { fromComp: "0px", + midComp: "6px"}), + new AnimTestcaseFromTo("16px", "0px", { midComp: "8px", + toComp: "0px"}), + new AnimTestcaseFromTo("10px", "20px", { midComp: "15px"}), + new AnimTestcaseFromTo("41px", "1px", { midComp: "21px"}), + ], + lengthPxSVG: [ + new AnimTestcaseFromTo("0px", "12px", { fromComp: "0", + midComp: "6", + toComp: "12"}), + new AnimTestcaseFromTo("16px", "0px", { fromComp: "16", + midComp: "8", + toComp: "0"}), + new AnimTestcaseFromTo("10px", "20px", { fromComp: "10", + midComp: "15", + toComp: "20"}), + new AnimTestcaseFromTo("41px", "1px", { fromComp: "41", + midComp: "21", + toComp: "1"}), + ], + lengthPctSVG: [ + new AnimTestcaseFromTo("20.5%", "0.5%", { midComp: "10.5%" }), + ], + lengthPxPctSVG: [ + new AnimTestcaseFromTo("10px", "10%", { midComp: "15px"}, + "need support for interpolating between " + + "px and percent values"), + ], + lengthPxNoUnitsSVG: [ + new AnimTestcaseFromTo("10", "20px", { fromComp: "10", + midComp: "15", + toComp: "20"}), + new AnimTestcaseFromTo("10px", "20", { fromComp: "10", + midComp: "15", + toComp: "20"}), + ], + opacity: [ + new AnimTestcaseFromTo("1", "0", { midComp: "0.5" }), + new AnimTestcaseFromTo("0.2", "0.12", { midComp: "0.16" }), + new AnimTestcaseFromTo("0.5", "0.7", { midComp: "0.6" }), + new AnimTestcaseFromTo("0.5", "inherit", + { midComp: "0.75", toComp: "1" }), + // Make sure we don't clamp out-of-range values before interpolation + new AnimTestcaseFromTo("0.2", "1.2", + { midComp: "0.7", toComp: "1" }, + "opacities with abs val >1 get clamped too early"), + new AnimTestcaseFromTo("-0.2", "0.6", + { fromComp: "0", midComp: "0.2" }), + new AnimTestcaseFromTo("-1.2", "1.6", + { fromComp: "0", midComp: "0.2", toComp: "1" }, + "opacities with abs val >1 get clamped too early"), + new AnimTestcaseFromTo("-0.6", "1.4", + { fromComp: "0", midComp: "0.4", toComp: "1" }, + "opacities with abs val >1 get clamped too early"), + ], + URIsAndNone: [ + new AnimTestcaseFromTo("url(#idA)", "url(#idB)", + { fromComp: "url(\"#idA\")", + toComp: "url(\"#idB\")"}), + new AnimTestcaseFromTo("none", "url(#idB)", + { toComp: "url(\"#idB\")"}), + new AnimTestcaseFromTo("url(#idB)", "inherit", + { fromComp: "url(\"#idB\")", + toComp: "none"}), + ], +}; + +// List of attribute/testcase-list bundles to be tested +var gFromToBundles = [ + new TestcaseBundle(gPropList.clip, [ + new AnimTestcaseFromTo("rect(1px, 2px, 3px, 4px)", + "rect(11px, 22px, 33px, 44px)", + { midComp: "rect(6px, 12px, 18px, 24px)" }), + new AnimTestcaseFromTo("rect(1px, auto, 3px, 4px)", + "rect(11px, auto, 33px, 44px)", + { midComp: "rect(6px, auto, 18px, 24px)" }), + new AnimTestcaseFromTo("auto", "auto"), + new AnimTestcaseFromTo("rect(auto, auto, auto, auto)", + "rect(auto, auto, auto, auto)"), + // Interpolation not supported in these next cases (with auto --> px-value) + new AnimTestcaseFromTo("rect(1px, auto, 3px, auto)", + "rect(11px, auto, 33px, 44px)"), + new AnimTestcaseFromTo("rect(1px, 2px, 3px, 4px)", + "rect(11px, auto, 33px, 44px)"), + new AnimTestcaseFromTo("rect(1px, 2px, 3px, 4px)", "auto"), + new AnimTestcaseFromTo("auto", "rect(1px, 2px, 3px, 4px)"), + ]), + new TestcaseBundle(gPropList.clip_path, _fromToTestLists.URIsAndNone), + new TestcaseBundle(gPropList.clip_rule, [ + new AnimTestcaseFromTo("nonzero", "evenodd"), + new AnimTestcaseFromTo("evenodd", "inherit", { toComp: "nonzero" }), + ]), + new TestcaseBundle(gPropList.color, + [].concat(_fromToTestLists.color, [ + // Note: inherited value is rgb(50, 50, 50) (set on <svg>) + new AnimTestcaseFromTo("inherit", "rgb(200, 200, 200)", + { fromComp: "rgb(50, 50, 50)", + midComp: "rgb(125, 125, 125)" }), + ])), + new TestcaseBundle(gPropList.color_interpolation, [ + new AnimTestcaseFromTo("sRGB", "auto", { fromComp: "srgb" }), + new AnimTestcaseFromTo("inherit", "linearRGB", + { fromComp: "srgb", toComp: "linearrgb" }), + ]), + new TestcaseBundle(gPropList.color_interpolation_filters, [ + new AnimTestcaseFromTo("sRGB", "auto", { fromComp: "srgb" }), + new AnimTestcaseFromTo("auto", "inherit", + { toComp: "linearrgb" }), + ]), + new TestcaseBundle(gPropList.cursor, [ + new AnimTestcaseFromTo("crosshair", "move"), + new AnimTestcaseFromTo("url('a.cur'), url('b.cur'), nw-resize", "sw-resize", + { fromComp: "url(\"" + _testPath + "/a.cur\"), " + + "url(\"" + _testPath + "/b.cur\"), " + + "nw-resize"}), + ]), + new TestcaseBundle(gPropList.direction, [ + new AnimTestcaseFromTo("ltr", "rtl"), + new AnimTestcaseFromTo("rtl", "inherit"), + ]), + new TestcaseBundle(gPropList.display, [ + // I'm not testing the "inherit" value for "display", because part of + // my test runs with "display: none" on everything, and so the + // inherited value isn't always the same. (i.e. the computed value + // of 'inherit' will be different in different tests) + new AnimTestcaseFromTo("block", "table-cell"), + new AnimTestcaseFromTo("inline", "inline-table"), + new AnimTestcaseFromTo("table-row", "none"), + ]), + new TestcaseBundle(gPropList.dominant_baseline, [ + new AnimTestcaseFromTo("use-script", "no-change"), + new AnimTestcaseFromTo("reset-size", "ideographic"), + new AnimTestcaseFromTo("alphabetic", "hanging"), + new AnimTestcaseFromTo("mathematical", "central"), + new AnimTestcaseFromTo("middle", "text-after-edge"), + new AnimTestcaseFromTo("text-before-edge", "auto"), + new AnimTestcaseFromTo("use-script", "inherit", { toComp: "auto" } ), + ]), + // NOTE: Mozilla doesn't currently support "enable-background", but I'm + // testing it here in case we ever add support for it, because it's + // explicitly not animatable in the SVG spec. + new TestcaseBundle(gPropList.enable_background, [ + new AnimTestcaseFromTo("new", "accumulate"), + ]), + new TestcaseBundle(gPropList.fill, + [].concat(_fromToTestLists.color, + _fromToTestLists.paintServer, + _fromToTestLists.colorFromInheritBlack)), + new TestcaseBundle(gPropList.fill_opacity, _fromToTestLists.opacity), + new TestcaseBundle(gPropList.fill_rule, [ + new AnimTestcaseFromTo("nonzero", "evenodd"), + new AnimTestcaseFromTo("evenodd", "inherit", { toComp: "nonzero" }), + ]), + new TestcaseBundle(gPropList.filter, _fromToTestLists.URIsAndNone), + new TestcaseBundle(gPropList.flood_color, + [].concat(_fromToTestLists.color, + _fromToTestLists.colorFromInheritBlack)), + new TestcaseBundle(gPropList.flood_opacity, _fromToTestLists.opacity), + new TestcaseBundle(gPropList.font, [ + // NOTE: 'line-height' is hard-wired at 10px in test_smilCSSFromTo.xhtml + // because if it's not explicitly set, its value varies across platforms. + // NOTE: System font values can't be tested here, because their computed + // values vary from platform to platform. However, they are tested + // visually, in the reftest "anim-css-font-1.svg" + new AnimTestcaseFromTo("10px serif", "30px serif", + { fromComp: "normal normal 400 10px / 10px serif", + toComp: "normal normal 400 30px / 10px serif"}), + new AnimTestcaseFromTo("10px serif", "30px sans-serif", + { fromComp: "normal normal 400 10px / 10px serif", + toComp: "normal normal 400 30px / 10px sans-serif"}), + new AnimTestcaseFromTo("1px / 90px cursive", "100px monospace", + { fromComp: "normal normal 400 1px / 10px cursive", + toComp: "normal normal 400 100px / 10px monospace"}), + new AnimTestcaseFromTo("italic small-caps 200 1px cursive", + "100px monospace", + { fromComp: "italic small-caps 200 1px / 10px cursive", + toComp: "normal normal 400 100px / 10px monospace"}), + new AnimTestcaseFromTo("oblique normal 200 30px / 10px cursive", + "normal small-caps 800 40px / 10px serif"), + ]), + new TestcaseBundle(gPropList.font_family, [ + new AnimTestcaseFromTo("serif", "sans-serif"), + new AnimTestcaseFromTo("cursive", "monospace"), + ]), + new TestcaseBundle(gPropList.font_size, + [].concat(_fromToTestLists.lengthNoUnits, + _fromToTestLists.lengthPx, [ + new AnimTestcaseFromTo("10px", "40%", { midComp: "15px", toComp: "20px" }), + new AnimTestcaseFromTo("160%", "80%", + { fromComp: "80px", + midComp: "60px", + toComp: "40px"}), + ])), + new TestcaseBundle(gPropList.font_size_adjust, [ + new AnimTestcaseFromTo("0.9", "0.1", { midComp: "0.5" }), + new AnimTestcaseFromTo("0.5", "0.6", { midComp: "0.55" }), + new AnimTestcaseFromTo("none", "0.4"), + ]), + new TestcaseBundle(gPropList.font_stretch, [ + new AnimTestcaseFromTo("normal", "wider", {}, + "need support for animating between " + + "relative 'font-stretch' values"), + new AnimTestcaseFromTo("narrower", "ultra-condensed", {}, + "need support for animating between " + + "relative 'font-stretch' values"), + new AnimTestcaseFromTo("ultra-condensed", "condensed", + { midComp: "extra-condensed" }), + new AnimTestcaseFromTo("semi-condensed", "semi-expanded", + { midComp: "normal" }), + new AnimTestcaseFromTo("expanded", "ultra-expanded", + { midComp: "extra-expanded" }), + new AnimTestcaseFromTo("ultra-expanded", "inherit", + { midComp: "expanded", toComp: "normal" }), + ]), + new TestcaseBundle(gPropList.font_style, [ + new AnimTestcaseFromTo("italic", "inherit", { toComp: "normal" }), + new AnimTestcaseFromTo("normal", "italic"), + new AnimTestcaseFromTo("italic", "oblique"), + new AnimTestcaseFromTo("oblique", "normal"), + ]), + new TestcaseBundle(gPropList.font_variant, [ + new AnimTestcaseFromTo("inherit", "small-caps", { fromComp: "normal" }), + new AnimTestcaseFromTo("small-caps", "normal"), + ]), + new TestcaseBundle(gPropList.font_weight, [ + new AnimTestcaseFromTo("100", "900", { midComp: "500" }), + new AnimTestcaseFromTo("700", "100", { midComp: "400" }), + new AnimTestcaseFromTo("inherit", "200", + { fromComp: "400", midComp: "300" }), + new AnimTestcaseFromTo("normal", "bold", + { fromComp: "400", midComp: "500", toComp: "700" }), + new AnimTestcaseFromTo("lighter", "bolder", {}, + "need support for animating between " + + "relative 'font-weight' values"), + ]), + // NOTE: Mozilla doesn't currently support "glyph-orientation-horizontal" or + // "glyph-orientation-vertical", but I'm testing them here in case we ever + // add support for them, because they're explicitly not animatable in the SVG + // spec. + new TestcaseBundle(gPropList.glyph_orientation_horizontal, + [ new AnimTestcaseFromTo("45deg", "60deg") ]), + new TestcaseBundle(gPropList.glyph_orientation_vertical, + [ new AnimTestcaseFromTo("45deg", "60deg") ]), + new TestcaseBundle(gPropList.image_rendering, [ + new AnimTestcaseFromTo("auto", "optimizeQuality", + { toComp: "optimizequality" }), + new AnimTestcaseFromTo("optimizeQuality", "optimizeSpeed", + { fromComp: "optimizequality", + toComp: "optimizespeed" }), + ]), + new TestcaseBundle(gPropList.letter_spacing, + [].concat(_fromToTestLists.lengthNoUnits, + _fromToTestLists.lengthPx, + _fromToTestLists.lengthPxPctSVG)), + new TestcaseBundle(gPropList.letter_spacing, + _fromToTestLists.lengthPctSVG, + "pct->pct animations don't currently work for " + + "*-spacing properties"), + new TestcaseBundle(gPropList.lighting_color, + [].concat(_fromToTestLists.color, + _fromToTestLists.colorFromInheritWhite)), + new TestcaseBundle(gPropList.marker, _fromToTestLists.URIsAndNone), + new TestcaseBundle(gPropList.marker_end, _fromToTestLists.URIsAndNone), + new TestcaseBundle(gPropList.marker_mid, _fromToTestLists.URIsAndNone), + new TestcaseBundle(gPropList.marker_start, _fromToTestLists.URIsAndNone), + new TestcaseBundle(gPropList.mask, _fromToTestLists.URIsAndNone), + new TestcaseBundle(gPropList.opacity, _fromToTestLists.opacity), + new TestcaseBundle(gPropList.overflow, [ + new AnimTestcaseFromTo("auto", "visible"), + new AnimTestcaseFromTo("inherit", "visible", { fromComp: "hidden" }), + new AnimTestcaseFromTo("scroll", "auto"), + ]), + new TestcaseBundle(gPropList.pointer_events, [ + new AnimTestcaseFromTo("visibleFill", "stroke", + { fromComp: "visiblefill" }), + new AnimTestcaseFromTo("none", "visibleStroke", + { toComp: "visiblestroke" }), + ]), + new TestcaseBundle(gPropList.shape_rendering, [ + new AnimTestcaseFromTo("auto", "optimizeSpeed", + { toComp: "optimizespeed" }), + new AnimTestcaseFromTo("crispEdges", "geometricPrecision", + { fromComp: "crispedges", + toComp: "geometricprecision" }), + ]), + new TestcaseBundle(gPropList.stop_color, + [].concat(_fromToTestLists.color, + _fromToTestLists.colorFromInheritBlack)), + new TestcaseBundle(gPropList.stop_opacity, _fromToTestLists.opacity), + new TestcaseBundle(gPropList.stroke, + [].concat(_fromToTestLists.color, + _fromToTestLists.paintServer, [ + // Note: inherited value is "none" (the default for "stroke" property) + new AnimTestcaseFromTo("inherit", "rgb(200, 200, 200)", + { fromComp: "none"})])), + new TestcaseBundle(gPropList.stroke_dasharray, + [].concat(_fromToTestLists.lengthPctSVG, + [ + new AnimTestcaseFromTo("inherit", "20", { fromComp: "none"}), + new AnimTestcaseFromTo("1", "none"), + new AnimTestcaseFromTo("10", "20", { midComp: "15"}), + new AnimTestcaseFromTo("1", "2, 3", { fromComp: "1, 1", + midComp: "1.5, 2"}), + new AnimTestcaseFromTo("2, 8", "6", { midComp: "4, 7"}), + new AnimTestcaseFromTo("1, 3", "1, 3, 5, 7, 9", + { fromComp: "1, 3, 1, 3, 1, 3, 1, 3, 1, 3", + midComp: "1, 3, 3, 5, 5, 2, 2, 4, 4, 6"}), + ])), + new TestcaseBundle(gPropList.stroke_dashoffset, + [].concat(_fromToTestLists.lengthNoUnitsSVG, + _fromToTestLists.lengthPxSVG, + _fromToTestLists.lengthPxPctSVG, + _fromToTestLists.lengthPctSVG, + _fromToTestLists.lengthPxNoUnitsSVG)), + new TestcaseBundle(gPropList.stroke_linecap, [ + new AnimTestcaseFromTo("butt", "round"), + new AnimTestcaseFromTo("round", "square"), + ]), + new TestcaseBundle(gPropList.stroke_linejoin, [ + new AnimTestcaseFromTo("miter", "round"), + new AnimTestcaseFromTo("round", "bevel"), + ]), + new TestcaseBundle(gPropList.stroke_miterlimit, [ + new AnimTestcaseFromTo("1", "2", { midComp: "1.5" }), + new AnimTestcaseFromTo("20.1", "10.1", { midComp: "15.1" }), + ]), + new TestcaseBundle(gPropList.stroke_opacity, _fromToTestLists.opacity), + new TestcaseBundle(gPropList.stroke_width, + [].concat(_fromToTestLists.lengthNoUnitsSVG, + _fromToTestLists.lengthPxSVG, + _fromToTestLists.lengthPxPctSVG, + _fromToTestLists.lengthPctSVG, + _fromToTestLists.lengthPxNoUnitsSVG, [ + new AnimTestcaseFromTo("inherit", "7px", + { fromComp: "1", midComp: "4", toComp: "7" }), + ])), + new TestcaseBundle(gPropList.text_anchor, [ + new AnimTestcaseFromTo("start", "middle"), + new AnimTestcaseFromTo("middle", "end"), + ]), + new TestcaseBundle(gPropList.text_decoration, [ + new AnimTestcaseFromTo("none", "underline"), + new AnimTestcaseFromTo("overline", "line-through"), + new AnimTestcaseFromTo("blink", "underline"), + ]), + new TestcaseBundle(gPropList.text_rendering, [ + new AnimTestcaseFromTo("auto", "optimizeSpeed", + { toComp: "optimizespeed" }), + new AnimTestcaseFromTo("optimizeSpeed", "geometricPrecision", + { fromComp: "optimizespeed", + toComp: "geometricprecision" }), + new AnimTestcaseFromTo("geometricPrecision", "optimizeLegibility", + { fromComp: "geometricprecision", + toComp: "optimizelegibility" }), + ]), + new TestcaseBundle(gPropList.unicode_bidi, [ + new AnimTestcaseFromTo("embed", "bidi-override"), + ]), + new TestcaseBundle(gPropList.vector_effect, [ + new AnimTestcaseFromTo("none", "non-scaling-stroke"), + ]), + new TestcaseBundle(gPropList.visibility, [ + new AnimTestcaseFromTo("visible", "hidden"), + new AnimTestcaseFromTo("hidden", "collapse"), + ]), + new TestcaseBundle(gPropList.word_spacing, + [].concat(_fromToTestLists.lengthNoUnits, + _fromToTestLists.lengthPx, + _fromToTestLists.lengthPxPctSVG)), + new TestcaseBundle(gPropList.word_spacing, + _fromToTestLists.lengthPctSVG, + "pct->pct animations don't currently work for " + + "*-spacing properties"), + // NOTE: Mozilla doesn't currently support "writing-mode", but I'm + // testing it here in case we ever add support for it, because it's + // explicitly not animatable in the SVG spec. + new TestcaseBundle(gPropList.writing_mode, [ + new AnimTestcaseFromTo("lr", "rl"), + ]), +]; diff --git a/dom/smil/test/db_smilCSSPaced.js b/dom/smil/test/db_smilCSSPaced.js new file mode 100644 index 000000000..3f069691f --- /dev/null +++ b/dom/smil/test/db_smilCSSPaced.js @@ -0,0 +1,321 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- */ +/* vim: set shiftwidth=4 tabstop=4 autoindent cindent noexpandtab: */ +/* 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/. */ + +/* testcase data for paced-mode animations of CSS properties */ + +// Lists of testcases for re-use across multiple properties of the same type +var _pacedTestLists = +{ + color: [ + new AnimTestcasePaced("rgb(2, 4, 6); " + + "rgb(4, 8, 12); " + + "rgb(8, 16, 24)", + { comp0: "rgb(2, 4, 6)", + comp1_6: "rgb(3, 6, 9)", + comp1_3: "rgb(4, 8, 12)", + comp2_3: "rgb(6, 12, 18)", + comp1: "rgb(8, 16, 24)" + }), + new AnimTestcasePaced("rgb(10, 10, 10); " + + "rgb(20, 10, 8); " + + "rgb(20, 30, 4)", + { comp0: "rgb(10, 10, 10)", + comp1_6: "rgb(15, 10, 9)", + comp1_3: "rgb(20, 10, 8)", + comp2_3: "rgb(20, 20, 6)", + comp1: "rgb(20, 30, 4)" + }), + new AnimTestcasePaced("olive; " + // rgb(128, 128, 0) + "currentColor; " + // rgb(50, 50, 50) + "rgb(206, 150, 206)", + { comp0: "rgb(128, 128, 0)", + comp1_6: "rgb(89, 89, 25)", + comp1_3: "rgb(50, 50, 50)", + comp2_3: "rgb(128, 100, 128)", + comp1: "rgb(206, 150, 206)" + }), + // Use the same RGB component values to make + // premultication effect easier to compute. + new AnimTestcasePaced("rgba(20, 40, 60, 0.2); " + + "rgba(20, 40, 60, 0.4); " + + "rgba(20, 40, 60, 0.8)", + { comp0: "rgba(20, 40, 60, 0.2)", + comp1_6: "rgba(20, 40, 60, 0.3)", + comp1_3: "rgba(20, 40, 60, 0.4)", + comp2_3: "rgba(20, 40, 60, 0.6)", + comp1: "rgba(20, 40, 60, 0.8)" + }), + ], + paintServer : [ + // Sanity check: These aren't interpolatable -- they should end up + // ignoring the calcMode="paced" and falling into discrete-mode. + new AnimTestcasePaced("url(#gradA); url(#gradB)", + { + comp0: "url(\"" + document.URL + "#gradA\") rgb(0, 0, 0)", + comp1_6: "url(\"" + document.URL + "#gradA\") rgb(0, 0, 0)", + comp1_3: "url(\"" + document.URL + "#gradA\") rgb(0, 0, 0)", + comp2_3: "url(\"" + document.URL + "#gradB\") rgb(0, 0, 0)", + comp1: "url(\"" + document.URL + "#gradB\") rgb(0, 0, 0)" + }, + "need support for URI-based paints"), + new AnimTestcasePaced("url(#gradA); url(#gradB); url(#gradC)", + { + comp0: "url(\"" + document.URL + "#gradA\") rgb(0, 0, 0)", + comp1_6: "url(\"" + document.URL + "#gradA\") rgb(0, 0, 0)", + comp1_3: "url(\"" + document.URL + "#gradB\") rgb(0, 0, 0)", + comp2_3: "url(\"" + document.URL + "#gradC\") rgb(0, 0, 0)", + comp1: "url(\"" + document.URL + "#gradC\") rgb(0, 0, 0)" + }, + "need support for URI-based paints"), + ], + lengthNoUnits : [ + new AnimTestcasePaced("2; 0; 4", + { comp0: "2px", + comp1_6: "1px", + comp1_3: "0px", + comp2_3: "2px", + comp1: "4px" + }), + new AnimTestcasePaced("10; 12; 8", + { comp0: "10px", + comp1_6: "11px", + comp1_3: "12px", + comp2_3: "10px", + comp1: "8px" + }), + ], + lengthNoUnitsSVG : [ + new AnimTestcasePaced("2; 0; 4", + { comp0: "2", + comp1_6: "1", + comp1_3: "0", + comp2_3: "2", + comp1: "4" + }), + new AnimTestcasePaced("10; 12; 8", + { comp0: "10", + comp1_6: "11", + comp1_3: "12", + comp2_3: "10", + comp1: "8" + }), + ], + lengthPx : [ + new AnimTestcasePaced("0px; 2px; 6px", + { comp0: "0px", + comp1_6: "1px", + comp1_3: "2px", + comp2_3: "4px", + comp1: "6px" + }), + new AnimTestcasePaced("10px; 12px; 8px", + { comp0: "10px", + comp1_6: "11px", + comp1_3: "12px", + comp2_3: "10px", + comp1: "8px" + }), + ], + lengthPxSVG : [ + new AnimTestcasePaced("0px; 2px; 6px", + { comp0: "0", + comp1_6: "1", + comp1_3: "2", + comp2_3: "4", + comp1: "6" + }), + new AnimTestcasePaced("10px; 12px; 8px", + { comp0: "10", + comp1_6: "11", + comp1_3: "12", + comp2_3: "10", + comp1: "8" + }), + ], + lengthPctSVG : [ + new AnimTestcasePaced("5%; 6%; 4%", + { comp0: "5%", + comp1_6: "5.5%", + comp1_3: "6%", + comp2_3: "5%", + comp1: "4%" + }), + ], + lengthPxPctSVG : [ + new AnimTestcasePaced("0px; 1%; 6px", + { comp0: "0px", + comp1_6: "1px", + comp1_3: "1%", + comp2_3: "4px", + comp1: "6px" + }, + "need support for interpolating between " + + "px and percent values"), + ], + opacity : [ + new AnimTestcasePaced("0; 0.2; 0.6", + { comp0: "0", + comp1_6: "0.1", + comp1_3: "0.2", + comp2_3: "0.4", + comp1: "0.6" + }), + new AnimTestcasePaced("0.7; 1.0; 0.4", + { comp0: "0.7", + comp1_6: "0.85", + comp1_3: "1", + comp2_3: "0.7", + comp1: "0.4" + }), + ], + rect : [ + new AnimTestcasePaced("rect(2px, 4px, 6px, 8px); " + + "rect(4px, 8px, 12px, 16px); " + + "rect(8px, 16px, 24px, 32px)", + { comp0: "rect(2px, 4px, 6px, 8px)", + comp1_6: "rect(3px, 6px, 9px, 12px)", + comp1_3: "rect(4px, 8px, 12px, 16px)", + comp2_3: "rect(6px, 12px, 18px, 24px)", + comp1: "rect(8px, 16px, 24px, 32px)" + }), + new AnimTestcasePaced("rect(10px, 10px, 10px, 10px); " + + "rect(20px, 10px, 50px, 8px); " + + "rect(20px, 30px, 130px, 4px)", + { comp0: "rect(10px, 10px, 10px, 10px)", + comp1_6: "rect(15px, 10px, 30px, 9px)", + comp1_3: "rect(20px, 10px, 50px, 8px)", + comp2_3: "rect(20px, 20px, 90px, 6px)", + comp1: "rect(20px, 30px, 130px, 4px)" + }), + new AnimTestcasePaced("rect(10px, auto, 10px, 10px); " + + "rect(20px, auto, 50px, 8px); " + + "rect(40px, auto, 130px, 4px)", + { comp0: "rect(10px, auto, 10px, 10px)", + comp1_6: "rect(15px, auto, 30px, 9px)", + comp1_3: "rect(20px, auto, 50px, 8px)", + comp2_3: "rect(30px, auto, 90px, 6px)", + comp1: "rect(40px, auto, 130px, 4px)" + }), + // Paced-mode animation is not supported in these next few cases + // (Can't compute subcomponent distance between 'auto' & px-values) + new AnimTestcasePaced("rect(10px, 10px, 10px, auto); " + + "rect(20px, 10px, 50px, 8px); " + + "rect(20px, 30px, 130px, 4px)", + { comp0: "rect(10px, 10px, 10px, auto)", + comp1_6: "rect(10px, 10px, 10px, auto)", + comp1_3: "rect(20px, 10px, 50px, 8px)", + comp2_3: "rect(20px, 30px, 130px, 4px)", + comp1: "rect(20px, 30px, 130px, 4px)" + }), + new AnimTestcasePaced("rect(10px, 10px, 10px, 10px); " + + "rect(20px, 10px, 50px, 8px); " + + "auto", + { comp0: "rect(10px, 10px, 10px, 10px)", + comp1_6: "rect(10px, 10px, 10px, 10px)", + comp1_3: "rect(20px, 10px, 50px, 8px)", + comp2_3: "auto", + comp1: "auto" + }), + new AnimTestcasePaced("auto; " + + "auto; " + + "rect(20px, 30px, 130px, 4px)", + { comp0: "auto", + comp1_6: "auto", + comp1_3: "auto", + comp2_3: "rect(20px, 30px, 130px, 4px)", + comp1: "rect(20px, 30px, 130px, 4px)" + }), + new AnimTestcasePaced("auto; auto; auto", + { comp0: "auto", + comp1_6: "auto", + comp1_3: "auto", + comp2_3: "auto", + comp1: "auto" + }), + ], +}; + +// TODO: test more properties here. +var gPacedBundles = +[ + new TestcaseBundle(gPropList.clip, _pacedTestLists.rect), + new TestcaseBundle(gPropList.color, _pacedTestLists.color), + new TestcaseBundle(gPropList.direction, [ + new AnimTestcasePaced("rtl; ltr; rtl") + ]), + new TestcaseBundle(gPropList.fill, + [].concat(_pacedTestLists.color, + _pacedTestLists.paintServer)), + new TestcaseBundle(gPropList.font_size, + [].concat(_pacedTestLists.lengthNoUnits, + _pacedTestLists.lengthPx, [ + new AnimTestcasePaced("20%; 24%; 16%", + { comp0: "10px", + comp1_6: "11px", + comp1_3: "12px", + comp2_3: "10px", + comp1: "8px" + }), + new AnimTestcasePaced("0px; 4%; 6px", + { comp0: "0px", + comp1_6: "1px", + comp1_3: "2px", + comp2_3: "4px", + comp1: "6px" + }), + ]) + ), + new TestcaseBundle(gPropList.font_size_adjust, [ + new AnimTestcasePaced("0.2; 0.6; 0.8", + { comp0: "0.2", + comp1_6: "0.3", + comp1_3: "0.4", + comp2_3: "0.6", + comp1: "0.8" + }), + new AnimTestcasePaced("none; none; 0.5", + { comp0: "none", + comp1_6: "none", + comp1_3: "none", + comp2_3: "0.5", + comp1: "0.5" + }), + ]), + new TestcaseBundle(gPropList.font_family, [ + // Sanity check: 'font-family' isn't interpolatable. It should end up + // ignoring the calcMode="paced" and falling into discrete-mode. + new AnimTestcasePaced("serif; sans-serif; monospace", + { comp0: "serif", + comp1_6: "serif", + comp1_3: "sans-serif", + comp2_3: "monospace", + comp1: "monospace" + }, + "need support for more font properties"), + ]), + new TestcaseBundle(gPropList.opacity, _pacedTestLists.opacity), + new TestcaseBundle(gPropList.stroke_dasharray, + [].concat(_pacedTestLists.lengthPctSVG, [ + new AnimTestcasePaced("7, 7, 7; 7, 10, 3; 1, 2, 3", + { comp0: "7, 7, 7", + comp1_6: "7, 8.5, 5", + comp1_3: "7, 10, 3", + comp2_3: "4, 6, 3", + comp1: "1, 2, 3" + }), + ])), + new TestcaseBundle(gPropList.stroke_dashoffset, + [].concat(_pacedTestLists.lengthNoUnitsSVG, + _pacedTestLists.lengthPxSVG, + _pacedTestLists.lengthPctSVG, + _pacedTestLists.lengthPxPctSVG)), + new TestcaseBundle(gPropList.stroke_width, + [].concat(_pacedTestLists.lengthNoUnitsSVG, + _pacedTestLists.lengthPxSVG, + _pacedTestLists.lengthPctSVG, + _pacedTestLists.lengthPxPctSVG)), + // XXXdholbert TODO: test 'stroke-dasharray' once we support animating it +]; diff --git a/dom/smil/test/db_smilCSSPropertyList.js b/dom/smil/test/db_smilCSSPropertyList.js new file mode 100644 index 000000000..f9f3de62e --- /dev/null +++ b/dom/smil/test/db_smilCSSPropertyList.js @@ -0,0 +1,93 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ts=2 sw=2 sts=2 et: */ +/* 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/. */ + +/* list of CSS properties recognized by SVG 1.1 spec, for use in mochitests */ + +// List of CSS Properties from SVG 1.1 Specification, Appendix N +var gPropList = +{ + // NOTE: AnimatedAttribute signature is: + // (attrName, attrType, sampleTarget, isAnimatable, isAdditive) + + // SKIP 'alignment-baseline' property: animatable but not supported by Mozilla + // SKIP 'baseline-shift' property: animatable but not supported by Mozilla + clip: new AdditiveAttribute("clip", "CSS", "marker"), + clip_path: new NonAdditiveAttribute("clip-path", "CSS", "rect"), + clip_rule: new NonAdditiveAttribute("clip-rule", "CSS", "circle"), + color: new AdditiveAttribute("color", "CSS", "rect"), + color_interpolation: + new NonAdditiveAttribute("color-interpolation", "CSS", "rect"), + color_interpolation_filters: + new NonAdditiveAttribute("color-interpolation-filters", "CSS", + "feFlood"), + // SKIP 'color-profile' property: animatable but not supported by Mozilla + // SKIP 'color-rendering' property: animatable but not supported by Mozilla + cursor: new NonAdditiveAttribute("cursor", "CSS", "rect"), + direction: new NonAnimatableAttribute("direction", "CSS", "text"), + display: new NonAdditiveAttribute("display", "CSS", "rect"), + dominant_baseline: + new NonAdditiveAttribute("dominant-baseline", "CSS", "text"), + enable_background: + // NOTE: Not supported by Mozilla, but explicitly non-animatable + new NonAnimatableAttribute("enable-background", "CSS", "marker"), + fill: new AdditiveAttribute("fill", "CSS", "rect"), + fill_opacity: new AdditiveAttribute("fill-opacity", "CSS", "rect"), + fill_rule: new NonAdditiveAttribute("fill-rule", "CSS", "rect"), + filter: new NonAdditiveAttribute("filter", "CSS", "rect"), + flood_color: new AdditiveAttribute("flood-color", "CSS", "feFlood"), + flood_opacity: new AdditiveAttribute("flood-opacity", "CSS", "feFlood"), + font: new NonAdditiveAttribute("font", "CSS", "text"), + font_family: new NonAdditiveAttribute("font-family", "CSS", "text"), + font_size: new AdditiveAttribute("font-size", "CSS", "text"), + font_size_adjust: + new NonAdditiveAttribute("font-size-adjust", "CSS", "text"), + font_stretch: new NonAdditiveAttribute("font-stretch", "CSS", "text"), + font_style: new NonAdditiveAttribute("font-style", "CSS", "text"), + font_variant: new NonAdditiveAttribute("font-variant", "CSS", "text"), + // XXXdholbert should 'font-weight' be additive? + font_weight: new NonAdditiveAttribute("font-weight", "CSS", "text"), + glyph_orientation_horizontal: + // NOTE: Not supported by Mozilla, but explicitly non-animatable + NonAnimatableAttribute("glyph-orientation-horizontal", "CSS", "text"), + glyph_orientation_vertical: + // NOTE: Not supported by Mozilla, but explicitly non-animatable + NonAnimatableAttribute("glyph-orientation-horizontal", "CSS", "text"), + image_rendering: + NonAdditiveAttribute("image-rendering", "CSS", "image"), + // SKIP 'kerning' property: animatable but not supported by Mozilla + letter_spacing: new AdditiveAttribute("letter-spacing", "CSS", "text"), + lighting_color: + new AdditiveAttribute("lighting-color", "CSS", "feDiffuseLighting"), + marker: new NonAdditiveAttribute("marker", "CSS", "line"), + marker_end: new NonAdditiveAttribute("marker-end", "CSS", "line"), + marker_mid: new NonAdditiveAttribute("marker-mid", "CSS", "line"), + marker_start: new NonAdditiveAttribute("marker-start", "CSS", "line"), + mask: new NonAdditiveAttribute("mask", "CSS", "line"), + opacity: new AdditiveAttribute("opacity", "CSS", "rect"), + overflow: new NonAdditiveAttribute("overflow", "CSS", "marker"), + pointer_events: new NonAdditiveAttribute("pointer-events", "CSS", "rect"), + shape_rendering: new NonAdditiveAttribute("shape-rendering", "CSS", "rect"), + stop_color: new AdditiveAttribute("stop-color", "CSS", "stop"), + stop_opacity: new AdditiveAttribute("stop-opacity", "CSS", "stop"), + stroke: new AdditiveAttribute("stroke", "CSS", "rect"), + stroke_dasharray: new NonAdditiveAttribute("stroke-dasharray", "CSS", "rect"), + stroke_dashoffset: new AdditiveAttribute("stroke-dashoffset", "CSS", "rect"), + stroke_linecap: new NonAdditiveAttribute("stroke-linecap", "CSS", "rect"), + stroke_linejoin: new NonAdditiveAttribute("stroke-linejoin", "CSS", "rect"), + stroke_miterlimit: new AdditiveAttribute("stroke-miterlimit", "CSS", "rect"), + stroke_opacity: new AdditiveAttribute("stroke-opacity", "CSS", "rect"), + stroke_width: new AdditiveAttribute("stroke-width", "CSS", "rect"), + text_anchor: new NonAdditiveAttribute("text-anchor", "CSS", "text"), + text_decoration: new NonAdditiveAttribute("text-decoration", "CSS", "text"), + text_rendering: new NonAdditiveAttribute("text-rendering", "CSS", "text"), + unicode_bidi: new NonAnimatableAttribute("unicode-bidi", "CSS", "text"), + vector_effect: new NonAdditiveAttribute("vector-effect", "CSS", "rect"), + visibility: new NonAdditiveAttribute("visibility", "CSS", "rect"), + word_spacing: new AdditiveAttribute("word-spacing", "CSS", "text"), + writing_mode: + // NOTE: Not supported by Mozilla, but explicitly non-animatable + new NonAnimatableAttribute("writing-mode", "CSS", "text"), +}; diff --git a/dom/smil/test/db_smilMappedAttrList.js b/dom/smil/test/db_smilMappedAttrList.js new file mode 100644 index 000000000..ede5dc23b --- /dev/null +++ b/dom/smil/test/db_smilMappedAttrList.js @@ -0,0 +1,131 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ts=2 sw=2 sts=2 et: */ +/* 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/. */ + +/* List of SVG presentational attributes in the SVG 1.1 spec, for use in + mochitests. (These are the attributes that are mapped to CSS properties) */ + +var gMappedAttrList = +{ + // NOTE: The list here should match the MappedAttributeEntry arrays in + // nsSVGElement.cpp + + // PresentationAttributes-FillStroke + fill: new AdditiveAttribute("fill", "XML", "rect"), + fill_opacity: new AdditiveAttribute("fill-opacity", "XML", "rect"), + fill_rule: new NonAdditiveAttribute("fill-rule", "XML", "rect"), + stroke: new AdditiveAttribute("stroke", "XML", "rect"), + stroke_dasharray: + new NonAdditiveAttribute("stroke-dasharray", "XML", "rect"), + stroke_dashoffset: new AdditiveAttribute("stroke-dashoffset", "XML", "rect"), + stroke_linecap: new NonAdditiveAttribute("stroke-linecap", "XML", "rect"), + stroke_linejoin: new NonAdditiveAttribute("stroke-linejoin", "XML", "rect"), + stroke_miterlimit: new AdditiveAttribute("stroke-miterlimit", "XML", "rect"), + stroke_opacity: new AdditiveAttribute("stroke-opacity", "XML", "rect"), + stroke_width: new AdditiveAttribute("stroke-width", "XML", "rect"), + + // PresentationAttributes-Graphics + clip_path: new NonAdditiveAttribute("clip-path", "XML", "rect"), + clip_rule: new NonAdditiveAttribute("clip-rule", "XML", "circle"), + color_interpolation: + new NonAdditiveAttribute("color-interpolation", "XML", "rect"), + cursor: new NonAdditiveAttribute("cursor", "XML", "rect"), + display: new NonAdditiveAttribute("display", "XML", "rect"), + filter: new NonAdditiveAttribute("filter", "XML", "rect"), + image_rendering: + NonAdditiveAttribute("image-rendering", "XML", "image"), + mask: new NonAdditiveAttribute("mask", "XML", "line"), + pointer_events: new NonAdditiveAttribute("pointer-events", "XML", "rect"), + shape_rendering: new NonAdditiveAttribute("shape-rendering", "XML", "rect"), + text_rendering: new NonAdditiveAttribute("text-rendering", "XML", "text"), + visibility: new NonAdditiveAttribute("visibility", "XML", "rect"), + + // PresentationAttributes-TextContentElements + // SKIP 'alignment-baseline' property: animatable but not supported by Mozilla + // SKIP 'baseline-shift' property: animatable but not supported by Mozilla + direction: new NonAnimatableAttribute("direction", "XML", "text"), + dominant_baseline: + new NonAdditiveAttribute("dominant-baseline", "XML", "text"), + glyph_orientation_horizontal: + // NOTE: Not supported by Mozilla, but explicitly non-animatable + NonAnimatableAttribute("glyph-orientation-horizontal", "XML", "text"), + glyph_orientation_vertical: + // NOTE: Not supported by Mozilla, but explicitly non-animatable + NonAnimatableAttribute("glyph-orientation-horizontal", "XML", "text"), + // SKIP 'kerning' property: animatable but not supported by Mozilla + letter_spacing: new AdditiveAttribute("letter-spacing", "XML", "text"), + text_anchor: new NonAdditiveAttribute("text-anchor", "XML", "text"), + text_decoration: new NonAdditiveAttribute("text-decoration", "XML", "text"), + unicode_bidi: new NonAnimatableAttribute("unicode-bidi", "XML", "text"), + word_spacing: new AdditiveAttribute("word-spacing", "XML", "text"), + + // PresentationAttributes-FontSpecification + font_family: new NonAdditiveAttribute("font-family", "XML", "text"), + font_size: new AdditiveAttribute("font-size", "XML", "text"), + font_size_adjust: + new NonAdditiveAttribute("font-size-adjust", "XML", "text"), + font_stretch: new NonAdditiveAttribute("font-stretch", "XML", "text"), + font_style: new NonAdditiveAttribute("font-style", "XML", "text"), + font_variant: new NonAdditiveAttribute("font-variant", "XML", "text"), + font_weight: new NonAdditiveAttribute("font-weight", "XML", "text"), + + // PresentationAttributes-GradientStop + stop_color: new AdditiveAttribute("stop-color", "XML", "stop"), + stop_opacity: new AdditiveAttribute("stop-opacity", "XML", "stop"), + + // PresentationAttributes-Viewports + overflow: new NonAdditiveAttribute("overflow", "XML", "marker"), + clip: new AdditiveAttribute("clip", "XML", "marker"), + + // PresentationAttributes-Makers + marker_end: new NonAdditiveAttribute("marker-end", "XML", "line"), + marker_mid: new NonAdditiveAttribute("marker-mid", "XML", "line"), + marker_start: new NonAdditiveAttribute("marker-start", "XML", "line"), + + // PresentationAttributes-Color + color: new AdditiveAttribute("color", "XML", "rect"), + + // PresentationAttributes-Filters + color_interpolation_filters: + new NonAdditiveAttribute("color-interpolation-filters", "XML", + "feFlood"), + + // PresentationAttributes-feFlood + flood_color: new AdditiveAttribute("flood-color", "XML", "feFlood"), + flood_opacity: new AdditiveAttribute("flood-opacity", "XML", "feFlood"), + + // PresentationAttributes-LightingEffects + lighting_color: + new AdditiveAttribute("lighting-color", "XML", "feDiffuseLighting"), +}; + +// Utility method to copy a list of TestcaseBundle objects for CSS properties +// into a list of TestcaseBundles for the corresponding mapped attributes. +function convertCSSBundlesToMappedAttr(bundleList) { + // Create mapping of property names to the corresponding + // mapped-attribute object in gMappedAttrList. + var propertyNameToMappedAttr = {}; + for (attributeLabel in gMappedAttrList) { + var propName = gMappedAttrList[attributeLabel].attrName; + propertyNameToMappedAttr[propName] = gMappedAttrList[attributeLabel]; + } + + var convertedBundles = []; + for (var bundleIdx in bundleList) { + var origBundle = bundleList[bundleIdx]; + var propName = origBundle.animatedAttribute.attrName; + if (propertyNameToMappedAttr[propName]) { + // There's a mapped attribute by this name! Duplicate the TestcaseBundle, + // using the Mapped Attribute instead of the CSS Property. + is(origBundle.animatedAttribute.attrType, "CSS", + "expecting to be converting from CSS to XML"); + convertedBundles.push( + new TestcaseBundle(propertyNameToMappedAttr[propName], + origBundle.testcaseList, + origBundle.skipReason)); + } + } + return convertedBundles; +} diff --git a/dom/smil/test/mochitest.ini b/dom/smil/test/mochitest.ini new file mode 100644 index 000000000..b5a0c51bb --- /dev/null +++ b/dom/smil/test/mochitest.ini @@ -0,0 +1,60 @@ +[DEFAULT] +support-files = + db_smilAnimateMotion.js + db_smilCSSFromBy.js + db_smilCSSFromTo.js + db_smilCSSPaced.js + db_smilCSSPropertyList.js + db_smilMappedAttrList.js + smilAnimateMotionValueLists.js + smilExtDoc_helper.svg + smilTestUtils.js + smilXHR_helper.svg + +[test_smilAccessKey.xhtml] +[test_smilAnimateMotion.xhtml] +[test_smilAnimateMotionInvalidValues.xhtml] +[test_smilAnimateMotionOverrideRules.xhtml] +[test_smilBackwardsSeeking.xhtml] +[test_smilCSSFontStretchRelative.xhtml] +[test_smilCSSFromBy.xhtml] +[test_smilCSSFromTo.xhtml] +# [test_smilCSSInherit.xhtml] +# disabled until bug 501183 is fixed +[test_smilCSSInvalidValues.xhtml] +[test_smilCSSPaced.xhtml] +[test_smilChangeAfterFrozen.xhtml] +[test_smilConditionalProcessing.html] +[test_smilContainerBinding.xhtml] +[test_smilCrossContainer.xhtml] +[test_smilDynamicDelayedBeginElement.xhtml] +[test_smilExtDoc.xhtml] +skip-if = toolkit == 'android' +[test_smilFillMode.xhtml] +[test_smilGetSimpleDuration.xhtml] +[test_smilGetStartTime.xhtml] +[test_smilHyperlinking.xhtml] +[test_smilInvalidValues.html] +[test_smilKeySplines.xhtml] +[test_smilKeyTimes.xhtml] +[test_smilKeyTimesPacedMode.xhtml] +[test_smilMappedAttrFromBy.xhtml] +[test_smilMappedAttrFromTo.xhtml] +[test_smilMappedAttrPaced.xhtml] +[test_smilMinTiming.html] +[test_smilRepeatDuration.html] +[test_smilRepeatTiming.xhtml] +skip-if = toolkit == 'android' #TIMED_OUT +[test_smilReset.xhtml] +[test_smilRestart.xhtml] +[test_smilSetCurrentTime.xhtml] +[test_smilSync.xhtml] +[test_smilSyncTransform.xhtml] +[test_smilSyncbaseTarget.xhtml] +[test_smilTextZoom.xhtml] +[test_smilTimeEvents.xhtml] +[test_smilTiming.xhtml] +[test_smilTimingZeroIntervals.xhtml] +[test_smilUpdatedInterval.xhtml] +[test_smilValues.xhtml] +[test_smilXHR.xhtml] diff --git a/dom/smil/test/smilAnimateMotionValueLists.js b/dom/smil/test/smilAnimateMotionValueLists.js new file mode 100644 index 000000000..364bc250e --- /dev/null +++ b/dom/smil/test/smilAnimateMotionValueLists.js @@ -0,0 +1,128 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ts=2 sw=2 sts=2 et: */ +/* 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/. */ + +/* Lists of valid & invalid values for the various <animateMotion> attributes */ +const gValidValues = [ + "10 10", + "10 10;", // Trailing semicolons are allowed + "10 10; ", + " 10 10em ", + "1 2 ; 3,4", + "1,2;3,4", + "0 0", + "0,0", +]; + +const gInvalidValues = [ + ";10 10", + "10 10;;", + "1 2 3", + "1 2 3 4", + "1,2;3,4 ,", + ",", " , ", + ";", " ; ", + "a", " a; ", ";a;", + "", " ", + "1,2;3,4,", + "1,,2", + ",1,2", +]; + +const gValidRotate = [ + "10", + "20.1", + "30.5deg", + "0.5rad", + "auto", + "auto-reverse" +]; + +const gInvalidRotate = [ + " 10 ", + " 10deg", + "10 deg", + "10deg ", + "10 rad ", + "aaa", + " 10.1 ", +]; + +const gValidToBy = [ + "0 0", + "1em,2", + "50.3em 0.2in", + " 1,2", + "1 2 " +]; + +const gInvalidToBy = [ + "0 0 0", + "0 0,0", + "0,0,0", + "1emm 2", + "1 2;", + "1 2,", + " 1,2 ,", + "abc", + ",", + "", + "1,,2", + "1,2," +]; + +const gValidPath = [ + "m0 0 L30 30", + "M20,20L10 10", + "M20,20 L30, 30h20", + "m50 50", "M50 50", + "m0 0", "M0, 0" +]; + +// paths must start with at least a valid "M" segment to be valid +const gInvalidPath = [ + "M20in 20", + "h30", + "L50 50", + "abc", +]; + +// paths that at least start with a valid "M" segment are valid - the spec says +// to parse everything up to the first invalid token +const gValidPathWithErrors = [ + "M20 20em", + "m0 0 L30,,30", + "M10 10 L50 50 abc", +]; + +const gValidKeyPoints = [ + "0; 0.5; 1", + "0;.5;1", + "0; 0; 1", + "0; 1; 1", + "0; 0; 1;", // Trailing semicolons are allowed + "0; 0; 1; ", + "0; 0.000; 1", + "0; 0.000001; 1", +]; + +// Should have 3 values to be valid. +// Same as number of keyTimes values +const gInvalidKeyPoints = [ + "0; 1", + "0; 0.5; 0.75; 1", + "0; 1;", + "0", + "1", + "a", + "", + " ", + "0; -0.1; 1", + "0; 1.1; 1", + "0; 0.1; 1.1", + "-0.1; 0.1; 1", + "0; a; 1", + "0;;1", +]; diff --git a/dom/smil/test/smilExtDoc_helper.svg b/dom/smil/test/smilExtDoc_helper.svg new file mode 100644 index 000000000..fbd9d091a --- /dev/null +++ b/dom/smil/test/smilExtDoc_helper.svg @@ -0,0 +1,7 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + <filter id="filter"> + <feFlood flood-color="red"> + <set attributeName="flood-color" to="lime" begin="0.001"/> + </feFlood> + </filter> +</svg> diff --git a/dom/smil/test/smilTestUtils.js b/dom/smil/test/smilTestUtils.js new file mode 100644 index 000000000..2304d499b --- /dev/null +++ b/dom/smil/test/smilTestUtils.js @@ -0,0 +1,858 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ts=2 sw=2 sts=2 et: */ +/* 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/. */ + +// Note: Class syntax roughly based on: +// https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Inheritance +const SVG_NS = "http://www.w3.org/2000/svg"; +const XLINK_NS = "http://www.w3.org/1999/xlink"; + +const MPATH_TARGET_ID = "smilTestUtilsTestingPath"; + +function extend(child, supertype) +{ + child.prototype.__proto__ = supertype.prototype; +} + +// General Utility Methods +var SMILUtil = +{ + // Returns the first matched <svg> node in the document + getSVGRoot : function() + { + return SMILUtil.getFirstElemWithTag("svg"); + }, + + // Returns the first element in the document with the matching tag + getFirstElemWithTag : function(aTargetTag) + { + var elemList = document.getElementsByTagName(aTargetTag); + return (elemList.length == 0 ? null : elemList[0]); + }, + + // Simple wrapper for getComputedStyle + getComputedStyleSimple: function(elem, prop) + { + return window.getComputedStyle(elem, null).getPropertyValue(prop); + }, + + getAttributeValue: function(elem, attr) + { + if (attr.attrName == SMILUtil.getMotionFakeAttributeName()) { + // Fake motion "attribute" -- "computed value" is the element's CTM + return elem.getCTM(); + } + if (attr.attrType == "CSS") { + return SMILUtil.getComputedStyleWrapper(elem, attr.attrName); + } + if (attr.attrType == "XML") { + // XXXdholbert This is appropriate for mapped attributes, but not + // for other attributes. + return SMILUtil.getComputedStyleWrapper(elem, attr.attrName); + } + }, + + // Smart wrapper for getComputedStyle, which will generate a "fake" computed + // style for recognized shorthand properties (font, font-variant, overflow, marker) + getComputedStyleWrapper : function(elem, propName) + { + // Special cases for shorthand properties (which aren't directly queriable + // via getComputedStyle) + var computedStyle; + if (propName == "font") { + var subProps = ["font-style", "font-variant-caps", "font-weight", + "font-size", "line-height", "font-family"]; + for (var i in subProps) { + var subPropStyle = SMILUtil.getComputedStyleSimple(elem, subProps[i]); + if (subPropStyle) { + if (subProps[i] == "line-height") { + // There needs to be a "/" before line-height + subPropStyle = "/ " + subPropStyle; + } + if (!computedStyle) { + computedStyle = subPropStyle; + } else { + computedStyle = computedStyle + " " + subPropStyle; + } + } + } + } else if (propName == "font-variant") { + // xxx - this isn't completely correct but it's sufficient for what's + // being tested here + computedStyle = SMILUtil.getComputedStyleSimple(elem, "font-variant-caps"); + } else if (propName == "marker") { + var subProps = ["marker-end", "marker-mid", "marker-start"]; + for (var i in subProps) { + if (!computedStyle) { + computedStyle = SMILUtil.getComputedStyleSimple(elem, subProps[i]); + } else { + is(computedStyle, SMILUtil.getComputedStyleSimple(elem, subProps[i]), + "marker sub-properties should match each other " + + "(they shouldn't be individually set)"); + } + } + } else if (propName == "overflow") { + var subProps = ["overflow-x", "overflow-y"]; + for (var i in subProps) { + if (!computedStyle) { + computedStyle = SMILUtil.getComputedStyleSimple(elem, subProps[i]); + } else { + is(computedStyle, SMILUtil.getComputedStyleSimple(elem, subProps[i]), + "overflow sub-properties should match each other " + + "(they shouldn't be individually set)"); + } + } + } else { + computedStyle = SMILUtil.getComputedStyleSimple(elem, propName); + } + return computedStyle; + }, + + getMotionFakeAttributeName : function() { + return "_motion"; + }, +}; + + +var CTMUtil = +{ + CTM_COMPONENTS_ALL : ["a", "b", "c", "d", "e", "f"], + CTM_COMPONENTS_ROTATE : ["a", "b", "c", "d" ], + + // Function to generate a CTM Matrix from a "summary" + // (a 3-tuple containing [tX, tY, theta]) + generateCTM : function(aCtmSummary) + { + if (!aCtmSummary || aCtmSummary.length != 3) { + ok(false, "Unexpected CTM summary tuple length: " + aCtmSummary.length); + } + var tX = aCtmSummary[0]; + var tY = aCtmSummary[1]; + var theta = aCtmSummary[2]; + var cosTheta = Math.cos(theta); + var sinTheta = Math.sin(theta); + var newCtm = { a : cosTheta, c: -sinTheta, e: tX, + b : sinTheta, d: cosTheta, f: tY }; + return newCtm; + }, + + /// Helper for isCtmEqual + isWithinDelta : function(aTestVal, aExpectedVal, aErrMsg, aIsTodo) { + var testFunc = aIsTodo ? todo : ok; + const delta = 0.00001; // allowing margin of error = 10^-5 + ok(aTestVal >= aExpectedVal - delta && + aTestVal <= aExpectedVal + delta, + aErrMsg + " | got: " + aTestVal + ", expected: " + aExpectedVal); + }, + + assertCTMEqual : function(aLeftCtm, aRightCtm, aComponentsToCheck, + aErrMsg, aIsTodo) { + var foundCTMDifference = false; + for (var j in aComponentsToCheck) { + var curComponent = aComponentsToCheck[j]; + if (!aIsTodo) { + CTMUtil.isWithinDelta(aLeftCtm[curComponent], aRightCtm[curComponent], + aErrMsg + " | component: " + curComponent, false); + } else if (aLeftCtm[curComponent] != aRightCtm[curComponent]) { + foundCTMDifference = true; + } + } + + if (aIsTodo) { + todo(!foundCTMDifference, aErrMsg + " | (currently marked todo)"); + } + }, + + assertCTMNotEqual : function(aLeftCtm, aRightCtm, aComponentsToCheck, + aErrMsg, aIsTodo) { + // CTM should not match initial one + var foundCTMDifference = false; + for (var j in aComponentsToCheck) { + var curComponent = aComponentsToCheck[j]; + if (aLeftCtm[curComponent] != aRightCtm[curComponent]) { + foundCTMDifference = true; + break; // We found a difference, as expected. Success! + } + } + + if (aIsTodo) { + todo(foundCTMDifference, aErrMsg + " | (currently marked todo)"); + } else { + ok(foundCTMDifference, aErrMsg); + } + }, +}; + + +// Wrapper for timing information +function SMILTimingData(aBegin, aDur) +{ + this._begin = aBegin; + this._dur = aDur; +} +SMILTimingData.prototype = +{ + _begin: null, + _dur: null, + getBeginTime : function() { return this._begin; }, + getDur : function() { return this._dur; }, + getEndTime : function() { return this._begin + this._dur; }, + getFractionalTime : function(aPortion) + { + return this._begin + aPortion * this._dur; + }, +} + +/** + * Attribute: a container for information about an attribute we'll + * attempt to animate with SMIL in our tests. + * + * See also the factory methods below: NonAnimatableAttribute(), + * NonAdditiveAttribute(), and AdditiveAttribute(). + * + * @param aAttrName The name of the attribute + * @param aAttrType The type of the attribute ("CSS" vs "XML") + * @param aTargetTag The name of an element that this attribute could be + * applied to. + * @param aIsAnimatable A bool indicating whether this attribute is defined as + * animatable in the SVG spec. + * @param aIsAdditive A bool indicating whether this attribute is defined as + * additive (i.e. supports "by" animation) in the SVG spec. + */ +function Attribute(aAttrName, aAttrType, aTargetTag, + aIsAnimatable, aIsAdditive) +{ + this.attrName = aAttrName; + this.attrType = aAttrType; + this.targetTag = aTargetTag; + this.isAnimatable = aIsAnimatable; + this.isAdditive = aIsAdditive; +} +Attribute.prototype = +{ + // Member variables + attrName : null, + attrType : null, + isAnimatable : null, + testcaseList : null, +}; + +// Generators for Attribute objects. These allow lists of attribute +// definitions to be more human-readible than if we were using Attribute() with +// boolean flags, e.g. "Attribute(..., true, true), Attribute(..., true, false) +function NonAnimatableAttribute(aAttrName, aAttrType, aTargetTag) +{ + return new Attribute(aAttrName, aAttrType, aTargetTag, false, false); +} +function NonAdditiveAttribute(aAttrName, aAttrType, aTargetTag) +{ + return new Attribute(aAttrName, aAttrType, aTargetTag, true, false); +} +function AdditiveAttribute(aAttrName, aAttrType, aTargetTag) +{ + return new Attribute(aAttrName, aAttrType, aTargetTag, true, true); +} + +/** + * TestcaseBundle: a container for a group of tests for a particular attribute + * + * @param aAttribute An Attribute object for the attribute + * @param aTestcaseList An array of AnimTestcase objects + */ +function TestcaseBundle(aAttribute, aTestcaseList, aSkipReason) +{ + this.animatedAttribute = aAttribute; + this.testcaseList = aTestcaseList; + this.skipReason = aSkipReason; +} +TestcaseBundle.prototype = +{ + // Member variables + animatedAttribute : null, + testcaseList : null, + skipReason : null, + + // Methods + go : function(aTimingData) { + if (this.skipReason) { + todo(false, "Skipping a bundle for '" + this.animatedAttribute.attrName + + "' because: " + this.skipReason); + } else { + // Sanity Check: Bundle should have > 0 testcases + if (!this.testcaseList || !this.testcaseList.length) { + ok(false, "a bundle for '" + this.animatedAttribute.attrName + + "' has no testcases"); + } + + var targetElem = + SMILUtil.getFirstElemWithTag(this.animatedAttribute.targetTag); + + if (!targetElem) { + ok(false, "Error: can't find an element of type '" + + this.animatedAttribute.targetTag + + "', so I can't test property '" + + this.animatedAttribute.attrName + "'"); + return; + } + + for (var testcaseIdx in this.testcaseList) { + var testcase = this.testcaseList[testcaseIdx]; + if (testcase.skipReason) { + todo(false, "Skipping a testcase for '" + + this.animatedAttribute.attrName + + "' because: " + testcase.skipReason); + } else { + testcase.runTest(targetElem, this.animatedAttribute, + aTimingData, false); + testcase.runTest(targetElem, this.animatedAttribute, + aTimingData, true); + } + } + } + }, +}; + +/** + * AnimTestcase: an abstract class that represents an animation testcase. + * (e.g. a set of "from"/"to" values to test) + */ +function AnimTestcase() {} // abstract => no constructor +AnimTestcase.prototype = +{ + // Member variables + _animElementTagName : "animate", // Can be overridden for e.g. animateColor + computedValMap : null, + skipReason : null, + + // Methods + /** + * runTest: Runs this AnimTestcase + * + * @param aTargetElem The node to be targeted in our test animation. + * @param aTargetAttr An Attribute object representing the attribute + * to be targeted in our test animation. + * @param aTimeData A SMILTimingData object with timing information for + * our test animation. + * @param aIsFreeze If true, indicates that our test animation should use + * fill="freeze"; otherwise, we'll default to fill="remove". + */ + runTest : function(aTargetElem, aTargetAttr, aTimeData, aIsFreeze) + { + // SANITY CHECKS + if (!SMILUtil.getSVGRoot().animationsPaused()) { + ok(false, "Should start each test with animations paused"); + } + if (SMILUtil.getSVGRoot().getCurrentTime() != 0) { + ok(false, "Should start each test at time = 0"); + } + + // SET UP + // Cache initial computed value + var baseVal = SMILUtil.getAttributeValue(aTargetElem, aTargetAttr); + + // Create & append animation element + var anim = this.setupAnimationElement(aTargetAttr, aTimeData, aIsFreeze); + aTargetElem.appendChild(anim); + + // Build a list of [seek-time, expectedValue, errorMessage] triplets + var seekList = this.buildSeekList(aTargetAttr, baseVal, aTimeData, aIsFreeze); + + // DO THE ACTUAL TESTING + this.seekAndTest(seekList, aTargetElem, aTargetAttr); + + // CLEAN UP + aTargetElem.removeChild(anim); + SMILUtil.getSVGRoot().setCurrentTime(0); + }, + + // HELPER FUNCTIONS + // setupAnimationElement: <animate> element + // Subclasses should extend this parent method + setupAnimationElement : function(aAnimAttr, aTimeData, aIsFreeze) + { + var animElement = document.createElementNS(SVG_NS, + this._animElementTagName); + animElement.setAttribute("attributeName", aAnimAttr.attrName); + animElement.setAttribute("attributeType", aAnimAttr.attrType); + animElement.setAttribute("begin", aTimeData.getBeginTime()); + animElement.setAttribute("dur", aTimeData.getDur()); + if (aIsFreeze) { + animElement.setAttribute("fill", "freeze"); + } + return animElement; + }, + + buildSeekList : function(aAnimAttr, aBaseVal, aTimeData, aIsFreeze) + { + if (!aAnimAttr.isAnimatable) { + return this.buildSeekListStatic(aAnimAttr, aBaseVal, aTimeData, + "defined as non-animatable in SVG spec"); + } + if (this.computedValMap.noEffect) { + return this.buildSeekListStatic(aAnimAttr, aBaseVal, aTimeData, + "testcase specified to have no effect"); + } + return this.buildSeekListAnimated(aAnimAttr, aBaseVal, + aTimeData, aIsFreeze) + }, + + seekAndTest : function(aSeekList, aTargetElem, aTargetAttr) + { + var svg = document.getElementById("svg"); + for (var i in aSeekList) { + var entry = aSeekList[i]; + SMILUtil.getSVGRoot().setCurrentTime(entry[0]); + is(SMILUtil.getAttributeValue(aTargetElem, aTargetAttr), + entry[1], entry[2]); + } + }, + + // methods that expect to be overridden in subclasses + buildSeekListStatic : function(aAnimAttr, aBaseVal, + aTimeData, aReasonStatic) {}, + buildSeekListAnimated : function(aAnimAttr, aBaseVal, + aTimeData, aIsFreeze) {}, +}; + + +// Abstract parent class to share code between from-to & from-by testcases. +function AnimTestcaseFrom() {} // abstract => no constructor +AnimTestcaseFrom.prototype = +{ + // Member variables + from : null, + + // Methods + setupAnimationElement : function(aAnimAttr, aTimeData, aIsFreeze) + { + // Call super, and then add my own customization + var animElem = AnimTestcase.prototype.setupAnimationElement.apply(this, + [aAnimAttr, aTimeData, aIsFreeze]); + animElem.setAttribute("from", this.from) + return animElem; + }, + + buildSeekListStatic : function(aAnimAttr, aBaseVal, aTimeData, aReasonStatic) + { + var seekList = new Array(); + var msgPrefix = aAnimAttr.attrName + + ": shouldn't be affected by animation "; + seekList.push([aTimeData.getBeginTime(), aBaseVal, + msgPrefix + "(at animation begin) - " + aReasonStatic]); + seekList.push([aTimeData.getFractionalTime(1/2), aBaseVal, + msgPrefix + "(at animation mid) - " + aReasonStatic]); + seekList.push([aTimeData.getEndTime(), aBaseVal, + msgPrefix + "(at animation end) - " + aReasonStatic]); + seekList.push([aTimeData.getEndTime() + aTimeData.getDur(), aBaseVal, + msgPrefix + "(after animation end) - " + aReasonStatic]); + return seekList; + }, + + buildSeekListAnimated : function(aAnimAttr, aBaseVal, aTimeData, aIsFreeze) + { + var seekList = new Array(); + var msgPrefix = aAnimAttr.attrName + ": "; + if (aTimeData.getBeginTime() > 0.1) { + seekList.push([aTimeData.getBeginTime() - 0.1, + aBaseVal, + msgPrefix + "checking that base value is set " + + "before start of animation"]); + } + + seekList.push([aTimeData.getBeginTime(), + this.computedValMap.fromComp || this.from, + msgPrefix + "checking that 'from' value is set " + + "at start of animation"]); + seekList.push([aTimeData.getFractionalTime(1/2), + this.computedValMap.midComp || + this.computedValMap.toComp || this.to, + msgPrefix + "checking value halfway through animation"]); + + var finalMsg; + var expectedEndVal; + if (aIsFreeze) { + expectedEndVal = this.computedValMap.toComp || this.to; + finalMsg = msgPrefix + "[freeze-mode] checking that final value is set "; + } else { + expectedEndVal = aBaseVal; + finalMsg = msgPrefix + + "[remove-mode] checking that animation is cleared "; + } + seekList.push([aTimeData.getEndTime(), + expectedEndVal, finalMsg + "at end of animation"]); + seekList.push([aTimeData.getEndTime() + aTimeData.getDur(), + expectedEndVal, finalMsg + "after end of animation"]); + return seekList; + }, +} +extend(AnimTestcaseFrom, AnimTestcase); + +/* + * A testcase for a simple "from-to" animation + * @param aFrom The 'from' value + * @param aTo The 'to' value + * @param aComputedValMap A hash-map that contains some computed values, + * if they're needed, as follows: + * - fromComp: Computed value version of |aFrom| (if different from |aFrom|) + * - midComp: Computed value that we expect to visit halfway through the + * animation (if different from |aTo|) + * - toComp: Computed value version of |aTo| (if different from |aTo|) + * - noEffect: Special flag -- if set, indicates that this testcase is + * expected to have no effect on the computed value. (e.g. the + * given values are invalid.) + * @param aSkipReason If this test-case is known to currently fail, this + * parameter should be a string explaining why. + * Otherwise, this value should be null (or omitted). + * + */ +function AnimTestcaseFromTo(aFrom, aTo, aComputedValMap, aSkipReason) +{ + this.from = aFrom; + this.to = aTo; + this.computedValMap = aComputedValMap || {}; // Let aComputedValMap be omitted + this.skipReason = aSkipReason; +} +AnimTestcaseFromTo.prototype = +{ + // Member variables + to : null, + + // Methods + setupAnimationElement : function(aAnimAttr, aTimeData, aIsFreeze) + { + // Call super, and then add my own customization + var animElem = AnimTestcaseFrom.prototype.setupAnimationElement.apply(this, + [aAnimAttr, aTimeData, aIsFreeze]); + animElem.setAttribute("to", this.to) + return animElem; + }, +} +extend(AnimTestcaseFromTo, AnimTestcaseFrom); + +/* + * A testcase for a simple "from-by" animation. + * + * @param aFrom The 'from' value + * @param aBy The 'by' value + * @param aComputedValMap A hash-map that contains some computed values that + * we expect to visit, as follows: + * - fromComp: Computed value version of |aFrom| (if different from |aFrom|) + * - midComp: Computed value that we expect to visit halfway through the + * animation (|aFrom| + |aBy|/2) + * - toComp: Computed value of the animation endpoint (|aFrom| + |aBy|) + * - noEffect: Special flag -- if set, indicates that this testcase is + * expected to have no effect on the computed value. (e.g. the + * given values are invalid. Or the attribute may be animatable + * and additive, but the particular "from" & "by" values that + * are used don't support addition.) + * @param aSkipReason If this test-case is known to currently fail, this + * parameter should be a string explaining why. + * Otherwise, this value should be null (or omitted). + */ +function AnimTestcaseFromBy(aFrom, aBy, aComputedValMap, aSkipReason) +{ + this.from = aFrom; + this.by = aBy; + this.computedValMap = aComputedValMap; + this.skipReason = aSkipReason; + if (this.computedValMap && + !this.computedValMap.noEffect && !this.computedValMap.toComp) { + ok(false, "AnimTestcaseFromBy needs expected computed final value"); + } +} +AnimTestcaseFromBy.prototype = +{ + // Member variables + by : null, + + // Methods + setupAnimationElement : function(aAnimAttr, aTimeData, aIsFreeze) + { + // Call super, and then add my own customization + var animElem = AnimTestcaseFrom.prototype.setupAnimationElement.apply(this, + [aAnimAttr, aTimeData, aIsFreeze]); + animElem.setAttribute("by", this.by) + return animElem; + }, + buildSeekList : function(aAnimAttr, aBaseVal, aTimeData, aIsFreeze) + { + if (!aAnimAttr.isAdditive) { + return this.buildSeekListStatic(aAnimAttr, aBaseVal, aTimeData, + "defined as non-additive in SVG spec"); + } + // Just use inherited method + return AnimTestcaseFrom.prototype.buildSeekList.apply(this, + [aAnimAttr, aBaseVal, aTimeData, aIsFreeze]); + }, +} +extend(AnimTestcaseFromBy, AnimTestcaseFrom); + +/* + * A testcase for a "paced-mode" animation + * @param aValues An array of values, to be used as the "Values" list + * @param aComputedValMap A hash-map that contains some computed values, + * if they're needed, as follows: + * - comp0: The computed value at the start of the animation + * - comp1_6: The computed value exactly 1/6 through animation + * - comp1_3: The computed value exactly 1/3 through animation + * - comp2_3: The computed value exactly 2/3 through animation + * - comp1: The computed value of the animation endpoint + * The math works out easiest if... + * (a) aValuesString has 3 entries in its values list: vA, vB, vC + * (b) dist(vB, vC) = 2 * dist(vA, vB) + * With this setup, we can come up with expected intermediate values according + * to the following rules: + * - comp0 should be vA + * - comp1_6 should be us halfway between vA and vB + * - comp1_3 should be vB + * - comp2_3 should be halfway between vB and vC + * - comp1 should be vC + * @param aSkipReason If this test-case is known to currently fail, this + * parameter should be a string explaining why. + * Otherwise, this value should be null (or omitted). + */ +function AnimTestcasePaced(aValuesString, aComputedValMap, aSkipReason) +{ + this.valuesString = aValuesString; + this.computedValMap = aComputedValMap; + this.skipReason = aSkipReason; + if (this.computedValMap && + (!this.computedValMap.comp0 || + !this.computedValMap.comp1_6 || + !this.computedValMap.comp1_3 || + !this.computedValMap.comp2_3 || + !this.computedValMap.comp1)) { + ok(false, "This AnimTestcasePaced has an incomplete computed value map"); + } +} +AnimTestcasePaced.prototype = +{ + // Member variables + valuesString : null, + + // Methods + setupAnimationElement : function(aAnimAttr, aTimeData, aIsFreeze) + { + // Call super, and then add my own customization + var animElem = AnimTestcase.prototype.setupAnimationElement.apply(this, + [aAnimAttr, aTimeData, aIsFreeze]); + animElem.setAttribute("values", this.valuesString) + animElem.setAttribute("calcMode", "paced"); + return animElem; + }, + buildSeekListAnimated : function(aAnimAttr, aBaseVal, aTimeData, aIsFreeze) + { + var seekList = new Array(); + var msgPrefix = aAnimAttr.attrName + ": checking value "; + seekList.push([aTimeData.getBeginTime(), + this.computedValMap.comp0, + msgPrefix + "at start of animation"]); + seekList.push([aTimeData.getFractionalTime(1/6), + this.computedValMap.comp1_6, + msgPrefix + "1/6 of the way through animation."]); + seekList.push([aTimeData.getFractionalTime(1/3), + this.computedValMap.comp1_3, + msgPrefix + "1/3 of the way through animation."]); + seekList.push([aTimeData.getFractionalTime(2/3), + this.computedValMap.comp2_3, + msgPrefix + "2/3 of the way through animation."]); + + var finalMsg; + var expectedEndVal; + if (aIsFreeze) { + expectedEndVal = this.computedValMap.comp1; + finalMsg = aAnimAttr.attrName + + ": [freeze-mode] checking that final value is set "; + } else { + expectedEndVal = aBaseVal; + finalMsg = aAnimAttr.attrName + + ": [remove-mode] checking that animation is cleared "; + } + seekList.push([aTimeData.getEndTime(), + expectedEndVal, finalMsg + "at end of animation"]); + seekList.push([aTimeData.getEndTime() + aTimeData.getDur(), + expectedEndVal, finalMsg + "after end of animation"]); + return seekList; + }, + buildSeekListStatic : function(aAnimAttr, aBaseVal, aTimeData, aReasonStatic) + { + var seekList = new Array(); + var msgPrefix = + aAnimAttr.attrName + ": shouldn't be affected by animation "; + seekList.push([aTimeData.getBeginTime(), aBaseVal, + msgPrefix + "(at animation begin) - " + aReasonStatic]); + seekList.push([aTimeData.getFractionalTime(1/6), aBaseVal, + msgPrefix + "(1/6 of the way through animation) - " + + aReasonStatic]); + seekList.push([aTimeData.getFractionalTime(1/3), aBaseVal, + msgPrefix + "(1/3 of the way through animation) - " + + aReasonStatic]); + seekList.push([aTimeData.getFractionalTime(2/3), aBaseVal, + msgPrefix + "(2/3 of the way through animation) - " + + aReasonStatic]); + seekList.push([aTimeData.getEndTime(), aBaseVal, + msgPrefix + "(at animation end) - " + aReasonStatic]); + seekList.push([aTimeData.getEndTime() + aTimeData.getDur(), aBaseVal, + msgPrefix + "(after animation end) - " + aReasonStatic]); + return seekList; + }, +}; +extend(AnimTestcasePaced, AnimTestcase); + +/* + * A testcase for an <animateMotion> animation. + * + * @param aAttrValueHash A hash-map mapping attribute names to values. + * Should include at least 'path', 'values', 'to' + * or 'by' to describe the motion path. + * @param aCtmMap A hash-map that contains summaries of the expected resulting + * CTM at various points during the animation. The CTM is + * summarized as a tuple of three numbers: [tX, tY, theta] + (indicating a translate(tX,tY) followed by a rotate(theta)) + * - ctm0: The CTM summary at the start of the animation + * - ctm1_6: The CTM summary at exactly 1/6 through animation + * - ctm1_3: The CTM summary at exactly 1/3 through animation + * - ctm2_3: The CTM summary at exactly 2/3 through animation + * - ctm1: The CTM summary at the animation endpoint + * + * NOTE: For paced-mode animation (the default for animateMotion), the math + * works out easiest if: + * (a) our motion path has 3 points: vA, vB, vC + * (b) dist(vB, vC) = 2 * dist(vA, vB) + * (See discussion in header comment for AnimTestcasePaced.) + * + * @param aSkipReason If this test-case is known to currently fail, this + * parameter should be a string explaining why. + * Otherwise, this value should be null (or omitted). + */ +function AnimMotionTestcase(aAttrValueHash, aCtmMap, aSkipReason) +{ + this.attrValueHash = aAttrValueHash; + this.ctmMap = aCtmMap; + this.skipReason = aSkipReason; + if (this.ctmMap && + (!this.ctmMap.ctm0 || + !this.ctmMap.ctm1_6 || + !this.ctmMap.ctm1_3 || + !this.ctmMap.ctm2_3 || + !this.ctmMap.ctm1)) { + ok(false, "This AnimMotionTestcase has an incomplete CTM map"); + } +} +AnimMotionTestcase.prototype = +{ + // Member variables + _animElementTagName : "animateMotion", + + // Implementations of inherited methods that we need to override: + // -------------------------------------------------------------- + setupAnimationElement : function(aAnimAttr, aTimeData, aIsFreeze) + { + var animElement = document.createElementNS(SVG_NS, + this._animElementTagName); + animElement.setAttribute("begin", aTimeData.getBeginTime()); + animElement.setAttribute("dur", aTimeData.getDur()); + if (aIsFreeze) { + animElement.setAttribute("fill", "freeze"); + } + for (var attrName in this.attrValueHash) { + if (attrName == "mpath") { + this.createPath(this.attrValueHash[attrName]); + this.createMpath(animElement); + } else { + animElement.setAttribute(attrName, this.attrValueHash[attrName]); + } + } + return animElement; + }, + + createPath : function(aPathDescription) + { + var path = document.createElementNS(SVG_NS, "path"); + path.setAttribute("d", aPathDescription); + path.setAttribute("id", MPATH_TARGET_ID); + return SMILUtil.getSVGRoot().appendChild(path); + }, + + createMpath : function(aAnimElement) + { + var mpath = document.createElementNS(SVG_NS, "mpath"); + mpath.setAttributeNS(XLINK_NS, "href", "#" + MPATH_TARGET_ID); + return aAnimElement.appendChild(mpath); + }, + + // Override inherited seekAndTest method since... + // (a) it expects a computedValMap and we have a computed-CTM map instead + // and (b) it expects we might have no effect (for non-animatable attrs) + buildSeekList : function(aAnimAttr, aBaseVal, aTimeData, aIsFreeze) + { + var seekList = new Array(); + var msgPrefix = "CTM mismatch "; + seekList.push([aTimeData.getBeginTime(), + CTMUtil.generateCTM(this.ctmMap.ctm0), + msgPrefix + "at start of animation"]); + seekList.push([aTimeData.getFractionalTime(1/6), + CTMUtil.generateCTM(this.ctmMap.ctm1_6), + msgPrefix + "1/6 of the way through animation."]); + seekList.push([aTimeData.getFractionalTime(1/3), + CTMUtil.generateCTM(this.ctmMap.ctm1_3), + msgPrefix + "1/3 of the way through animation."]); + seekList.push([aTimeData.getFractionalTime(2/3), + CTMUtil.generateCTM(this.ctmMap.ctm2_3), + msgPrefix + "2/3 of the way through animation."]); + + var finalMsg; + var expectedEndVal; + if (aIsFreeze) { + expectedEndVal = CTMUtil.generateCTM(this.ctmMap.ctm1); + finalMsg = aAnimAttr.attrName + + ": [freeze-mode] checking that final value is set "; + } else { + expectedEndVal = aBaseVal; + finalMsg = aAnimAttr.attrName + + ": [remove-mode] checking that animation is cleared "; + } + seekList.push([aTimeData.getEndTime(), + expectedEndVal, finalMsg + "at end of animation"]); + seekList.push([aTimeData.getEndTime() + aTimeData.getDur(), + expectedEndVal, finalMsg + "after end of animation"]); + return seekList; + }, + + // Override inherited seekAndTest method + // (Have to use assertCTMEqual() instead of is() for comparison, to check each + // component of the CTM and to allow for a small margin of error.) + seekAndTest : function(aSeekList, aTargetElem, aTargetAttr) + { + var svg = document.getElementById("svg"); + for (var i in aSeekList) { + var entry = aSeekList[i]; + SMILUtil.getSVGRoot().setCurrentTime(entry[0]); + CTMUtil.assertCTMEqual(aTargetElem.getCTM(), entry[1], + CTMUtil.CTM_COMPONENTS_ALL, entry[2], false); + } + }, + + // Override "runTest" method so we can remove any <path> element that we + // created at the end of each test. + runTest : function(aTargetElem, aTargetAttr, aTimeData, aIsFreeze) + { + AnimTestcase.prototype.runTest.apply(this, + [aTargetElem, aTargetAttr, aTimeData, aIsFreeze]); + var pathElem = document.getElementById(MPATH_TARGET_ID); + if (pathElem) { + SMILUtil.getSVGRoot().removeChild(pathElem); + } + } +}; +extend(AnimMotionTestcase, AnimTestcase); + +// MAIN METHOD +function testBundleList(aBundleList, aTimingData) +{ + for (var bundleIdx in aBundleList) { + aBundleList[bundleIdx].go(aTimingData); + } +} diff --git a/dom/smil/test/smilXHR_helper.svg b/dom/smil/test/smilXHR_helper.svg new file mode 100644 index 000000000..cb0b51c36 --- /dev/null +++ b/dom/smil/test/smilXHR_helper.svg @@ -0,0 +1,8 @@ +<svg id="svg" xmlns="http://www.w3.org/2000/svg" width="120px" height="120px"> + <circle id="circ" cx="20" cy="20" r="15" fill="blue"> + <animate id="animXML" attributeName="cx" attributeType="XML" + from="500" to="600" begin="0s" dur="4s"/> + <animate id="animCSS" attributeName="opacity" attributeType="CSS" + from="0.2" to="0.3" begin="0s" dur="4s"/> + </circle> +</svg> diff --git a/dom/smil/test/test_smilAccessKey.xhtml b/dom/smil/test/test_smilAccessKey.xhtml new file mode 100644 index 000000000..ef7e1b73d --- /dev/null +++ b/dom/smil/test/test_smilAccessKey.xhtml @@ -0,0 +1,362 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Test for SMIL accessKey support</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=587910">Mozilla Bug + 587910</a> +<p id="display"></p> +<div id="content" style="display: none"> +<svg id="svg" xmlns="http://www.w3.org/2000/svg" width="120px" height="120px"> + <circle cx="20" cy="20" r="15" fill="blue" id="circle"/> +</svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ +/** Test for SMIL accessKey support **/ + +const gSvgns = "http://www.w3.org/2000/svg"; +var gSvg = document.getElementById("svg"); +SimpleTest.waitForExplicitFinish(); + +function main() +{ + gSvg.pauseAnimations(); + + // Basic syntax + testOk('accessKey(a)', 'a'); + testOk(' accessKey(a) ', 'a'); + testNotOk('accessKey (a)', 'a'); + testNotOk('accessKey( a)', 'a'); + testNotOk('accessKey(a )', 'a'); + testNotOk('accessKey(a)', 'b'); + testNotOk('accessKey()', ' '); + + // Test the test framework itself + testOk('accessKey(a)', 97); + + // Allow for either accessKey (SVG / SMIL Animation) or accesskey (SMIL2+) + testOk('accesskey(a)', 'a'); + + // Offset + testOk('accessKey(a)+0s', 'a'); + testOk('accessKey(a) + 0min', 'a'); + testOk('accessKey(a) -0h', 'a'); + testOk('accessKey(a)+100ms', 'a', 0, 0.1); + testOk('accessKey(a)-0.1s', 'a', 0, -0.1); + + // Id references are not allowed + testNotOk('svg.accessKey(a)', 'a'); + testNotOk('window.accessKey(a)', 'a'); + + // Case sensitivity + testOk('accessKey(A)', 'A'); + testNotOk('accessKey(a)', 'A'); + testNotOk('accessKey(A)', 'a'); + + // Test unusual characters + testOk('accessKey(-)', '-'); + testOk('accessKey(\\)', '\\'); + testOk('accessKey( )', ' '); + testOk('accessKey(\x0D)', 0, KeyboardEvent.DOM_VK_RETURN); + testOk('accessKey(\n)', 0, KeyboardEvent.DOM_VK_RETURN); // New line + testOk('accessKey(\r)', 0, KeyboardEvent.DOM_VK_RETURN); // Carriage return + testOk('accessKey(\x08)', 0, KeyboardEvent.DOM_VK_BACK_SPACE); + testOk('accessKey(\x1B)', 0, KeyboardEvent.DOM_VK_ESCAPE); + testOk('accessKey(\x7F)', 0, KeyboardEvent.DOM_VK_DELETE); + + // Check some disallowed keys + // -- For now we don't allow tab since the interaction with focus causes + // confusing results + testNotOk('accessKey(\x09)', 0, 9); // Tab + + // Test setting the keyCode field + testNotOk('accessKey(a)', 0, 97); + testOk('accessKey(a)', 97, 66); // Give priority to charCode field + testNotOk('accessKey(a)', 98, 97); // Give priority to charCode field + + // Test unicode + testOk("accessKey(\u20AC)", 8364); // euro-symbol + + // Test an astral character just to make sure we don't crash + testOk("accessKey(\uD835\uDC00)", 119808); // mathematical bold capital A + // 0x1D400 + // Test bad surrogate pairs don't confuse us either + testNotOk("accessKey(\uD800\uD800)", 97); + testNotOk("accessKey(\uD80020)", 97); + testNotOk("accessKey(\uD800)", 97); + + // Test modifiers + // -- When matching on charCode ignore shift and alt + testNotOk('accessKey(a)', 'a', 0, 0, { ctrl: true }); + testNotOk('accessKey(a)', 'a', 0, 0, { meta: true }); + testOk('accessKey(a)', 'a', 0, 0, { alt: true }); + testOk('accessKey(a)', 'a', 0, 0, { shift: true }); + testNotOk('accessKey(a)', 'a', 0, 0, { shift: true, ctrl: true }); + testNotOk('accessKey(a)', 'a', 0, 0, { alt: true, meta: true }); + // -- When matching on keyCode ignore all + testNotOk('accessKey(\x0D)', 0, 13, 0, { ctrl: true }); + testNotOk('accessKey(\x0D)', 0, 13, 0, { meta: true }); + testNotOk('accessKey(\x0D)', 0, 13, 0, { alt: true }); + testNotOk('accessKey(\x0D)', 0, 13, 0, { shift: true }); + testNotOk('accessKey(\x0D)', 0, 13, 0, { shift: true, ctrl: true }); + + testOpenEnd(); + testPreventDefault(); + testDispatchToWindow(); + testAdoptNode(); + testFauxEvent(); + + SimpleTest.finish(); +} + +function testOk(spec, charCode, keyCode, offset, modifiers) +{ + if (typeof offset == 'undefined') offset = 0; + var msg = "No interval created for '" + spec + + "' with input [charCode: " + charCode + "; keyCode: " + keyCode + "]" + + getModifiersDescr(modifiers); + ok(test(spec, charCode, keyCode, offset, modifiers), msg); +} + +function testNotOk(spec, charCode, keyCode, offset, modifiers) +{ + if (typeof offset == 'undefined') offset = 0; + var msg = "Interval unexpectedly created for '" + spec + + "' with input [charCode: " + charCode + "; keyCode: " + keyCode + "]" + + getModifiersDescr(modifiers); + ok(!test(spec, charCode, keyCode, offset, modifiers), msg); +} + +function getModifiersDescr(modifiers) +{ + if (typeof modifiers != 'object') + return ''; + var str = ' modifiers set:'; + for (var key in modifiers) { + if (modifiers[key]) str += ' ' + key; + } + return str; +} + +function test(spec, charCode, keyCode, offset, modifiers) +{ + gSvg.setCurrentTime(1); + ok(gSvg.animationsPaused(), "Expected animations to be paused"); + + var anim = createAnim(spec); + var evt = createEvent(charCode, keyCode, modifiers); + + document.getElementById('circle').dispatchEvent(evt); + + var gotStartTimeOk = true; + try { + var start = anim.getStartTime(); + if (offset) { + var expected = gSvg.getCurrentTime() + offset; + ok(Math.abs(expected - start) <= 0.00001, + "Unexpected start time for animation with begin: " + spec + + " got " + start + ", expected " + expected); + } else { + is(start, gSvg.getCurrentTime() + offset, + "Unexpected start time for animation with begin: " + spec); + } + } catch(e) { + is(e.name, "InvalidStateError", + "Unexpected exception: " + e.name); + is(e.code, DOMException.INVALID_STATE_ERR, + "Unexpected exception code: " + e.code); + gotStartTimeOk = false; + } + + anim.parentNode.removeChild(anim); + + return gotStartTimeOk; +} + +function createAnim(beginSpec) +{ + var anim = document.createElementNS(gSvgns, 'animate'); + anim.setAttribute('attributeName', 'cx'); + anim.setAttribute('values', '0; 100'); + anim.setAttribute('dur', '10s'); + anim.setAttribute('begin', beginSpec); + return document.getElementById('circle').appendChild(anim); +} + +function createEvent(charCode, keyCode, modifiers) +{ + if (typeof charCode == 'string') { + is(charCode.length, 1, + "If charCode is a string it should be 1 character long"); + charCode = charCode.charCodeAt(0); + } else if (typeof charCode == 'undefined') { + charCode = 0; + } + args = { ctrl: false, alt: false, shift: false, meta: false }; + if (typeof modifiers == 'object') { + for (var key in modifiers) + args[key] = modifiers[key]; + } + if (typeof keyCode == 'undefined') keyCode = 0; + var evt = document.createEvent("KeyboardEvent"); + evt.initKeyEvent("keypress", true, true, window, + args['ctrl'], + args['alt'], + args['shift'], + args['meta'], + keyCode, + charCode); + return evt; +} + +function testOpenEnd() +{ + // Test that an end specification with an accesskey value is treated as open + // ended + gSvg.setCurrentTime(0); + ok(gSvg.animationsPaused(), "Expected animations to be paused"); + + var anim = createAnim('0s; 2s'); + anim.setAttribute('end', '1s; accessKey(a)'); + + gSvg.setCurrentTime(2); + + try { + is(anim.getStartTime(), 2, + "Unexpected start time for second interval of open-ended animation"); + } catch(e) { + is(e.name, "InvalidStateError", + "Unexpected exception:" + e.name); + is(e.code, DOMException.INVALID_STATE_ERR, + "Unexpected exception code:" + e.code); + ok(false, "Failed to recognise accessKey as qualifying for creating an " + + "open-ended interval"); + } + + anim.parentNode.removeChild(anim); +} + +function testPreventDefault() +{ + // SVG/SMIL don't specify what should happen if preventDefault is called on + // the keypress event. For now, for consistency with event timing we ignore + // it. + gSvg.setCurrentTime(1); + ok(gSvg.animationsPaused(), "Expected animations to be paused"); + + var anim = createAnim('accessKey(a)'); + var evt = createEvent('a'); + + var circle = document.getElementById('circle'); + var func = function(evt) { evt.preventDefault(); } + circle.addEventListener('keypress', func, false); + circle.dispatchEvent(evt); + + try { + var start = anim.getStartTime(); + } catch(e) { + ok(false, "preventDefault() cancelled accessKey handling"); + } + + circle.removeEventListener('keypress', func, false); + anim.parentNode.removeChild(anim); +} + +function testDispatchToWindow() +{ + gSvg.setCurrentTime(1); + ok(gSvg.animationsPaused(), "Expected animations to be paused"); + + var anim = createAnim('accessKey(a)'); + var evt = createEvent('a'); + + window.dispatchEvent(evt); + + try { + var start = anim.getStartTime(); + } catch(e) { + ok(false, "Key event dispatched to the window failed to trigger " + + "accesskey handling"); + } + + anim.parentNode.removeChild(anim); +} + +function testAdoptNode() +{ + gSvg.setCurrentTime(1); + ok(gSvg.animationsPaused(), "Expected animations to be paused"); + + // Create a new document with an animation element + var newdoc = document.implementation.createDocument(gSvgns, 'svg', null); + var anim = newdoc.createElementNS(gSvgns, 'animate'); + anim.setAttribute('attributeName', 'cx'); + anim.setAttribute('values', '0; 100'); + anim.setAttribute('dur', '10s'); + anim.setAttribute('begin', 'accesskey(a)'); + newdoc.documentElement.appendChild(anim); + + // Adopt + ok(anim.ownerDocument !== document, + "Expected newly created animation to belong to a different doc"); + document.adoptNode(anim); + document.getElementById('circle').appendChild(anim); + ok(anim.ownerDocument === document, + "Expected newly created animation to belong to the same doc"); + + var evt = createEvent('a'); + + // Now fire an event at the original window and check nothing happens + newdoc.dispatchEvent(evt); + try { + var start = anim.getStartTime(); + ok(false, "Adopted node still receiving accesskey events from old doc"); + } catch(e) { + // Ok + } + + // And then fire at our window + document.dispatchEvent(evt); + try { + var start = anim.getStartTime(); + } catch(e) { + ok(false, "Adopted node failed to catch accesskey event"); + } + + anim.parentNode.removeChild(anim); +} + +function testFauxEvent() +{ + // Test a non-KeyEvent labelled as a key event + gSvg.setCurrentTime(0); + ok(gSvg.animationsPaused(), "Expected animations to be paused"); + + var anim = createAnim('accessKey(a)'); + var evt = document.createEvent("SVGEvents"); + evt.initEvent("keypress", true, true); + document.getElementById('circle').dispatchEvent(evt); + + // We're really just testing that the above didn't crash us, but while we're + // at it, just do a sanity check that we didn't also create an interval + try { + var start = anim.getStartTime(); + ok(false, "Faux event generated interval"); + } catch(e) { + // All is well + } + + anim.parentNode.removeChild(anim); +} + +window.addEventListener("load", main, false); +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/smil/test/test_smilAnimateMotion.xhtml b/dom/smil/test/test_smilAnimateMotion.xhtml new file mode 100644 index 000000000..250c0b80f --- /dev/null +++ b/dom/smil/test/test_smilAnimateMotion.xhtml @@ -0,0 +1,51 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=436418 +--> +<head> + <title>Test for animateMotion behavior</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="smilTestUtils.js"></script> + <script type="text/javascript" src="db_smilAnimateMotion.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=436418">Mozilla Bug 436418</a> +<p id="display"></p> +<div id="content" style="visibility: hidden"> + +<!-- NOTE: Setting font-size so we can test 'em' units --> +<svg xmlns="http://www.w3.org/2000/svg" + width="200px" height="200px" style="font-size: 500px" + onload="this.pauseAnimations()"> + <!-- XXXdholbert Right now, 'em' conversions are correct if we set font-size + on rect using the inline style attr. However, if we use 'font-size' attr, + then 'em' units end up using the inherited font-size instead. Bug? --> + <rect x="20" y="20" width="200" height="200" style="font-size: 10px"/> +</svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function main() +{ + // Start out with document paused + var svg = SMILUtil.getSVGRoot(); + ok(svg.animationsPaused(), "should be paused by <svg> load handler"); + is(svg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler"); + + var timingData = new SMILTimingData(1.0, 6.0); + testBundleList(gMotionBundles, timingData); + + SimpleTest.finish(); +} + +window.addEventListener("load", main, false); +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/smil/test/test_smilAnimateMotionInvalidValues.xhtml b/dom/smil/test/test_smilAnimateMotionInvalidValues.xhtml new file mode 100644 index 000000000..0554c7fd8 --- /dev/null +++ b/dom/smil/test/test_smilAnimateMotionInvalidValues.xhtml @@ -0,0 +1,176 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=436418 +--> +<head> + <title>Test for animateMotion acceptance of invalid values</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="smilTestUtils.js" /> + <script type="text/javascript" src="smilAnimateMotionValueLists.js" /> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=436418">Mozilla Bug 436418</a> +<p id="display"></p> +<div id="content" style="visibility: hidden"> +<svg xmlns="http://www.w3.org/2000/svg" id="svg" + width="200px" height="200px" + onload="this.pauseAnimations()"> + <rect id="rect" x="20" y="20" width="200" height="200"/> +</svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ + +// Constant strings (& string-arrays) +const SVGNS = "http://www.w3.org/2000/svg"; +const XLINKNS = "http://www.w3.org/1999/xlink"; + +// Constant objects +const gSvg = document.getElementById("svg"); +const gRect = document.getElementById("rect"); +const gUnAnimatedCTM = gRect.getCTM(); + +SimpleTest.waitForExplicitFinish(); + +function createAnim() +{ + var anim = document.createElementNS(SVGNS, "animateMotion"); + anim.setAttribute("dur", "2s"); + return gRect.appendChild(anim); +} + +function removeElem(aElem) +{ + aElem.parentNode.removeChild(aElem); +} + +function testAttr(aAttrName, aAttrValueArray, aIsValid) +{ + var componentsToCheck; + + for (var i in aAttrValueArray) { + var curVal = aAttrValueArray[i]; + var anim = createAnim(); + anim.setAttribute(aAttrName, curVal); + if (aAttrName == "rotate") { + // Apply a diagonal translation (so rotate='auto' will have an effect) + // and just test the rotation matrix components + anim.setAttribute("values", "0 0; 50 50"); + componentsToCheck = CTMUtil.CTM_COMPONENTS_ROTATE; + } else { + // Apply a supplementary rotation to make sure that we don't apply it if + // our value is rejected. + anim.setAttribute("rotate", Math.PI/4); + componentsToCheck = CTMUtil.CTM_COMPONENTS_ALL; + if (aAttrName == "keyPoints") { + // Add three times so we can test a greater range of values for + // keyPoints + anim.setAttribute("values", "0 0; 25 25; 50 50"); + anim.setAttribute("keyTimes", "0; 0.5; 1"); + anim.setAttribute("calcMode", "discrete"); + } + } + + var curCTM = gRect.getCTM(); + if (aIsValid) { + var errMsg = "CTM should have changed when applying animateMotion " + + "with '" + aAttrName + "' set to valid value '" + curVal + "'"; + CTMUtil.assertCTMNotEqual(curCTM, gUnAnimatedCTM, componentsToCheck, + errMsg, false); + } else { + var errMsg = "CTM should not have changed when applying animateMotion " + + "with '" + aAttrName + "' set to invalid value '" + curVal + "'"; + CTMUtil.assertCTMEqual(curCTM, gUnAnimatedCTM, componentsToCheck, + errMsg, false); + } + removeElem(anim); + } +} + +function createPath(aPathDescription) +{ + var path = document.createElementNS(SVGNS, "path"); + path.setAttribute("d", aPathDescription); + path.setAttribute("id", "thePath"); + return gSvg.appendChild(path); +} + +function createMpath(aAnimElement) +{ + var mpath = document.createElementNS(SVGNS, "mpath"); + mpath.setAttributeNS(XLINKNS, "href", "#thePath"); + return aAnimElement.appendChild(mpath); +} + +function testMpathElem(aPathValueArray, aIsValid) +{ + for (var i in aPathValueArray) { + var curVal = aPathValueArray[i]; + var anim = createAnim(); + var mpath = createMpath(anim); + var path = createPath(curVal); + + // Apply a supplementary rotation to make sure that we don't apply it if + // our value is rejected. + anim.setAttribute("rotate", Math.PI/4); + componentsToCheck = CTMUtil.CTM_COMPONENTS_ALL; + + if (aIsValid) { + var errMsg = "CTM should have changed when applying animateMotion " + + "with mpath linking to a path with valid value '" + curVal + "'"; + + CTMUtil.assertCTMNotEqual(gRect.getCTM(), gUnAnimatedCTM, + componentsToCheck, errMsg, false); + } else { + var errMsg = "CTM should not have changed when applying animateMotion " + + "with mpath linking to a path with invalid value '" + curVal + "'"; + CTMUtil.assertCTMEqual(gRect.getCTM(), gUnAnimatedCTM, + componentsToCheck, errMsg, false); + } + removeElem(anim); + removeElem(path); + removeElem(mpath); + } +} + +// Main Function +function main() +{ + // Start out with document paused + var svg = SMILUtil.getSVGRoot(); + ok(svg.animationsPaused(), "should be paused by <svg> load handler"); + is(svg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler"); + + testAttr("values", gValidValues, true); + testAttr("values", gInvalidValues, false); + + testAttr("rotate", gValidRotate, true); + testAttr("rotate", gInvalidRotate, false); + + testAttr("to", gValidToBy, true); + testAttr("to", gInvalidToBy, false); + + testAttr("by", gValidToBy, true); + testAttr("by", gInvalidToBy, false); + + testAttr("path", gValidPath, true); + testAttr("path", gInvalidPath, false); + testAttr("path", gValidPathWithErrors, true); + + testAttr("keyPoints", gValidKeyPoints, true); + testAttr("keyPoints", gInvalidKeyPoints, false); + + testMpathElem(gValidPath, true); + testMpathElem(gInvalidPath, false); + + SimpleTest.finish(); +} + +window.addEventListener("load", main, false); +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/smil/test/test_smilAnimateMotionOverrideRules.xhtml b/dom/smil/test/test_smilAnimateMotionOverrideRules.xhtml new file mode 100644 index 000000000..a428ff332 --- /dev/null +++ b/dom/smil/test/test_smilAnimateMotionOverrideRules.xhtml @@ -0,0 +1,215 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=436418 +--> +<head> + <title>Test for overriding of path-defining attributes for animateMotion</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="smilTestUtils.js" /> + <script type="text/javascript" src="smilAnimateMotionValueLists.js" /> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=436418">Mozilla Bug 436418</a> +<p id="display"></p> +<div id="content" style="visibility: hidden"> +<svg xmlns="http://www.w3.org/2000/svg" id="svg" + width="200px" height="200px" + onload="this.pauseAnimations()"> + <!-- Paths for mpath to refer to --> + <path id="validPathElem" d="M10 10 h-10"/> + <path id="invalidPathElem" d="abc"/> + + <!-- The rect whose motion is animated --> + <rect id="rect" x="20" y="20" width="200" height="200"/> +</svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ + +// Constant strings (& string-arrays) +const SVGNS = "http://www.w3.org/2000/svg"; +const XLINKNS = "http://www.w3.org/1999/xlink"; + +// Constant objects +const gSvg = document.getElementById("svg"); +const gRect = document.getElementById("rect"); +const gUnAnimatedCTM = gRect.getCTM(); + +// Values for path-defining attributes, and their expected +// CTMs halfway through the animation +var gMpathValidTarget = "#validPathElem"; +var gMpathCTM = CTMUtil.generateCTM([ 5, 10, 0 ]); + +var gMpathInvalidTargetA = "#invalidPathElem"; +var gMpathInvalidTargetB = "#nonExistentElem"; + +var gInvalidAttrValue = "i-am-invalid"; // Invalid for all tested attributes + +var gPathValidValue = "M20 20 h10"; +var gPathCTM = CTMUtil.generateCTM([ 25, 20, 0 ]); + +var gValuesValidValue = "30 30; 40 30" +var gValuesCTM = CTMUtil.generateCTM([ 35, 30, 0 ]); + +var gFromValidValue = "50 50"; + +var gByValidValue = "10 2"; +var gPureByCTM = CTMUtil.generateCTM([ 5, 1, 0 ]); +var gFromByCTM = CTMUtil.generateCTM([ 55, 51, 0 ]); + +var gToValidValue = "80 60"; +var gPureToCTM = CTMUtil.generateCTM([ 40, 30, 0 ]); +var gFromToCTM = CTMUtil.generateCTM([ 65, 55, 0 ]); + + +SimpleTest.waitForExplicitFinish(); + +function createAnim() +{ + var anim = document.createElementNS(SVGNS, "animateMotion"); + return gRect.appendChild(anim); +} + +function removeElem(aElem) +{ + aElem.parentNode.removeChild(aElem); +} + +function createMpath(aAnimElement, aHrefVal) +{ + var mpath = document.createElementNS(SVGNS, "mpath"); + mpath.setAttributeNS(XLINKNS, "href", aHrefVal); + return aAnimElement.appendChild(mpath); +} + +function runTest() { + // Start out with valid values for all path-defining attributes + var attrSettings = { + "mpath" : gMpathValidTarget, + "path" : gPathValidValue, + "values" : gValuesValidValue, + "from" : gFromValidValue, + "to" : gToValidValue, + "by" : gByValidValue, + }; + + // Test that <mpath> overrides everything below it + testAttrSettings(attrSettings, gMpathCTM, + "<mpath> should win"); + var mpathInvalidTargets = [gMpathInvalidTargetA, gMpathInvalidTargetB]; + for (var i in mpathInvalidTargets) { + var curInvalidValue = mpathInvalidTargets[i]; + attrSettings["mpath"] = curInvalidValue; + testAttrSettings(attrSettings, gUnAnimatedCTM, + "invalid <mpath> should block animation"); + } + delete attrSettings["mpath"]; + + // Test that 'path' overrides everything below it + testAttrSettings(attrSettings, gPathCTM, + "'path' should win vs all but mpath"); + attrSettings["path"] = gInvalidAttrValue; + testAttrSettings(attrSettings, gUnAnimatedCTM, + "invalid 'path' should block animation vs all but mpath"); + delete attrSettings["path"]; + + // Test that 'values' overrides everything below it + testAttrSettings(attrSettings, gValuesCTM, + "'values' should win vs from/by/to"); + attrSettings["values"] = gInvalidAttrValue; + testAttrSettings(attrSettings, gUnAnimatedCTM, + "invalid 'values' should block animation vs from/by/to"); + delete attrSettings["values"]; + + // Test that 'from' & 'to' overrides 'by' + testAttrSettings(attrSettings, gFromToCTM, + "'from/to' should win vs 'by'"); + attrSettings["to"] = gInvalidAttrValue; + testAttrSettings(attrSettings, gUnAnimatedCTM, + "invalid 'to' should block animation vs 'by'"); + delete attrSettings["to"]; + + // Test that 'from' & 'by' are effective + testAttrSettings(attrSettings, gFromByCTM, + "'from/by' should be visible"); + attrSettings["by"] = gInvalidAttrValue; + testAttrSettings(attrSettings, gUnAnimatedCTM, + "invalid 'by' should block animation"); + delete attrSettings["from"]; + + // REINSERT "to" & fix up "by" so we can test pure-"to" vs pure-"by" + attrSettings["to"] = gToValidValue; + attrSettings["by"] = gByValidValue; + testAttrSettings(attrSettings, gPureToCTM, + "pure-'to' should be effective & beat pure-'by'"); + attrSettings["to"] = gInvalidAttrValue; + testAttrSettings(attrSettings, gUnAnimatedCTM, + "invalid pure-'to' should block animation vs pure-'by'"); + delete attrSettings["to"]; + + // Test that pure-"by" is effective + testAttrSettings(attrSettings, gPureByCTM, + "pure-by should be visible"); + attrSettings["by"] = gInvalidAttrValue; + testAttrSettings(attrSettings, gUnAnimatedCTM, + "invalid 'by' should block animation"); + delete attrSettings["by"]; + + // Make sure that our hash is empty now. + for (var unexpectedKey in attrSettings) { + ok(false, "Unexpected mapping remains in attrSettings: " + + unexpectedKey + "-->" + unexpectedValue); + } +} + +function testAttrSettings(aAttrValueHash, aExpectedCTM, aErrMsg) +{ + var isDebug = false; // XXdholbert + !isDebug || todo(false, "ENTERING testAttrSettings"); + // Set up animateMotion element + var animElement = document.createElementNS(SVGNS, "animateMotion"); + animElement.setAttribute("dur", "2s"); + for (var attrName in aAttrValueHash) { + !isDebug || todo(false, "setting '" + attrName +"' to '" + + aAttrValueHash[attrName] +"'"); + if (attrName == "mpath") { + createMpath(animElement, aAttrValueHash[attrName]); + } else { + animElement.setAttribute(attrName, aAttrValueHash[attrName]); + } + } + + gRect.appendChild(animElement); + + // Seek to halfway through animation + SMILUtil.getSVGRoot().setCurrentTime(1); // Seek halfway through animation + + // Check CTM against expected value + CTMUtil.assertCTMEqual(gRect.getCTM(), aExpectedCTM, + CTMUtil.CTM_COMPONENTS_ALL, aErrMsg, false); + + // CLEAN UP + SMILUtil.getSVGRoot().setCurrentTime(0); + removeElem(animElement); +} + +// Main Function +function main() +{ + // Start out with document paused + var svg = SMILUtil.getSVGRoot(); + ok(svg.animationsPaused(), "should be paused by <svg> load handler"); + is(svg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler"); + + runTest(); + SimpleTest.finish(); +} + +window.addEventListener("load", main, false); +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/smil/test/test_smilBackwardsSeeking.xhtml b/dom/smil/test/test_smilBackwardsSeeking.xhtml new file mode 100644 index 000000000..7a40bf718 --- /dev/null +++ b/dom/smil/test/test_smilBackwardsSeeking.xhtml @@ -0,0 +1,191 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Test for backwards seeking behavior </title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> +<svg id="svg" xmlns="http://www.w3.org/2000/svg" /> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ +/** Test for backwards seeking behavior **/ + +var gSvg = document.getElementById("svg"); + +SimpleTest.waitForExplicitFinish(); + +function main() +{ + // Pause our document, so that the setCurrentTime calls are the only + // thing affecting document time + gSvg.pauseAnimations(); + + // We define a series of scenarios, sample times, and expected return values + // from getStartTime. + // + // Each scenario is basically a variation on the following arrangement: + // + // <svg> + // <set ... dur="1s" begin="<A-BEGIN>"/> + // <set ... dur="1s" begin="<B-BEGIN>"/> + // </svg> + // + // Each test then consists of the following: + // animA: attributes to be applied to a + // animB: attributes to be applied to b + // times: a series of triples which consist of: + // <sample time, a's expected start time, b's expected start time> + // * The sample time is the time passed to setCurrentTime and so is + // in seconds. + // * The expected start times are compared with the return value of + // getStartTime. To check for an unresolved start time where + // getStartTime would normally throw an exception use + // 'unresolved'. + // * We also allow the special notation to indicate a call to + // beginElement + // <'beginElementAt', id of animation element, offset> + // + // In the diagrams below '^' means the time before the seek and '*' is the + // seek time. + var testCases = Array(); + + // 0: Simple case + // + // A: +------- + // B: +------- begin: a.begin + // * ^ + testCases[0] = { + 'animA': {'begin':'1s', 'id':'a'}, + 'animB': {'begin':'a.begin'}, + 'times': [ [0, 1, 1], + [1, 1, 1], + [2, 'unresolved', 'unresolved'], + [0, 1, 1], + [1.5, 1, 1], + [1, 1, 1], + [2, 'unresolved', 'unresolved'] ] + }; + + // 1: Restored times should be live + // + // When we restore times they should be live. So we have the following + // scenario. + // + // A: +------- + // B: +------- begin: a.begin + // * ^ + // + // Then we call beginElement at an earlier time which should give us the + // following. + // + // A: +------- + // B: +------- + // * ^ + // + // If the times are not live however we'll end up with this + // + // A: +------- + // B: +-+------- + // * ^ + testCases[1] = { + 'animA': {'begin':'1s', 'id':'a', 'restart':'whenNotActive'}, + 'animB': {'begin':'a.begin', 'restart':'always'}, + 'times': [ [0, 1, 1], + [2, 'unresolved', 'unresolved'], + [0.25, 1, 1], + ['beginElementAt', 'a', 0.25], // = start time of 0.5 + [0.25, 0.5, 0.5], + [1, 0.5, 0.5], + [1.5, 'unresolved', 'unresolved'] ] + }; + + // 2: Multiple intervals A + // + // A: +- +- + // B: +- +- begin: a.begin+4s + // * ^ + testCases[2] = { + 'animA': {'begin':'1s; 3s', 'id':'a'}, + 'animB': {'begin':'a.begin+4s'}, + 'times': [ [0, 1, 5], + [3, 3, 5], + [6.5, 'unresolved', 7], + [4, 'unresolved', 5], + [6, 'unresolved', 7], + [2, 3, 5], + ['beginElementAt', 'a', 0], + [2, 2, 5], + [5, 'unresolved', 5], + [6, 'unresolved', 6], + [7, 'unresolved', 7], + [8, 'unresolved', 'unresolved'] ] + }; + + for (var i = 0; i < testCases.length; i++) { + gSvg.setCurrentTime(0); + var test = testCases[i]; + + // Create animation elements + var animA = createAnim(test.animA); + var animB = createAnim(test.animB); + + // Run samples + for (var j = 0; j < test.times.length; j++) { + var times = test.times[j]; + if (times[0] == 'beginElementAt') { + var anim = getElement(times[1]); + anim.beginElementAt(times[2]); + } else { + gSvg.setCurrentTime(times[0]); + checkStartTime(animA, times[1], times[0], i, 'a'); + checkStartTime(animB, times[2], times[0], i, 'b'); + } + } + + // Tidy up + animA.parentNode.removeChild(animA); + animB.parentNode.removeChild(animB); + } + + SimpleTest.finish(); +} + +function createAnim(attr) +{ + const svgns = "http://www.w3.org/2000/svg"; + var anim = document.createElementNS(svgns, 'set'); + anim.setAttribute('attributeName','x'); + anim.setAttribute('to','10'); + anim.setAttribute('dur','1s'); + for (name in attr) { + anim.setAttribute(name, attr[name]); + } + return gSvg.appendChild(anim); +} + +function checkStartTime(anim, expectedStartTime, sampleTime, caseNum, id) +{ + var startTime = 'unresolved'; + try { + startTime = anim.getStartTime(); + } catch (e) { + if (e.name != "InvalidStateError" || + e.code != DOMException.INVALID_STATE_ERR) + throw e; + } + + var msg = "Test case " + caseNum + ", t=" + sampleTime + " animation '" + + id + "': Unexpected getStartTime:"; + is(startTime, expectedStartTime, msg); +} + +window.addEventListener("load", main, false); +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/smil/test/test_smilCSSFontStretchRelative.xhtml b/dom/smil/test/test_smilCSSFontStretchRelative.xhtml new file mode 100644 index 000000000..08caff267 --- /dev/null +++ b/dom/smil/test/test_smilCSSFontStretchRelative.xhtml @@ -0,0 +1,102 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Test for Animation Behavior on CSS Properties</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="smilTestUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> +<svg id="svg" xmlns="http://www.w3.org/2000/svg"> +</svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ +/* This testcase verifies that animated values of "wider" and "narrower" for + "font-stretch" have the expected effect, across all possible inherited + values of the property. + XXXdholbert Currently, we don't support animating relative values of + font-stretch, so most of the tests here use todo_is() rather than is(). +*/ + +SimpleTest.waitForExplicitFinish(); + +const gPropertyName="font-stretch"; + +// List of non-relative font-stretch values, from smallest to largest +const gFontStretchValues = [ + "ultra-condensed", + "extra-condensed", + "condensed", + "semi-condensed", + "normal", + "semi-expanded", + "expanded", + "extra-expanded", + "ultra-expanded" +]; + +function testFontStretchValue(baseValue, narrowerStep, widerStep) +{ + var svg = SMILUtil.getSVGRoot(); + var gElem = document.createElementNS(SVG_NS, "g"); + gElem.setAttribute("style", "font-stretch: " + baseValue); + svg.appendChild(gElem); + + var textElem = document.createElementNS(SVG_NS, "text"); + gElem.appendChild(textElem); + + var animElem = document.createElementNS(SVG_NS, "set"); + animElem.setAttribute("attributeName", gPropertyName); + animElem.setAttribute("attributeType", "CSS"); + animElem.setAttribute("begin", "0s"); + animElem.setAttribute("dur", "indefinite"); + textElem.appendChild(animElem); + + // CHECK EFFECT OF 'narrower' + // NOTE: Using is() instead of todo_is() for ultra-condensed, since + // 'narrower' has no effect on that value. + var myIs = (baseValue == "ultra-condensed" ? is : todo_is); + animElem.setAttribute("to", "narrower"); + SMILUtil.getSVGRoot().setCurrentTime(1.0); // Force a resample + myIs(SMILUtil.getComputedStyleSimple(textElem, gPropertyName), narrowerStep, + "checking effect of 'narrower' on inherited value '" + baseValue + "'"); + + // CHECK EFFECT OF 'wider' + // NOTE: using is() instead of todo_is() for ultra-expanded, since + // 'wider' has no effect on that value. + myIs = (baseValue == "ultra-expanded" ? is : todo_is); + animElem.setAttribute("to", "wider"); + SMILUtil.getSVGRoot().setCurrentTime(1.0); // Force a resample + myIs(SMILUtil.getComputedStyleSimple(textElem, gPropertyName), widerStep, + "checking effect of 'wider' on inherited value '" + baseValue + "'"); + + // Removing animation should clear animated effects + textElem.removeChild(animElem); + svg.removeChild(gElem); +} + +function main() +{ + var valuesList = gFontStretchValues; + for (var baseIdx in valuesList) { + // 'narrower' and 'wider' are expected to shift us by one slot, but not + // past the ends of the list of possible values. + var narrowerIdx = Math.max(baseIdx - 1, 0); + var widerIdx = Math.min(baseIdx + 1, valuesList.length - 1); + + testFontStretchValue(valuesList[baseIdx], + valuesList[narrowerIdx], valuesList[widerIdx]); + } + + SimpleTest.finish(); +} + +window.addEventListener("load", main, false); +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/smil/test/test_smilCSSFromBy.xhtml b/dom/smil/test/test_smilCSSFromBy.xhtml new file mode 100644 index 000000000..d6ac7ff0e --- /dev/null +++ b/dom/smil/test/test_smilCSSFromBy.xhtml @@ -0,0 +1,49 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Test for Animation Behavior on CSS Properties</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="smilTestUtils.js"></script> + <script type="text/javascript" src="db_smilCSSPropertyList.js"></script> + <script type="text/javascript" src="db_smilCSSFromBy.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content"> +<svg xmlns="http://www.w3.org/2000/svg" + width="200px" height="200px" font-size="50px" style="color: rgb(50,50,50)" + onload="this.pauseAnimations()"> + <rect x="20" y="20" width="200" height="200"/> + <!-- NOTE: hard-wiring 'line-height' so that computed value of 'font' is + more predictable. (otherwise, line-height varies depending on platform) + --> + <text x="20" y="20" style="line-height: 10px !important">testing 123</text> + <line/> + <marker/> + <filter><feDiffuseLighting/></filter> +</svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function main() +{ + // Start out with document paused + var svg = SMILUtil.getSVGRoot(); + ok(svg.animationsPaused(), "should be paused by <svg> load handler"); + is(svg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler"); + + testBundleList(gFromByBundles, new SMILTimingData(1.0, 1.0)); + + SimpleTest.finish(); +} + +window.addEventListener("load", main, false); +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/smil/test/test_smilCSSFromTo.xhtml b/dom/smil/test/test_smilCSSFromTo.xhtml new file mode 100644 index 000000000..88f3ad715 --- /dev/null +++ b/dom/smil/test/test_smilCSSFromTo.xhtml @@ -0,0 +1,76 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Test for Animation Behavior on CSS Properties</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="smilTestUtils.js"></script> + <script type="text/javascript" src="db_smilCSSPropertyList.js"></script> + <script type="text/javascript" src="db_smilCSSFromTo.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content"> +<svg xmlns="http://www.w3.org/2000/svg" + width="200px" height="200px" font-size="50px" style="color: rgb(50,50,50)" + onload="this.pauseAnimations()"> + <rect x="20" y="20" width="200" height="200"/> + <!-- NOTE: hard-wiring 'line-height' so that computed value of 'font' is + more predictable. (otherwise, line-height varies depending on platform) + --> + <text x="20" y="20" style="line-height: 10px !important">testing 123</text> + <line/> + <image/> + <marker/> + <clipPath><circle/></clipPath> + <filter><feFlood/></filter> + <filter><feDiffuseLighting/></filter> + <linearGradient><stop/></linearGradient> +</svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function checkForUntestedProperties(bundleList) +{ + // Create the set of all the properties we know about + var propertySet = {}; + for (propertyLabel in gPropList) { + // insert property + propertySet[gPropList[propertyLabel].attrName] = null; + } + // Remove tested properties from the set + for (var bundleIdx in bundleList) { + var bundle = bundleList[bundleIdx]; + delete propertySet[bundle.animatedAttribute.attrName]; + } + // Warn about remaining (untested) properties + for (var untestedProp in propertySet) { + ok(false, "No tests for property '" + untestedProp + "'"); + } +} + +function main() +{ + // Start out with document paused + var svg = SMILUtil.getSVGRoot(); + ok(svg.animationsPaused(), "should be paused by <svg> load handler"); + is(svg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler"); + + // FIRST: Warn about any properties that are missing tests + checkForUntestedProperties(gFromToBundles); + + // Run the actual tests + testBundleList(gFromToBundles, new SMILTimingData(1.0, 1.0)); + + SimpleTest.finish(); +} + +window.addEventListener("load", main, false); +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/smil/test/test_smilCSSInherit.xhtml b/dom/smil/test/test_smilCSSInherit.xhtml new file mode 100644 index 000000000..9da18f52b --- /dev/null +++ b/dom/smil/test/test_smilCSSInherit.xhtml @@ -0,0 +1,85 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Test for Animation Behavior on CSS Properties</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="smilTestUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> +<svg id="svg" xmlns="http://www.w3.org/2000/svg" width="300px" height="200px" + onload="this.pauseAnimations()"> + <!-- At 50% through the animation, the following should be true: + * First <g> has font-size = 5px (1/2 between 0px and 10px) + * Next <g> has font-size = 10px (1/2 between inherit=5px and 15px) + * Next <g> has font-size = 15px (1/2 between inherit=10px and 20px) + * Next <g> has font-size = 20px (1/2 between inherit=15px and 25px) + * Next <g> has font-size = 25px (1/2 between inherit=20px and 30px) + * Next <g> has font-size = 30px (1/2 between inherit=25px and 35px) + * Next <g> has font-size = 35px (1/2 between inherit=30px and 40px) + * Next <g> has font-size = 40px (1/2 between inherit=35px and 45px) + * Next <g> has font-size = 45px (1/2 between inherit=40px and 50px) + * Next <g> has font-size = 50px (1/2 between inherit=45px and 55px) + * <text> has font-size = 75px (1/2 between inherit=50px and 100px) + --> + <g><animate attributeName="font-size" attributeType="CSS" + from="0px" to="10px" begin="0s" dur="1s"/> + <g><animate attributeName="font-size" attributeType="CSS" + from="inherit" to="15px" begin="0s" dur="1s"/> + <g><animate attributeName="font-size" attributeType="CSS" + from="inherit" to="20px" begin="0s" dur="1s"/> + <g><animate attributeName="font-size" attributeType="CSS" + from="inherit" to="25px" begin="0s" dur="1s"/> + <g><animate attributeName="font-size" attributeType="CSS" + from="inherit" to="30px" begin="0s" dur="1s"/> + <g><animate attributeName="font-size" attributeType="CSS" + from="inherit" to="35px" begin="0s" dur="1s"/> + <g><animate attributeName="font-size" attributeType="CSS" + from="inherit" to="40px" begin="0s" dur="1s"/> + <g><animate attributeName="font-size" attributeType="CSS" + from="inherit" to="45px" begin="0s" dur="1s"/> + <g><animate attributeName="font-size" attributeType="CSS" + from="inherit" to="50px" begin="0s" dur="1s"/> + <g><animate attributeName="font-size" attributeType="CSS" + from="inherit" to="55px" begin="0s" dur="1s"/> + <text y="100px" x="0px"> + abc + <animate attributeName="font-size" attributeType="CSS" + from="inherit" to="100px" begin="0s" dur="1s"/> + </text></g></g></g></g></g></g></g></g></g></g> +</svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ +SimpleTest.waitForExplicitFinish(); + +function main() { + // Pause & seek to halfway through animation + var svg = SMILUtil.getSVGRoot(); + ok(svg.animationsPaused(), "should be paused by <svg> load handler"); + is(svg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler"); + svg.setCurrentTime(0.5); + + var text = document.getElementsByTagName("text")[0]; + var computedVal = SMILUtil.getComputedStyleSimple(text, "font-size"); + var expectedVal = "75px"; + + // NOTE: There's a very small chance (1/11! = 1/39,916,800) that we'll happen + // to composite our 11 animations in the correct order, in which cast this + // "todo_is" test would sporadically pass. I think this is infrequent enough + // to accept as a sporadic pass rate until this bug is fixed (at which point + // this "todo_is" will become an "is") + todo_is(computedVal, expectedVal, + "deeply-inherited font-size halfway through animation"); + + SimpleTest.finish(); +} + +window.addEventListener("load", main, false); +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/smil/test/test_smilCSSInvalidValues.xhtml b/dom/smil/test/test_smilCSSInvalidValues.xhtml new file mode 100644 index 000000000..be5da6224 --- /dev/null +++ b/dom/smil/test/test_smilCSSInvalidValues.xhtml @@ -0,0 +1,59 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Test for Animation Behavior on CSS Properties</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="smilTestUtils.js"></script> + <script type="text/javascript" src="db_smilCSSPropertyList.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content"> +<svg xmlns="http://www.w3.org/2000/svg" + onload="this.pauseAnimations()"> + <rect x="20" y="20" width="200" height="200"/> +</svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +var invalidTestcaseBundles = [ + new TestcaseBundle(gPropList.opacity, [ + new AnimTestcaseFromTo("", "", { noEffect: true }), + new AnimTestcaseFromTo("", "0.5", { noEffect: true }), + new AnimTestcaseFromTo(".", "0.5", { noEffect: true }), + new AnimTestcaseFromTo("0.5", "-", { noEffect: true }), + new AnimTestcaseFromTo("0.5", "bogus", { noEffect: true }), + new AnimTestcaseFromTo("bogus", "bogus", { noEffect: true }), + ]), + new TestcaseBundle(gPropList.color, [ + new AnimTestcaseFromTo("", "", { noEffect: true }), + new AnimTestcaseFromTo("", "red", { noEffect: true }), + new AnimTestcaseFromTo("greeeen", "red", { noEffect: true }), + new AnimTestcaseFromTo("rgb(red, 255, 255)", "red", { noEffect: true }), + new AnimTestcaseFromTo("#FFFFFFF", "red", { noEffect: true }), + new AnimTestcaseFromTo("bogus", "bogus", { noEffect: true }), + ]), +]; +function main() +{ + // Start out with document paused + var svg = SMILUtil.getSVGRoot(); + ok(svg.animationsPaused(), "should be paused by <svg> load handler"); + is(svg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler"); + + // Run the tests + testBundleList(invalidTestcaseBundles, new SMILTimingData(1.0, 1.0)); + + SimpleTest.finish(); +} + +window.addEventListener("load", main, false); +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/smil/test/test_smilCSSPaced.xhtml b/dom/smil/test/test_smilCSSPaced.xhtml new file mode 100644 index 000000000..21040dc70 --- /dev/null +++ b/dom/smil/test/test_smilCSSPaced.xhtml @@ -0,0 +1,44 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Test for Animation Behavior on CSS Properties</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="smilTestUtils.js"></script> + <script type="text/javascript" src="db_smilCSSPropertyList.js"></script> + <script type="text/javascript" src="db_smilCSSPaced.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content"> +<svg xmlns="http://www.w3.org/2000/svg" + width="200px" height="200px" font-size="50px" style="color: rgb(50,50,50)" + onload="this.pauseAnimations()"> + <rect x="20" y="20" width="200" height="200"/> + <text x="20" y="20">testing 123</text> + <marker/> +</svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function main() +{ + // Start out with document paused + var svg = SMILUtil.getSVGRoot(); + ok(svg.animationsPaused(), "should be paused by <svg> load handler"); + is(svg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler"); + + testBundleList(gPacedBundles, new SMILTimingData(1.0, 6.0)); + + SimpleTest.finish(); +} + +window.addEventListener("load", main, false); +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/smil/test/test_smilChangeAfterFrozen.xhtml b/dom/smil/test/test_smilChangeAfterFrozen.xhtml new file mode 100644 index 000000000..91e87bc34 --- /dev/null +++ b/dom/smil/test/test_smilChangeAfterFrozen.xhtml @@ -0,0 +1,571 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Test for SMIL when things change after an animation is frozen</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="smilTestUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=533291">Mozilla Bug 533291</a> +<p id="display"></p> +<!-- Bug 628848: The following should be display: none but we currently don't + handle percentage lengths properly when the whole fragment is display: none + --> +<div id="content" style="visibility: hidden"> +<svg id="svg" xmlns="http://www.w3.org/2000/svg" width="120px" height="120px" + onload="this.pauseAnimations()"> + <g id="circleParent"> + <circle cx="0" cy="20" r="15" fill="blue" id="circle"/> + </g> +</svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ +/** Test for SMIL values that are context-sensitive **/ + +/* See bugs 533291 and 562815. + + The format of each test is basically: + 1) create some animated and frozen state + 2) test the animated values + 3) change the context + 4) test that context-sensitive animation values have changed + + Ideally, after changing the context (3), the animated state would instantly + update. However, this is not currently the case for many situations. + + For CSS properties we have bug 545282 - In animations involving 'inherit' + / 'currentColor', changes to inherited value / 'color' don't show up in + animated value immediately + + For SVG lengths we have bug 508206 - Relative units used in + animation don't update immediately + + (There are a few of todo_is's in the following tests so that if those bugs + are ever resolved we'll know to update this test case accordingly.) + + So in between (3) and (4) we force a sample. This is currently done by + calling SVGSVGElement.setCurrentTime with the same current time which has the + side effect of forcing a sample. + + What we *are* testing is that we're not too zealous with caching animation + values whilst in the frozen state. Normally we'd say, "Hey, we're frozen, + let's just use the same animation result as last time" but for some + context-sensitive animation values that doesn't work. +*/ + +/* Global Variables */ +const SVGNS = "http://www.w3.org/2000/svg"; + +// Animation parameters -- not used for <set> animation +const ANIM_DUR = "4s"; +const TIME_ANIM_END = "4"; +const TIME_AFTER_ANIM_END = "5"; + +const gSvg = document.getElementById("svg"); +const gCircle = document.getElementById("circle"); +const gCircleParent = document.getElementById("circleParent"); + +SimpleTest.waitForExplicitFinish(); + +// MAIN FUNCTION +// ------------- + +function main() +{ + ok(gSvg.animationsPaused(), "should be paused by <svg> load handler"); + is(gSvg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler"); + + const tests = + [ testBaseValueChange, + testCurrentColorChange, + testCurrentColorChangeUsingStyle, + testCurrentColorChangeOnFallback, + testInheritChange, + testInheritChangeUsingStyle, + testEmUnitChangeOnProp, + testEmUnitChangeOnPropBase, + testEmUnitChangeOnLength, + testPercentUnitChangeOnProp, + testPercentUnitChangeOnLength, + testRelativeFontSize, + testRelativeFontWeight, + testRelativeFont, + testCalcFontSize, + testDashArray, + testClip + ]; + + while (tests.length) { + tests.shift()(); + } + SimpleTest.finish(); +} + +// HELPER FUNCTIONS +// ---------------- +function createAnimSetTo(attrName, toVal) +{ + var anim = document.createElementNS(SVGNS,"set"); + anim.setAttribute("attributeName", attrName); + anim.setAttribute("to", toVal); + return gCircle.appendChild(anim); +} + +function createAnimBy(attrName, byVal) +{ + var anim = document.createElementNS(SVGNS,"animate"); + anim.setAttribute("attributeName", attrName); + anim.setAttribute("dur", ANIM_DUR); + anim.setAttribute("begin","0s"); + anim.setAttribute("by", byVal); + anim.setAttribute("fill", "freeze"); + return gCircle.appendChild(anim); +} + +function createAnimFromTo(attrName, fromVal, toVal) +{ + var anim = document.createElementNS(SVGNS,"animate"); + anim.setAttribute("attributeName", attrName); + anim.setAttribute("dur", ANIM_DUR); + anim.setAttribute("begin","0s"); + anim.setAttribute("from", fromVal); + anim.setAttribute("to", toVal); + anim.setAttribute("fill", "freeze"); + return gCircle.appendChild(anim); +} + +// Common setup code for each test function: seek to 0, and make sure +// the previous test cleaned up its animations. +function setupTest() { + gSvg.setCurrentTime(0); + if (gCircle.firstChild) { + ok(false, "Previous test didn't clean up after itself."); + } +} + +// THE TESTS +// --------- + +function testBaseValueChange() +{ + setupTest(); + var anim = createAnimBy("cx", "50"); + gSvg.setCurrentTime(TIME_ANIM_END); + is(gCircle.cx.animVal.value, 50, + "Checking animated cx as anim ends"); + + gSvg.setCurrentTime(TIME_AFTER_ANIM_END); + is(gCircle.cx.animVal.value, 50, + "Checking animated cx after anim ends"); + + gCircle.setAttribute("cx", 20); + is(gCircle.cx.animVal.value, 70, + "Checking animated cx after anim ends & after changing base val"); + + anim.parentNode.removeChild(anim); // clean up +} + +function testCurrentColorChange() +{ + gCircle.setAttribute("color", "red"); // At first: currentColor=red + var anim = createAnimSetTo("fill", "currentColor"); + + gSvg.setCurrentTime(0); // trigger synchronous sample + is(SMILUtil.getComputedStyleSimple(gCircle, "fill"), "rgb(255, 0, 0)", + "Checking animated fill=currentColor after animating"); + + gCircle.setAttribute("color", "lime"); // Change: currentColor=lime + // Bug 545282: We should really detect this change and update immediately but + // currently we don't until we get sampled again + todo_is(SMILUtil.getComputedStyleSimple(gCircle, "fill"), "rgb(0, 255, 0)", + "Checking animated fill=currentColor after updating context but before " + + "sampling"); + gSvg.setCurrentTime(0); + is(SMILUtil.getComputedStyleSimple(gCircle, "fill"), "rgb(0, 255, 0)", + "Checking animated fill=currentColor after updating context"); + + // Clean up + gCircle.removeAttribute("color"); + gCircle.removeChild(gCircle.firstChild); +} + +function testCurrentColorChangeUsingStyle() +{ + setupTest(); + gCircle.setAttribute("style", "color: red"); // At first: currentColor=red + var anim = createAnimSetTo("fill", "currentColor"); + + gSvg.setCurrentTime(0); + is(SMILUtil.getComputedStyleSimple(gCircle, "fill"), "rgb(255, 0, 0)", + "Checking animated fill=currentColor after animating (using style attr)"); + + gCircle.setAttribute("style", "color: lime"); // Change: currentColor=lime + gSvg.setCurrentTime(0); + is(SMILUtil.getComputedStyleSimple(gCircle, "fill"), "rgb(0, 255, 0)", + "Checking animated fill=currentColor after updating context " + + "(using style attr)"); + + // Clean up + gCircle.removeAttribute("style"); + gCircle.removeChild(gCircle.firstChild); +} + +function getFallbackColor(pServerStr) +{ + return pServerStr.substr(pServerStr.indexOf(" ")+1); +} + +function testCurrentColorChangeOnFallback() +{ + setupTest(); + gCircle.setAttribute("color", "red"); // At first: currentColor=red + var anim = createAnimSetTo("fill", "url(#missingGrad) currentColor"); + + gSvg.setCurrentTime(0); + var fallback = + getFallbackColor(SMILUtil.getComputedStyleSimple(gCircle, "fill")); + is(fallback, "rgb(255, 0, 0)", + "Checking animated fallback fill=currentColor after animating"); + + gCircle.setAttribute("color", "lime"); // Change: currentColor=lime + gSvg.setCurrentTime(0); + fallback = getFallbackColor(SMILUtil.getComputedStyleSimple(gCircle, "fill")); + is(fallback, "rgb(0, 255, 0)", + "Checking animated fallback fill=currentColor after updating context"); + + gCircle.removeAttribute("style"); + gCircle.removeChild(gCircle.firstChild); +} + +function testInheritChange() +{ + setupTest(); + gCircleParent.setAttribute("fill", "red"); // At first: inherit=red + var anim = createAnimSetTo("fill", "inherit"); + + gSvg.setCurrentTime(0); + is(SMILUtil.getComputedStyleSimple(gCircle, "fill"), "rgb(255, 0, 0)", + "Checking animated fill=inherit after animating"); + + gCircleParent.setAttribute("fill", "lime"); // Change: inherit=lime + gSvg.setCurrentTime(0); + is(SMILUtil.getComputedStyleSimple(gCircle, "fill"), "rgb(0, 255, 0)", + "Checking animated fill=inherit after updating context"); + + gCircleParent.removeAttribute("fill"); + gCircle.removeChild(gCircle.firstChild); +} + +function testInheritChangeUsingStyle() +{ + setupTest(); + gCircleParent.setAttribute("style", "fill: red"); // At first: inherit=red + var anim = createAnimSetTo("fill", "inherit"); + + gSvg.setCurrentTime(0); + is(SMILUtil.getComputedStyleSimple(gCircle, "fill"), "rgb(255, 0, 0)", + "Checking animated fill=inherit after animating (using style attr)"); + + gCircleParent.setAttribute("style", "fill: lime"); // Change: inherit=lime + gSvg.setCurrentTime(0); + is(SMILUtil.getComputedStyleSimple(gCircle, "fill"), "rgb(0, 255, 0)", + "Checking animated fill=inherit after updating context " + + "(using style attr)"); + + gCircleParent.removeAttribute("style"); + gCircle.removeChild(gCircle.firstChild); +} + +function testEmUnitChangeOnProp() +{ + setupTest(); + gCircleParent.setAttribute("font-size", "10px"); // At first: font-size: 10px + var anim = createAnimSetTo("font-size", "2em"); + + gSvg.setCurrentTime(0); + is(SMILUtil.getComputedStyleSimple(gCircle, "font-size"), "20px", + "Checking animated font-size=2em after animating ends"); + + gCircleParent.setAttribute("font-size", "20px"); // Change: font-size: 20px + gSvg.setCurrentTime(0); + is(SMILUtil.getComputedStyleSimple(gCircle, "font-size"), "40px", + "Checking animated font-size=2em after updating context"); + + gCircleParent.removeAttribute("font-size"); + gCircle.removeChild(gCircle.firstChild); +} + +function testEmUnitChangeOnPropBase() +{ + // Test the case where the base value for our animation sandwich is + // context-sensitive. + // Currently, this is taken care of by the compositor which keeps a cached + // base value and compares it with the current base value. This test then just + // serves as a regression test in case the compositor's behaviour changes. + setupTest(); + gSvg.setAttribute("font-size", "10px"); // At first: font-size: 10px + gCircleParent.setAttribute("font-size", "1em"); // Base: 10px + var anim = createAnimBy("font-size", "10px"); + + gSvg.setCurrentTime(TIME_AFTER_ANIM_END); + is(SMILUtil.getComputedStyleSimple(gCircle, "font-size"), "20px", + "Checking animated font-size=20px after anim ends"); + + gSvg.setAttribute("font-size", "20px"); // Change: font-size: 20px + gSvg.setCurrentTime(TIME_AFTER_ANIM_END); + is(SMILUtil.getComputedStyleSimple(gCircle, "font-size"), "30px", + "Checking animated font-size=30px after updating context"); + + gCircleParent.removeAttribute("font-size"); + gCircle.removeChild(gCircle.firstChild); +} + +function testEmUnitChangeOnLength() +{ + setupTest(); + gCircleParent.setAttribute("font-size", "10px"); // At first: font-size: 10px + var anim = createAnimSetTo("cx", "2em"); + + gSvg.setCurrentTime(0); + is(gCircle.cx.animVal.value, 20, + "Checking animated length=2em after animating"); + + gCircleParent.setAttribute("font-size", "20px"); // Change: font-size: 20px + // Bug 508206: We should really detect this change and update immediately but + // currently we don't until we get sampled again + todo_is(gCircle.cx.animVal.value, 40, + "Checking animated length=2em after updating context but before sampling"); + + gSvg.setCurrentTime(0); + is(gCircle.cx.animVal.value, 40, + "Checking animated length=2em after updating context and after " + + "resampling"); + + gCircleParent.removeAttribute("font-size"); + gCircle.removeChild(gCircle.firstChild); +} + +function testPercentUnitChangeOnProp() +{ + setupTest(); + gCircleParent.setAttribute("font-size", "10px"); // At first: font-size: 10px + var anim = createAnimSetTo("font-size", "150%"); + + gSvg.setCurrentTime(0); + is(SMILUtil.getComputedStyleSimple(gCircle, "font-size"), "15px", + "Checking animated font-size=150% after animating"); + + gCircleParent.setAttribute("font-size", "20px"); // Change: font-size: 20px + gSvg.setCurrentTime(0); + is(SMILUtil.getComputedStyleSimple(gCircle, "font-size"), "30px", + "Checking animated font-size=150% after updating context"); + + gCircleParent.removeAttribute("font-size"); + gCircle.removeChild(gCircle.firstChild); +} + +function testPercentUnitChangeOnLength() +{ + setupTest(); + var oldHeight = gSvg.getAttribute("height"); + gSvg.setAttribute("height", "100px"); // At first: viewport height: 100px + var anim = createAnimSetTo("cy", "100%"); + + gSvg.setCurrentTime(0); // Force synchronous sample so animation takes effect + // Due to bug 627594 (SVGLength.value for percent value lengths doesn't + // reflect updated viewport until reflow) the following will fail. + // Check that it does indeed fail so that when that bug is fixed this test + // can be updated. + todo_is(gCircle.cy.animVal.value, 100, + "Checking animated length=100% after animating but before reflow"); + // force a layout flush (Bug 627594) + gSvg.getCTM(); + // Even after doing a reflow though we'll still fail due to bug 508206 + // (Relative units used in animation don't update immediately) + todo_is(gCircle.cy.animVal.value, 100, + "Checking animated length=100% after animating but before resampling"); + gSvg.setCurrentTime(0); + // Now we should be up to date + is(gCircle.cy.animVal.value, 100, + "Checking animated length=100% after animating"); + + gSvg.setAttribute("height", "50px"); // Change: height: 50px + // force a layout flush (Bug 627594) + gSvg.getCTM(); + gSvg.setCurrentTime(0); // Bug 508206 + is(gCircle.cy.animVal.value, 50, + "Checking animated length=100% after updating context"); + + gSvg.setAttribute("height", oldHeight); + gCircle.removeChild(gCircle.firstChild); +} + +function testRelativeFontSize() +{ + setupTest(); + gCircleParent.setAttribute("font-size", "10px"); // At first: font-size: 10px + var anim = createAnimSetTo("font-size", "larger"); + + gSvg.setCurrentTime(0); + var fsize = parseInt(SMILUtil.getComputedStyleSimple(gCircle, "font-size")); + // CSS 2 suggests a scaling factor of 1.2 so we should be looking at something + // around about 12 or so + ok(fsize > 10 && fsize < 20, + "Checking animated font-size > 10px after animating"); + + gCircleParent.setAttribute("font-size", "20px"); // Change: font-size: 20px + gSvg.setCurrentTime(0); + fsize = parseInt(SMILUtil.getComputedStyleSimple(gCircle, "font-size")); + ok(fsize > 20, "Checking animated font-size > 20px after updating context"); + + gCircleParent.removeAttribute("font-size"); + gCircle.removeChild(gCircle.firstChild); +} + +function testRelativeFontWeight() +{ + setupTest(); + gCircleParent.setAttribute("font-weight", "100"); // At first: font-weight 100 + var anim = createAnimSetTo("font-weight", "bolder"); + // CSS 2: 'bolder': Specifies the next weight that is assigned to a font + // that is darker than the inherited one. If there is no such weight, it + // simply results in the next darker numerical value (and the font remains + // unchanged), unless the inherited value was '900', in which case the + // resulting weight is also '900'. + + gSvg.setCurrentTime(0); + var weight = + parseInt(SMILUtil.getComputedStyleSimple(gCircle, "font-weight")); + ok(weight > 100, "Checking animated font-weight > 100 after animating"); + + gCircleParent.setAttribute("font-weight", "800"); // Change: font-weight 800 + gSvg.setCurrentTime(0); + weight = parseInt(SMILUtil.getComputedStyleSimple(gCircle, "font-weight")); + is(weight, 900, + "Checking animated font-weight = 900 after updating context"); + + gCircleParent.removeAttribute("font-weight"); + gCircle.removeChild(gCircle.firstChild); +} + +function testRelativeFont() +{ + // Test a relative font-size as part of a 'font' spec since the code path + // is different in this case + // It turns out that, due to the way we store shorthand font properties, we + // don't need to worry about marking such values as context-sensitive since we + // seem to store them in their relative form. If, however, we change the way + // we store shorthand font properties in the future, this will serve as + // a useful regression test. + setupTest(); + gCircleParent.setAttribute("font-size", "10px"); // At first: font-size: 10px + // We must be sure to set every part of the shorthand property to some + // non-context sensitive value because we want to test that even if only the + // font-size is relative we will update it appropriately. + var anim = + createAnimSetTo("font", "normal normal bold larger/normal sans-serif"); + + gSvg.setCurrentTime(0); + var fsize = parseInt(SMILUtil.getComputedStyleSimple(gCircle, "font-size")); + ok(fsize > 10 && fsize < 20, + "Checking size of shorthand 'font' > 10px after animating"); + + gCircleParent.setAttribute("font-size", "20px"); // Change: font-size: 20px + gSvg.setCurrentTime(0); + fsize = parseInt(SMILUtil.getComputedStyleSimple(gCircle, "font-size")); + ok(fsize > 20, + "Checking size of shorthand 'font' > 20px after updating context"); + + gCircleParent.removeAttribute("font-size"); + gCircle.removeChild(gCircle.firstChild); +} + +function testCalcFontSize() +{ + setupTest(); + gCircleParent.setAttribute("font-size", "10px"); // At first: font-size: 10px + var anim = createAnimSetTo("font-size", "-moz-calc(110% + 0.1em)"); + + gSvg.setCurrentTime(0); + var fsize = parseInt(SMILUtil.getComputedStyleSimple(gCircle, "font-size")); + // Font size should be 1.1 * 10px + 0.1 * 10px = 12 + is(fsize, 12, "Checking animated calc font-size == 12px after animating"); + + gCircleParent.setAttribute("font-size", "20px"); // Change: font-size: 20px + gSvg.setCurrentTime(0); + fsize = parseInt(SMILUtil.getComputedStyleSimple(gCircle, "font-size")); + is(fsize, 24, "Checking animated calc font-size == 24px after updating " + + "context"); + + gCircleParent.removeAttribute("font-size"); + gCircle.removeChild(gCircle.firstChild); +} + +function testDashArray() +{ + // stroke dasharrays don't currently convert units--but if someone ever fixes + // that, hopefully this test will fail and remind us not to cache percentage + // values in that case + setupTest(); + var oldHeight = gSvg.getAttribute("height"); + var oldWidth = gSvg.getAttribute("width"); + gSvg.setAttribute("height", "100px"); // At first: viewport: 100x100px + gSvg.setAttribute("width", "100px"); + var anim = createAnimFromTo("stroke-dasharray", "0 5", "0 50%"); + + gSvg.setCurrentTime(TIME_AFTER_ANIM_END); + + // Now we should be up to date + is(SMILUtil.getComputedStyleSimple(gCircle, "stroke-dasharray"), "0, 50%", + "Checking animated stroke-dasharray after animating"); + + gSvg.setAttribute("height", "50px"); // Change viewport: 50x50px + gSvg.setAttribute("width", "50px"); + gSvg.setCurrentTime(TIME_AFTER_ANIM_END); + is(SMILUtil.getComputedStyleSimple(gCircle, "stroke-dasharray"), "0, 50%", + "Checking animated stroke-dasharray after updating context"); + + gSvg.setAttribute("height", oldHeight); + gSvg.setAttribute("width", oldWidth); + gCircle.removeChild(gCircle.firstChild); +} + +function testClip() +{ + setupTest(); + gCircleParent.setAttribute("font-size", "20px"); // At first: font-size: 20px + + // The clip property only applies to elements that establish a new + // viewport so we need to create a nested svg and add animation to that + var nestedSVG = document.createElementNS(SVGNS, "svg"); + nestedSVG.setAttribute("clip", "rect(0px 0px 0px 0px)"); + gCircleParent.appendChild(nestedSVG); + + var anim = createAnimSetTo("clip", "rect(1em 1em 1em 1em)"); + // createAnimSetTo will make the animation a child of gCircle so we need to + // move it so it targets nestedSVG instead + nestedSVG.appendChild(anim); + + gSvg.setCurrentTime(TIME_AFTER_ANIM_END); + is(SMILUtil.getComputedStyleSimple(nestedSVG, "clip"), + "rect(20px, 20px, 20px, 20px)", + "Checking animated clip rect after animating"); + + gCircleParent.setAttribute("font-size", "10px"); // Change: font-size: 10px + gSvg.setCurrentTime(TIME_AFTER_ANIM_END); + is(SMILUtil.getComputedStyleSimple(nestedSVG, "clip"), + "rect(10px, 10px, 10px, 10px)", + "Checking animated clip rect after updating context"); + + gCircleParent.removeAttribute("font-size"); + gCircleParent.removeChild(nestedSVG); +} + +window.addEventListener("load", main, false); +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/smil/test/test_smilConditionalProcessing.html b/dom/smil/test/test_smilConditionalProcessing.html new file mode 100644 index 000000000..21d08adb0 --- /dev/null +++ b/dom/smil/test/test_smilConditionalProcessing.html @@ -0,0 +1,80 @@ +<!doctype html> +<html> +<head> + <meta charset="utf-8"> + <title>Test conditional processing tests applied to animations</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content"> +<svg id="svg" width="120px" height="120px" + onload="this.pauseAnimations()"> + <circle r="50" fill="blue" id="circle"> + <set attributeName="cy" to="100" begin="0s" dur="100s" id="a"/> + <set attributeName="cx" to="100" begin="a.end" dur="100s" id="b"/> + </circle> +</svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +var svg = document.getElementById("svg"), + a = document.getElementById("a"), + b = document.getElementById("b"), + circle = document.getElementById("circle"); + +// Check initial state +svg.setCurrentTime(50); +is(a.getStartTime(), 0, "a has resolved start time at start"); +is(circle.cy.animVal.value, 100, "a is in effect at start"); +is(b.getStartTime(), 100, "b has resolved start time at start"); + +// Add a failing conditional processing test +a.setAttribute("systemLanguage", "no-such-language"); +ok(hasUnresolvedStartTime(a), + "a has unresolved start time with failing conditional processing test"); +is(circle.cy.animVal.value, 0, + "a is not in effect with failing conditional processing test"); +ok(hasUnresolvedStartTime(b), + "b has unresolved start time with failing conditional processing test on a"); + +// Remove failing conditional processing test +a.removeAttribute("systemLanguage"); +is(a.getStartTime(), 0, "a has resolved start time after removing test"); +is(circle.cy.animVal.value, 100, "a is in effect after removing test"); +is(b.getStartTime(), 100, "b has resolved start time after removing test on a"); + +// Add another failing conditional processing test +// According to the spec, if a null string or empty string value is set for +// the 'systemLanguage' attribute, the attribute returns "false". +a.setAttribute("systemLanguage", ""); + +// Fast forward until |a| would have finished +var endEventsReceived = 0; +a.addEventListener("endEvent", function() { endEventsReceived++; }); +svg.setCurrentTime(150); +is(endEventsReceived, 0, + "a does not dispatch end events with failing condition processing test"); +is(circle.cx.animVal.value, 0, + "b is not in effect with failing conditional processing test on a"); + +// Make test pass +a.setAttribute("systemLanguage", "en"); +is(circle.cx.animVal.value, 100, + "b is in effect with passing conditional processing test on a"); + +function hasUnresolvedStartTime(anim) { + // getStartTime throws INVALID_STATE_ERR when there is no current interval + try { + anim.getStartTime(); + return false; + } catch(e) { + return true; + } +} + +</script> +</pre> +</body> +</html> diff --git a/dom/smil/test/test_smilContainerBinding.xhtml b/dom/smil/test/test_smilContainerBinding.xhtml new file mode 100644 index 000000000..1a47703bf --- /dev/null +++ b/dom/smil/test/test_smilContainerBinding.xhtml @@ -0,0 +1,101 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Test for adding and removing animations from a time container</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> +<svg id="svg" xmlns="http://www.w3.org/2000/svg" width="120px" height="120px" + onload="this.pauseAnimations()"> + <circle cx="-20" cy="20" r="15" fill="blue" id="circle"> + <set attributeName="cy" to="120" begin="0s; 2s" dur="1s" id="b"/> + </circle> +</svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ +/** Test for adding and removing animations from a time container **/ + +SimpleTest.waitForExplicitFinish(); + +function main() { + var svg = getElement("svg"); + ok(svg.animationsPaused(), "should be paused by <svg> load handler"); + is(svg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler"); + + // Create animation and check initial state + var anim = createAnim(); + anim.setAttribute('begin','b.begin+2s; 6s'); + ok(noStart(anim), "Animation has start time before attaching to document."); + + // Attach animation to container + var circle = getElement("circle"); + circle.appendChild(anim); + + // Check state after attaching + is(anim.getStartTime(), 2); + + // Unbind from tree -- the syncbase instance time(s) should become unresolved + // but the offset time should remain + anim.parentNode.removeChild(anim); + is(anim.getStartTime(), 6); + + // Rebind and check everything is re-resolved + circle.appendChild(anim); + is(anim.getStartTime(), 2); + + // Advance document time to t=1s + // Now the current interval for b is 2s-3s but the current interval for anim + // is still 2s-2.5s based on b's previous interval + svg.setCurrentTime(1); + is(anim.getStartTime(), 2); + + // Unbind + anim.parentNode.removeChild(anim); + is(anim.getStartTime(), 6); + + // Rebind + // At this point only the current interval will be re-added to anim (this is + // for consistency since old intervals may or may not have been filtered). + // Therefore the start time should be 4s instead of 2s. + circle.appendChild(anim); + is(anim.getStartTime(), 4); + + SimpleTest.finish(); +} + +function createAnim() { + const svgns="http://www.w3.org/2000/svg"; + var anim = document.createElementNS(svgns,'set'); + anim.setAttribute('attributeName','cx'); + anim.setAttribute('to','100'); + anim.setAttribute('dur','0.5s'); + return anim; +} + +function noStart(elem) { + var exceptionCaught = false; + + try { + elem.getStartTime(); + } catch(e) { + exceptionCaught = true; + is (e.name, "InvalidStateError", + "Unexpected exception from getStartTime."); + is (e.code, DOMException.INVALID_STATE_ERR, + "Unexpected exception code from getStartTime."); + } + + return exceptionCaught; +} + +window.addEventListener("load", main, false); +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/smil/test/test_smilCrossContainer.xhtml b/dom/smil/test/test_smilCrossContainer.xhtml new file mode 100644 index 000000000..2067973d6 --- /dev/null +++ b/dom/smil/test/test_smilCrossContainer.xhtml @@ -0,0 +1,132 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Test for moving animations between time containers</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> +<svg id="svga" xmlns="http://www.w3.org/2000/svg" width="120px" height="120px" + onload="this.pauseAnimations()"> + <circle cx="-20" cy="20" r="15" fill="blue" id="circlea"/> +</svg> +<svg id="svgb" xmlns="http://www.w3.org/2000/svg" width="120px" height="120px" + onload="this.pauseAnimations()"> + <circle cx="-20" cy="20" r="15" fill="blue" id="circleb"> + <set attributeName="cy" to="120" begin="4s" dur="1s" id="syncb"/> + </circle> +</svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ +/** Test for moving animations between time containers **/ + +SimpleTest.waitForExplicitFinish(); + +function main() { + var svga = getElement("svga"); + ok(svga.animationsPaused(), "should be paused by <svg> load handler"); + is(svga.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler"); + svga.setCurrentTime(1); + + var svgb = getElement("svgb"); + ok(svgb.animationsPaused(), "should be paused by <svg> load handler"); + is(svgb.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler"); + svgb.setCurrentTime(1); + + // Create animation and check initial state + var anim = createAnim(); + ok(noStart(anim), "Animation has start time before attaching to document"); + + // Attach animation to first container + var circlea = getElement("circlea"); + var circleb = getElement("circleb"); + circlea.appendChild(anim); + + // Check state after attaching + is(anim.getStartTime(), 2, + "Unexpected start time after attaching animation to target"); + is(circlea.cx.animVal.value, -20, + "Unexpected animated value for yet-to-start animation"); + is(circleb.cx.animVal.value, -20, + "Unexpected animated value for unanimated target"); + + // Move animation from first container to second + circleb.appendChild(anim); + + // Advance first container and check animation has no effect + svga.setCurrentTime(2); + is(anim.getStartTime(), 2, + "Unexpected start time after moving animation"); + is(circlea.cx.animVal.value, -20, + "Unexpected animated value for non-longer-animated target"); + is(circleb.cx.animVal.value, -20, + "Unexpected animated value for now yet-to-start animation"); + + // Advance second container and check the animation only affects it + svgb.setCurrentTime(2); + is(anim.getStartTime(), 2, "Start time changed after time container seek"); + is(circlea.cx.animVal.value, -20, + "Unanimated target changed after seek on other container"); + is(circleb.cx.animVal.value, 100, "Animated target not animated after seek"); + + // Remove animation so that it belongs to no container and check that + // advancing the second container to the next milestone doesn't cause a crash + // (when the animation controller goes to run the next milestone sample). + anim.parentNode.removeChild(anim); + svgb.setCurrentTime(3); + + // Do likewise with syncbase relationships + + // Create the syncbase relationship + anim.setAttribute('begin', 'syncb.begin'); + + // Attach to second time container (where t=3s) + circleb.appendChild(anim); + is(anim.getStartTime(), 4, + "Unexpected start time for cross-time container syncbase dependency"); + + // Move to first time container (where t=1s). + // Because we're dealing with different time containers and both are paused, + // future times are effectively unresolved. + circlea.appendChild(anim); + ok(noStart(anim), "Unexpected start time for paused time container"); + + SimpleTest.finish(); +} + +function createAnim() { + const svgns="http://www.w3.org/2000/svg"; + var anim = document.createElementNS(svgns,'set'); + anim.setAttribute('attributeName','cx'); + anim.setAttribute('to','100'); + anim.setAttribute('begin','2s'); + anim.setAttribute('dur','1s'); + return anim; +} + +function noStart(elem) { + var exceptionCaught = false; + + try { + elem.getStartTime(); + } catch(e) { + exceptionCaught = true; + is (e.name, "InvalidStateError", + "Unexpected exception from getStartTime."); + is (e.code, DOMException.INVALID_STATE_ERR, + "Unexpected exception code from getStartTime"); + } + + return exceptionCaught; +} + +window.addEventListener("load", main, false); +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/smil/test/test_smilDynamicDelayedBeginElement.xhtml b/dom/smil/test/test_smilDynamicDelayedBeginElement.xhtml new file mode 100644 index 000000000..b2af10c6f --- /dev/null +++ b/dom/smil/test/test_smilDynamicDelayedBeginElement.xhtml @@ -0,0 +1,103 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=699143 +--> +<head> + <title>Test for Bug 699143</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="smilTestUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=699143">Mozilla Bug 699143</a> +<p id="display"></p> +<div id="content" style="display: none"> + <svg xmlns="http://www.w3.org/2000/svg"> + <rect id="r" height="500px" width="500px" fill="blue"/> + </svg> +</div> +<pre id="test"> +<script type="text/javascript"> +<![CDATA[ + +/** Test for Bug 699143 **/ +SimpleTest.waitForExplicitFinish(); + +// Values for 'width' attr on the <rect> above +const INITIAL_VAL = "500px" +const FROM_VAL = "20px"; +const TO_VAL = "80px"; + +// Helper functions + +// This function allows 10ms to pass +function allowTimeToPass() { + var initialDate = new Date(); + while (new Date() - initialDate < 10) {} +} + +// This function returns a newly created <animate> element for use in this test +function createAnim() { + var a = document.createElementNS('http://www.w3.org/2000/svg', 'animate'); + a.setAttribute('attributeName', 'width'); + a.setAttribute('from', FROM_VAL); + a.setAttribute('to', TO_VAL); + a.setAttribute('begin', 'indefinite'); + a.setAttribute('dur', '3s'); + a.setAttribute('fill', 'freeze'); + return a; +} + +// Main Functions +function main() { + // In unpatched Firefox builds, we'll only trigger Bug 699143 if we insert + // an animation and call beginElement() **after** the document start-time. + // Hence, we use executeSoon here to allow some time to pass. (And then + // we'll use a short busy-loop, for good measure.) + SimpleTest.executeSoon(runTest); +} + +function runTest() { + var svg = SMILUtil.getSVGRoot(); + + // In case our executeSoon fired immediately, we force a very small amount + // of time to pass here, using a 10ms busy-loop. + allowTimeToPass(); + + is(svg.getCurrentTime(), 0, + "even though we've allowed time to pass, we shouldn't have bothered " + + "updating the current time, since there aren't any animation elements"); + + // Insert an animation elem (should affect currentTime but not targeted attr) + var r = document.getElementById("r"); + var a = createAnim(); + r.appendChild(a); + isnot(svg.getCurrentTime(), 0, + "insertion of first animation element should have triggered a " + + "synchronous sample and updated our current time"); + is(r.width.animVal.valueAsString, INITIAL_VAL, + "inserted animation shouldn't have affected its targeted attribute, " + + "since it doesn't have any intervals yet"); + + // Trigger the animation & be sure it takes effect + a.beginElement(); + is(r.width.animVal.valueAsString, FROM_VAL, + "beginElement() should activate our animation & set its 'from' val"); + + // Rewind to time=0 & check target attr, to be sure beginElement()-generated + // interval starts later than that. + svg.setCurrentTime(0); + is(r.width.animVal.valueAsString, INITIAL_VAL, + "after rewinding to 0, our beginElement()-generated interval " + + "shouldn't be active yet"); + + SimpleTest.finish(); +} + +window.addEventListener("load", main, false); + +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/smil/test/test_smilExtDoc.xhtml b/dom/smil/test/test_smilExtDoc.xhtml new file mode 100644 index 000000000..772aebdaa --- /dev/null +++ b/dom/smil/test/test_smilExtDoc.xhtml @@ -0,0 +1,80 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=628888 +--> +<head> + <title>Test for Bug 628888 - Animations in external document sometimes don't run</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body style="margin:0px"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=628888">Mozilla Bug 628888</a> +<p id="display"></p> +<div id="content" style="background: red; width: 50px; height: 50px"/> + +<pre id="test"> +<script type="application/javascript"> +<![CDATA[ + +/* Test for Bug 628888 - Animations in external document sometimes don't run + * + * This bug concerns a condition where an external document is loaded after the + * page show event is dispatched, leaving the external document paused. + * + * To reproduce the bug we attach an external document with animation after the + * page show event has fired. + * + * However, it is difficult to test if the animation is playing or not since we + * don't receive events from animations running in an external document. + * + * Our approach is to simply render the result to a canvas (which requires + * elevated privileges and that is why we are using a MochiTest rather + * than a reftest) and poll one of the pixels to see if it changes colour. + * + * This should mean the test succeeds quickly but fails slowly. + */ + +const POLL_INTERVAL = 100; // ms +const POLL_TIMEOUT = 10000; // ms +var accumulatedWaitTime = 0; + +function pageShow() +{ + var content = document.getElementById("content"); + content.style.filter = "url(smilExtDoc_helper.svg#filter)"; + window.setTimeout(checkResult, 0); +} + +function checkResult() +{ + var content = document.getElementById("content"); + var bbox = content.getBoundingClientRect(); + + var canvas = SpecialPowers.snapshotRect(window, bbox); + var ctx = canvas.getContext("2d"); + + var imgd = ctx.getImageData(bbox.width/2, bbox.height/2, 1, 1); + var isGreen = (imgd.data[0] == 0) && + (imgd.data[1] == 255) && + (imgd.data[2] == 0); + if (isGreen) { + ok(true, "Filter is animated as expected"); + } else if (accumulatedWaitTime >= POLL_TIMEOUT) { + ok(false, "No animation detected after waiting " + POLL_TIMEOUT + "ms"); + } else { + accumulatedWaitTime += POLL_INTERVAL; + window.setTimeout(checkResult, POLL_INTERVAL); + return; + } + // Hide our content since mochitests normally try to be visually "quiet" + content.style.display = 'none'; + SimpleTest.finish(); +} +window.addEventListener('pageshow', pageShow, false); +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestFlakyTimeout("untriaged"); +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/smil/test/test_smilFillMode.xhtml b/dom/smil/test/test_smilFillMode.xhtml new file mode 100644 index 000000000..b0f4b84c7 --- /dev/null +++ b/dom/smil/test/test_smilFillMode.xhtml @@ -0,0 +1,86 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Test for SMIL fill modes</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content"> +<svg id="svg" xmlns="http://www.w3.org/2000/svg" width="120px" height="120px" + onload="this.pauseAnimations()"> + <circle cx="-100" cy="20" r="15" fill="blue" id="circle"/> +</svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ +/** Test for SMIL fill modes **/ + +/* Global Variables */ +const svgns="http://www.w3.org/2000/svg"; +var svg = document.getElementById("svg"); +var circle = document.getElementById('circle'); + +SimpleTest.waitForExplicitFinish(); + +function createAnim() { + var anim = document.createElementNS(svgns,'animate'); + anim.setAttribute('attributeName','cx'); + anim.setAttribute('dur','4s'); + anim.setAttribute('begin','0s'); + anim.setAttribute('values', '10; 20'); + return circle.appendChild(anim); +} + +function main() { + ok(svg.animationsPaused(), "should be paused by <svg> load handler"); + is(svg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler"); + + var tests = + [ testSetLaterA, + testSetLaterB, + testRemoveLater ]; + for (var i = 0; i < tests.length; i++) { + var anim = createAnim(); + svg.setCurrentTime(0); + tests[i](anim); + anim.parentNode.removeChild(anim); + } + SimpleTest.finish(); +} + +function checkSample(time, expectedValue) { + svg.setCurrentTime(time); + is(circle.cx.animVal.value, expectedValue, + "Updated fill mode not applied to animation"); +} + +// Test that we can update the fill mode after an interval has played and it +// will be updated correctly. +function testSetLaterA(anim) { + checkSample(5, -100); + anim.setAttribute('fill', 'freeze'); + is(circle.cx.animVal.value, 20, + "Fill not applied for retrospectively set fill mode"); +} + +function testSetLaterB(anim) { + anim.setAttribute('fill', 'freeze'); + checkSample(5, 20); +} + +function testRemoveLater(anim) { + anim.setAttribute('fill', 'freeze'); + checkSample(5, 20); + anim.setAttribute('fill', 'remove'); + is(circle.cx.animVal.value, -100, + "Fill not removed for retrospectively set fill mode"); +} + +window.addEventListener("load", main, false); +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/smil/test/test_smilGetSimpleDuration.xhtml b/dom/smil/test/test_smilGetSimpleDuration.xhtml new file mode 100644 index 000000000..5c4dc33eb --- /dev/null +++ b/dom/smil/test/test_smilGetSimpleDuration.xhtml @@ -0,0 +1,86 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Test for getSimpleDuration Behavior </title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> +<svg id="svg" xmlns="http://www.w3.org/2000/svg" width="120px" height="120px"> + <circle cx="20" cy="20" r="15" fill="blue"> + <animate attributeName="cx" attributeType="XML" + from="20" to="100" begin="1s" id="anim"/> + </circle> +</svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ +/** Test for getSimpleDuration Behavior **/ + +/* Global Variables */ +var svg = document.getElementById("svg"); + +SimpleTest.waitForExplicitFinish(); + +function main() { + var anim = document.getElementById("anim"); + + /* Check initial state */ + checkForException(anim, "dur not set"); + + /* Check basic operation */ + anim.setAttribute("dur", "1s"); + is(anim.getSimpleDuration(), 1); + anim.setAttribute("dur", "1.5s"); + is(anim.getSimpleDuration(), 1.5); + + /* Check exceptional states */ + anim.setAttribute("dur", "0s"); + checkForException(anim, "dur=0s"); + anim.setAttribute("dur", "-1s"); + checkForException(anim, "dur=-1s"); + anim.setAttribute("dur", "indefinite"); + checkForException(anim, "dur=indefinite"); + anim.setAttribute("dur", "media"); + checkForException(anim, "dur=media"); + anim.setAttribute("dur", "abc"); + checkForException(anim, "dur=abc"); + anim.removeAttribute("dur"); + checkForException(anim, "dur not set"); + + /* Check range/syntax */ + anim.setAttribute("dur", "100ms"); + millisecondCompare(anim.getSimpleDuration(), 0.1); + anim.setAttribute("dur", "24h"); + is(anim.getSimpleDuration(), 60 * 60 * 24); + + SimpleTest.finish(); +} + +function millisecondCompare(a, b) { + is(Math.round(a * 1000), Math.round(b * 1000)); +} + +function checkForException(anim, descr) { + var gotException = false; + try { + var dur = anim.getSimpleDuration(); + } catch(e) { + is (e.name, "NotSupportedError", + "Wrong exception from getSimpleDuration"); + is (e.code, DOMException.NOT_SUPPORTED_ERR, + "Wrong exception from getSimpleDuration"); + gotException = true; + } + ok(gotException, + "Exception not thrown for indefinite simple duration when " + descr); +} + +window.addEventListener("load", main, false); +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/smil/test/test_smilGetStartTime.xhtml b/dom/smil/test/test_smilGetStartTime.xhtml new file mode 100644 index 000000000..9b608487c --- /dev/null +++ b/dom/smil/test/test_smilGetStartTime.xhtml @@ -0,0 +1,103 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Test for getStartTime Behavior </title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> +<svg id="svg" xmlns="http://www.w3.org/2000/svg" width="120px" height="120px" + onload="this.pauseAnimations()"> + <circle cx="20" cy="20" r="15" fill="blue"> + <animate attributeName="cx" attributeType="XML" + from="20" to="100" begin="indefinite" dur="1s" id="anim"/> + </circle> +</svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ +/** Test for getStartTime Behavior **/ + +SimpleTest.waitForExplicitFinish(); + +function main() { + var svg = document.getElementById("svg"); + ok(svg.animationsPaused(), "should be paused by <svg> load handler"); + is(svg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler"); + + var anim = document.getElementById("anim"); + // indefinite + var exceptionCaught = false; + try { + anim.getStartTime(); + } catch(e) { + exceptionCaught = true; + is(e.name, "InvalidStateError", + "Unexpected exception from getStartTime."); + is(e.code, DOMException.INVALID_STATE_ERR, + "Unexpected exception code from getStartTime."); + } + ok(exceptionCaught, "No exception thrown for indefinite start time."); + + // 1s + anim.setAttribute("begin", "1s"); + is(anim.getStartTime(), 1, "Unexpected start time with begin=1s"); + + // We have to be careful here when choosing a negative time that we choose + // a time that will create an interval that reaches past t=0 as SMIL has + // special rules for throwing away intervals that end before t=0 + anim.setAttribute("begin", "-0.5s"); + is(anim.getStartTime(), -0.5, "Unexpected start time with begin=-0.5s"); + + // Once the animation has begun, the begin time is fixed so we need to end the + // element (or advance the timeline) to override the previous start time + anim.endElement(); + + // However, now we have an end instance, and the SMIL model dictates that if + // we have end instances and no end event conditions and all end instances are + // before our next begin, there's no valid interval. To overcome this we add + // an indefinite end. + anim.setAttribute("end", "indefinite"); + + // Now test over the lifetime of the animation when there are multiple + // intervals + anim.setAttribute("begin", "1s; 3s"); + is(anim.getStartTime(), 1, "Unexpected start time before first interval"); + + svg.setCurrentTime(1); + is(anim.getStartTime(), 1, + "Unexpected start time at start of first interval"); + + svg.setCurrentTime(1.5); + is(anim.getStartTime(), 1, "Unexpected start time during first interval"); + + svg.setCurrentTime(2); + is(anim.getStartTime(), 3, "Unexpected start time after first interval"); + + svg.setCurrentTime(3); + is(anim.getStartTime(), 3, "Unexpected start time during second interval"); + + svg.setCurrentTime(4); + exceptionCaught = false; + try { + anim.getStartTime(); + } catch(e) { + exceptionCaught = true; + is(e.name, "InvalidStateError", + "Unexpected exception from getStartTime."); + is(e.code, DOMException.INVALID_STATE_ERR, + "Unexpected exception code from getStartTime."); + } + ok(exceptionCaught, "No exception thrown for in postactive state."); + + SimpleTest.finish(); +} + +window.addEventListener("load", main, false); +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/smil/test/test_smilHyperlinking.xhtml b/dom/smil/test/test_smilHyperlinking.xhtml new file mode 100644 index 000000000..542a02073 --- /dev/null +++ b/dom/smil/test/test_smilHyperlinking.xhtml @@ -0,0 +1,233 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Test for hyperlinking</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display:none"> +<svg id="svg" xmlns="http://www.w3.org/2000/svg" width="120px" height="120px" + onload="this.pauseAnimations()"> + <circle cx="-100" cy="20" r="15" fill="blue" id="circle"/> +</svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ +/** Test for SMIL keySplines **/ + +/* Global Variables */ +const SVGNS="http://www.w3.org/2000/svg"; +var gSvg = document.getElementById("svg"); +var gAnim; + +var gTestStages = + [ testActive, + testSeekToFirst, + testKickStart, + testKickStartWithUnresolved, + testFiltering + ]; + +SimpleTest.waitForExplicitFinish(); + +function continueTest() +{ + if (gTestStages.length == 0) { + SimpleTest.finish(); + return; + } + + window.location.hash = ""; + if (gAnim) { + gAnim.parentNode.removeChild(gAnim); + } + gAnim = createAnim(); + gSvg.setCurrentTime(0); + gTestStages.shift()(); +} + +function createAnim() { + var anim = document.createElementNS(SVGNS,'animate'); + anim.setAttribute('attributeName','cx'); + anim.setAttribute('from','0'); + anim.setAttribute('to','100'); + anim.setAttribute('dur','1s'); + anim.setAttribute('begin','indefinite'); + anim.setAttribute('id','anim'); + return document.getElementById('circle').appendChild(anim); +} + +// Traversing a hyperlink, condition 1: +// +// "If the target element is active, seek the document time back to the +// (current) begin time of the element. If there are multiple begin times, use +// the begin time that corresponds to the current "begin instance"." +// +function testActive() { + gAnim.setAttribute('begin','2s; 4s'); + gSvg.setCurrentTime(2.5); + fireLink(rewindActiveInterval1); +} + +function rewindActiveInterval1() { + is(gSvg.getCurrentTime(), 2, + "Unexpected time after activating link to animation in the middle of " + + "first active interval"); + + // Seek to second interval + gSvg.setCurrentTime(4.5); + fireLink(rewindActiveInterval2); +} + +function rewindActiveInterval2() { + is(gSvg.getCurrentTime(), 4, + "Unexpected time after activating link to animation in the middle of " + + "second active interval"); + + // Try a negative time + gAnim.setAttribute("begin", "-0.5"); + gSvg.setCurrentTime(0.2); + fireLink(rewindActiveIntervalAtZero); +} + +function rewindActiveIntervalAtZero() { + is(gSvg.getCurrentTime(), 0, + "Unexpected time after activating link to animation in the middle of " + + "an active interval that overlaps zero"); + + continueTest(); +} + +// Traversing a hyperlink, condition 2: +// +// "Else if the target element begin time is resolved (i.e., there is any +// resolved time in the list of begin times, or if the begin time was forced by +// an earlier hyperlink or a beginElement() method call), seek the document time +// (forward or back, as needed) to the earliest resolved begin time of the +// target element. Note that the begin time may be resolved as a result of an +// earlier hyperlink, DOM or event activation. Once the begin time is resolved, +// hyperlink traversal always seeks." +// +function testSeekToFirst() { + // Seek forwards + gAnim.setAttribute('begin','2s'); + gSvg.setCurrentTime(0); + fireLink(forwardToInterval1); +} + +function forwardToInterval1() { + is(gSvg.getCurrentTime(), 2, + "Unexpected time after activating link to animation scheduled to start " + + "the future"); + + // Seek backwards + gSvg.setCurrentTime(3.5); + fireLink(backwardToInterval1); +} + +function backwardToInterval1() { + is(gSvg.getCurrentTime(), 2, + "Unexpected time after activating link to animation that ran in the past"); + + // What if the first begin instance is negative? + gAnim.setAttribute('begin','-0.5s'); + gSvg.setCurrentTime(1); + fireLink(backwardToZero); +} + +function backwardToZero() { + is(gSvg.getCurrentTime(), 0, + "Unexpected time after activating link to animation that ran in the " + + "past with a negative time"); + + continueTest(); +} + +// Traversing a hyperlink, condition 3: +// +// "Else (animation begin time is unresolved) just resolve the target animation +// begin time at current document time. Disregard the sync-base or event base of +// the animation, and do not "back-propagate" any timing logic to resolve the +// child, but rather treat it as though it were defined with begin="indefinite" +// and just resolve begin time to the current document time." +// +function testKickStart() { + gSvg.setCurrentTime(1); + fireLink(startedAt1s); +} + +function startedAt1s() { + is(gSvg.getCurrentTime(), 1, + "Unexpected time after kick-starting animation with indefinite start " + + "by hyperlink"); + is(gAnim.getStartTime(), 1, + "Unexpected start time for kick-started animation"); + + continueTest(); +} + +function testKickStartWithUnresolved() { + gAnim.setAttribute("begin", "circle.click"); + gSvg.setCurrentTime(3); + fireLink(startedAt3s); +} + +function startedAt3s() { + is(gSvg.getCurrentTime(), 3, + "Unexpected time after kick-starting animation with unresolved start " + + "by hyperlink"); + is(gAnim.getStartTime(), 3, + "Unexpected start time for kick-started animation with unresolved begin " + + "condition"); + + continueTest(); +} + +function testFiltering() { + gAnim.setAttribute('begin','-3s; 1s; 2s; 3s; 4s; 5s; 6s; 7s; 8s; 9s; 10s'); + gSvg.setCurrentTime(12); + fireLink(rewindToFirst); +} + +function rewindToFirst() { + is(gSvg.getCurrentTime(), 1, + "Unexpected time after triggering animation with a hyperlink after " + + "numerous intervals have passed"); + + continueTest(); +} + +function fireLink(callback) { + // First we need to reset the hash because otherwise the redundant hashchange + // events will be suppressed + if (window.location.hash === '') { + fireLinkPart2(callback); + } else { + window.location.hash = ''; + window.addEventListener("hashchange", + function clearHash() { + window.removeEventListener("hashchange", clearHash, false); + window.setTimeout(fireLinkPart2, 0, callback); + }, + false); + } +} + +function fireLinkPart2(callback) { + window.addEventListener("hashchange", + function triggerCallback() { + window.removeEventListener("hashchange", triggerCallback, false); + window.setTimeout(callback, 0); + }, + false); + window.location.hash = '#anim'; +} + +window.addEventListener("load", continueTest, false); +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/smil/test/test_smilInvalidValues.html b/dom/smil/test/test_smilInvalidValues.html new file mode 100644 index 000000000..9cd03f49b --- /dev/null +++ b/dom/smil/test/test_smilInvalidValues.html @@ -0,0 +1,113 @@ +<!doctype html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=941315 +--> +<head> + <meta charset="utf-8"> + <title>Test invalid values cause the model to be updated (bug 941315)</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=941315">Mozilla Bug 941315</a> +<p id="display"></p> +<div id="content" style="display: none"> +<svg width="100%" height="1" onload="this.pauseAnimations()"> + <rect> + <animate id="a" dur="100s"/> + <animate id="b" dur="5s" begin="a.end"/> + </rect> + <circle cx="-100" cy="20" r="15" fill="blue" id="circle"/> +</svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + var a = $('a'), + b = $('b'); + + // Animation doesn't start until onload + SimpleTest.waitForExplicitFinish(); + window.addEventListener("load", runTests, false); + + // Make testing getStartTime easier + SVGAnimationElement.prototype.safeGetStartTime = function() { + try { + return this.getStartTime(); + } catch(e) { + if (e.name == "InvalidStateError" && + e.code == DOMException.INVALID_STATE_ERR) { + return 'none'; + } else { + ok(false, "Unexpected exception: " + e); + return null; + } + } + }; + + function runTests() { + [testSimpleDuration, testMin, testMax, testRepeatDur, testRepeatCount] + .forEach(function(test) { + is(b.getStartTime(), 100, "initial state before running " + test.name); + test(); + is(b.getStartTime(), 100, "final state after running " + test.name); + }); + SimpleTest.finish(); + } + + function testSimpleDuration() { + // Verify a valid value updates as expected + a.setAttribute("dur", "50s"); + is(b.safeGetStartTime(), 50, "valid simple duration"); + + // Check an invalid value also causes the model to be updated + a.setAttribute("dur", "abc"); // -> indefinite + is(b.safeGetStartTime(), "none", "invalid simple duration"); + + // Restore state + a.setAttribute("dur", "100s"); + } + + function testMin() { + a.setAttribute("min", "200s"); + is(b.safeGetStartTime(), 200, "valid min duration"); + + a.setAttribute("min", "abc"); // -> indefinite + is(b.safeGetStartTime(), 100, "invalid min duration"); + + a.removeAttribute("min"); + } + + function testMax() { + a.setAttribute("max", "50s"); + is(b.safeGetStartTime(), 50, "valid max duration"); + + a.setAttribute("max", "abc"); // -> indefinite + is(b.safeGetStartTime(), 100, "invalid max duration"); + + a.removeAttribute("max"); + } + + function testRepeatDur() { + a.setAttribute("repeatDur", "200s"); + is(b.safeGetStartTime(), 200, "valid repeatDur duration"); + + a.setAttribute("repeatDur", "abc"); // -> indefinite + is(b.safeGetStartTime(), 100, "invalid repeatDur duration"); + + a.removeAttribute("repeatDur"); + } + + function testRepeatCount() { + a.setAttribute("repeatCount", "2"); + is(b.safeGetStartTime(), 200, "valid repeatCount duration"); + + a.setAttribute("repeatCount", "abc"); // -> indefinite + is(b.safeGetStartTime(), 100, "invalid repeatCount duration"); + + a.removeAttribute("repeatCount"); + } +</script> +</pre> +</body> +</html> diff --git a/dom/smil/test/test_smilKeySplines.xhtml b/dom/smil/test/test_smilKeySplines.xhtml new file mode 100644 index 000000000..a7ccb58c4 --- /dev/null +++ b/dom/smil/test/test_smilKeySplines.xhtml @@ -0,0 +1,296 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Test for SMIL keySplines</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content"> +<svg id="svg" xmlns="http://www.w3.org/2000/svg" width="120px" height="120px" + onload="this.pauseAnimations()"> + <circle cx="-100" cy="20" r="15" fill="blue" id="circle"/> +</svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ +/** Test for SMIL keySplines **/ + +/* Global Variables */ +const svgns="http://www.w3.org/2000/svg"; +var svg = document.getElementById("svg"); +var circle = document.getElementById('circle'); + +SimpleTest.waitForExplicitFinish(); + +function createAnim() { + var anim = document.createElementNS(svgns,'animate'); + anim.setAttribute('attributeName','cx'); + anim.setAttribute('dur','10s'); + anim.setAttribute('begin','0s'); + anim.setAttribute('fill', 'freeze'); + return circle.appendChild(anim); +} + +function main() { + ok(svg.animationsPaused(), "should be paused by <svg> load handler"); + is(svg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler"); + + var tests = + [ testSimpleA, // these first four are from SVG 1.1 + testSimpleB, + testSimpleC, + testSimpleD, + testSimpleE, // bug 501569 + testMultipleIntervalsA, + testMultipleIntervalsB, + testMultipleIntervalsC, + testOneValue, + testFromTo, + testWrongNumSplines, + testToAnimation, + testOkSyntax, + testBadSyntaxA, + testBadSyntaxB + ]; + for (var i = 0; i < tests.length; i++) { + var anim = createAnim(); + svg.setCurrentTime(0); + tests[i](anim); + anim.parentNode.removeChild(anim); + } + SimpleTest.finish(); +} + +function checkSample(time, expectedValue) { + svg.setCurrentTime(time); + is(circle.cx.animVal.value, expectedValue); +} + +function checkSampleRough(time, expectedValue, precision) { + const defaultPrecision = 0.00001; + if (typeof precision == "undefined") { + precision = defaultPrecision; + } + svg.setCurrentTime(time); + var diff = Math.abs(expectedValue - circle.cx.animVal.value); + ok(diff <= precision, + "Unexpected sample value got " + circle.cx.animVal.value + + ", expected " + expectedValue + " [error is " + diff + + ", tolerance is " + precision + "]"); +} + +/* + * These first four tests are the examples given in SVG 1.1, section 19.2.7 + */ + +function testSimpleA(anim) { + anim.setAttribute('dur','4s'); + anim.setAttribute('values', '10; 20'); + anim.setAttribute('keyTimes', '0; 1'); + anim.setAttribute('calcMode', 'spline'); + anim.setAttribute('keySplines', '0 0 1 1'); + checkSample(0, 10); + checkSample(1, 12.5); + checkSample(2, 15); + checkSample(3, 17.5); + checkSample(4, 20); +} + +function testSimpleB(anim) { + anim.setAttribute('dur','4s'); + anim.setAttribute('values', '10; 20'); + anim.setAttribute('keyTimes', '0; 1'); + anim.setAttribute('calcMode', 'spline'); + anim.setAttribute('keySplines', '.5 0 .5 1'); + checkSample(0, 10); + checkSampleRough(1, 11.058925); + checkSample(2, 15); + checkSampleRough(3, 18.941075); + checkSample(4, 20); +} + +function testSimpleC(anim) { + anim.setAttribute('dur','4s'); + anim.setAttribute('values', '10; 20'); + anim.setAttribute('keyTimes', '0; 1'); + anim.setAttribute('calcMode', 'spline'); + anim.setAttribute('keySplines', '0 .75 .25 1'); + checkSample(0, 10); + checkSampleRough(1, 18.101832); + checkSampleRough(2, 19.413430); + checkSampleRough(3, 19.886504); + checkSample(4, 20); +} + +function testSimpleD(anim) { + anim.setAttribute('dur','4s'); + anim.setAttribute('values', '10; 20'); + anim.setAttribute('keyTimes', '0; 1'); + anim.setAttribute('calcMode', 'spline'); + anim.setAttribute('keySplines', '1 0 .25 .25'); + checkSample(0, 10); + checkSampleRough(1, 10.076925); + checkSampleRough(2, 10.644369); + checkSampleRough(3, 16.908699); + checkSample(4, 20); +} + +// Bug 501569 -- nsSMILKeySpline(1, 0, 0, 1) miscalculates values just under 0.5 +function testSimpleE(anim) { + anim.setAttribute('dur','10s'); + anim.setAttribute('values', '0; 10'); + anim.setAttribute('keyTimes', '0; 1'); + anim.setAttribute('calcMode', 'spline'); + anim.setAttribute('keySplines', '1 0 0 1'); + checkSample(0, 0); + checkSampleRough(0.001, 0); + checkSampleRough(4.95, 3.409174); + checkSampleRough(4.98, 3.819443); + checkSampleRough(4.99, 4.060174); + checkSampleRough(4.999, 4.562510); + checkSample(5, 5); + checkSampleRough(5.001, 5.437490); + checkSampleRough(5.01, 5.939826); + checkSampleRough(5.015, 6.075002); + checkSampleRough(5.02, 6.180557); + checkSampleRough(9.9999, 10); + checkSample(10, 10); +} + +function testMultipleIntervalsA(anim) { + anim.setAttribute('dur','4s'); + anim.setAttribute('values', '10; 20; 30'); + anim.setAttribute('keyTimes', '0; 0.25; 1'); + anim.setAttribute('calcMode', 'spline'); + anim.setAttribute('keySplines', '0 0 1 1; .5 0 .5 1;'); + checkSample(0.5, 15); + checkSampleRough(0.999, 20, 0.02); + checkSample(1, 20); + checkSampleRough(1.001, 20, 0.05); + checkSample(2.5, 25); + checkSampleRough(3.25, 29, 0.1); +} + +function testMultipleIntervalsB(anim) { + // as above but without keyTimes + anim.setAttribute('dur','4s'); + anim.setAttribute('values', '10; 20; 30'); + anim.setAttribute('calcMode', 'spline'); + anim.setAttribute('keySplines', '0 0 1 1; .5 0 .5 1;'); + checkSample(1, 15); + checkSampleRough(1.999, 20, 0.01); + checkSample(2, 20); + checkSampleRough(2.001, 20, 0.01); + checkSample(3, 25); + checkSampleRough(3.5, 29, 0.1); +} + +function testMultipleIntervalsC(anim) { + // test some unusual (but valid) syntax + anim.setAttribute('dur','4s'); + anim.setAttribute('values', '10; 20; 30'); + anim.setAttribute('calcMode', 'spline'); + anim.setAttribute('keySplines', ' 0 .75 0.25 1 ; 1, 0 ,.25 .25 \t'); + checkSampleRough(3.5, 26.9, 0.2); +} + +function testOneValue(anim) { + anim.setAttribute('dur','4s'); + anim.setAttribute('values', '5'); + anim.setAttribute('calcMode', 'spline'); + anim.setAttribute('keySplines', '0 0 1 1'); + checkSample(0, 5); + checkSample(1.5, 5); +} + +function testFromTo(anim) { + anim.setAttribute('dur','4s'); + anim.setAttribute('from', '10'); + anim.setAttribute('to', '20'); + anim.setAttribute('calcMode', 'spline'); + anim.setAttribute('keySplines', '.5 0 .5 1'); + checkSample(0, 10); + checkSampleRough(1, 11, 0.1); +} + +function testWrongNumSplines(anim) { + anim.setAttribute('dur','4s'); + anim.setAttribute('from', '10'); + anim.setAttribute('to', '20'); + anim.setAttribute('calcMode', 'spline'); + anim.setAttribute('keySplines', '.5 0 .5 1; 0 0 1 1'); + // animation is in error, should do nothing + checkSample(1.5, -100); +} + +function testToAnimation(anim) { + anim.setAttribute('dur','4s'); + anim.setAttribute('to', '20'); + anim.setAttribute('calcMode', 'spline'); + anim.setAttribute('keySplines', '.5 0 .5 1'); + checkSample(0, -100); + checkSampleRough(1, -87.3, 0.1); +} + +function testOkSyntax(anim) { + var okStrs = ['0,0,0,0', // all commas + ' 0 0 , 0 ,0 ', // mix of separators + '0 0 0 0;', // trailing semi-colon + '0 0 0 0 ;']; // " " + + for (var i = 0; i < okStrs.length; i++) { + testAnim = createAnim(); + testAnim.setAttribute('dur','4s'); + testAnim.setAttribute('from', '0'); + testAnim.setAttribute('to', '20'); + testAnim.setAttribute('calcMode', 'spline'); + testAnim.setAttribute('keySplines', okStrs[i]); + checkSample(4, 20); + testAnim.parentNode.removeChild(testAnim); + } +} + +function testBadSyntaxA(anim) { + var badStrs = ['', // empty + ' ', // whitespace only + '0,1.1,0,0', // bad range + '0,0,0,-0.1', // " " + ' 0 0 , 0 0 ,', // stray comma + '1-1 0 0', // path-style separators + '0 0 0', // wrong number of values + '0 0 0 0 0', // " " + '0 0 0 0 0 0 0 0', // " " + '0 0 0; 0 0 0 0', // " " + '0 0 0; 0', // mis-placed semi-colon + ';0 0 0 0']; // " " + + for (var i = 0; i < badStrs.length; i++) { + testAnim = createAnim(); + testAnim.setAttribute('dur','4s'); + testAnim.setAttribute('from', '0'); + testAnim.setAttribute('to', '20'); + testAnim.setAttribute('calcMode', 'spline'); + testAnim.setAttribute('keySplines', badStrs[i]); + checkSample(4, -100); + testAnim.parentNode.removeChild(testAnim); + } +} + +function testBadSyntaxB(anim) { + // test some illegal syntax + anim.setAttribute('dur','4s'); + anim.setAttribute('values', '10; 20; 30'); + anim.setAttribute('calcMode', 'spline'); + anim.setAttribute('keySplines', ' 0 .75 0.25 1 ; 1, A0 ,.25 .25 \t'); + // animation is in error, should do nothing + checkSample(3.5, -100); +} + +window.addEventListener("load", main, false); +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/smil/test/test_smilKeyTimes.xhtml b/dom/smil/test/test_smilKeyTimes.xhtml new file mode 100644 index 000000000..85266ed19 --- /dev/null +++ b/dom/smil/test/test_smilKeyTimes.xhtml @@ -0,0 +1,391 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Test for SMIL keyTimes</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=557885">Mozilla Bug + 557885</a> +<p id="display"></p> +<div id="content"> +<svg id="svg" xmlns="http://www.w3.org/2000/svg" width="120px" height="120px"> + <circle cx="-100" cy="20" r="15" fill="blue" id="circle"/> +</svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ +/** Test for SMIL keyTimes **/ + +var gSvg = document.getElementById("svg"); +SimpleTest.waitForExplicitFinish(); + +function main() +{ + gSvg.pauseAnimations(); + + var testCases = Array(); + + // Simple case + testCases.push({ + 'attr' : { 'values': '0; 50; 100', + 'keyTimes': '0; .8; 1' }, + 'times': [ [ 4, 25 ], + [ 8, 50 ], + [ 9, 75 ], + [ 10, 100 ] ] + }); + + // Parsing tests + testCases.push(parseOk(' 0 ; .8;1 ')); // extra whitespace + testCases.push(parseNotOk(';0; .8; 1')); // leading semi-colon + testCases.push(parseNotOk('; .8; 1')); // leading semi-colon + testCases.push(parseOk('0; .8; 1;')); // trailing semi-colon + testCases.push(parseNotOk('')); // empty string + testCases.push(parseNotOk(' ')); // empty string + testCases.push(parseNotOk('0; .8')); // too few values + testCases.push(parseNotOk('0; .8; .9; 1')); // too many values + testCases.push(parseNotOk('0; 1; .8')); // non-increasing + testCases.push(parseNotOk('0; .8; .9')); // final value non-1 with + // calcMode=linear + testCases.push(parseOk('0; .8; .9', { 'calcMode': 'discrete' })); + testCases.push(parseNotOk('0.01; .8; 1')); // first value not 0 + testCases.push(parseNotOk('0.01; .8; 1', { 'calcMode': 'discrete' })); + // first value not 0 + testCases.push(parseNotOk('0; .8; 1.1')); // out of range + testCases.push(parseNotOk('-0.1; .8; 1')); // out of range + + + // 2 values + testCases.push({ + 'attr' : { 'values': '0; 50', + 'keyTimes': '0; 1' }, + 'times': [ [ 6, 30 ] ] + }); + + // 1 value + testCases.push({ + 'attr' : { 'values': '50', + 'keyTimes': ' 0' }, + 'times': [ [ 7, 50 ] ] + }); + + // 1 bad value + testCases.push({ + 'attr' : { 'values': '50', + 'keyTimes': '0.1' }, + 'times': [ [ 0, -100 ] ] + }); + + // 1 value, calcMode=discrete + testCases.push({ + 'attr' : { 'values': '50', + 'calcMode': 'discrete', + 'keyTimes': ' 0' }, + 'times': [ [ 7, 50 ] ] + }); + + // 1 bad value, calcMode=discrete + testCases.push({ + 'attr' : { 'values': '50', + 'calcMode': 'discrete', + 'keyTimes': '0.1' }, + 'times': [ [ 0, -100 ] ] + }); + + // from-to + testCases.push({ + 'attr' : { 'from': '10', + 'to': '20', + 'keyTimes': '0.0; 1.0' }, + 'times': [ [ 3.5, 13.5 ] ] + }); + + // from-to calcMode=discrete + testCases.push({ + 'attr' : { 'from': '10', + 'to': '20', + 'calcMode': 'discrete', + 'keyTimes': '0.0; 0.7' }, + 'times': [ [ 0, 10 ], + [ 6.9, 10 ], + [ 7.0, 20 ], + [ 10.0, 20 ], + [ 11.0, 20 ] ] + }); + + // from-to calcMode=discrete one keyTime only + testCases.push({ + 'attr' : { 'values': '20', + 'calcMode': 'discrete', + 'keyTimes': '0' }, + 'times': [ [ 0, 20 ], + [ 6.9, 20 ], + [ 7.0, 20 ], + [ 10.0, 20 ], + [ 11.0, 20 ] ] + }); + + // from-to calcMode=discrete one keyTime, mismatches no. values + testCases.push({ + 'attr' : { 'values': '10; 20', + 'calcMode': 'discrete', + 'keyTimes': '0' }, + 'times': [ [ 0, -100 ] ] + }); + + // to + testCases.push({ + 'attr' : { 'to': '100', + 'keyTimes': '0.0; 1.0' }, + 'times': [ [ 0, -100 ], + [ 7, 40 ] ] + }); + + // to -- bad number of keyTimes (too many) + testCases.push({ + 'attr' : { 'to': '100', + 'keyTimes': '0.0; 0.5; 1.0' }, + 'times': [ [ 2, -100 ] ] + }); + + // unfrozen to calcMode=discrete two keyTimes + testCases.push({ + 'attr' : { 'to': '100', + 'calcMode': 'discrete', + 'keyTimes': '0.0; 1.0', + 'fill': 'remove' }, + 'times': [ [ 0, -100 ], + [ 7, -100 ], + [ 10, -100 ], + [ 12, -100 ]] + }); + + // frozen to calcMode=discrete two keyTimes + testCases.push({ + 'attr' : { 'to': '100', + 'calcMode': 'discrete', + 'keyTimes': '0.0; 1.0' }, + 'times': [ [ 0, -100 ], + [ 7, -100 ], + [ 10, 100 ], + [ 12, 100 ] ] + }); + + // to calcMode=discrete -- bad number of keyTimes (one, expecting two) + testCases.push({ + 'attr' : { 'to': '100', + 'calcMode': 'discrete', + 'keyTimes': '0' }, + 'times': [ [ 0, -100 ], + [ 7, -100 ] ] + }); + + // values calcMode=discrete + testCases.push({ + 'attr' : { 'values': '0; 10; 20; 30', + 'calcMode': 'discrete', + 'keyTimes': '0;.2;.4;.6' }, + 'times': [ [ 0, 0 ], + [ 1.9, 0 ], + [ 2, 10 ], + [ 3.9, 10 ], + [ 4.0, 20 ], + [ 5.9, 20 ], + [ 6.0, 30 ], + [ 9.9, 30 ], + [ 10.0, 30 ] ] + }); + + // The following two accumulate tests are from SMIL 3.0 + // (Note that this behaviour differs from that defined for SVG Tiny 1.2 which + // specifically excludes the last value: "Note that in the case of discrete + // animation, the frozen value that is used is the value of the animation just + // before the end of the active duration.") + // accumulate=none + testCases.push({ + 'attr' : { 'values': '0; 10; 20', + 'calcMode': 'discrete', + 'keyTimes': '0;.5;1', + 'fill': 'freeze', + 'repeatCount': '2', + 'accumulate': 'none' }, + 'times': [ [ 0, 0 ], + [ 5, 10 ], + [ 10, 0 ], + [ 15, 10 ], + [ 20, 20 ], + [ 25, 20 ] ] + }); + + // accumulate=sum + testCases.push({ + 'attr' : { 'values': '0; 10; 20', + 'calcMode': 'discrete', + 'keyTimes': '0;.5;1', + 'fill': 'freeze', + 'repeatCount': '2', + 'accumulate': 'sum' }, + 'times': [ [ 0, 0 ], + [ 5, 10 ], + [ 10, 20 ], + [ 15, 30 ], + [ 20, 40 ], + [ 25, 40 ] ] + }); + + // If the interpolation mode is paced, the keyTimes attribute is ignored. + testCases.push({ + 'attr' : { 'values': '0; 10; 20', + 'calcMode': 'paced', + 'keyTimes': '0;.2;1' }, + 'times': [ [ 0, 0 ], + [ 2, 4 ], + [ 5, 10 ] ] + }); + + // SMIL 3 has: + // If the simple duration is indefinite and the interpolation mode is + // linear or spline, any keyTimes specification will be ignored. + // However, since keyTimes represent "a proportional offset into the simple + // duration of the animation element" surely discrete animation too cannot use + // keyTimes when the simple duration is indefinite. Hence SVGT 1.2 is surely + // more correct when it has: + // If the simple duration is indefinite, any 'keyTimes' specification will + // be ignored. + // (linear) + testCases.push({ + 'attr' : { 'values': '0; 10; 20', + 'dur': 'indefinite', + 'keyTimes': '0;.2;1' }, + 'times': [ [ 0, 0 ], + [ 5, 0 ] ] + }); + // (spline) + testCases.push({ + 'attr' : { 'values': '0; 10; 20', + 'dur': 'indefinite', + 'calcMode': 'spline', + 'keyTimes': '0;.2;1', + 'keySplines': '0 0 1 1; 0 0 1 1' }, + 'times': [ [ 0, 0 ], + [ 5, 0 ] ] + }); + // (discrete) + testCases.push({ + 'attr' : { 'values': '0; 10; 20', + 'dur': 'indefinite', + 'calcMode': 'discrete', + 'keyTimes': '0;.2;1' }, + 'times': [ [ 0, 0 ], + [ 5, 0 ] ] + }); + + for (var i = 0; i < testCases.length; i++) { + gSvg.setCurrentTime(0); + var test = testCases[i]; + + // Create animation elements + var anim = createAnim(test.attr); + + // Run samples + for (var j = 0; j < test.times.length; j++) { + var times = test.times[j]; + gSvg.setCurrentTime(times[0]); + checkSample(anim, times[1], times[0], i); + } + + anim.parentNode.removeChild(anim); + } + + // fallback to discrete for non-additive animation + var attr = { 'values': 'butt; round; square', + 'attributeName': 'stroke-linecap', + 'calcMode': 'linear', + 'keyTimes': '0;.2;1', + 'fill': 'remove' }; + var anim = createAnim(attr); + var samples = [ [ 0, 'butt' ], + [ 1.9, 'butt' ], + [ 2.0, 'round' ], + [ 9.9, 'round' ], + [ 10, 'butt' ] // fill=remove so we'll never set it to square + ]; + for (var i = 0; i < samples.length; i++) { + var sample = samples[i]; + gSvg.setCurrentTime(sample[0]); + checkLineCapSample(anim, sample[1], sample[0], + "[non-interpolatable fallback]"); + } + anim.parentNode.removeChild(anim); + + SimpleTest.finish(); +} + +function parseOk(str, extra) +{ + var attr = { 'values': '0; 50; 100', + 'keyTimes': str }; + if (typeof(extra) == "object") { + for (name in extra) { + attr[name] = extra[name]; + } + } + return { + 'attr' : attr, + 'times': [ [ 0, 0 ] ] + }; +} + +function parseNotOk(str, extra) +{ + var result = parseOk(str, extra); + result.times = [ [ 0, -100 ] ]; + return result; +} + +function createAnim(attr) +{ + const svgns = "http://www.w3.org/2000/svg"; + var anim = document.createElementNS(svgns, 'animate'); + anim.setAttribute('attributeName','cx'); + anim.setAttribute('dur','10s'); + anim.setAttribute('begin','0s'); + anim.setAttribute('fill','freeze'); + for (name in attr) { + anim.setAttribute(name, attr[name]); + } + return document.getElementById('circle').appendChild(anim); +} + +function checkSample(anim, expectedValue, sampleTime, caseNum) +{ + var msg = "Test case " + caseNum + + " (keyTimes: '" + anim.getAttribute('keyTimes') + "'" + + " calcMode: " + anim.getAttribute('calcMode') + "), " + + "t=" + sampleTime + + ": Unexpected sample value:"; + is(anim.targetElement.cx.animVal.value, expectedValue, msg); +} + +function checkLineCapSample(anim, expectedValue, sampleTime, caseDescr) +{ + var msg = "Test case " + caseDescr + + " (keyTimes: '" + anim.getAttribute('keyTimes') + "'" + + " calcMode: " + anim.getAttribute('calcMode') + "), " + + "t=" + sampleTime + + ": Unexpected sample value:"; + var actualValue = + window.getComputedStyle(anim.targetElement, null). + getPropertyValue('stroke-linecap'); + is(actualValue, expectedValue, msg); +} + +window.addEventListener("load", main, false); +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/smil/test/test_smilKeyTimesPacedMode.xhtml b/dom/smil/test/test_smilKeyTimesPacedMode.xhtml new file mode 100644 index 000000000..eff537cfd --- /dev/null +++ b/dom/smil/test/test_smilKeyTimesPacedMode.xhtml @@ -0,0 +1,123 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Tests updated intervals</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=555026">Mozilla Bug 555026</a> +<p id="display"></p> +<div id="content"> +<svg id="svg" xmlns="http://www.w3.org/2000/svg" width="120px" height="120px" + onload="this.pauseAnimations()"> + <circle r="10" id="circle"/> +</svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ +/** Test that we ignore keyTimes attr when calcMode="paced" **/ + +/* Global Variables */ +const SVGNS = "http://www.w3.org/2000/svg"; +const ANIM_DUR = "2s"; +const HALF_TIME = "1"; +const ATTR_NAME = "cx" +const KEYTIMES_TO_TEST = [ + // potentially-valid values (depending on number of values in animation) + "0; 0.2; 1", + "0; 0.5", + "0; 1", + // invalid values: + "", "abc", "-0.5", "0; 0.5; 1.01", "5" +]; +const gSvg = document.getElementById("svg"); +const gCircle = document.getElementById("circle"); + +SimpleTest.waitForExplicitFinish(); + + +// MAIN FUNCTIONS +function main() { + ok(gSvg.animationsPaused(), "should be paused by <svg> load handler"); + is(gSvg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler"); + + testByAnimation(); + testToAnimation(); + testValuesAnimation(); + SimpleTest.finish(); +} + +function testByAnimation() { + for (var i = 0; i < KEYTIMES_TO_TEST.length; i++) { + setupTest(); + var anim = createAnim(); + anim.setAttribute("by", "200"); + var curKeyTimes = KEYTIMES_TO_TEST[i]; + anim.setAttribute("keyTimes", curKeyTimes); + + gSvg.setCurrentTime(HALF_TIME); + is(gCircle.cx.animVal.value, 100, + "Checking animVal with 'by' and keyTimes='" + curKeyTimes + "'"); + + anim.parentNode.removeChild(anim); // clean up + } +} + +function testToAnimation() { + for (var i = 0; i < KEYTIMES_TO_TEST.length; i++) { + setupTest(); + var anim = createAnim(); + anim.setAttribute("to", "200"); + var curKeyTimes = KEYTIMES_TO_TEST[i]; + anim.setAttribute("keyTimes", curKeyTimes); + + gSvg.setCurrentTime(HALF_TIME); + is(gCircle.cx.animVal.value, 100, + "Checking animVal with 'to' and keyTimes='" + curKeyTimes + "'"); + + anim.parentNode.removeChild(anim); // clean up + } +} + +function testValuesAnimation() { + for (var i = 0; i < KEYTIMES_TO_TEST.length; i++) { + setupTest(); + var anim = createAnim(); + anim.setAttribute("values", "100; 110; 200"); + var curKeyTimes = KEYTIMES_TO_TEST[i]; + anim.setAttribute("keyTimes", curKeyTimes); + + gSvg.setCurrentTime(HALF_TIME); + is(gCircle.cx.animVal.value, 150, + "Checking animVal with 'values' and keyTimes='" + curKeyTimes + "'"); + + anim.parentNode.removeChild(anim); // clean up + } +} + +// HELPER FUNCTIONS +// Common setup code for each test function: seek to 0, and make sure +// the previous test cleaned up its animations. +function setupTest() { + gSvg.setCurrentTime(0); + if (gCircle.firstChild) { + ok(false, "Previous test didn't clean up after itself."); + } +} + +function createAnim() { + var anim = document.createElementNS(SVGNS,"animate"); + anim.setAttribute("attributeName", ATTR_NAME); + anim.setAttribute("dur", ANIM_DUR); + anim.setAttribute("begin", "0s"); + anim.setAttribute("calcMode", "paced"); + return gCircle.appendChild(anim); +} + +window.addEventListener("load", main, false); +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/smil/test/test_smilMappedAttrFromBy.xhtml b/dom/smil/test/test_smilMappedAttrFromBy.xhtml new file mode 100644 index 000000000..48fea5256 --- /dev/null +++ b/dom/smil/test/test_smilMappedAttrFromBy.xhtml @@ -0,0 +1,51 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Test for Animation Behavior on CSS Properties</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="smilTestUtils.js"></script> + <script type="text/javascript" src="db_smilMappedAttrList.js"></script> + <script type="text/javascript" src="db_smilCSSPropertyList.js"></script> + <script type="text/javascript" src="db_smilCSSFromBy.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content"> +<svg xmlns="http://www.w3.org/2000/svg" + width="200px" height="200px" font-size="50px" style="color: rgb(50,50,50)" + onload="this.pauseAnimations()"> + <rect x="20" y="20" width="200" height="200"/> + <!-- NOTE: hard-wiring 'line-height' so that computed value of 'font' is + more predictable. (otherwise, line-height varies depending on platform) + --> + <text x="20" y="20" style="line-height: 10px !important">testing 123</text> + <line/> + <marker/> + <filter><feDiffuseLighting/></filter> +</svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function main() +{ + // Start out with document paused + var svg = SMILUtil.getSVGRoot(); + ok(svg.animationsPaused(), "should be paused by <svg> load handler"); + is(svg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler"); + + var testBundles = convertCSSBundlesToMappedAttr(gFromByBundles); + testBundleList(testBundles, new SMILTimingData(1.0, 1.0)); + + SimpleTest.finish(); +} + +window.addEventListener("load", main, false); +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/smil/test/test_smilMappedAttrFromTo.xhtml b/dom/smil/test/test_smilMappedAttrFromTo.xhtml new file mode 100644 index 000000000..86e647e29 --- /dev/null +++ b/dom/smil/test/test_smilMappedAttrFromTo.xhtml @@ -0,0 +1,79 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Test for Animation Behavior on CSS Properties</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="smilTestUtils.js"></script> + <script type="text/javascript" src="db_smilMappedAttrList.js"></script> + <script type="text/javascript" src="db_smilCSSPropertyList.js"></script> + <script type="text/javascript" src="db_smilCSSFromTo.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content"> +<svg xmlns="http://www.w3.org/2000/svg" + width="200px" height="200px" font-size="50px" style="color: rgb(50,50,50)" + onload="this.pauseAnimations()"> + <rect x="20" y="20" width="200" height="200"/> + <!-- NOTE: hard-wiring 'line-height' so that computed value of 'font' is + more predictable. (otherwise, line-height varies depending on platform) + --> + <text x="20" y="20">testing 123</text> + <line/> + <image/> + <marker/> + <clipPath><circle/></clipPath> + <filter><feFlood/></filter> + <filter><feDiffuseLighting/></filter> + <linearGradient><stop/></linearGradient> +</svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function checkForUntestedAttributes(bundleList) +{ + // Create the set of all the attributes we know about + var attributeSet = {}; + for (attributeLabel in gMappedAttrList) { + // insert attribute + attributeSet[gMappedAttrList[attributeLabel].attrName] = null; + } + // Remove tested properties from the set + for (var bundleIdx in bundleList) { + var bundle = bundleList[bundleIdx]; + delete attributeSet[bundle.animatedAttribute.attrName]; + } + // Warn about remaining (untested) properties + for (var untestedProp in attributeSet) { + ok(false, "No tests for attribute '" + untestedProp + "'"); + } +} + +function main() +{ + // Start out with document paused + var svg = SMILUtil.getSVGRoot(); + ok(svg.animationsPaused(), "should be paused by <svg> load handler"); + is(svg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler"); + + var testBundles = convertCSSBundlesToMappedAttr(gFromToBundles); + + // FIRST: Warn about any attributes that are missing tests + checkForUntestedAttributes(testBundles); + + // Run the actual tests + testBundleList(testBundles, new SMILTimingData(1.0, 1.0)); + + SimpleTest.finish(); +} + +window.addEventListener("load", main, false); +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/smil/test/test_smilMappedAttrPaced.xhtml b/dom/smil/test/test_smilMappedAttrPaced.xhtml new file mode 100644 index 000000000..c56b3aeb7 --- /dev/null +++ b/dom/smil/test/test_smilMappedAttrPaced.xhtml @@ -0,0 +1,46 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Test for Animation Behavior on CSS Properties</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="smilTestUtils.js"></script> + <script type="text/javascript" src="db_smilMappedAttrList.js"></script> + <script type="text/javascript" src="db_smilCSSPropertyList.js"></script> + <script type="text/javascript" src="db_smilCSSPaced.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content"> +<svg xmlns="http://www.w3.org/2000/svg" + width="200px" height="200px" font-size="50px" style="color: rgb(50,50,50)" + onload="this.pauseAnimations()"> + <rect x="20" y="20" width="200" height="200"/> + <text x="20" y="20">testing 123</text> + <marker/> +</svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function main() +{ + // Start out with document paused + var svg = SMILUtil.getSVGRoot(); + ok(svg.animationsPaused(), "should be paused by <svg> load handler"); + is(svg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler"); + + var testBundles = convertCSSBundlesToMappedAttr(gPacedBundles); + testBundleList(testBundles, new SMILTimingData(1.0, 6.0)); + + SimpleTest.finish(); +} + +window.addEventListener("load", main, false); +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/smil/test/test_smilMinTiming.html b/dom/smil/test/test_smilMinTiming.html new file mode 100644 index 000000000..1a82f3c96 --- /dev/null +++ b/dom/smil/test/test_smilMinTiming.html @@ -0,0 +1,93 @@ +<!doctype html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=948245 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 948245</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=948245">Mozilla Bug 948245</a> +<p id="display"></p> +<div id="content"> +<svg id="svg" onload="this.pauseAnimations()"> + <rect fill="red" id="rect" x="0"> + <animate attributeName="x" to="100" id="animation" dur="100s" min="200s"/> + </rect> +</svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + // The 'min' attribute introduces a kind of additional state into the SMIL + // model. If the 'min' attribute extends the active duration, the additional + // time between the amount of time the animation normally runs for (called the + // 'repeat duration') and the extended active duration is filled using the + // fill mode. + // + // Below we refer to this period of time between the end of the repeat + // duration and the end of the active duration as the 'extended period'. + // + // This test verifies that as we jump in and out of these states we produce + // the correct values. + // + // The test animation above produces an active interval that is longer than + // the 'repeating duration' of the animation. + var rect = $('rect'), + animation = $('animation'); + + // Animation doesn't start until onload + SimpleTest.waitForExplicitFinish(); + window.addEventListener("load", runTests, false); + + function runTests() { + ok($('svg').animationsPaused(), "should be paused by <svg> load handler"); + + // In the extended period (t=150s) we should not be animating or filling + // since the default fill mode is "none". + animation.ownerSVGElement.setCurrentTime(150); + is(rect.x.animVal.value, 0, + "Shouldn't fill in extended period with fill='none'"); + + // If we set the fill mode we should start filling. + animation.setAttribute("fill", "freeze"); + is(rect.x.animVal.value, 100, + "Should fill in extended period with fill='freeze'"); + + // If we unset the fill attribute we should stop filling. + animation.removeAttribute("fill"); + is(rect.x.animVal.value, 0, "Shouldn't fill after unsetting fill"); + + // If we jump back into the repeated interval (at t=50s) we should be + // animating. + animation.ownerSVGElement.setCurrentTime(50); + is(rect.x.animVal.value, 50, "Should be active in repeating interval"); + + // If we jump to the boundary at the start of the extended period we should + // not be filling (since we removed the fill attribute above). + animation.ownerSVGElement.setCurrentTime(100); + is(rect.x.animVal.value, 0, + "Shouldn't fill after seeking to boundary of extended period"); + + // If we apply a fill mode at this boundary point we should do regular fill + // behavior of using the last value in the interpolation range. + animation.setAttribute("fill", "freeze"); + is(rect.x.animVal.value, 100, + "Should fill at boundary to extended period"); + + // Check that if we seek past the interval we fill with the value at the end + // of the _repeat_duration_ not the value at the end of the + // _active_duration_. + animation.setAttribute("repeatCount", "1.5"); + animation.ownerSVGElement.setCurrentTime(225); + is(rect.x.animVal.value, 50, + "Should fill with the end of the repeat duration value"); + + SimpleTest.finish(); + } +</script> +</pre> +</body> +</html> diff --git a/dom/smil/test/test_smilRepeatDuration.html b/dom/smil/test/test_smilRepeatDuration.html new file mode 100644 index 000000000..3690a9566 --- /dev/null +++ b/dom/smil/test/test_smilRepeatDuration.html @@ -0,0 +1,139 @@ +<!doctype html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=948245 +--> +<head> + <meta charset="utf-8"> + <title>Test for repeat duration calculation (Bug 948245)</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" +href="https://bugzilla.mozilla.org/show_bug.cgi?id=948245">Mozilla Bug 948245</a> +<p id="display"></p> +<div id="content" style="display: none"> +<svg id="svg" onload="this.pauseAnimations()"> + <rect> + <animate id="a"/> + <animate id="b" begin="a.end"/> + </rect> +</svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + // Tests the calculation of the repeat duration which is one of the steps + // towards determining the active duration. + // + // The repeat duration is determined by the following three attributes: + // + // dur: may be definite (e.g. '2s') or 'indefinite' (the default) + // repeatCount: may be definite (e.g. '2.5'), 'indefinite', or not set + // repeatDur: may be definite (e.g. '5s'), 'indefinite', or not set + // + // That leaves 18 combinations to test. + var testCases = + [ + // 1. repeatDur: definite, repeatCount: definite, dur: definite + // (Two test cases here to ensure we get the minimum) + { repeatDur: 15, repeatCount: 2, dur: 10, result: 15 }, + { repeatDur: 25, repeatCount: 2, dur: 10, result: 20 }, + // 2. repeatDur: indefinite, repeatCount: definite, dur: definite + { repeatDur: 'indefinite', repeatCount: 2, dur: 10, result: 20 }, + // 3. repeatDur: not set, repeatCount: definite, dur: definite + { repeatCount: 2, dur: 10, result: 20 }, + // 4. repeatDur: definite, repeatCount: indefinite, dur: definite + { repeatDur: 15, repeatCount: 'indefinite', dur: 10, result: 15 }, + // 5. repeatDur: indefinite, repeatCount: indefinite, dur: definite + { repeatDur: 'indefinite', repeatCount: 'indefinite', dur: 10, + result: 'indefinite' }, + // 6. repeatDur: not set, repeatCount: indefinite, dur: definite + { repeatCount: 'indefinite', dur: 10, result: 'indefinite' }, + // 7. repeatDur: definite, repeatCount: not set, dur: definite + { repeatDur: 15, dur: 10, result: 15 }, + // 8. repeatDur: indefinite, repeatCount: not set, dur: definite + { repeatDur: 'indefinite', dur: 10, result: 'indefinite' }, + // 9. repeatDur: not set, repeatCount: not set, dur: definite + { dur: 10, result: 10 }, + // 10. repeatDur: definite, repeatCount: definite, dur: indefinite + { repeatDur: 15, repeatCount: 2, dur: 'indefinite', result: 15 }, + // 11. repeatDur: indefinite, repeatCount: definite, dur: indefinite + { repeatDur: 'indefinite', repeatCount: 2, dur: 'indefinite', + result: 'indefinite' }, + // 12. repeatDur: not set, repeatCount: definite, dur: indefinite + { repeatCount: 2, dur: 'indefinite', result: 'indefinite' }, + // 13. repeatDur: definite, repeatCount: indefinite, dur: indefinite + { repeatDur: 15, repeatCount: 'indefinite', dur: 'indefinite', + result: 15 }, + // 14. repeatDur: indefinite, repeatCount: indefinite, dur: indefinite + { repeatDur: 'indefinite', repeatCount: 'indefinite', dur: 'indefinite', + result: 'indefinite' }, + // 15. repeatDur: not set, repeatCount: indefinite, dur: indefinite + { repeatCount: 'indefinite', dur: 'indefinite', result: 'indefinite' }, + // 16. repeatDur: definite, repeatCount: not set, dur: indefinite + { repeatDur: 15, dur: 'indefinite', result: 15 }, + // 17. repeatDur: indefinite, repeatCount: not set, dur: indefinite + { repeatDur: 'indefinite', dur: 'indefinite', result: 'indefinite' }, + // 18. repeatDur: not set, repeatCount: not set, dur: indefinite + { dur: 'indefinite', result: 'indefinite' } + ]; + + // We can test the repeat duration by setting these attributes on animation + // 'a' and checking the start time of 'b' which is defined to start when 'a' + // finishes. + // + // Since 'a' has no end/min/max attributes the end of its active interval + // should coincide with the end of its repeat duration. + // + // Sometimes the repeat duration is defined to be 'indefinite'. In this case + // calling getStartTime on b will throw an exception so we need to catch that + // exception and translate it to 'indefinite' as follows: + function getRepeatDuration() { + try { + return $('b').getStartTime(); + } catch(e) { + if (e.name == "InvalidStateError" && + e.code == DOMException.INVALID_STATE_ERR) { + return 'indefinite'; + } else { + ok(false, "Unexpected exception: " + e); + return null; + } + } + } + + // Animation doesn't start until onload + SimpleTest.waitForExplicitFinish(); + window.addEventListener("load", runTests, false); + + // Run through each of the test cases + function runTests() { + ok($('svg').animationsPaused(), "should be paused by <svg> load handler"); + + testCases.forEach(function(test) { + var a = $('a'); + + // Set the attributes + var msgPieces = []; + [ 'repeatDur', 'repeatCount', 'dur' ].forEach(function(attr) { + if (typeof test[attr] != "undefined") { + a.setAttribute(attr, test[attr].toString()); + msgPieces.push(attr + ': ' + test[attr].toString()); + } else { + a.removeAttribute(attr); + msgPieces.push(attr + ': <not set>'); + } + }); + var msg = msgPieces.join(', '); + + // Check the result + is(getRepeatDuration(), test.result, msg); + }); + + SimpleTest.finish(); + } +</script> +</pre> +</body> +</html> diff --git a/dom/smil/test/test_smilRepeatTiming.xhtml b/dom/smil/test/test_smilRepeatTiming.xhtml new file mode 100644 index 000000000..74a9c17af --- /dev/null +++ b/dom/smil/test/test_smilRepeatTiming.xhtml @@ -0,0 +1,96 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=485157 +--> +<head> + <title>Test repeat timing</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=485157">Mozilla Bug + 485157</a> +<p id="display"></p> +<div id="content" style="display: none"> +<svg id="svg" xmlns="http://www.w3.org/2000/svg" width="100px" height="100px"> + <rect width="100" height="100" fill="green"> + <set attributeName="width" to="100" dur="20s" repeatCount="5" begin="0s" + id="a" onrepeat="startWaiting(evt)"/> + <set attributeName="fill" attributeType="CSS" to="green" + begin="a.repeat(1)" onbegin="expectedBegin()" dur="20s"/> + <set attributeName="x" to="100" + begin="a.repeat(2)" onbegin="unexpectedBegin(this)" dur="20s"/> + <set attributeName="y" to="100" + begin="a.repeat(0)" onbegin="unexpectedBegin(this)" dur="20s"/> + <set attributeName="width" to="100" + begin="a.repeat(-1)" onbegin="unexpectedBegin(this)" dur="20s"/> + <set attributeName="height" to="100" + begin="a.repeat(a)" onbegin="unexpectedBegin(this)" dur="20s"/> + </rect> +</svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ +/** Test SMIL repeat timing **/ + +/* Global Variables */ +const gTimeoutDur = 5000; // Time until we give up waiting for events in ms +var gSvg = document.getElementById('svg'); +var gRect = document.getElementById('circle'); +var gTimeoutID; +var gGotBegin = false; + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestFlakyTimeout("untriaged"); + +function testBegin() +{ + gSvg.setCurrentTime(19.999); +} + +function startWaiting(evt) +{ + is(evt.detail, 1, "Unexpected repeat event received: test broken"); + if (gGotBegin) + return; + + gTimeoutID = setTimeout(timeoutFail, gTimeoutDur); +} + +function timeoutFail() +{ + ok(false, "Timed out waiting for begin event"); + finish(); +} + +function expectedBegin() +{ + is(gGotBegin, false, + "Got begin event more than once for non-repeating animation"); + gGotBegin = true; + clearTimeout(gTimeoutID); + // Wait a moment before finishing in case there are erroneous events waiting + // to be processed. + setTimeout(finish, 10); +} + +function unexpectedBegin(elem) +{ + ok(false, "Got unexpected begin from animation with spec: " + + elem.getAttribute('begin')); +} + +function finish() +{ + gSvg.pauseAnimations(); + SimpleTest.finish(); +} + +window.addEventListener("load", testBegin, false); +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/smil/test/test_smilReset.xhtml b/dom/smil/test/test_smilReset.xhtml new file mode 100644 index 000000000..272d5cc0a --- /dev/null +++ b/dom/smil/test/test_smilReset.xhtml @@ -0,0 +1,82 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Tests for SMIL Reset Behavior </title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> +<svg id="svg" xmlns="http://www.w3.org/2000/svg" width="120px" height="120px" + onload="this.pauseAnimations()"> + <circle cx="20" cy="20" r="15" fill="blue"> + <animate attributeName="cx" from="20" to="100" begin="2s" dur="4s" + id="anim1" attributeType="XML"/> + </circle> +</svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ +/** Tests for SMIL Reset Behavior **/ + +SimpleTest.waitForExplicitFinish(); + +function main() { + var svg = document.getElementById("svg"); + ok(svg.animationsPaused(), "should be paused by <svg> load handler"); + is(svg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler"); + + var anim = document.getElementById("anim1"); + is(anim.getStartTime(), 2, "Unexpected initial start time"); + + svg.setCurrentTime(1); + anim.beginElementAt(2); + + // We now have two instance times: 2, 3 + + // Restart (and reset) animation at t=1 + anim.beginElement(); + + // Instance times should now be 1, 2 (3 should have be reset) + is(anim.getStartTime(), 1, + "Unexpected start time after restart. Perhaps the added instance time " + + "was cleared"); + svg.setCurrentTime(4); + // Instance times will now be 2 (1 will have be reset when we restarted) + is(anim.getStartTime(), 2, "Unexpected start time after seek"); + + // Create a two new instance times at t=4, 5 + anim.beginElement(); + anim.beginElementAt(1); + is(anim.getStartTime(), 4, "Unexpected start time after beginElement"); + + // Here is a white box test to make sure we don't discard instance times + // created by DOM calls when setting/unsetting the 'begin' spec + anim.removeAttribute('begin'); + is(anim.getStartTime(), 4, "Unexpected start time after clearing begin spec"); + svg.setCurrentTime(6); + is(anim.getStartTime(), 5, + "Second DOM instance time cleared when begin spec was removed"); + + // And likewise, when we set it again + anim.beginElementAt(1); // Instance times now t=5s, 7s + anim.setAttribute('begin', '1s'); // + t=1s + is(anim.getStartTime(), 5, "Unexpected start time after setting begin spec"); + svg.setCurrentTime(8); + is(anim.getStartTime(), 7, + "Second DOM instance time cleared when begin spec was added"); + + // But check we do update state appropriately + anim.setAttribute('begin', '8s'); + is(anim.getStartTime(), 8, "Interval not updated with updated begin spec"); + + SimpleTest.finish(); +} + +window.addEventListener("load", main, false); +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/smil/test/test_smilRestart.xhtml b/dom/smil/test/test_smilRestart.xhtml new file mode 100644 index 000000000..a4eab2e8c --- /dev/null +++ b/dom/smil/test/test_smilRestart.xhtml @@ -0,0 +1,102 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Test for SMIL Restart Behavior </title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> +<svg id="svg" xmlns="http://www.w3.org/2000/svg" width="120px" height="120px" + onload="this.pauseAnimations()"> + <!-- These 3 circles only differ in their animation's "restart" value --> + <circle cx="20" cy="20" r="15" fill="blue"> + <animate attributeName="cx" from="20" to="100" begin="1s" dur="4s" + restart="always" id="always" attributeType="XML"/> + </circle> + <circle cx="20" cy="60" r="15" fill="blue"> + <animate attributeName="cx" from="20" to="100" begin="1s" dur="4s" + restart="whenNotActive" id="whenNotActive" attributeType="XML"/> + </circle> + <circle cx="20" cy="100" r="15" fill="blue"> + <animate attributeName="cx" from="20" to="100" begin="1s" dur="4s" + restart="never" id="never" attributeType="XML"/> + </circle> +</svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ +/** Test for SMIL Restart Behavior **/ + +/* Global Variables */ +var svg = document.getElementById("svg"); +var always = document.getElementById("always"); +var whenNotActive = document.getElementById("whenNotActive"); +var never = document.getElementById("never"); + +SimpleTest.waitForExplicitFinish(); + +function tryRestart(elem, state, expected) { + var restartTime = svg.getCurrentTime(); + elem.beginElement(); + var restart = false; + try { + restart = (elem.getStartTime() === restartTime); + } catch (e) { + if (e.name != "InvalidStateError" || + e.code != DOMException.INVALID_STATE_ERR) + throw e; + restart = false; + } + if (expected) { + var msg = elem.id + " can't restart in " + state + " state"; + ok(restart, msg); + } else { + var msg = elem.id + " can restart in " + state + " state"; + ok(!restart, msg); + } +} + +function main() { + ok(svg.animationsPaused(), "should be paused by <svg> load handler"); + is(svg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler"); + + // At first everything should be starting at 1s + is(always.getStartTime(), 1); + is(whenNotActive.getStartTime(), 1); + is(never.getStartTime(), 1); + + // Now try to restart everything early, should be allowed by all + tryRestart(always, "waiting", true); + tryRestart(whenNotActive, "waiting", true); + tryRestart(never, "waiting", true); + + // Now skip to half-way + var newTime = always.getStartTime() + 0.5 * always.getSimpleDuration(); + svg.setCurrentTime(newTime); + + // Only 'always' should be able to be restarted + tryRestart(always, "active", true); + tryRestart(whenNotActive, "active", false); + tryRestart(never, "active", false); + + // Now skip to the end + newTime = always.getStartTime() + always.getSimpleDuration() + 1; + svg.setCurrentTime(newTime); + + // All animations have finished, so 'always' and 'whenNotActive' should be + // able to be restarted + tryRestart(always, "postactive", true); + tryRestart(whenNotActive, "postactive", true); + tryRestart(never, "postactive", false); + + SimpleTest.finish(); +} + +window.addEventListener("load", main, false); +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/smil/test/test_smilSetCurrentTime.xhtml b/dom/smil/test/test_smilSetCurrentTime.xhtml new file mode 100644 index 000000000..36f64a49f --- /dev/null +++ b/dom/smil/test/test_smilSetCurrentTime.xhtml @@ -0,0 +1,76 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Test for setCurrentTime Behavior </title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> +<svg id="svg" xmlns="http://www.w3.org/2000/svg" + onload="this.pauseAnimations()" /> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ +/** Test for basic setCurrentTime / getCurrentTime Behavior **/ + +/* Global Variables & Constants */ +const PRECISION_LEVEL = 0.0000001; // Allow small level of floating-point error +const gTimes = [0, 1.5, 0.2, 0.99, -400.5, 10000000, -1]; +const gWaitTime = 20; +var gSvg = document.getElementById("svg"); + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestFlakyTimeout("untriaged"); + +function main() { + ok(gSvg.animationsPaused(), "should be paused by <svg> load handler"); + is(gSvg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler"); + + // Test that seeking takes effect immediately + for (var i = 0; i < gTimes.length; i++) { + gSvg.setCurrentTime(gTimes[i]); + // We adopt the SVGT1.2 behavior of clamping negative times to 0 + assertFloatsEqual(gSvg.getCurrentTime(), Math.max(gTimes[i], 0.0)); + } + + // Test that seeking isn't messed up by timeouts + // (using tail recursion to set up the chain of timeout function calls) + var func = function() { + checkTimesAfterIndex(0); + } + setTimeout(func, gWaitTime); +} + +/* This method seeks to the time at gTimes[index], + * and then sets up a timeout to... + * - verify that the seek worked + * - make a recursive call for the next index. + */ +function checkTimesAfterIndex(index) { + if (index == gTimes.length) { + // base case -- we're done! + SimpleTest.finish(); + return; + } + + gSvg.setCurrentTime(gTimes[index]); + var func = function() { + assertFloatsEqual(gSvg.getCurrentTime(), Math.max(gTimes[index], 0.0)); + checkTimesAfterIndex(index + 1); + } + setTimeout(func, gWaitTime); +} + +function assertFloatsEqual(aVal, aExpected) { + ok(Math.abs(aVal - aExpected) <= PRECISION_LEVEL, + "getCurrentTime returned " + aVal + " after seeking to " + aExpected) +} + +window.addEventListener("load", main, false); +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/smil/test/test_smilSync.xhtml b/dom/smil/test/test_smilSync.xhtml new file mode 100644 index 000000000..0db9dd5a2 --- /dev/null +++ b/dom/smil/test/test_smilSync.xhtml @@ -0,0 +1,255 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Test for SMIL sync behaviour </title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content"> +<svg id="svg" xmlns="http://www.w3.org/2000/svg" width="120px" height="120px"> + <circle cx="20" cy="20" r="15" fill="blue"> + <animate attributeName="cx" attributeType="XML" from="20" to="100" + begin="indefinite" dur="4s" restart="always" id="anim1"/> + </circle> + <circle cx="20" cy="20" r="15" fill="blue"> + <animate attributeName="cx" attributeType="XML" from="0" to="50" + begin="0" dur="1s" additive="sum" fill="freeze" id="anim2"/> + </circle> + <circle cx="20" cy="20" r="15" fill="blue"> + <animate attributeName="cx" attributeType="XML" from="0" to="50" + begin="0" dur="10s" additive="sum" fill="freeze" id="anim3"/> + </circle> + <circle cx="20" cy="20" r="15" fill="blue"> + <animate attributeName="cx" attributeType="XML" from="0" to="50" + begin="0" dur="10s" additive="sum" fill="freeze" id="anim4"/> + </circle> + <circle cx="20" cy="20" r="15" fill="blue"> + <animate attributeName="cx" attributeType="XML" from="0" to="50" + begin="0" dur="40s" additive="sum" fill="freeze" id="anim5"/> + </circle> + <circle cx="20" cy="20" r="15" fill="blue"> + <animate attributeName="cx" attributeType="XML" from="20" to="100" + begin="100s" dur="4s" restart="always" id="anim6"/> + </circle> +</svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ +/** Test for SMIL sync behavior **/ + +/* Global Variables */ +var svg = document.getElementById("svg"); + +SimpleTest.waitForExplicitFinish(); + +function main() { + testBeginAt(document.getElementById("anim1")); + testChangeBaseVal(document.getElementById("anim2")); + testChangeWhilePaused(document.getElementById("anim3")); + testChangeAnimAttribute(document.getElementById("anim4")); + testChangeTimingAttribute(document.getElementById("anim5")); + testSetCurrentTime(document.getElementById("anim6")); + SimpleTest.finish(); +} + +function testBeginAt(anim) { + // This (hugely important) test checks that a call to beginElement updates to + // the new interval + + // Check some pre-conditions + is(anim.getAttribute("restart"), "always"); + ok(anim.getSimpleDuration() >= 4); + + // First start the animation + svg.setCurrentTime(2); + anim.beginElement(); + + // Then restart it--twice + svg.setCurrentTime(4); + anim.beginElement(); + anim.beginElementAt(-1); + + // The first restart should win if the state machine has been successfully + // updated. If we get '3' back instead we haven't updated properly. + is(anim.getStartTime(), 4); +} + +function testChangeBaseVal(anim) { + // Check that a change to the base value is updated even after animation is + // frozen + + // preconditions -- element should have ended + try { + anim.getStartTime(); + ok(false, "Element has not ended yet."); + } catch (e) { } + + // check frozen value is applied + var target = anim.targetElement; + is(target.cx.animVal.value, 70); + is(target.cx.baseVal.value, 20); + + // change base val and re-check + target.cx.baseVal.value = 30; + is(target.cx.animVal.value, 80); + is(target.cx.baseVal.value, 30); +} + +function testChangeWhilePaused(anim) { + // Check that a change to the base value is updated even when the animation is + // paused + + svg.pauseAnimations(); + svg.setCurrentTime(anim.getSimpleDuration() / 2); + + // check paused value is applied + var target = anim.targetElement; + is(target.cx.animVal.value, 45); + is(target.cx.baseVal.value, 20); + + // change base val and re-check + target.cx.baseVal.value = 30; + is(target.cx.animVal.value, 55); + is(target.cx.baseVal.value, 30); +} + +function testChangeAnimAttribute(anim) { + // Check that a change to an animation attribute causes an update even when + // the animation is frozen and paused + + // Make sure animation is paused and frozen + svg.pauseAnimations(); + svg.setCurrentTime(anim.getStartTime() + anim.getSimpleDuration() + 1); + + // Check frozen value is applied + var target = anim.targetElement; + is(target.cx.animVal.value, 70); + is(target.cx.baseVal.value, 20); + + // Make the animation no longer additive + anim.removeAttribute("additive"); + is(target.cx.animVal.value, 50); + is(target.cx.baseVal.value, 20); +} + +function testChangeTimingAttribute(anim) { + // Check that a change to a timing attribute causes an update even when + // the animation is paused + + svg.pauseAnimations(); + svg.setCurrentTime(anim.getSimpleDuration() / 2); + + // Check part-way value is applied + var target = anim.targetElement; + is(target.cx.animVal.value, 45); + is(target.cx.baseVal.value, 20); + + // Make the animation no longer additive + anim.setAttribute("dur", String(anim.getSimpleDuration() / 2) + "s"); + is(target.cx.animVal.value, 70); + is(target.cx.baseVal.value, 20); + + // Remove fill + anim.removeAttribute("fill"); + is(target.cx.animVal.value, 20); + is(target.cx.baseVal.value, 20); +} + +function testSetCurrentTime(anim) { + // This test checks that a call to setCurrentTime flushes restarts + // + // Actually, this same scenario arises in test_smilRestart.xhtml but we + // isolate this particular situation here for easier diagnosis if this ever + // fails. + // + // At first we have: + // currentTime begin="100s" + // v v + // Doc time: 0---\/\/\/-------99----------100------- + // + svg.setCurrentTime(99); + is(anim.getStartTime(), 100); + + // Then we restart giving us: + // + // beginElement begin="100s" + // v v + // Doc time: 0---\/\/\/-------99----------100------- + // + // So our current interval is + // + // begin="100s" + // v + // +---------------| + // Doc time: 0---\/\/\/-------99-100-101-102-103----- + // + anim.beginElement(); + is(anim.getStartTime(), svg.getCurrentTime()); + + // Then we skip to half-way, i.e. + // + // currentTime + // v + // begin="100s" + // v + // +---------------| + // Doc time: 0---\/\/\/-------99-100-101-102-103----- + // + // At this point we should flush our restarts and early end the first interval + // and start the second interval, giving us + // + // So our timegraph looks like: + // + // currentTime + // v + // +---------------| + // +---| + // Doc time: 0---\/\/\/-------99-100-101-102-103-104- + // + var newTime = anim.getStartTime() + 0.5 * anim.getSimpleDuration(); + svg.setCurrentTime(newTime); + + // Finally we call beginElement again giving us + // + // currentTime + // v + // +---------------| + // +---| + // +---| + // Doc time: 0---\/\/\/-------99-100-101-102-103-104-105- + // + // If, however, setCurrentTime failed to flush restarts out starting point + // we do come to update the timegraph would be: + // + // beginElementAt + // v + // begin="100s" + // v + // +---------------| + // Doc time: 0---\/\/\/-------99-100-101-102-103----- + // + // And as soon as we encountered the begin="100s" spec we'd do a restart + // according to the SMIL algorithms and a restart involves a reset which + // clears the instance times created by DOM calls and so we'd end up with + // just: + // + // currentTime + // v + // +---------------| + // +---| + // Doc time: 0---\/\/\/-------99-100-101-102-103-104- + // + // Which is probably not what the author intended. + // + anim.beginElement(); + is(anim.getStartTime(), svg.getCurrentTime()); +} + +window.addEventListener("load", main, false); +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/smil/test/test_smilSyncTransform.xhtml b/dom/smil/test/test_smilSyncTransform.xhtml new file mode 100644 index 000000000..65debc97c --- /dev/null +++ b/dom/smil/test/test_smilSyncTransform.xhtml @@ -0,0 +1,66 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Test for SMIL sync behaviour for transform types</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> +<svg id="svg" xmlns="http://www.w3.org/2000/svg" width="120px" height="120px"> + <circle cx="20" cy="20" r="15" fill="blue"> + <animateTransform attributeName="transform" type="rotate" + from="90" to="180" begin="0s" dur="2s" fill="freeze" + additive="sum" id="anim1"/> + </circle> + <circle cx="20" cy="20" r="15" fill="blue"> + <animateTransform attributeName="transform" type="scale" + from="1" to="2" begin="2s" dur="2s" id="anim2"/> + </circle> +</svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ +/** Test for SMIL sync behavior for transform types **/ + +/* Global Variables */ +var svg = document.getElementById("svg"); + +SimpleTest.waitForExplicitFinish(); + +function main() { + testChangeBaseVal(document.getElementById("anim1")); + SimpleTest.finish(); +} + +function testChangeBaseVal(anim) { + // Check that a change to the base value is updated even after animation is + // frozen + + var target = anim.targetElement; + + var baseList = target.transform.baseVal; + var animList = target.transform.animVal; + + // make sure element has ended + svg.setCurrentTime(anim.getSimpleDuration()); + + // check frozen value is applied + is(baseList.numberOfItems, 0); + is(animList.numberOfItems, 1); + + // change base val and re-check + var newTransform = svg.createSVGTransform(); + newTransform.setScale(1,2); + baseList.appendItem(newTransform); + is(baseList.numberOfItems, 1); + is(animList.numberOfItems, 2); +} + +window.addEventListener("load", main, false); +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/smil/test/test_smilSyncbaseTarget.xhtml b/dom/smil/test/test_smilSyncbaseTarget.xhtml new file mode 100644 index 000000000..979c15391 --- /dev/null +++ b/dom/smil/test/test_smilSyncbaseTarget.xhtml @@ -0,0 +1,180 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Test for syncbase targetting</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> +<svg id="svg" xmlns="http://www.w3.org/2000/svg" width="120px" height="120px" + onload="this.pauseAnimations()"> + <circle cx="-20" cy="20" r="15" fill="blue" id="circle"> + <set attributeName="cx" to="0" begin="2s" dur="1s" id="a"/> + <set attributeName="cx" to="0" begin="2s" dur="1s" xml:id="b"/> + <set attributeName="cx" to="0" begin="2s" dur="1s" id="あ"/> + <set attributeName="cx" to="0" begin="2s" dur="1s" id="a.b"/> + <set attributeName="cx" to="0" begin="2s" dur="1s" id="a-b"/> + <set attributeName="cx" to="0" begin="2s" dur="1s" id="a:b"/> + <set attributeName="cx" to="0" begin="2s" dur="1s" id="-a"/> + <set attributeName="cx" to="0" begin="2s" dur="1s" id="0"/> + </circle> +</svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ +/** Test for syncbase targetting behavior **/ + +SimpleTest.waitForExplicitFinish(); + +function main() { + var svg = getElement("svg"); + ok(svg.animationsPaused(), "should be paused by <svg> load handler"); + is(svg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler"); + + testSpecs(); + testChangeId(); + testRemoveTimebase(); + + SimpleTest.finish(); +} + +function testSpecs() { + var anim = createAnim(); + + // Sanity check--initial state + ok(noStart(anim), "Unexpected initial value for indefinite start time."); + + var specs = [ [ 'a.begin', 2 ], + [ 'b.begin', 'todo' ], // xml:id support, bug 275196 + [ 'あ.begin', 2 ], // unicode id + [ ' a.begin ', 2 ], // whitespace + [ 'a\\.b.begin', 2 ], // escaping + [ 'a\\-b.begin', 2 ], // escaping + [ 'a:b.begin', 2 ], + // Invalid + [ '-a.begin', 'notok' ], // invalid XML ID + [ '\\-a.begin', 'notok' ], // invalid XML ID + [ '0.begin', 'notok' ], // invalid XML ID + [ '\xB7.begin', 'notok' ], // invalid XML ID + [ '\x7B.begin', 'notok' ], // invalid XML ID + [ '.begin', 'notok' ], + [ ' .end ', 'notok' ], + [ 'a.begin-5a', 'notok' ], + // Offsets + [ ' a.begin + 1min', 2 + 60 ], + [ ' a.begin-0.5s', 1.5 ], + ]; + for (var i = 0; i < specs.length; i++) { + var spec = specs[i][0]; + var expected = specs[i][1]; + anim.setAttribute('begin', spec); + try { + if (typeof(expected) == 'number') { + is(anim.getStartTime(), expected, + "Unexpected start time with spec: " + spec); + } else if (expected == 'todo') { + todo_is(anim.getStartTime(), 2,"Unexpected success with spec: " + spec); + } else { + anim.getStartTime(); + ok(false, "Unexpected success with spec: " + spec); + } + } catch(e) { + if (e.name == "InvalidStateError" && + e.code == DOMException.INVALID_STATE_ERR) { + if (typeof(expected) == 'number') + ok(false, "Failed with spec: " + spec); + else if (expected == 'todo') + todo(false, "Yet to implement: " + spec); + else + ok(true); + } else { + ok(false, "Unexpected exception: " + e + "(with spec: " + spec + ")"); + } + } + } + + anim.parentNode.removeChild(anim); +} + +function testChangeId() { + var anim = createAnim(); + + anim.setAttribute('begin', 'a.begin'); + is(anim.getStartTime(), 2, "Unexpected start time."); + + var a = getElement('a'); + a.setAttribute('id', 'a1'); + ok(noStart(anim), "Unexpected return value after changing target ID."); + + a.setAttribute('id', 'a'); + is(anim.getStartTime(), 2, + "Unexpected start time after resetting target ID."); + + anim.parentNode.removeChild(anim); +} + +function testRemoveTimebase() { + var anim = createAnim(); + anim.setAttribute('begin', 'a.begin'); + ok(!noStart(anim), "Unexpected start time before removing timebase."); + + var circle = getElement('circle'); + var a = getElement('a'); + // Sanity check + is(a, circle.firstElementChild, "Unexpected document structure"); + + // Remove timebase + a.parentNode.removeChild(a); + ok(noStart(anim), "Unexpected start time after removing timebase."); + + // Reinsert timebase + circle.insertBefore(a, circle.firstElementChild); + ok(!noStart(anim), "Unexpected start time after re-inserting timebase."); + + // Remove dependent element + anim.parentNode.removeChild(anim); + ok(noStart(anim), "Unexpected start time after removing dependent."); + + // Create a new dependent + var anim2 = createAnim(); + anim2.setAttribute('begin', 'a.begin'); + is(anim2.getStartTime(), 2, + "Unexpected start time after adding new dependent."); +} + +function createAnim() { + const svgns="http://www.w3.org/2000/svg"; + var anim = document.createElementNS(svgns,'animate'); + anim.setAttribute('attributeName','cx'); + anim.setAttribute('from','0'); + anim.setAttribute('to','100'); + anim.setAttribute('begin','indefinite'); + anim.setAttribute('dur','1s'); + return getElement('circle').appendChild(anim); +} + +function noStart(elem) { + var exceptionCaught = false; + + try { + elem.getStartTime(); + } catch(e) { + exceptionCaught = true; + is (e.name, "InvalidStateError", + "Unexpected exception from getStartTime."); + is (e.code, DOMException.INVALID_STATE_ERR, + "Unexpected exception code from getStartTime."); + } + + return exceptionCaught; +} + +window.addEventListener("load", main, false); +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/smil/test/test_smilTextZoom.xhtml b/dom/smil/test/test_smilTextZoom.xhtml new file mode 100644 index 000000000..814b81f6e --- /dev/null +++ b/dom/smil/test/test_smilTextZoom.xhtml @@ -0,0 +1,89 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Test for SMIL Animation Behavior with textZoom</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="smilTestUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content"> + <svg xmlns="http://www.w3.org/2000/svg" width="300px" height="200px" + onload="this.pauseAnimations()"> + <text y="100px" x="0px" style="font-size: 5px"> + abc + <animate attributeName="font-size" attributeType="CSS" fill="freeze" + from="20px" to="40px" begin="1s" dur="1s"/> + </text> + <rect y="100px" x="50px" style="stroke-width: 5px"> + <animate attributeName="stroke-width" attributeType="CSS" fill="freeze" + from="20px" to="40px" begin="1s" dur="1s"/> + </rect> + </svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ +SimpleTest.waitForExplicitFinish(); + +// Helper function +function verifyStyle(aNode, aPropertyName, aExpectedVal) +{ + var computedVal = SMILUtil.getComputedStyleSimple(aNode, aPropertyName); + is(computedVal, aExpectedVal, "computed value of " + aPropertyName); +} + +function main() +{ + // Start out pause + var svg = SMILUtil.getSVGRoot(); + ok(svg.animationsPaused(), "should be paused by <svg> load handler"); + is(svg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler"); + + // Set text zoom to 2x + var origTextZoom = SpecialPowers.getTextZoom(window); + SpecialPowers.setTextZoom(window, 2); + + try { + // Verify computed style values at various points during animation. + // * Correct behavior is for the computed values of 'font-size' to be + // the same as their corresponding specified values, since text zoom + // should not affect SVG text elements. + // * I also include tests for an identical animation of the "stroke-width" + // property, which should _not_ be affected by textZoom. + var text = document.getElementsByTagName("text")[0]; + var rect = document.getElementsByTagName("rect")[0]; + + verifyStyle(text, "font-size", "5px"); + verifyStyle(rect, "stroke-width", "5px"); + svg.setCurrentTime(1); + verifyStyle(text, "font-size", "20px"); + verifyStyle(rect, "stroke-width", "20"); + svg.setCurrentTime(1.5); + verifyStyle(text, "font-size", "30px"); + verifyStyle(rect, "stroke-width", "30"); + svg.setCurrentTime(2); + verifyStyle(text, "font-size", "40px"); + verifyStyle(rect, "stroke-width", "40"); + svg.setCurrentTime(3); + verifyStyle(text, "font-size", "40px"); + verifyStyle(rect, "stroke-width", "40"); + } catch (e) { + // If anything goes wrong, make sure we restore textZoom before bubbling + // the exception upwards, so that we don't mess up subsequent tests. + SpecialPowers.setTextZoom(window, origTextZoom); + + throw e; + } + + // We're done! Restore original text-zoom before finishing + SpecialPowers.setTextZoom(window, origTextZoom); + SimpleTest.finish(); +} + +window.addEventListener("load", main, false); +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/smil/test/test_smilTimeEvents.xhtml b/dom/smil/test/test_smilTimeEvents.xhtml new file mode 100644 index 000000000..bf6924ddb --- /dev/null +++ b/dom/smil/test/test_smilTimeEvents.xhtml @@ -0,0 +1,337 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=572270 +--> +<head> + <title>Test TimeEvents dispatching</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=572270">Mozilla Bug + 572270</a> +<p id="display"></p> +<div id="content" style="display: none"> +<svg id="svg" xmlns="http://www.w3.org/2000/svg" width="100px" height="100px"> + <g font-size="10px"> + <circle cx="0" cy="0" r="15" fill="blue" id="circle" + onbegin="parentHandler(evt)" onrepeat="parentHandler(evt)" + onend="parentHandler(evt)"> + <animate attributeName="cy" from="0" to="100" dur="60s" begin="2s" + id="anim" repeatCount="2" + onbegin="handleOnBegin(evt)" onrepeat="handleOnRepeat(evt)" + onend="handleOnEnd(evt)"/> + </circle> + </g> +</svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ +/** Test SMIL TimeEvents dispatching **/ + +/* Global Variables */ +const gTimeoutDur = 60000; // Time until we give up waiting for events in ms +var gSvg = document.getElementById("svg"); +var gAnim = document.getElementById('anim'); +var gCircle = document.getElementById('circle'); +var gExpectedEvents = new Array(); +var gTimeoutID; +var gTestStages = + [ testPlaybackBegin, + testPlaybackRepeat, + testPlaybackEnd, + testForwardsSeekToMid, + testForwardsSeekToNextInterval, + testForwardsSeekPastEnd, + testBackwardsSeekToMid, + testBackwardsSeekToStart, + testCreateEvent, + testRegistration + ]; + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestFlakyTimeout("untriaged"); + +function continueTest() +{ + if (gTestStages.length == 0) { + SimpleTest.finish(); + return; + } + gTestStages.shift()(); +} + +function testPlaybackBegin() +{ + // Test events are dispatched through normal playback + gSvg.pauseAnimations(); + gSvg.setCurrentTime(1.99); + gExpectedEvents.push("beginEvent", "beginEvent"); // Two registered handlers + gTimeoutID = setTimeout(timeoutFail, gTimeoutDur); + gSvg.unpauseAnimations(); +} + +function testPlaybackRepeat() +{ + gSvg.pauseAnimations(); + gSvg.setCurrentTime(61.99); + gExpectedEvents.push(["repeatEvent", 1], ["repeatEvent", 1]); + gTimeoutID = setTimeout(timeoutFail, gTimeoutDur); + gSvg.unpauseAnimations(); +} + +function testPlaybackEnd() +{ + gSvg.pauseAnimations(); + gSvg.setCurrentTime(121.99); + gExpectedEvents.push("endEvent", "endEvent"); + gTimeoutID = setTimeout(timeoutFail, gTimeoutDur); + gSvg.unpauseAnimations(); +} + +function testForwardsSeekToMid() +{ + gSvg.pauseAnimations(); + // Set animation parameters to something that repeats a lot + gSvg.setCurrentTime(0); + gAnim.setAttribute('begin', '2s; 102s'); + gAnim.setAttribute('dur', '15s'); + gAnim.setAttribute('repeatCount', '6'); + gSvg.setCurrentTime(46.99); + gExpectedEvents.push("beginEvent", "beginEvent", + ["repeatEvent", 3], ["repeatEvent", 3]); + gTimeoutID = setTimeout(timeoutFail, gTimeoutDur); + gSvg.unpauseAnimations(); +} + +function testForwardsSeekToNextInterval() +{ + // Skip to next interval -- we shouldn't get any additional begin or end + // events in between + gSvg.pauseAnimations(); + gSvg.setCurrentTime(131.99); + gExpectedEvents.push(["repeatEvent", 2], ["repeatEvent", 2]); + gTimeoutID = setTimeout(timeoutFail, gTimeoutDur); + gSvg.unpauseAnimations(); +} + +function testForwardsSeekPastEnd() +{ + gSvg.pauseAnimations(); + gSvg.setCurrentTime(200); + gExpectedEvents.push("endEvent", "endEvent"); + gTimeoutID = setTimeout(timeoutFail, gTimeoutDur); + gSvg.unpauseAnimations(); +} + +function testBackwardsSeekToMid() +{ + gSvg.pauseAnimations(); + gSvg.setCurrentTime(31.99); + gExpectedEvents.push("beginEvent", "beginEvent", + ["repeatEvent", 2], ["repeatEvent", 2]); + gTimeoutID = setTimeout(timeoutFail, gTimeoutDur); + gSvg.unpauseAnimations(); +} + +function testBackwardsSeekToStart() +{ + gSvg.pauseAnimations(); + gExpectedEvents.push("endEvent", "endEvent"); + gTimeoutID = setTimeout(timeoutFail, gTimeoutDur); + gSvg.setCurrentTime(0); +} + +function testCreateEvent() +{ + var evt; + try { + evt = document.createEvent("TimeEvents"); + } catch (e) { + ok(false, "Failed to create TimeEvent via script: " + e); + return; + } + evt.initTimeEvent("repeatEvent", null, 3); + is(evt.type, "repeatEvent", "Unexpected type for user-generated event"); + is(evt.detail, 3, "Unexpected detail for user-generated event"); + is(evt.target, null, "Unexpected event target"); + is(evt.currentTarget, null, "Unexpected event current target"); + is(evt.eventPhase, evt.NONE); + is(evt.bubbles, false, "Event should not bubble"); + is(evt.cancelable, false, "Event should not be cancelable"); + is(evt.view, null, "Event view should be null"); + + // Prior to dispatch we should be able to change the event type + evt.initTimeEvent("beginEvent", document.defaultView, 0); + is(evt.type, "beginEvent", "Failed to update event type before dispatch"); + is(evt.detail, 0, "Failed to update event detail before dispatch"); + is(evt.view, document.defaultView, "Event view should be set"); + + // But not directly as it's readonly + try { + evt.type = "endEvent"; + } catch(e) { } + is(evt.type, "beginEvent", "Event type should be readonly"); + + // Likewise the detail field should be readonly + try { + evt.detail = "8"; + } catch(e) { } + is(evt.detail, 0, "Event detail should be readonly"); + + // Dispatch + gExpectedEvents.push("beginEvent", "beginEvent"); + gTimeoutID = setTimeout(timeoutFail, gTimeoutDur); + gAnim.dispatchEvent(evt); +} + +function testRegistration() +{ + gSvg.pauseAnimations(); + // Reset animation to something simple + gSvg.setCurrentTime(0); + gAnim.setAttribute('begin', '2s'); + gAnim.setAttribute('dur', '50s'); + + // Remove attribute handler + gAnim.removeAttribute('onbegin'); + + // Add bogus handlers + gAnim.setAttribute('onbeginElement', 'handleOnBegin(evt)'); + gAnim.addEventListener("begin", handleOnBegin, false); + gAnim.addEventListener("onbegin", handleOnBegin, false); + + // We should now have just one legitimate listener: the one registered to + // handle 'beginElement' + gSvg.setCurrentTime(1.99); + gExpectedEvents.push("beginEvent"); + gTimeoutID = setTimeout(timeoutFail, gTimeoutDur); + gSvg.unpauseAnimations(); +} + +function handleOnBegin(evt) +{ + is(evt.type, "beginEvent", "Expected begin event but got " + evt.type); + checkExpectedEvent(evt); +} + +function handleOnRepeat(evt) +{ + is(evt.type, "repeatEvent", "Expected repeat event but got " + evt.type); + checkExpectedEvent(evt); +} + +function handleOnEnd(evt) +{ + is(evt.type, "endEvent", "Expected end event but got " + evt.type); + checkExpectedEvent(evt); +} + +function sanityCheckEvent(evt) +{ + is(evt.target, gAnim, "Unexpected event target"); + is(evt.currentTarget, gAnim, "Unexpected event current target"); + is(evt.eventPhase, evt.AT_TARGET); + is(evt.bubbles, false, "Event should not bubble"); + is(evt.cancelable, false, "Event should not be cancelable"); + if (SpecialPowers.getBoolPref("dom.event.highrestimestamp.enabled")) { + var now = window.performance.now(); + ok(evt.timeStamp > 0 && evt.timeStamp < now, + "Event timeStamp (" + evt.timeStamp + ") should be > 0 but " + + "before the current time (" + now + ")"); + } else { + is(evt.timeStamp, 0, "Event timeStamp should be 0"); + } + ok(evt.view !== null, "Event view not set"); +} + +function checkExpectedEvent(evt) +{ + sanityCheckEvent(evt); + ok(gExpectedEvents.length > 0, "Unexpected event: " + evt.type); + if (gExpectedEvents.length == 0) return; + + var expected = gExpectedEvents.shift(); + if (typeof expected == 'string') { + is(evt.type, expected, "Unexpected event type"); + is(evt.detail, 0, "Unexpected event detail (repeat iteration)"); + } else { + is(evt.type, expected[0], "Unexpected event type"); + is(evt.detail, expected[1], "Unexpected event detail (repeat iteration)"); + } + if (gExpectedEvents.length == 0) { + clearTimeout(gTimeoutID); + continueTest(); + } +} + +function timeoutFail() +{ + ok(false, "Timed out waiting for events: " + gExpectedEvents.join(', ')); + SimpleTest.finish(); // No point continuing +} + +function parentHandler(evt) +{ + ok(false, "Handler on parent got called but event shouldn't bubble."); +} + +window.addEventListener("load", continueTest, false); + +// Register event handlers *in addition* to the handlers already added via the +// "onbegin", "onend", "onrepeat" attributes on the <animate> and <circle> +// elements. This is to test that both types of registration work. +gAnim.addEventListener("beginEvent", handleOnBegin, false); +gAnim.addEventListener("repeatEvent", handleOnRepeat, false); +gAnim.addEventListener("endEvent", handleOnEnd, false); +gCircle.addEventListener("beginEvent", parentHandler, false); + +var expectedEvents = + ["begin", "beginEvent", "repeat", "repeatEvent", "end", "endEvent", "SVGZoom", "zoom"]; + +for (var i = 0; i < expectedEvents.length; ++i) { + is((new Event(expectedEvents[i])).type, expectedEvents[i], "Unexpected event type!"); +} + +var timeEvents = ["begin", "repeat", "end"]; +var expectedEvents = ["begin", "beginEvent", "repeat", "repeatEvent", "end", "endEvent"]; +var d = document.createElement("div"); +for (var i = 0; i < timeEvents.length; ++i) { + d.addEventListener(timeEvents[i], function(e) { + is(e.type, expectedEvents[0], "Got the expected event type."); + expectedEvents.shift(); + }); + + // Without "Event" suffix. + var e = document.createEvent("timeevent"); + e.initEvent(timeEvents[i], true, true); + d.dispatchEvent(e); + + // With "Event" suffix. + e = document.createEvent("timeevent"); + e.initEvent(timeEvents[i] + "Event", true, true); + d.dispatchEvent(e); +} +is(expectedEvents.length, 0, "Got all the expected events."); + +expectedEvents = ["zoom", "SVGZoom"]; +d.addEventListener("zoom", function(e) { + is(e.type, expectedEvents[0]); + expectedEvents.shift(); +}); + +var zoomEvent = document.createEvent("svgzoomevent"); +zoomEvent.initEvent("zoom", true, true); +d.dispatchEvent(zoomEvent); +zoomEvent = document.createEvent("svgzoomevent"); +zoomEvent.initEvent("SVGZoom", true, true); +d.dispatchEvent(zoomEvent); +is(expectedEvents.length, 0, "Got all the expected events."); + +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/smil/test/test_smilTiming.xhtml b/dom/smil/test/test_smilTiming.xhtml new file mode 100644 index 000000000..cab1d4080 --- /dev/null +++ b/dom/smil/test/test_smilTiming.xhtml @@ -0,0 +1,291 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Test for SMIL timing</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content"> +<svg id="svg" xmlns="http://www.w3.org/2000/svg" width="120px" height="120px" + onload="this.pauseAnimations()"> + <circle cx="-100" cy="20" r="15" fill="blue" id="circle"/> +</svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ +/** Test for SMIL timing **/ + +/* Global Variables */ +const svgns = "http://www.w3.org/2000/svg"; +var gSvg = document.getElementById("svg"); +var gCircle = document.getElementById('circle'); + +SimpleTest.waitForExplicitFinish(); + +function main() { + ok(gSvg.animationsPaused(), "should be paused by <svg> load handler"); + is(gSvg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler"); + + var testCases = Array(); + + const secPerMin = 60; + const secPerHour = secPerMin * 60; + + // In the following tests that compare start times, getStartTime will round + // the start time to three decimal places since we expect our implementation + // to be millisecond accurate. + + // Offset syntax + // -- Basic tests, sign and whitespace + testCases.push(StartTimeTest('3s', 3)); + testCases.push(StartTimeTest('0s', 0)); + testCases.push(StartTimeTest('+2s', 2)); + testCases.push(StartTimeTest('-1s\t\r', -1)); + testCases.push(StartTimeTest('- 1s', -1)); + testCases.push(StartTimeTest(' -1s', -1)); + testCases.push(StartTimeTest(' - 1s', -1)); + testCases.push(StartTimeTest(' \t\n\r-1s', -1)); + testCases.push(StartTimeTest('+\n5s', 5)); + testCases.push(StartTimeTest('-\n5s', -5)); + testCases.push(StartTimeTest('\t 5s', 5)); + // -- These tests are from SMILANIM 3.6.7 + testCases.push(StartTimeTest('02:30:03', 2*secPerHour + 30*secPerMin + 3)); + testCases.push(StartTimeTest('50:00:10.25', 50*secPerHour + 10.25)); + testCases.push(StartTimeTest('02:33', 2*secPerMin + 33)); + testCases.push(StartTimeTest('00:10.5', 10.5)); + testCases.push(StartTimeTest('3.2h', 3.2*secPerHour)); + testCases.push(StartTimeTest('45min', 45*secPerMin)); + testCases.push(StartTimeTest('30s', 30)); + testCases.push(StartTimeTest('5ms', 0.005)); + testCases.push(StartTimeTest('12.467', 12.467)); + testCases.push(StartTimeTest('00.5s', 0.5)); + testCases.push(StartTimeTest('00:00.005', 0.005)); + // -- Additional tests + testCases.push(StartTimeTest('61:59:59', 61*secPerHour + 59*secPerMin + 59)); + testCases.push(StartTimeTest('02:59.999999999999999999999', 3*secPerMin)); + testCases.push(StartTimeTest('1234:23:45', + 1234*secPerHour + 23*secPerMin + 45)); + testCases.push(StartTimeTest('61min', 61*secPerMin)); + testCases.push(StartTimeTest('0:30:03', 30*secPerMin + 3)); + // -- Fractional precision + testCases.push(StartTimeTest('25.4567', 25.457)); + testCases.push(StartTimeTest('0.123456789', 0.123)); + testCases.push(StartTimeTest('0.00000000000000000000001', 0)); + testCases.push(StartTimeTest('-0.00000000000000000000001', 0)); + testCases.push(StartTimeTest('0.0009', 0.001)); + testCases.push(StartTimeTest('0.99999999999999999999999999999999999999', 1)); + testCases.push(StartTimeTest('23.4567ms', 0.023)); + testCases.push(StartTimeTest('23.7ms', 0.024)); + // -- Test errors + testCases.push(StartTimeTest(' + +3s', 'none')); + testCases.push(StartTimeTest(' +-3s', 'none')); + testCases.push(StartTimeTest('1:12:12:12', 'none')); + testCases.push(StartTimeTest('4:50:60', 'none')); + testCases.push(StartTimeTest('4:60:0', 'none')); + testCases.push(StartTimeTest('4:60', 'none')); + testCases.push(StartTimeTest('4:-1:00', 'none')); + testCases.push(StartTimeTest('4 5m', 'none')); + testCases.push(StartTimeTest('4 5ms', 'none')); + testCases.push(StartTimeTest('02:3:03', 'none')); + testCases.push(StartTimeTest('45.7 s', 'none')); + testCases.push(StartTimeTest(' 3 h ', 'none')); + testCases.push(StartTimeTest('2:33 ', 'none')); + testCases.push(StartTimeTest('02:33 2', 'none')); + testCases.push(StartTimeTest('\u000B 02:33', 'none')); + testCases.push(StartTimeTest('h', 'none')); + testCases.push(StartTimeTest('23.s', 'none')); + testCases.push(StartTimeTest('23.', 'none')); + testCases.push(StartTimeTest('23.54.2s', 'none')); + testCases.push(StartTimeTest('23sec', 'none')); + testCases.push(StartTimeTest('five', 'none')); + testCases.push(StartTimeTest('', 'none')); + testCases.push(StartTimeTest('02:33s', 'none')); + testCases.push(StartTimeTest('02:33 s', 'none')); + testCases.push(StartTimeTest('2.54e6', 'none')); + testCases.push(StartTimeTest('02.5:33', 'none')); + testCases.push(StartTimeTest('2:-45:33', 'none')); + testCases.push(StartTimeTest('2:4.5:33', 'none')); + testCases.push(StartTimeTest('45m', 'none')); + testCases.push(StartTimeTest(':20:30', 'none')); + testCases.push(StartTimeTest('1.5:30', 'none')); + testCases.push(StartTimeTest('15:-30', 'none')); + testCases.push(StartTimeTest('::30', 'none')); + testCases.push(StartTimeTest('15:30s', 'none')); + testCases.push(StartTimeTest('2:1.:30', 'none')); + testCases.push(StartTimeTest('2:.1:30', 'none')); + testCases.push(StartTimeTest('2.0:15:30', 'none')); + testCases.push(StartTimeTest('2.:15:30', 'none')); + testCases.push(StartTimeTest('.2:15:30', 'none')); + testCases.push(StartTimeTest('70:15', 'none')); + testCases.push(StartTimeTest('media', 'none')); + testCases.push(StartTimeTest('5mi', 'none')); + testCases.push(StartTimeTest('5hours', 'none')); + testCases.push(StartTimeTest('h05:30', 'none')); + testCases.push(StartTimeTest('05:40\x9A', 'none')); + testCases.push(StartTimeTest('05:40\u30D5', 'none')); + testCases.push(StartTimeTest('05:40β', 'none')); + + // List syntax + testCases.push(StartTimeTest('3', 3)); + testCases.push(StartTimeTest('3;', 3)); + testCases.push(StartTimeTest('3; ', 3)); + testCases.push(StartTimeTest('3 ; ', 3)); + testCases.push(StartTimeTest('3;;', 'none')); + testCases.push(StartTimeTest('3;; ', 'none')); + testCases.push(StartTimeTest(';3', 'none')); + testCases.push(StartTimeTest(' ;3', 'none')); + testCases.push(StartTimeTest('3;4', 3)); + testCases.push(StartTimeTest(' 3 ; 4 ', 3)); + + // List syntax on end times + testCases.push({ + 'attr' : { 'begin': '0s', + 'end': '1s; 2s' }, + 'times': [ [ 0, 0 ], + [ 1, -100 ] ] + }); + testCases.push({ + 'attr' : { 'begin': '0s', + 'end': '1s; 2s; ' }, + 'times': [ [ 0, 0 ], + [ 1, -100 ] ] + }); + testCases.push({ + 'attr' : { 'begin': '0s', + 'end': '3s; 2s' }, + 'times': [ [ 0, 0 ], + [ 1, 10 ], + [ 2, -100 ] ] + }); + + // Simple case + testCases.push({ + 'attr' : { 'begin': '3s' }, + 'times': [ [ 0, -100 ], + [ 4, 10 ] ] + }); + + // Multiple begins + testCases.push({ + 'attr' : { 'begin': '2s; 6s', + 'dur': '2s' }, + 'times': [ [ 0, -100 ], + [ 3, 50 ], + [ 4, -100 ], + [ 7, 50 ], + [ 8, -100 ] ] + }); + + // Negative begins + testCases.push({ + 'attr' : { 'begin': '-3s; 1s ; 4s', + 'dur': '2s ', + 'fill': 'freeze' }, + 'times': [ [ 0, -100 ], + [ 0.5, -100 ], + [ 1, 0 ], + [ 2, 50 ], + [ 3, 100 ], + [ 5, 50 ] ] + }); + + // Sorting + testCases.push({ + 'attr' : { 'begin': '-3s; 110s; 1s; 4s; -5s; -10s', + 'end': '111s; -5s; -15s; 6s; -5s; 1.2s', + 'dur': '2s ', + 'fill': 'freeze' }, + 'times': [ [ 0, -100 ], + [ 1, 0 ], + [ 2, 10 ], + [ 4, 0 ], + [ 5, 50 ], + [ 109, 100 ], + [ 110, 0 ], + [ 112, 50 ] ] + }); + + for (var i = 0; i < testCases.length; i++) { + gSvg.setCurrentTime(0); + var test = testCases[i]; + + // Generate string version of params for output messages + var params = ""; + for (var name in test.attr) { + params += name + '="' + test.attr[name] + '" '; + } + params = params.trim(); + + // Create animation elements + var anim = createAnim(test.attr); + + // Run samples + if ('times' in test) { + for (var j = 0; j < test.times.length; j++) { + var curSample = test.times[j]; + checkSample(curSample[0], curSample[1], params); + } + } + + // Check start time + if ('startTime' in test) { + is(getStartTime(anim), test.startTime, + "Got unexpected start time for " + params); + } + + anim.parentNode.removeChild(anim); + } + + SimpleTest.finish(); +} + +function createAnim(attr) { + var anim = document.createElementNS(svgns,'animate'); + anim.setAttribute('attributeName','cx'); + anim.setAttribute('from','0'); + anim.setAttribute('to','100'); + anim.setAttribute('dur','10s'); + anim.setAttribute('begin','indefinite'); + for (name in attr) { + anim.setAttribute(name, attr[name]); + } + return gCircle.appendChild(anim); +} + +function checkSample(time, expectedValue, params) { + gSvg.setCurrentTime(time); + var msg = "Unexpected sample value for " + params + + " at t=" + time + ": "; + is(gCircle.cx.animVal.value, expectedValue); +} + +function getStartTime(anim) { + var startTime; + try { + startTime = anim.getStartTime(); + // We round start times to 3 decimal places to make comparisons simpler + startTime = parseFloat(startTime.toFixed(3)); + } catch(e) { + if (e.name == "InvalidStateError" && + e.code == DOMException.INVALID_STATE_ERR) { + startTime = 'none'; + } else { + ok(false, "Unexpected exception: " + e); + } + } + return startTime; +} + +function StartTimeTest(beginSpec, expectedStartTime) { + return { 'attr' : { 'begin': beginSpec }, + 'startTime': expectedStartTime }; +} + +window.addEventListener("load", main, false); +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/smil/test/test_smilTimingZeroIntervals.xhtml b/dom/smil/test/test_smilTimingZeroIntervals.xhtml new file mode 100644 index 000000000..610cb5798 --- /dev/null +++ b/dom/smil/test/test_smilTimingZeroIntervals.xhtml @@ -0,0 +1,285 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Test for SMIL timing with zero-duration intervals</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content"> +<svg id="svg" xmlns="http://www.w3.org/2000/svg" width="120px" height="120px" + onload="this.pauseAnimations()"> + <circle cx="-100" cy="20" r="15" fill="blue" id="circle"/> +</svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ +/** Test for SMIL timing with zero-duration intervals **/ + +/* Global Variables */ +const svgns="http://www.w3.org/2000/svg"; +var svg = document.getElementById("svg"); +var circle = document.getElementById('circle'); + +SimpleTest.waitForExplicitFinish(); + +function createAnim() { + var anim = document.createElementNS(svgns,'animate'); + anim.setAttribute('attributeName','cx'); + anim.setAttribute('from','0'); + anim.setAttribute('to','100'); + anim.setAttribute('dur','10s'); + anim.setAttribute('begin','indefinite'); + return circle.appendChild(anim); +} + +function removeAnim(anim) { + anim.parentNode.removeChild(anim); +} + +function main() { + ok(svg.animationsPaused(), "should be paused by <svg> load handler"); + is(svg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler"); + + var tests = + [ testZeroDurationIntervalsA, + testZeroDurationIntervalsB, + testZeroDurationIntervalsC, + testZeroDurationIntervalsD, + testZeroDurationIntervalsE, + testZeroDurationIntervalsF, + testZeroDurationIntervalsG, + testZeroDurationIntervalsH, + testZeroDurationIntervalsI, + testZeroDurationIntervalsJ, + testZeroDurationIntervalsK, + testZeroDurationIntervalsL, + testZeroDurationIntervalsM, + testZeroDurationIntervalsN, + testZeroDurationIntervalsO + ]; + for (var i = 0; i < tests.length; i++) { + var anim = createAnim(); + svg.setCurrentTime(0); + tests[i](anim); + removeAnim(anim); + } + SimpleTest.finish(); +} + +function checkSample(time, expectedValue) { + svg.setCurrentTime(time); + is(circle.cx.animVal.value, expectedValue); +} + +function testZeroDurationIntervalsA(anim) { + // The zero-duration interval should play, followed by a second interval + // starting at the same point. There is no end for the interval + // at 4s so it should not play. + anim.setAttribute('begin', '1s ;4s'); + anim.setAttribute('end', '1s; 2s'); + anim.setAttribute('dur', '2s '); + anim.setAttribute('fill', 'freeze'); + checkSample(0,-100); + checkSample(1,0); + checkSample(1.1,5); + checkSample(2,50); + checkSample(3,50); + checkSample(4,50); + checkSample(5,50); + checkSample(6,50); +} + +function testZeroDurationIntervalsB(anim) { + // This interval should however actually restart as there is a valid end-point + anim.setAttribute('begin', '1s ;4s'); + anim.setAttribute('end', '1.1s; indefinite'); + anim.setAttribute('dur', '2s '); + anim.setAttribute('fill', 'freeze'); + checkSample(0,-100); + checkSample(1,0); + checkSample(1.1,5); + checkSample(2,5); + checkSample(4,0); + checkSample(5,50); +} + +function testZeroDurationIntervalsC(anim) { + // -0.5s has already been used as the endpoint of one interval so don't use it + // a second time + anim.setAttribute('begin', '-2s; -0.5s'); + anim.setAttribute('end', '-0.5s; 1s'); + anim.setAttribute('dur', '2s'); + anim.setAttribute('fill', 'freeze'); + checkSample(0,25); + checkSample(1.5,75); +} + +function testZeroDurationIntervalsD(anim) { + // Two end points that could make a zero-length interval + anim.setAttribute('begin', '-2s; -0.5s'); + anim.setAttribute('end', '-0.5s; -0.5s; 1s'); + anim.setAttribute('dur', '2s'); + anim.setAttribute('fill', 'freeze'); + checkSample(0,25); + checkSample(1.5,75); +} + +function testZeroDurationIntervalsE(anim) { + // Should give us 1s-1s, 1s-5s + anim.setAttribute('begin', '1s'); + anim.setAttribute('end', '1s; 5s'); + anim.setAttribute('fill', 'freeze'); + is(anim.getStartTime(),1); + checkSample(0,-100); + checkSample(1,0); + checkSample(6,40); +} + +function testZeroDurationIntervalsF(anim) { + // Should give us 1s-1s + anim.setAttribute('begin', '1s'); + anim.setAttribute('end', '1s'); + anim.setAttribute('fill', 'freeze'); + is(anim.getStartTime(),1); + checkSample(0,-100); + checkSample(1,0); + checkSample(2,0); + try { + anim.getStartTime(); + ok(false, "Failed to throw exception when there's no current interval."); + } catch (e) { } +} + +function testZeroDurationIntervalsG(anim) { + // Test a non-zero interval after a zero interval + // Should give us 1-2s, 3-3s, 3-4s + anim.setAttribute('begin', '1s; 3s'); + anim.setAttribute('end', '3s; 5s'); + anim.setAttribute('dur', '1s'); + anim.setAttribute('fill', 'freeze'); + checkSample(0,-100); + checkSample(1,0); + checkSample(2,100); + checkSample(3,0); + checkSample(5,100); +} + +function testZeroDurationIntervalsH(anim) { + // Test multiple non-adjacent zero-intervals + // Should give us 1-1s, 1-2s, 3-3s, 3-4s + anim.setAttribute('begin', '1s; 3s'); + anim.setAttribute('end', '1s; 3s; 5s'); + anim.setAttribute('dur', '1s'); + anim.setAttribute('fill', 'freeze'); + checkSample(0,-100); + checkSample(1,0); + checkSample(2,100); + checkSample(3,0); + checkSample(5,100); +} + +function testZeroDurationIntervalsI(anim) { + // Test skipping values that are the same + // Should give us 1-1s, 1-2s + anim.setAttribute('begin', '1s; 1s'); + anim.setAttribute('end', '1s; 1s; 2s'); + anim.setAttribute('fill', 'freeze'); + is(anim.getStartTime(),1); + checkSample(0,-100); + checkSample(1,0); + checkSample(2,10); + checkSample(3,10); +} + +function testZeroDurationIntervalsJ(anim) { + // Should give us 0-0.5s, 1-1s, 1-3s + anim.setAttribute('begin', '0s; 1s; 1s'); + anim.setAttribute('end', '1s; 3s'); + anim.setAttribute('dur', '0.5s'); + anim.setAttribute('fill', 'freeze'); + is(anim.getStartTime(),0); + checkSample(0,0); + checkSample(0.6,100); + checkSample(1,0); + checkSample(2,100); +} + +function testZeroDurationIntervalsK(anim) { + // Should give us -0.5-1s + anim.setAttribute('begin', '-0.5s'); + anim.setAttribute('end', '-0.5s; 1s'); + anim.setAttribute('fill', 'freeze'); + is(anim.getStartTime(),-0.5); + checkSample(0,5); + checkSample(1,15); + checkSample(2,15); +} + +function testZeroDurationIntervalsL(anim) { + // Test that multiple end values are ignored + // Should give us 1-1s, 1-3s + anim.setAttribute('begin', '1s'); + anim.setAttribute('end', '1s; 1s; 1s; 3s'); + anim.setAttribute('fill', 'freeze'); + is(anim.getStartTime(),1); + checkSample(0,-100); + checkSample(1,0); + checkSample(2,10); + checkSample(4,20); +} + +function testZeroDurationIntervalsM(anim) { + // Test 0-duration interval at start + anim.setAttribute('begin', '0s'); + anim.setAttribute('end', '0s'); + anim.setAttribute('fill', 'freeze'); + try { + anim.getStartTime(); + ok(false, "Failed to throw exception when there's no current interval."); + } catch (e) { } + checkSample(0,0); + checkSample(1,0); +} + +function testZeroDurationIntervalsN(anim) { + // Test 0-active-duration interval at start (different code path to above) + anim.setAttribute('begin', '0s'); + anim.setAttribute('repeatDur', '0s'); + anim.setAttribute('fill', 'freeze'); + try { + anim.getStartTime(); + ok(false, "Failed to throw exception when there's no current interval."); + } catch (e) { } + checkSample(0,0); + checkSample(1,0); +} + +function testZeroDurationIntervalsO(anim) { + // Make a zero-duration interval by constraining the active duration + // We should not loop infinitely but should look for the next begin time after + // that (in this case that is 2s, which would otherwise have been skipped + // because restart=whenNotActive) + // Should give us 1-1s, 2-2s + anim.setAttribute('begin', '1s; 2s'); + anim.setAttribute('repeatDur', '0s'); + anim.setAttribute('restart', 'whenNotActive'); + anim.setAttribute('fill', 'freeze'); + is(anim.getStartTime(),1); + checkSample(0,-100); + checkSample(1,0); + checkSample(1.5,0); + checkSample(3,0); + try { + anim.getStartTime(); + ok(false, "Failed to throw exception when there's no current interval."); + } catch (e) { } +} + +window.addEventListener("load", main, false); +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/smil/test/test_smilUpdatedInterval.xhtml b/dom/smil/test/test_smilUpdatedInterval.xhtml new file mode 100644 index 000000000..26b793dc6 --- /dev/null +++ b/dom/smil/test/test_smilUpdatedInterval.xhtml @@ -0,0 +1,64 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Tests updated intervals</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> +<svg id="svg" xmlns="http://www.w3.org/2000/svg" width="120px" height="120px" + onload="this.pauseAnimations()"> + <circle cx="20" cy="20" r="15" fill="blue" id="circle"> + <animate attributeName="cx" from="0" to="100" begin="2s" dur="4s" + id="anim1" attributeType="XML"/> + </circle> +</svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ +/** Tests for updated intervals **/ + +/* Global Variables */ +SimpleTest.waitForExplicitFinish(); + +function main() { + var svg = document.getElementById("svg"); + ok(svg.animationsPaused(), "should be paused by <svg> load handler"); + is(svg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler"); + + var anim = document.getElementById("anim1"); + + // Check regular operation + svg.setCurrentTime(3); + is(anim.getStartTime(), 2, "Unexpected initial start time"); + + // Add an instance time before the current interval at t=1s + anim.beginElementAt(-2); + + // We shouldn't change the begin time + is(anim.getStartTime(), 2, "Start time shouldn't have changed"); + + // Or the end--that is, if we go to t=5.5 we should still be running + svg.setCurrentTime(5.5); + try { + is(anim.getSimpleDuration(), 4, "Simple duration shouldn't have changed"); + is(anim.getStartTime(), 2, "Start time shouldn't have changed after seek"); + } catch (e) { + if (e.name != "InvalidStateError" || + e.code != DOMException.INVALID_STATE_ERR) + throw e; + ok(false, "Animation ended too early, even though begin time and " + + "simple duration didn't change"); + } + + SimpleTest.finish(); +} + +window.addEventListener("load", main, false); +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/smil/test/test_smilValues.xhtml b/dom/smil/test/test_smilValues.xhtml new file mode 100644 index 000000000..d2bce96a3 --- /dev/null +++ b/dom/smil/test/test_smilValues.xhtml @@ -0,0 +1,171 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Test for SMIL values</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=557885">Mozilla Bug + 474742</a> +<p id="display"></p> +<div id="content"> +<svg id="svg" xmlns="http://www.w3.org/2000/svg" width="120px" height="120px"> + <circle cx="-100" cy="20" r="15" fill="blue" id="circle"/> +</svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ +/** Test for SMIL values **/ + +var gSvg = document.getElementById("svg"); +SimpleTest.waitForExplicitFinish(); + +function main() +{ + gSvg.pauseAnimations(); + + var testCases = Array(); + + // Single value + testCases.push({ + 'attr' : { 'values': 'a' }, + 'times': [ [ 0, 'a' ] ] + }); + + // The parsing below is based on the following discussion: + // + // http://lists.w3.org/Archives/Public/www-svg/2011Nov/0136.html + // + // In summary: + // * Values lists are semi-colon delimited and semi-colon terminated. + // * However, if there are extra non-whitespace characters after the final + // semi-colon then there's an implied semi-colon at the end. + // + // This differs to what is specified in SVG 1.1 but is consistent with the + // majority of browsers and with existing content (particularly that generated + // by Ikivo Animator). + + // Trailing semi-colon + testCases.push({ + 'attr' : { 'values': 'a;' }, + 'times': [ [ 0, 'a' ], [ 10, 'a' ] ] + }); + + // Trailing semi-colon + whitespace + testCases.push({ + 'attr' : { 'values': 'a; ' }, + 'times': [ [ 0, 'a' ], [ 10, 'a' ] ] + }); + + // Whitespace + trailing semi-colon + testCases.push({ + 'attr' : { 'values': 'a ;' }, + 'times': [ [ 0, 'a' ], [ 10, 'a' ] ] + }); + + // Empty at end + testCases.push({ + 'attr' : { 'values': 'a;;' }, + 'times': [ [ 0, 'a' ], [ 5, '' ], [ 10, '' ] ] + }); + + // Empty at end + whitespace + testCases.push({ + 'attr' : { 'values': 'a;; ' }, + 'times': [ [ 0, 'a' ], [ 4, 'a' ], [ 5, '' ], [ 10, '' ] ] + }); + + // Empty in middle + testCases.push({ + 'attr' : { 'values': 'a;;b' }, + 'times': [ [ 0, 'a' ], [ 5, '' ], [ 10, 'b' ] ] + }); + + // Empty in middle + trailing semi-colon + testCases.push({ + 'attr' : { 'values': 'a;;b;' }, + 'times': [ [ 0, 'a' ], [ 5, '' ], [ 10, 'b' ] ] + }); + + // Whitespace in middle + testCases.push({ + 'attr' : { 'values': 'a; ;b' }, + 'times': [ [ 0, 'a' ], [ 5, '' ], [ 10, 'b' ] ] + }); + + // Empty at start + testCases.push({ + 'attr' : { 'values': ';a' }, + 'times': [ [ 0, '' ], [ 5, 'a' ], [ 10, 'a' ] ] + }); + + // Whitespace at start + testCases.push({ + 'attr' : { 'values': ' ;a' }, + 'times': [ [ 0, '' ], [ 5, 'a' ], [ 10, 'a' ] ] + }); + + // Embedded whitespace + testCases.push({ + 'attr' : { 'values': ' a b ; c d ' }, + 'times': [ [ 0, 'a b' ], [ 5, 'c d' ], [ 10, 'c d' ] ] + }); + + // Whitespace only + testCases.push({ + 'attr' : { 'values': ' ' }, + 'times': [ [ 0, '' ], [ 10, '' ] ] + }); + + for (var i = 0; i < testCases.length; i++) { + gSvg.setCurrentTime(0); + var test = testCases[i]; + + // Create animation elements + var anim = createAnim(test.attr); + + // Run samples + for (var j = 0; j < test.times.length; j++) { + var curSample = test.times[j]; + gSvg.setCurrentTime(curSample[0]); + checkSample(anim, curSample[1], curSample[0], i); + } + + anim.parentNode.removeChild(anim); + } + + SimpleTest.finish(); +} + +function createAnim(attr) +{ + const svgns = "http://www.w3.org/2000/svg"; + var anim = document.createElementNS(svgns, 'animate'); + anim.setAttribute('attributeName','class'); + anim.setAttribute('dur','10s'); + anim.setAttribute('begin','0s'); + anim.setAttribute('fill','freeze'); + for (name in attr) { + anim.setAttribute(name, attr[name]); + } + return document.getElementById('circle').appendChild(anim); +} + +function checkSample(anim, expectedValue, sampleTime, caseNum) +{ + var msg = "Test case " + caseNum + + " (values: '" + anim.getAttribute('values') + "')," + + "t=" + sampleTime + + ": Unexpected sample value:"; + is(typeof anim.targetElement.className, "object"); + is(anim.targetElement.className.animVal, expectedValue, msg); +} + +window.addEventListener("load", main, false); +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/smil/test/test_smilXHR.xhtml b/dom/smil/test/test_smilXHR.xhtml new file mode 100644 index 000000000..d5202090e --- /dev/null +++ b/dom/smil/test/test_smilXHR.xhtml @@ -0,0 +1,88 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Test for SMIL Behavior in Data Documents</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="smilTestUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=529387">Mozilla Bug 529387</a> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ +/** Test for SMIL Behavior in Data Documents, with XMLHttpRequest **/ + +SimpleTest.waitForExplicitFinish(); + +function tryPausing(svg) { + // Check that pausing has no effect + ok(!svg.animationsPaused(), + "shouldn't be paused (because we shouldn't have even started"); + svg.pauseAnimations(); + ok(!svg.animationsPaused(), "attempts to pause should have no effect"); + svg.unpauseAnimations(); + ok(!svg.animationsPaused(), "still shouldn't be paused, after pause/unpause"); +} + +function trySeeking(svg) { + // Check that seeking is ineffective + is(svg.getCurrentTime(), 0, "should start out at time=0"); + svg.setCurrentTime(1); + is(svg.getCurrentTime(), 0, "shouldn't be able to seek away from time=0"); +} + +function tryBeginEnd(anim) { + // Check that beginning / ending a particular animation element will trigger + // exceptions. + var didThrow = false; + ok(anim, "need a non-null animate element"); + try { + anim.beginElement(); + } catch (e) { + didThrow = true; + } + ok(didThrow, "beginElement should fail"); + + didThrow = false; + try { + anim.endElement(); + } catch (e) { + didThrow = true; + } + ok(didThrow, "endElement should fail"); +} + +function main() { + var xhr = new XMLHttpRequest(); + xhr.open("GET", "smilXHR_helper.svg", false); + xhr.send(); + var xdoc = xhr.responseXML; + + var svg = xdoc.getElementById("svg"); + var circ = xdoc.getElementById("circ"); + var animXML = xdoc.getElementById("animXML"); + var animCSS = xdoc.getElementById("animCSS"); + + tryPausing(svg); + trySeeking(svg); + tryBeginEnd(animXML); + tryBeginEnd(animCSS); + + // Check that the actual values of our animated attr/prop aren't affected + is(circ.cx.animVal.value, circ.cx.baseVal.value, + "animation of attribute shouldn't be taking effect"); + is(SMILUtil.getComputedStyleSimple(circ, "opacity"), "1", + "animation of CSS property shouldn't be taking effect"); + + SimpleTest.finish(); +} + +window.addEventListener("load", main, false); +]]> +</script> +</pre> +</body> +</html> |