summaryrefslogtreecommitdiffstats
path: root/dom/smil/test
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /dom/smil/test
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/smil/test')
-rw-r--r--dom/smil/test/db_smilAnimateMotion.js309
-rw-r--r--dom/smil/test/db_smilCSSFromBy.js207
-rw-r--r--dom/smil/test/db_smilCSSFromTo.js625
-rw-r--r--dom/smil/test/db_smilCSSPaced.js356
-rw-r--r--dom/smil/test/db_smilCSSPropertyList.js104
-rw-r--r--dom/smil/test/db_smilMappedAttrList.js148
-rw-r--r--dom/smil/test/file_smilWithTransition.html79
-rw-r--r--dom/smil/test/mochitest.ini63
-rw-r--r--dom/smil/test/smilAnimateMotionValueLists.js116
-rw-r--r--dom/smil/test/smilExtDoc_helper.svg7
-rw-r--r--dom/smil/test/smilTestUtils.js1014
-rw-r--r--dom/smil/test/smilXHR_helper.svg8
-rw-r--r--dom/smil/test/test_smilAccessKey.xhtml79
-rw-r--r--dom/smil/test/test_smilAdditionFallback.html30
-rw-r--r--dom/smil/test/test_smilAnimateMotion.xhtml51
-rw-r--r--dom/smil/test/test_smilAnimateMotionInvalidValues.xhtml176
-rw-r--r--dom/smil/test/test_smilAnimateMotionOverrideRules.xhtml215
-rw-r--r--dom/smil/test/test_smilBackwardsSeeking.xhtml191
-rw-r--r--dom/smil/test/test_smilCSSFontStretchRelative.xhtml102
-rw-r--r--dom/smil/test/test_smilCSSFromBy.xhtml49
-rw-r--r--dom/smil/test/test_smilCSSFromTo.xhtml76
-rw-r--r--dom/smil/test/test_smilCSSInherit.xhtml85
-rw-r--r--dom/smil/test/test_smilCSSInvalidValues.xhtml59
-rw-r--r--dom/smil/test/test_smilCSSPaced.xhtml44
-rw-r--r--dom/smil/test/test_smilChangeAfterFrozen.xhtml571
-rw-r--r--dom/smil/test/test_smilConditionalProcessing.html93
-rw-r--r--dom/smil/test/test_smilContainerBinding.xhtml101
-rw-r--r--dom/smil/test/test_smilCrossContainer.xhtml132
-rw-r--r--dom/smil/test/test_smilDynamicDelayedBeginElement.xhtml103
-rw-r--r--dom/smil/test/test_smilExtDoc.xhtml80
-rw-r--r--dom/smil/test/test_smilFillMode.xhtml86
-rw-r--r--dom/smil/test/test_smilGetSimpleDuration.xhtml86
-rw-r--r--dom/smil/test/test_smilGetStartTime.xhtml103
-rw-r--r--dom/smil/test/test_smilHyperlinking.xhtml229
-rw-r--r--dom/smil/test/test_smilInvalidValues.html113
-rw-r--r--dom/smil/test/test_smilKeySplines.xhtml296
-rw-r--r--dom/smil/test/test_smilKeyTimes.xhtml391
-rw-r--r--dom/smil/test/test_smilKeyTimesPacedMode.xhtml123
-rw-r--r--dom/smil/test/test_smilMappedAttrFromBy.xhtml51
-rw-r--r--dom/smil/test/test_smilMappedAttrFromTo.xhtml79
-rw-r--r--dom/smil/test/test_smilMappedAttrPaced.xhtml46
-rw-r--r--dom/smil/test/test_smilMinTiming.html93
-rw-r--r--dom/smil/test/test_smilRepeatDuration.html139
-rw-r--r--dom/smil/test/test_smilRepeatTiming.xhtml96
-rw-r--r--dom/smil/test/test_smilReset.xhtml82
-rw-r--r--dom/smil/test/test_smilRestart.xhtml102
-rw-r--r--dom/smil/test/test_smilSetCurrentTime.xhtml76
-rw-r--r--dom/smil/test/test_smilSync.xhtml255
-rw-r--r--dom/smil/test/test_smilSyncTransform.xhtml66
-rw-r--r--dom/smil/test/test_smilSyncbaseTarget.xhtml180
-rw-r--r--dom/smil/test/test_smilTextZoom.xhtml99
-rw-r--r--dom/smil/test/test_smilTiming.xhtml291
-rw-r--r--dom/smil/test/test_smilTimingZeroIntervals.xhtml285
-rw-r--r--dom/smil/test/test_smilUpdatedInterval.xhtml64
-rw-r--r--dom/smil/test/test_smilValues.xhtml171
-rw-r--r--dom/smil/test/test_smilWithTransition.html18
-rw-r--r--dom/smil/test/test_smilWithXlink.xhtml48
-rw-r--r--dom/smil/test/test_smilXHR.xhtml88
58 files changed, 9029 insertions, 0 deletions
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..e33c38819c
--- /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", { midComp: "oblique 7deg" }),
+ ]),
+ 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..48fbe9a154
--- /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(98, 98, 19)",
+ comp1_3: "rgb(67, 67, 39)",
+ comp2_3: "rgb(115, 92, 115)",
+ 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..8b6bddd4e1
--- /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 ? 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..49e9e7f7b0
--- /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) {
+ 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>