diff options
Diffstat (limited to '')
59 files changed, 9034 insertions, 0 deletions
diff --git a/dom/smil/test/.eslintrc.js b/dom/smil/test/.eslintrc.js new file mode 100644 index 0000000000..845ed3f013 --- /dev/null +++ b/dom/smil/test/.eslintrc.js @@ -0,0 +1,5 @@ +"use strict"; + +module.exports = { + extends: ["plugin:mozilla/mochitest-test"], +}; diff --git a/dom/smil/test/db_smilAnimateMotion.js b/dom/smil/test/db_smilAnimateMotion.js new file mode 100644 index 0000000000..31c338586f --- /dev/null +++ b/dom/smil/test/db_smilAnimateMotion.js @@ -0,0 +1,309 @@ +/* -*- 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 0000000000..737037271d --- /dev/null +++ b/dom/smil/test/db_smilCSSFromBy.js @@ -0,0 +1,207 @@ +/* -*- 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)", + } + ), + + // The "from" and "by" values in the test case below 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. + // + // That gives us: + // + // to-value = (rgb(100, 100, 100) * 0.6 + rgb(240, 240, 240) * 1.0)) * 1 + // = rgb(300, 300, 300) + // midComp = (rgb(100, 100, 100) * 0.6 * 0.5 + rgb(300, 300, 300) * 1.0 * 0.5) * (1 / 0.8) + // = rgb(225, 225, 225) + // + // + new AnimTestcaseFromBy( + "rgba(100, 100, 100, 0.6)", + "rgba(240, 240, 240, 1)", + { midComp: "rgba(225, 225, 225, 0.8)", 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", + }), + ], + lengthPx: [ + new AnimTestcaseFromBy("0px", "8px", { + fromComp: "0px", + midComp: "4px", + toComp: "8px", + }), + new AnimTestcaseFromBy("1px", "10px", { + fromComp: "1px", + midComp: "6px", + toComp: "11px", + }), + ], + 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"), + ]), + // Bug 1457353: Change from nsColor to StyleComplexColor causes addition + // with currentcolor to break. Bug 1465307 for work to re-enable. + 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.lengthNoUnits, _fromByTestLists.lengthPx) + ), +]; diff --git a/dom/smil/test/db_smilCSSFromTo.js b/dom/smil/test/db_smilCSSFromTo.js new file mode 100644 index 0000000000..daa75eccd1 --- /dev/null +++ b/dom/smil/test/db_smilCSSFromTo.js @@ -0,0 +1,625 @@ +/* -*- 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", + }), + ], + lengthPx: [ + new AnimTestcaseFromTo("0px", "12px", { + fromComp: "0px", + midComp: "6px", + toComp: "12px", + }), + new AnimTestcaseFromTo("16px", "0px", { + fromComp: "16px", + midComp: "8px", + toComp: "0px", + }), + new AnimTestcaseFromTo("10px", "20px", { + fromComp: "10px", + midComp: "15px", + toComp: "20px", + }), + new AnimTestcaseFromTo("41px", "1px", { + fromComp: "41px", + midComp: "21px", + toComp: "1px", + }), + ], + 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: "10px", + midComp: "15px", + toComp: "20px", + }), + new AnimTestcaseFromTo("10px", "20", { + fromComp: "10px", + midComp: "15px", + toComp: "20px", + }), + ], + 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", + }), + ], +}; + +function _tweakForLetterSpacing(testcases) { + return testcases.map(function(t) { + let valMap = Object.assign({}, t.computedValMap); + for (let prop of Object.keys(valMap)) { + if (valMap[prop] == "0px") { + valMap[prop] = "normal"; + } + } + return new AnimTestcaseFromTo(t.from, t.to, valMap); + }); +} + +// 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("alphabetic", "hanging"), + new AnimTestcaseFromTo("mathematical", "central"), + new AnimTestcaseFromTo("middle", "text-after-edge"), + new AnimTestcaseFromTo("text-before-edge", "auto"), + new AnimTestcaseFromTo("alphabetic", "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", { + fromComp: "50%", + midComp: "62.5%", + toComp: "75%", + }), + new AnimTestcaseFromTo("semi-condensed", "semi-expanded", { + fromComp: "87.5%", + midComp: "100%", + toComp: "112.5%", + }), + new AnimTestcaseFromTo("expanded", "ultra-expanded", { + fromComp: "125%", + midComp: "162.5%", + toComp: "200%", + }), + new AnimTestcaseFromTo("ultra-expanded", "inherit", { + fromComp: "200%", + midComp: "150%", + toComp: "100%", + }), + ]), + 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: "550", + 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, + _tweakForLetterSpacing( + [].concat(_fromToTestLists.lengthNoUnits, _fromToTestLists.lengthPx) + ) + ), + 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.lengthNoUnits, + _fromToTestLists.lengthPx, + _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.lengthNoUnits, + _fromToTestLists.lengthPx, + _fromToTestLists.lengthPxPctSVG, + _fromToTestLists.lengthPctSVG, + _fromToTestLists.lengthPxNoUnitsSVG, + [ + new AnimTestcaseFromTo("inherit", "7px", { + fromComp: "1px", + midComp: "4px", + toComp: "7px", + }), + ] + ) + ), + new TestcaseBundle(gPropList.text_anchor, [ + new AnimTestcaseFromTo("start", "middle"), + new AnimTestcaseFromTo("middle", "end"), + ]), + new TestcaseBundle(gPropList.text_decoration_line, [ + 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 0000000000..e5212dd80e --- /dev/null +++ b/dom/smil/test/db_smilCSSPaced.js @@ -0,0 +1,356 @@ +/* -*- 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)", + } + ), + // 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)", + } + ), + ], + currentColor_color: [ + 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)", + } + ), + ], + currentColor_fill: [ + // Bug 1467622 changed the distance calculation + // involving currentColor, comp values below + // are no longer evenly spaced. + new AnimTestcasePaced( + "olive; " + // rgb(128, 128, 0) + "currentColor; " + // rgb(50, 50, 50) + "rgb(206, 150, 206)", + { + comp0: "rgb(128, 128, 0)", + comp1_6: "rgb(99, 99, 18)", + comp1_3: "rgb(71, 71, 37)", + comp2_3: "rgb(111, 89, 111)", + comp1: "rgb(206, 150, 206)", + } + ), + ], + 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", + }), + ], + 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", + }), + ], + 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, + [].concat(_pacedTestLists.color, _pacedTestLists.currentColor_color) + ), + new TestcaseBundle(gPropList.direction, [ + new AnimTestcasePaced("rtl; ltr; rtl"), + ]), + new TestcaseBundle( + gPropList.fill, + [].concat( + _pacedTestLists.color, + _pacedTestLists.currentColor_fill, + _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: "7px, 7px, 7px", + comp1_6: "7px, 8.5px, 5px", + comp1_3: "7px, 10px, 3px", + comp2_3: "4px, 6px, 3px", + comp1: "1px, 2px, 3px", + }), + ]) + ), + new TestcaseBundle( + gPropList.stroke_dashoffset, + [].concat( + _pacedTestLists.lengthNoUnits, + _pacedTestLists.lengthPx, + _pacedTestLists.lengthPctSVG, + _pacedTestLists.lengthPxPctSVG + ) + ), + new TestcaseBundle( + gPropList.stroke_width, + [].concat( + _pacedTestLists.lengthNoUnits, + _pacedTestLists.lengthPx, + _pacedTestLists.lengthPctSVG, + _pacedTestLists.lengthPxPctSVG + ) + ), +]; diff --git a/dom/smil/test/db_smilCSSPropertyList.js b/dom/smil/test/db_smilCSSPropertyList.js new file mode 100644 index 0000000000..237c9db585 --- /dev/null +++ b/dom/smil/test/db_smilCSSPropertyList.js @@ -0,0 +1,104 @@ +/* -*- 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 + 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_line: new NonAdditiveAttribute( + "text-decoration-line", + "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 0000000000..81f71ef32b --- /dev/null +++ b/dom/smil/test/db_smilMappedAttrList.js @@ -0,0 +1,148 @@ +/* -*- 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 + // SVGElement.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_line: new NonAdditiveAttribute( + "text-decoration-line", + "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/file_smilWithTransition.html b/dom/smil/test/file_smilWithTransition.html new file mode 100644 index 0000000000..b91398436b --- /dev/null +++ b/dom/smil/test/file_smilWithTransition.html @@ -0,0 +1,79 @@ +<!doctype html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1315874 +--> +<head> + <meta charset="utf-8"> + <title>Test SMIL does not trigger CSS Transitions (bug 1315874)</title> +</head> +<body> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1315874">Mozilla Bug + 1315874</a> +<svg> + <rect width="100%" height="100%" + style="fill: red; transition: fill 10s" id="rect"> + <animate attributeName="fill" to="lime" dur="1s" fill="freeze"> + </rect> +</svg> +<script type="text/javascript"> + // Bring SimpleTest's function from opener. + if (opener) { + var is = opener.is.bind(opener); + var ok = opener.ok.bind(opener); + function finish() { + var o = opener; + self.close(); + o.SimpleTest.finish(); + } + } + + window.addEventListener('load', runTests); + + var rect = document.getElementById('rect'); + var svg = document.getElementsByTagName('svg')[0]; + is(getComputedStyle(rect).fill, 'rgb(255, 0, 0)', + 'The initial color should be red.'); + + function runTests() { + waitForFrame().then(function() { + svg.setCurrentTime(1); + is(getComputedStyle(rect).fill, 'rgb(0, 255, 0)', + 'The end color should be lime.'); + + return waitForAnimationFrames(2); + }).then(function() { + var anim = document.getAnimations()[0]; + ok(!anim, 'Transition should not be created by restyling for SMIL'); + finish(); + }); + } + + // Utility methods from testcommon.js + // For detail, see dom/animation/test/testcommon.js. + + function waitForFrame() { + return new Promise(function(resolve, reject) { + requestAnimationFrame(function(time) { + resolve(); + }); + }); + } + + function waitForAnimationFrames(frameCount) { + return new Promise(function(resolve, reject) { + function handleFrame() { + if (--frameCount <= 0) { + resolve(); + } else { + window.requestAnimationFrame(handleFrame); + } + } + window.requestAnimationFrame(handleFrame); + }); + } +</script> +</pre> +</body> +</html> diff --git a/dom/smil/test/mochitest.ini b/dom/smil/test/mochitest.ini new file mode 100644 index 0000000000..1b81646dbc --- /dev/null +++ b/dom/smil/test/mochitest.ini @@ -0,0 +1,63 @@ +[DEFAULT] +support-files = + db_smilAnimateMotion.js + db_smilCSSFromBy.js + db_smilCSSFromTo.js + db_smilCSSPaced.js + db_smilCSSPropertyList.js + db_smilMappedAttrList.js + file_smilWithTransition.html + smilAnimateMotionValueLists.js + smilExtDoc_helper.svg + smilTestUtils.js + smilXHR_helper.svg + +[test_smilAccessKey.xhtml] +[test_smilAdditionFallback.html] +[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] +skip-if = true # bug 1358955. +[test_smilConditionalProcessing.html] +[test_smilContainerBinding.xhtml] +[test_smilCrossContainer.xhtml] +[test_smilDynamicDelayedBeginElement.xhtml] +[test_smilExtDoc.xhtml] +[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] +[test_smilReset.xhtml] +[test_smilRestart.xhtml] +[test_smilSetCurrentTime.xhtml] +[test_smilSync.xhtml] +[test_smilSyncTransform.xhtml] +[test_smilSyncbaseTarget.xhtml] +[test_smilTextZoom.xhtml] +[test_smilTiming.xhtml] +[test_smilTimingZeroIntervals.xhtml] +[test_smilUpdatedInterval.xhtml] +[test_smilValues.xhtml] +[test_smilWithTransition.html] +skip-if = toolkit == 'android' && !is_fennec +[test_smilWithXlink.xhtml] +[test_smilXHR.xhtml] diff --git a/dom/smil/test/smilAnimateMotionValueLists.js b/dom/smil/test/smilAnimateMotionValueLists.js new file mode 100644 index 0000000000..6e05ebd7e1 --- /dev/null +++ b/dom/smil/test/smilAnimateMotionValueLists.js @@ -0,0 +1,116 @@ +/* -*- 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", + " 10 ", + " 10deg", + "10deg ", + " 10.1 ", +]; + +const gInvalidRotate = ["10 deg", "10 rad ", "aaa"]; + +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 0000000000..fbd9d091a4 --- /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 0000000000..98aa3750bd --- /dev/null +++ b/dom/smil/test/smilTestUtils.js @@ -0,0 +1,1014 @@ +/* -*- 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() { + return SMILUtil.getFirstElemWithTag("svg"); + }, + + // Returns the first element in the document with the matching tag + getFirstElemWithTag(aTargetTag) { + var elemList = document.getElementsByTagName(aTargetTag); + return elemList.length == 0 ? null : elemList[0]; + }, + + // Simple wrapper for getComputedStyle + getComputedStyleSimple(elem, prop) { + return window.getComputedStyle(elem).getPropertyValue(prop); + }, + + getAttributeValue(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(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() { + return "_motion"; + }, + + // Return stripped px value from specified value. + stripPx: str => str.replace(/px\s*$/, ""), +}; + +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(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(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(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(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() { + return this._begin; + }, + getDur() { + return this._dur; + }, + getEndTime() { + return this._begin + this._dur; + }, + getFractionalTime(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(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(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(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(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(aSeekList, aTargetElem, aTargetAttr) { + var svg = document.getElementById("svg"); + for (var i in aSeekList) { + var entry = aSeekList[i]; + SMILUtil.getSVGRoot().setCurrentTime(entry[0]); + + // Bug 1379908: The computed value of stroke-* properties should be + // serialized with px units, but currently Gecko and Servo don't do that + // when animating these values. + if ( + ["stroke-width", "stroke-dasharray", "stroke-dashoffset"].includes( + aTargetAttr.attrName + ) + ) { + var attr = SMILUtil.stripPx( + SMILUtil.getAttributeValue(aTargetElem, aTargetAttr) + ); + var expectedVal = SMILUtil.stripPx(entry[1]); + is(attr, expectedVal, entry[2]); + return; + } + is( + SMILUtil.getAttributeValue(aTargetElem, aTargetAttr), + entry[1], + entry[2] + ); + } + }, + + // methods that expect to be overridden in subclasses + buildSeekListStatic(aAnimAttr, aBaseVal, aTimeData, aReasonStatic) {}, + buildSeekListAnimated(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(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(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(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(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(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(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(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(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(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(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(aPathDescription) { + var path = document.createElementNS(SVG_NS, "path"); + path.setAttribute("d", aPathDescription); + path.setAttribute("id", MPATH_TARGET_ID); + return SMILUtil.getSVGRoot().appendChild(path); + }, + + createMpath(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(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(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(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 0000000000..cb0b51c360 --- /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 0000000000..5910dc1c27 --- /dev/null +++ b/dom/smil/test/test_smilAccessKey.xhtml @@ -0,0 +1,79 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Test for SMIL accessKey support</title> + <script 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 lack of SMIL accessKey support **/ + +const gSvgns = 'http://www.w3.org/2000/svg'; +SimpleTest.waitForExplicitFinish(); + +function main() +{ + testBeginValueIsNotSupported('accessKey(a)'); + testBeginValueIsNotSupported('accesskey(a)'); + + is(getStartTime('accesskey(a); 1s'), 1, + 'Start time for accesskey(a) followed by a literal time'); + is(getStartTime('3s; accessKey(a)'), 3, + 'Start time for accesskey(a) preceded by a literal time'); + + SimpleTest.finish(); +} + +function createAnim(beginSpec) { + const 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 testBeginValueIsNotSupported(beginSpec) { + const anim = createAnim(beginSpec); + + try { + anim.getStartTime(); + ok(false, + `Should have failed to get start time for begin value: ${beginSpec}`); + } catch(e) { + is(e.name, 'InvalidStateError', `Unexpected exception: ${e.name}`); + is(e.code, DOMException.INVALID_STATE_ERR, + `Unexpected exception code: ${e.code}`); + } + + anim.remove(); +} + +function getStartTime(beginSpec) { + const anim = createAnim(beginSpec); + let startTime; + try { + startTime = anim.getStartTime(); + } catch (e) { } + anim.remove(); + + return startTime; +} + +window.addEventListener('load', main); +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/smil/test/test_smilAdditionFallback.html b/dom/smil/test/test_smilAdditionFallback.html new file mode 100644 index 0000000000..a73457728c --- /dev/null +++ b/dom/smil/test/test_smilAdditionFallback.html @@ -0,0 +1,30 @@ +<!doctype html> +<meta charset=utf-8> +<head> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<p id=display></p> +<div id=content> +<svg id=svg> +<!-- These two animations will have a default duration of indefinite which means + they will keep producing their first value forever --> +<animate calcMode="discrete" attributeName="height" by="10" dur="1s" + fill="freeze"/> +<animate calcMode="discrete" attributeName="height" by="10" dur="1s" + fill="freeze"/> +</svg> +</div> +<pre id="test"> +<script> +'use strict'; + +SimpleTest.waitForExplicitFinish(); + +window.addEventListener('load', () => { + const svg = document.getElementById('svg'); + is(getComputedStyle(svg).height, '0px', 'Computed height should be zero'); + SimpleTest.finish(); +}); +</script> +</pre> diff --git a/dom/smil/test/test_smilAnimateMotion.xhtml b/dom/smil/test/test_smilAnimateMotion.xhtml new file mode 100644 index 0000000000..68ac7fb96a --- /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 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); +]]> +</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 0000000000..c2d72e5435 --- /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 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.remove(); +} + +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); +]]> +</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 0000000000..3a147cd094 --- /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 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.remove(); +} + +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); +]]> +</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 0000000000..20974e6de3 --- /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 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.remove(); + animB.remove(); + } + + 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); +]]> +</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 0000000000..01988a881b --- /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 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", "50%"], + ["extra-condensed", "62.5%"], + ["condensed", "75%"], + ["semi-condensed", "87.5%"], + ["normal", "100%"], + ["semi-expanded", "112.5%"], + ["expanded", "125%"], + ["extra-expanded", "150%"], + ["ultra-expanded", "200%"], +]; + +function testFontStretchValue([baseValue, computedValue], [narrowerStep, computedNarrowerStep], [widerStep, computedWiderStep]) +{ + 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), computedNarrowerStep, + "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), computedWiderStep, + "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); +]]> +</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 0000000000..586305fb8a --- /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 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); +]]> +</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 0000000000..501adbc4a8 --- /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 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); +]]> +</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 0000000000..4e262d3aa9 --- /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 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); +]]> +</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 0000000000..5e8e682af9 --- /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 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); +]]> +</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 0000000000..1f6b3509ae --- /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 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); +]]> +</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 0000000000..97fe131678 --- /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 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.remove(); // 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.firstChild.remove(); +} + +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.firstChild.remove(); +} + +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.firstChild.remove(); +} + +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.firstChild.remove(); +} + +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.firstChild.remove(); +} + +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.firstChild.remove(); +} + +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.firstChild.remove(); +} + +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.firstChild.remove(); +} + +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.firstChild.remove(); +} + +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.firstChild.remove(); +} + +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.firstChild.remove(); +} + +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.firstChild.remove(); +} + +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.firstChild.remove(); +} + +function testCalcFontSize() +{ + setupTest(); + gCircleParent.setAttribute("font-size", "10px"); // At first: font-size: 10px + var anim = createAnimSetTo("font-size", "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.firstChild.remove(); +} + +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.firstChild.remove(); +} + +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); +]]> +</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 0000000000..302c445b6e --- /dev/null +++ b/dom/smil/test/test_smilConditionalProcessing.html @@ -0,0 +1,93 @@ +<!doctype html> +<html> +<head> + <meta charset="utf-8"> + <title>Test conditional processing tests applied to animations</title> + <script 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"> + +SimpleTest.waitForExplicitFinish(); + +function run() { + SpecialPowers.pushPrefEnv({"set": [["intl.accept_languages", "en"]]}, runInternal); +} + +function runInternal() { + 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"); + + SimpleTest.finish(); +} + +function hasUnresolvedStartTime(anim) { + // getStartTime throws INVALID_STATE_ERR when there is no current interval + try { + anim.getStartTime(); + return false; + } catch (e) { + return true; + } +} + +window.addEventListener("load", run); + +</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 0000000000..20e1013d4b --- /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 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.remove(); + 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.remove(); + 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); +]]> +</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 0000000000..e4d30ef26d --- /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 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.remove(); + 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); +]]> +</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 0000000000..10da496454 --- /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 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); + +]]> +</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 0000000000..0323cbfb77 --- /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 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); +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 0000000000..ed270863bd --- /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 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.remove(); + } + 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); +]]> +</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 0000000000..a54139a898 --- /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 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); +]]> +</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 0000000000..f854dd7cd9 --- /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 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); +]]> +</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 0000000000..38e4bd3b9b --- /dev/null +++ b/dom/smil/test/test_smilHyperlinking.xhtml @@ -0,0 +1,229 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Test for hyperlinking</title> + <script 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.remove(); + } + 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() { + window.setTimeout(fireLinkPart2, 0, callback); + }, {once: true}); + } +} + +function fireLinkPart2(callback) { + window.addEventListener("hashchange", + function() { + window.setTimeout(callback, 0); + }, {once: true}); + window.location.hash = '#anim'; +} + +window.addEventListener("load", continueTest); +]]> +</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 0000000000..d6a262fc8d --- /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 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); + + // 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 0000000000..c12f6f1e09 --- /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 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.remove(); + } + 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 -- SMILKeySpline(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.remove(); + } +} + +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.remove(); + } +} + +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); +]]> +</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 0000000000..43d3c91895 --- /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 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.remove(); + } + + // 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.remove(); + + 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). + getPropertyValue('stroke-linecap'); + is(actualValue, expectedValue, msg); +} + +window.addEventListener("load", main); +]]> +</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 0000000000..f2d6571fa8 --- /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 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.remove(); // 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.remove(); // 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.remove(); // 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); +]]> +</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 0000000000..5b70b5b83f --- /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 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); +]]> +</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 0000000000..57ef83800e --- /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 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); +]]> +</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 0000000000..81adea6390 --- /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 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); +]]> +</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 0000000000..f0bdd42502 --- /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 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); + + 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 0000000000..b746bb45d4 --- /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 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); + + // 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 0000000000..ace63d37b0 --- /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 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); +]]> +</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 0000000000..a53f73c112 --- /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 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); +]]> +</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 0000000000..3e03dfdbcc --- /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 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); +]]> +</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 0000000000..91ded84c8c --- /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 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); +]]> +</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 0000000000..36b2a91198 --- /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 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); +]]> +</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 0000000000..79c5cbf0b9 --- /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 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); +]]> +</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 0000000000..496cb6751e --- /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 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.remove(); +} + +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.remove(); +} + +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.remove(); + 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.remove(); + 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); +]]> +</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 0000000000..5f65bd778e --- /dev/null +++ b/dom/smil/test/test_smilTextZoom.xhtml @@ -0,0 +1,99 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Test for SMIL Animation Behavior with textZoom</title> + <script 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); + + // Bug 1379908: The computed value of stroke-* properties should be + // serialized with px units, but currently Gecko and Servo don't do that + // when animating these values. + if ('stroke-width' == aPropertyName) { + var expectedVal = SMILUtil.stripPx(aExpectedVal); + var unitlessComputedVal = SMILUtil.stripPx(computedVal); + is(unitlessComputedVal, expectedVal, "computed value of " + aPropertyName); + return; + } + 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", "20px"); + svg.setCurrentTime(1.5); + verifyStyle(text, "font-size", "30px"); + verifyStyle(rect, "stroke-width", "30px"); + svg.setCurrentTime(2); + verifyStyle(text, "font-size", "40px"); + verifyStyle(rect, "stroke-width", "40px"); + svg.setCurrentTime(3); + verifyStyle(text, "font-size", "40px"); + verifyStyle(rect, "stroke-width", "40px"); + } 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); +]]> +</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 0000000000..0dc8525382 --- /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 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;;', 3)); + testCases.push(StartTimeTest('3;; ', 3)); + testCases.push(StartTimeTest(';3', 3)); + testCases.push(StartTimeTest(' ;3', 3)); + 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.remove(); + } + + 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); +]]> +</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 0000000000..d54b74600b --- /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 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.remove(); +} + +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); +]]> +</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 0000000000..3045a815de --- /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 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); +]]> +</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 0000000000..b25a153472 --- /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 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.remove(); + } + + 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); +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/smil/test/test_smilWithTransition.html b/dom/smil/test/test_smilWithTransition.html new file mode 100644 index 0000000000..f464f9a39d --- /dev/null +++ b/dom/smil/test/test_smilWithTransition.html @@ -0,0 +1,18 @@ +<!doctype html> +<meta charset=utf-8> +<head> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<pre id="test"> +<script> +'use strict'; + +SimpleTest.waitForExplicitFinish(); +SpecialPowers.pushPrefEnv( + { "set": [["dom.animations-api.getAnimations.enabled", true]]}, + function() { + window.open("file_smilWithTransition.html"); + }); +</script> +</html> diff --git a/dom/smil/test/test_smilWithXlink.xhtml b/dom/smil/test/test_smilWithXlink.xhtml new file mode 100644 index 0000000000..16c6016deb --- /dev/null +++ b/dom/smil/test/test_smilWithXlink.xhtml @@ -0,0 +1,48 @@ +<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>Test for animate with xlink:href attribute.</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style>
+ div#target {
+ width: 300px;
+ height: 100px;
+ background-color: red;
+ }
+ </style>
+</head>
+<body>
+<p id="display"></p>
+<div id="target"></div>
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ id="svg">
+ <animate xlink:href="#target"
+ attributeName="width" from="0" to="200" dur="10s" fill="freeze"/>
+</svg>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+function runTest() {
+ var svg = document.getElementById("svg");
+ var target = document.getElementById("target");
+
+ svg.pauseAnimations();
+ svg.setCurrentTime(5);
+
+ var cs = getComputedStyle(target);
+ is(cs.width, "100px", "SMIL should affect outer element.");
+
+ SimpleTest.finish();
+ return;
+}
+
+window.addEventListener("load", runTest);
+
+]]>
+</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 0000000000..eb0f84268c --- /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 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); +]]> +</script> +</pre> +</body> +</html> |