--- a/devtools/client/animationinspector/test/browser.ini
+++ b/devtools/client/animationinspector/test/browser.ini
@@ -9,16 +9,17 @@ support-files =
doc_keyframes.html
doc_modify_playbackRate.html
doc_negative_animation.html
doc_pseudo_elements.html
doc_script_animation.html
doc_short_duration_animation.html
doc_simple_animation.html
doc_multiple_animation_types.html
+ doc_multiple_easings.html
doc_multiple_property_types.html
doc_timing_combination_animation.html
head.js
!/devtools/client/commandline/test/helpers.js
!/devtools/client/framework/test/shared-head.js
!/devtools/client/inspector/test/head.js
!/devtools/client/inspector/test/shared-head.js
!/devtools/client/shared/test/test-actor-registry.js
@@ -45,16 +46,17 @@ skip-if = os == "linux" && !debug # Bug
[browser_animation_refresh_on_removed_animation.js]
skip-if = os == "linux" && !debug # Bug 1227792
[browser_animation_refresh_when_active.js]
[browser_animation_running_on_compositor.js]
[browser_animation_same_nb_of_playerWidgets_and_playerFronts.js]
[browser_animation_shows_player_on_valid_node.js]
[browser_animation_spacebar_toggles_animations.js]
[browser_animation_spacebar_toggles_node_animations.js]
+[browser_animation_summarygraph_for_multiple_easings.js]
[browser_animation_target_highlight_select.js]
[browser_animation_target_highlighter_lock.js]
[browser_animation_timeline_currentTime.js]
[browser_animation_timeline_header.js]
[browser_animation_timeline_iterationStart.js]
[browser_animation_timeline_pause_button_01.js]
[browser_animation_timeline_pause_button_02.js]
[browser_animation_timeline_pause_button_03.js]
--- a/devtools/client/animationinspector/test/browser_animation_animated_properties_path.js
+++ b/devtools/client/animationinspector/test/browser_animation_animated_properties_path.js
@@ -16,16 +16,17 @@
requestLongerTimeout(5);
const TEST_CASES = [
{
"background-color": {
expectedClass: "color",
expectedValues: [
+ { x: 0, y: 0 },
{ x: 0, y: 1, color: "rgb(255, 0, 0)" },
{ x: 1000, y: 1, color: "rgb(0, 255, 0)" }
]
},
"font-size": {
expectedClass: "length",
expectedValues: [
{ x: 0, y: 0 },
@@ -63,63 +64,69 @@ const TEST_CASES = [
]
}
},
{
"background-color": {
expectedClass: "color",
expectedValues: [
+ { x: 0, y: 0 },
{ x: 0, y: 1, color: "rgb(0, 255, 0)" },
{ x: 1000, y: 1, color: "rgb(255, 0, 0)" }
]
},
"font-size": {
expectedClass: "length",
expectedValues: [
+ { x: 0, y: 0 },
{ x: 0, y: 1 },
{ x: 1000, y: 0 },
]
},
"margin-left": {
expectedClass: "coord",
expectedValues: [
+ { x: 0, y: 0 },
{ x: 0, y: 1 },
{ x: 1000, y: 0 },
]
},
"opacity": {
expectedClass: "opacity",
expectedValues: [
+ { x: 0, y: 0 },
{ x: 0, y: 1 },
{ x: 1000, y: 0 },
]
},
"text-align": {
expectedClass: "discrete",
expectedValues: [
{ x: 0, y: 0 },
{ x: 499.999, y: 0 },
{ x: 500, y: 1 },
{ x: 1000, y: 1 },
]
},
"transform": {
expectedClass: "transform",
expectedValues: [
+ { x: 0, y: 0 },
{ x: 0, y: 1 },
{ x: 1000, y: 0 },
]
}
},
{
"background-color": {
expectedClass: "color",
expectedValues: [
+ { x: 0, y: 0 },
{ x: 0, y: 1, color: "rgb(255, 0, 0)" },
{ x: 500, y: 1, color: "rgb(0, 0, 255)" },
{ x: 1000, y: 1, color: "rgb(0, 255, 0)" }
]
},
"font-size": {
expectedClass: "length",
expectedValues: [
@@ -159,20 +166,22 @@ const TEST_CASES = [
expectedClass: "transform",
expectedValues: [
{ x: 0, y: 0 },
{ x: 500, y: 1 },
{ x: 1000, y: 0 },
]
}
},
+
{
"background-color": {
expectedClass: "color",
expectedValues: [
+ { x: 0, y: 0 },
{ x: 0, y: 1, color: "rgb(255, 0, 0)" },
{ x: 499.999, y: 1, color: "rgb(255, 0, 0)" },
{ x: 500, y: 1, color: "rgb(128, 128, 0)" },
{ x: 999.999, y: 1, color: "rgb(128, 128, 0)" },
{ x: 1000, y: 1, color: "rgb(0, 255, 0)" }
]
},
"font-size": {
@@ -237,70 +246,26 @@ const TEST_CASES = [
}
];
add_task(function* () {
yield addTab(URL_ROOT + "doc_multiple_property_types.html");
const {panel} = yield openAnimationInspector();
const timelineComponent = panel.animationsTimelineComponent;
const detailEl = timelineComponent.details.containerEl;
+ const hasClosePath = true;
for (let i = 0; i < TEST_CASES.length; i++) {
info(`Click to select the animation[${ i }]`);
yield clickOnAnimation(panel, i);
const timeBlock = timelineComponent.timeBlocks[0];
const state = timeBlock.animation.state;
const properties = TEST_CASES[i];
for (let property in properties) {
const testcase = properties[property];
info(`Test path of ${ property }`);
const className = testcase.expectedClass;
const pathEl = detailEl.querySelector(`path.${ className }`);
ok(pathEl, `Path element with class '${ className }' should exis`);
- checkPathSegments(pathEl, state, testcase.expectedValues);
+ assertPathSegments(pathEl, state.duration, hasClosePath, testcase.expectedValues);
}
}
});
-
-function checkPathSegments(pathEl, { duration }, expectedValues) {
- const pathSegList = pathEl.pathSegList;
-
- const firstPathSeg = pathSegList.getItem(0);
- is(firstPathSeg.x, 0, "The x of first segment should be 0");
- is(firstPathSeg.y, 0, "The y of first segment should be 0");
-
- expectedValues.forEach(expectedValue => {
- ok(hasSegment(pathSegList, expectedValue.x, expectedValue.y),
- `The path segment of x ${ expectedValue.x }, y ${ expectedValue.y } should exist`);
-
- if (expectedValue.color) {
- checkColor(pathEl.closest("svg"), expectedValue.x / duration, expectedValue.color);
- }
- });
-
- const closePathSeg = pathSegList.getItem(pathSegList.numberOfItems - 1);
- is(closePathSeg.pathSegType, closePathSeg.PATHSEG_CLOSEPATH,
- "The actual last segment should be close path");
-}
-
-function checkColor(svgEl, offset, color) {
- const stopEl = findStopElement(svgEl, offset);
- ok(stopEl, `stop element at offset ${ offset } should exist`);
- is(stopEl.getAttribute("stop-color"), color,
- `stop-color of stop element at offset ${ offset } should be ${ color }`);
-}
-
-function hasSegment(pathSegList, x, y) {
- for (let i = 1; i < pathSegList.numberOfItems - 1; i++) {
- const pathSeg = pathSegList.getItem(i);
- if (parseFloat(pathSeg.x.toFixed(3)) === x &&
- parseFloat(pathSeg.y.toFixed(6)) === y) {
- return true;
- }
- }
- return false;
-}
-
-function findStopElement(svgEl, offset) {
- return [...svgEl.querySelectorAll("stop")].find(stopEl => {
- return stopEl.getAttribute("offset") == offset;
- });
-}
new file mode 100644
--- /dev/null
+++ b/devtools/client/animationinspector/test/browser_animation_summarygraph_for_multiple_easings.js
@@ -0,0 +1,149 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+requestLongerTimeout(2);
+
+// Test summary graph for animations that have multiple easing.
+// There are two ways that we can set the easing for animations.
+// One is effect easing, another one is keyframe easing for properties.
+// The summary graph shows effect easing as dashed line if the easing is not 'linear'.
+// If 'linear', does not show.
+// Also, shows graph which combine the keyframe easings of all properties.
+
+const TEST_CASES = {
+ "no-easing": {
+ expectedKeyframeEasingGraphs: [
+ [
+ { x: 0, y: 0 },
+ { x: 25000, y: 0.25 },
+ { x: 50000, y: 0.5 },
+ { x: 75000, y: 0.75 },
+ { x: 99000, y: 0.99 },
+ { x: 100000, y: 1 },
+ { x: 100000, y: 0 },
+ ]
+ ]
+ },
+ "effect-easing": {
+ expectedEffectEasingGraph: [
+ { x: 0, y: 0 },
+ { x: 49999, y: 0.0 },
+ { x: 50000, y: 0.5 },
+ { x: 99999, y: 0.5 },
+ { x: 100000, y: 0 },
+ ],
+ expectedKeyframeEasingGraphs: [
+ [
+ { x: 0, y: 0 },
+ { x: 49999, y: 0.0 },
+ { x: 50000, y: 0.5 },
+ { x: 99999, y: 0.5 },
+ { x: 100000, y: 1 },
+ { x: 100000, y: 0 },
+ ]
+ ]
+ },
+ "keyframe-easing": {
+ expectedKeyframeEasingGraphs: [
+ [
+ { x: 0, y: 0 },
+ { x: 49999, y: 0.0 },
+ { x: 50000, y: 0.5 },
+ { x: 99999, y: 0.5 },
+ { x: 100000, y: 1 },
+ { x: 100000, y: 0 },
+ ]
+ ]
+ },
+ "both-easing": {
+ expectedEffectEasingGraph: [
+ { x: 0, y: 0 },
+ { x: 9999, y: 0.0 },
+ { x: 10000, y: 0.1 },
+ { x: 19999, y: 0.1 },
+ { x: 20000, y: 0.2 },
+ { x: 29999, y: 0.2 },
+ { x: 30000, y: 0.3 },
+ { x: 39999, y: 0.3 },
+ { x: 40000, y: 0.4 },
+ { x: 49999, y: 0.4 },
+ { x: 50000, y: 0.5 },
+ { x: 59999, y: 0.5 },
+ { x: 60000, y: 0.6 },
+ { x: 69999, y: 0.6 },
+ { x: 70000, y: 0.7 },
+ { x: 79999, y: 0.7 },
+ { x: 80000, y: 0.8 },
+ { x: 89999, y: 0.8 },
+ { x: 90000, y: 0.9 },
+ { x: 99999, y: 0.9 },
+ { x: 100000, y: 0 },
+ ],
+ expectedKeyframeEasingGraphs: [
+ [
+ // KeyframeEffect::GetProperties returns sorted properties by the name.
+ // Therefor, the test html, the 'marginLeft' is upper than 'opacity'.
+ { x: 0, y: 0 },
+ { x: 19999, y: 0.0 },
+ { x: 20000, y: 0.2 },
+ { x: 39999, y: 0.2 },
+ { x: 40000, y: 0.4 },
+ { x: 59999, y: 0.4 },
+ { x: 60000, y: 0.6 },
+ { x: 79999, y: 0.6 },
+ { x: 80000, y: 0.8 },
+ { x: 99999, y: 0.8 },
+ { x: 100000, y: 1 },
+ { x: 100000, y: 0 },
+ ],
+ [
+ { x: 0, y: 0 },
+ { x: 49999, y: 0.0 },
+ { x: 50000, y: 0.5 },
+ { x: 99999, y: 0.5 },
+ { x: 100000, y: 1 },
+ { x: 100000, y: 0 },
+ ]
+ ]
+ }
+};
+
+add_task(function* () {
+ yield addTab(URL_ROOT + "doc_multiple_easings.html");
+ const { panel } = yield openAnimationInspector();
+ const timelineComponent = panel.animationsTimelineComponent;
+ timelineComponent.timeBlocks.forEach(timeBlock => {
+ const state = timeBlock.animation.state;
+ const testcase = TEST_CASES[state.name];
+ if (!testcase) {
+ return;
+ }
+
+ info(`Test effect easing graph of ${ state.name }`);
+ const effectEl = timeBlock.containerEl.querySelector(".effect-easing");
+ if (testcase.expectedEffectEasingGraph) {
+ ok(effectEl, "<g> element for effect easing should exist");
+ const pathEl = effectEl.querySelector("path");
+ ok(pathEl, "<path> element for effect easing should exist");
+ assertPathSegments(pathEl, state.duration, false,
+ testcase.expectedEffectEasingGraph);
+ } else {
+ ok(!effectEl, "<g> element for effect easing should not exist");
+ }
+
+ info(`Test keyframes easing graph of ${ state.name }`);
+ const keyframeEls = timeBlock.containerEl.querySelectorAll(".keyframes-easing");
+ const expectedKeyframeEasingGraphs = testcase.expectedKeyframeEasingGraphs;
+ is(keyframeEls.length, expectedKeyframeEasingGraphs.length,
+ `There should be ${ expectedKeyframeEasingGraphs.length } <g> elements `
+ + `for keyframe easing`);
+ expectedKeyframeEasingGraphs.forEach((expectedValues, index) => {
+ const pathEl = keyframeEls[index].querySelector("path");
+ ok(pathEl, "<path> element for keyframe easing should exist");
+ assertPathSegments(pathEl, state.duration, false, expectedValues);
+ });
+ });
+});
--- a/devtools/client/animationinspector/test/browser_animation_timeline_iterationStart.js
+++ b/devtools/client/animationinspector/test/browser_animation_timeline_iterationStart.js
@@ -45,22 +45,25 @@ function checkAnimationTooltip(el, {iter
let regex = new RegExp("Iteration start: " + iterationStartString +
" \\(" + iterationStartTimeString + "s\\)");
ok(title.match(regex), "The tooltip shows the expected iteration start");
}
function checkProgressAtStartingTime(el, { iterationStart }) {
info("Check the progress of starting time");
- const pathEl = el.querySelector(".iteration-path");
- const pathSegList = pathEl.pathSegList;
- const pathSeg = pathSegList.getItem(1);
- const progress = pathSeg.y;
- is(progress, iterationStart % 1,
- `The progress at starting point should be ${ iterationStart % 1 }`);
+ const groupEls = el.querySelectorAll("svg g");
+ groupEls.forEach(groupEl => {
+ const pathEl = groupEl.querySelector(".iteration-path");
+ const pathSegList = pathEl.pathSegList;
+ const pathSeg = pathSegList.getItem(1);
+ const progress = pathSeg.y;
+ is(progress, iterationStart % 1,
+ `The progress at starting point should be ${ iterationStart % 1 }`);
+ });
}
function checkKeyframeOffset(timeBlockEl, frameEl, {iterationStart}) {
info("Check that the first keyframe is offset correctly");
let start = getKeyframeOffset(frameEl);
is(start, 0, "The frame offset for iteration start");
}
--- a/devtools/client/animationinspector/test/browser_animation_timeline_short_duration.js
+++ b/devtools/client/animationinspector/test/browser_animation_timeline_short_duration.js
@@ -34,69 +34,74 @@ add_task(function* () {
timeBlock = timelineComponent.timeBlocks[0];
containerEl = timeBlock.containerEl;
state = timeBlock.animation.state;
checkSummaryGraph(containerEl, state, true);
});
function checkSummaryGraph(el, state, isDetail) {
info("Check the coordinates of summary graph");
- const pathEls = el.querySelectorAll(".iteration-path");
- let expectedIterationCount = 0;
- if (isDetail) {
- expectedIterationCount = state.iterationCount ? state.iterationCount : 1;
- } else {
- expectedIterationCount = state.iterationCount ? state.iterationCount : 2;
- }
- is(pathEls.length, expectedIterationCount,
- `The count of path shoud be ${ expectedIterationCount }`);
- pathEls.forEach((pathEl, index) => {
- const startX = index * state.duration;
- const endX = startX + state.duration;
+ const groupEls = el.querySelectorAll("svg g");
+ groupEls.forEach(groupEl => {
+ const pathEls = groupEl.querySelectorAll(".iteration-path");
+ let expectedIterationCount = 0;
+ if (isDetail) {
+ expectedIterationCount = state.iterationCount ? state.iterationCount : 1;
+ } else {
+ expectedIterationCount = state.iterationCount ? state.iterationCount : 2;
+ }
+ is(pathEls.length, expectedIterationCount,
+ `The count of path shoud be ${ expectedIterationCount }`);
+ pathEls.forEach((pathEl, index) => {
+ const startX = index * state.duration;
+ const endX = startX + state.duration;
- const pathSegList = pathEl.pathSegList;
- const firstPathSeg = pathSegList.getItem(0);
- is(firstPathSeg.x, startX,
- `The x of first segment should be ${ startX }`);
- is(firstPathSeg.y, 0, "The y of first segment should be 0");
+ const pathSegList = pathEl.pathSegList;
+ const firstPathSeg = pathSegList.getItem(0);
+ is(firstPathSeg.x, startX,
+ `The x of first segment should be ${ startX }`);
+ is(firstPathSeg.y, 0, "The y of first segment should be 0");
- // The easing of test animation is 'linear'.
- // Therefore, the y of second path segment will be 0.
- const secondPathSeg = pathSegList.getItem(1);
- is(secondPathSeg.x, startX,
- `The x of second segment should be ${ startX }`);
- is(secondPathSeg.y, 0, "The y of second segment should be 0");
+ // The easing of test animation is 'linear'.
+ // Therefore, the y of second path segment will be 0.
+ const secondPathSeg = pathSegList.getItem(1);
+ is(secondPathSeg.x, startX,
+ `The x of second segment should be ${ startX }`);
+ is(secondPathSeg.y, 0, "The y of second segment should be 0");
- const thirdLastPathSeg = pathSegList.getItem(pathSegList.numberOfItems - 4);
- approximate(thirdLastPathSeg.x, endX - 0.001, 0.005,
- `The x of third last segment should be approximately ${ endX - 0.001 }`);
- approximate(thirdLastPathSeg.y, 0.999, 0.005,
- " The y of third last segment should be approximately "
- + thirdLastPathSeg.x);
+ const thirdLastPathSeg = pathSegList.getItem(pathSegList.numberOfItems - 4);
+ approximate(thirdLastPathSeg.x, endX - 0.001, 0.005,
+ "The x of third last segment should be approximately "
+ + (endX - 0.001));
+ approximate(thirdLastPathSeg.y, 0.999, 0.005,
+ " The y of third last segment should be approximately "
+ + thirdLastPathSeg.x);
- // The test animation is not 'forwards' fill-mode.
- // Therefore, the y of second last path segment will be 0.
- const secondLastPathSeg =
- pathSegList.getItem(pathSegList.numberOfItems - 3);
- is(secondLastPathSeg.x, endX,
- `The x of second last segment should be ${ endX }`);
- // We use computed style of 'opacity' to create summary graph.
- // So, if currentTime is same to the duration, although progress is null opacity is 1.
- const expectedY =
- state.iterationCount && expectedIterationCount === index + 1 ? 1 : 0;
- is(secondLastPathSeg.y, expectedY,
- `The y of second last segment should be ${ expectedY }`);
+ // The test animation is not 'forwards' fill-mode.
+ // Therefore, the y of second last path segment will be 0.
+ const secondLastPathSeg =
+ pathSegList.getItem(pathSegList.numberOfItems - 3);
+ is(secondLastPathSeg.x, endX,
+ `The x of second last segment should be ${ endX }`);
+ // We use computed style of 'opacity' to create summary graph.
+ // So, if currentTime is same to the duration, although progress is null
+ // opacity is 1.
+ const expectedY =
+ state.iterationCount && expectedIterationCount === index + 1 ? 1 : 0;
+ is(secondLastPathSeg.y, expectedY,
+ `The y of second last segment should be ${ expectedY }`);
- const lastPathSeg = pathSegList.getItem(pathSegList.numberOfItems - 2);
- is(lastPathSeg.x, endX, `The x of last segment should be ${ endX }`);
- is(lastPathSeg.y, 0, "The y of last segment should be 0");
+ const lastPathSeg = pathSegList.getItem(pathSegList.numberOfItems - 2);
+ is(lastPathSeg.x, endX, `The x of last segment should be ${ endX }`);
+ is(lastPathSeg.y, 0, "The y of last segment should be 0");
- const closePathSeg = pathSegList.getItem(pathSegList.numberOfItems - 1);
- is(closePathSeg.pathSegType, closePathSeg.PATHSEG_CLOSEPATH,
- `The actual last segment should be close path`);
+ const closePathSeg = pathSegList.getItem(pathSegList.numberOfItems - 1);
+ is(closePathSeg.pathSegType, closePathSeg.PATHSEG_CLOSEPATH,
+ `The actual last segment should be close path`);
+ });
});
}
function approximate(value, expected, permissibleRange, message) {
const min = expected - permissibleRange;
const max = expected + permissibleRange;
ok(min <= value && value <= max, message);
}
--- a/devtools/client/animationinspector/test/browser_animation_timeline_shows_delay.js
+++ b/devtools/client/animationinspector/test/browser_animation_timeline_shows_delay.js
@@ -52,45 +52,48 @@ function checkDelayAndName(timelineEl, h
let delayLeft = Math.round(delay.getBoundingClientRect().x);
let sidebarWidth = Math.round(targetNode.getBoundingClientRect().width);
ok(delayLeft >= sidebarWidth,
"The delay element isn't displayed over the sidebar");
}
}
function checkPath(animationEl, state) {
- // Check existance of delay path.
- const delayPathEl = animationEl.querySelector(".delay-path");
- if (!state.iterationCount && state.delay < 0) {
- // Infinity
- ok(!delayPathEl, "The delay path for Infinity should not exist");
- return;
- }
- if (state.delay === 0) {
- ok(!delayPathEl, "The delay path for zero delay should not exist");
- return;
- }
- ok(delayPathEl, "The delay path should exist");
+ const groupEls = animationEl.querySelectorAll("svg g");
+ groupEls.forEach(groupEl => {
+ // Check existance of delay path.
+ const delayPathEl = groupEl.querySelector(".delay-path");
+ if (!state.iterationCount && state.delay < 0) {
+ // Infinity
+ ok(!delayPathEl, "The delay path for Infinity should not exist");
+ return;
+ }
+ if (state.delay === 0) {
+ ok(!delayPathEl, "The delay path for zero delay should not exist");
+ return;
+ }
+ ok(delayPathEl, "The delay path should exist");
- // Check delay path coordinates.
- const pathSegList = delayPathEl.pathSegList;
- const startingPathSeg = pathSegList.getItem(0);
- const endingPathSeg = pathSegList.getItem(pathSegList.numberOfItems - 2);
- if (state.delay < 0) {
- ok(delayPathEl.classList.contains("negative"),
- "The delay path should have 'negative' class");
- const startingX = state.delay;
- const endingX = 0;
- is(startingPathSeg.x, startingX,
- `The x of starting point should be ${ startingX }`);
- is(endingPathSeg.x, endingX,
- `The x of ending point should be ${ endingX }`);
- } else {
- ok(!delayPathEl.classList.contains("negative"),
- "The delay path should not have 'negative' class");
- const startingX = 0;
- const endingX = state.delay;
- is(startingPathSeg.x, startingX,
- `The x of starting point should be ${ startingX }`);
- is(endingPathSeg.x, endingX,
- `The x of ending point should be ${ endingX }`);
- }
+ // Check delay path coordinates.
+ const pathSegList = delayPathEl.pathSegList;
+ const startingPathSeg = pathSegList.getItem(0);
+ const endingPathSeg = pathSegList.getItem(pathSegList.numberOfItems - 2);
+ if (state.delay < 0) {
+ ok(delayPathEl.classList.contains("negative"),
+ "The delay path should have 'negative' class");
+ const startingX = state.delay;
+ const endingX = 0;
+ is(startingPathSeg.x, startingX,
+ `The x of starting point should be ${ startingX }`);
+ is(endingPathSeg.x, endingX,
+ `The x of ending point should be ${ endingX }`);
+ } else {
+ ok(!delayPathEl.classList.contains("negative"),
+ "The delay path should not have 'negative' class");
+ const startingX = 0;
+ const endingX = state.delay;
+ is(startingPathSeg.x, startingX,
+ `The x of starting point should be ${ startingX }`);
+ is(endingPathSeg.x, endingX,
+ `The x of ending point should be ${ endingX }`);
+ }
+ });
}
--- a/devtools/client/animationinspector/test/browser_animation_timeline_shows_endDelay.js
+++ b/devtools/client/animationinspector/test/browser_animation_timeline_shows_endDelay.js
@@ -43,37 +43,40 @@ function checkEndDelayAndName(animationE
// Check that the endDelay is not displayed on top of the name.
let endDelayRight = Math.round(endDelay.getBoundingClientRect().right);
let nameLeft = Math.round(name.getBoundingClientRect().left);
ok(endDelayRight >= nameLeft,
"The endDelay element does not span over the name element");
}
function checkPath(animationEl, state) {
- // Check existance of enddelay path.
- const endDelayPathEl = animationEl.querySelector(".enddelay-path");
- ok(endDelayPathEl, "The endDelay path should exist");
+ const groupEls = animationEl.querySelectorAll("svg g");
+ groupEls.forEach(groupEl => {
+ // Check existance of enddelay path.
+ const endDelayPathEl = groupEl.querySelector(".enddelay-path");
+ ok(endDelayPathEl, "The endDelay path should exist");
- // Check enddelay path coordinates.
- const pathSegList = endDelayPathEl.pathSegList;
- const startingPathSeg = pathSegList.getItem(0);
- const endingPathSeg = pathSegList.getItem(pathSegList.numberOfItems - 2);
- if (state.endDelay < 0) {
- ok(endDelayPathEl.classList.contains("negative"),
- "The endDelay path should have 'negative' class");
- const endingX = state.delay + state.iterationCount * state.duration;
- const startingX = endingX + state.endDelay;
- is(startingPathSeg.x, startingX,
- `The x of starting point should be ${ startingX }`);
- is(endingPathSeg.x, endingX,
- `The x of ending point should be ${ endingX }`);
- } else {
- ok(!endDelayPathEl.classList.contains("negative"),
- "The endDelay path should not have 'negative' class");
- const startingX =
- state.delay + state.iterationCount * state.duration;
- const endingX = startingX + state.endDelay;
- is(startingPathSeg.x, startingX,
- `The x of starting point should be ${ startingX }`);
- is(endingPathSeg.x, endingX,
- `The x of ending point should be ${ endingX }`);
- }
+ // Check enddelay path coordinates.
+ const pathSegList = endDelayPathEl.pathSegList;
+ const startingPathSeg = pathSegList.getItem(0);
+ const endingPathSeg = pathSegList.getItem(pathSegList.numberOfItems - 2);
+ if (state.endDelay < 0) {
+ ok(endDelayPathEl.classList.contains("negative"),
+ "The endDelay path should have 'negative' class");
+ const endingX = state.delay + state.iterationCount * state.duration;
+ const startingX = endingX + state.endDelay;
+ is(startingPathSeg.x, startingX,
+ `The x of starting point should be ${ startingX }`);
+ is(endingPathSeg.x, endingX,
+ `The x of ending point should be ${ endingX }`);
+ } else {
+ ok(!endDelayPathEl.classList.contains("negative"),
+ "The endDelay path should not have 'negative' class");
+ const startingX =
+ state.delay + state.iterationCount * state.duration;
+ const endingX = startingX + state.endDelay;
+ is(startingPathSeg.x, startingX,
+ `The x of starting point should be ${ startingX }`);
+ is(endingPathSeg.x, endingX,
+ `The x of ending point should be ${ endingX }`);
+ }
+ });
}
--- a/devtools/client/animationinspector/test/browser_animation_timeline_shows_iterations.js
+++ b/devtools/client/animationinspector/test/browser_animation_timeline_shows_iterations.js
@@ -15,33 +15,38 @@ add_task(function* () {
info("Selecting the test node");
yield selectNodeAndWaitForAnimations(".delayed", inspector);
info("Getting the animation element from the panel");
const timelineComponent = panel.animationsTimelineComponent;
const timelineEl = timelineComponent.rootWrapperEl;
let animation = timelineEl.querySelector(".time-block");
+
// Get iteration count from summary graph path.
let iterationCount = getIterationCount(animation);
+ is(animation.querySelectorAll("svg g").length, 1,
+ "The animation timeline contains one g element");
is(iterationCount, 10,
"The animation timeline contains the right number of iterations");
ok(!animation.querySelector(".infinity"),
"The summary graph does not have any elements "
+ " that have infinity class");
info("Selecting another test node with an infinite animation");
yield selectNodeAndWaitForAnimations(".animated", inspector);
info("Getting the animation element from the panel again");
animation = timelineEl.querySelector(".time-block");
iterationCount = getIterationCount(animation);
+ is(animation.querySelectorAll("svg g").length, 1,
+ "The animation timeline contains one g element");
is(iterationCount, 1,
"The animation timeline contains one iteration");
ok(animation.querySelector(".infinity"),
"The summary graph has an element that has infinity class");
});
function getIterationCount(timeblockEl) {
- return timeblockEl.querySelectorAll(".iteration-path").length;
+ return timeblockEl.querySelectorAll("svg g .iteration-path").length;
}
--- a/devtools/client/animationinspector/test/browser_animation_timeline_takes_rate_into_account.js
+++ b/devtools/client/animationinspector/test/browser_animation_timeline_takes_rate_into_account.js
@@ -19,24 +19,40 @@ add_task(function* () {
let timelineEl = panel.animationsTimelineComponent.rootWrapperEl;
let timeBlocks = timelineEl.querySelectorAll(".time-block");
is(timeBlocks.length, 2, "2 animations are displayed");
info("The first animation has its rate set to 1, let's measure it");
let el = timeBlocks[0];
- let duration = getDuration(el.querySelector("path"));
let delay = parseInt(el.querySelector(".delay").style.width, 10);
+ let duration = null;
+ el.querySelectorAll("svg g").forEach(groupEl => {
+ const dur = getDuration(groupEl.querySelector("path"));
+ if (!duration) {
+ duration = dur;
+ return;
+ }
+ is(duration, dur, "The durations shuld be same at all paths in one group");
+ });
info("The second animation has its rate set to 2, so should be shorter");
let el2 = timeBlocks[1];
- let duration2 = getDuration(el2.querySelector("path"));
let delay2 = parseInt(el2.querySelector(".delay").style.width, 10);
+ let duration2 = null;
+ el2.querySelectorAll("svg g").forEach(groupEl => {
+ const dur = getDuration(groupEl.querySelector("path"));
+ if (!duration2) {
+ duration2 = dur;
+ return;
+ }
+ is(duration2, dur, "The durations shuld be same at all paths in one group");
+ });
// The width are calculated by the animation-inspector dynamically depending
// on the size of the panel, and therefore depends on the test machine/OS.
// Let's not try to be too precise here and compare numbers.
let durationDelta = (2 * duration2) - duration;
ok(durationDelta <= 1, "The duration width is correct");
let delayDelta = (2 * delay2) - delay;
ok(delayDelta <= 1, "The delay width is correct");
--- a/devtools/client/animationinspector/test/browser_animation_timeline_ui.js
+++ b/devtools/client/animationinspector/test/browser_animation_timeline_ui.js
@@ -32,17 +32,19 @@ add_task(function* () {
ok(animationEl.querySelector(".target"),
"The animated node target element is in the DOM");
ok(animationEl.querySelector(".time-block"),
"The timeline element is in the DOM");
is(animationEl.querySelector(".name").textContent,
animation.state.name,
"The name on the timeline is correct");
- ok(animationEl.querySelector("svg path"),
+ is(animationEl.querySelectorAll("svg g").length, 1,
+ "The g element should be one since this doc's all animation has only one shape");
+ ok(animationEl.querySelector("svg g path"),
"The timeline has svg and path element as summary graph");
const expectedBackgroundColor =
i % 2 === 0 ? "rgba(128, 128, 128, 0.03)" : "rgba(0, 0, 0, 0)";
const backgroundColor =
animationEl.ownerDocument.defaultView.getComputedStyle(animationEl).backgroundColor;
is(backgroundColor, expectedBackgroundColor,
"The background-color shoud be changed to alternate");
new file mode 100644
--- /dev/null
+++ b/devtools/client/animationinspector/test/doc_multiple_easings.html
@@ -0,0 +1,86 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <style>
+ div {
+ background-color: lime;
+ height: 50px;
+ }
+ </style>
+</head>
+<body>
+ <script>
+ "use strict";
+ const DURATION = 100 * 1000;
+ [
+ {
+ id: "no-easing",
+ frames: { opacity: [1, 0] },
+ timing: {
+ duration: DURATION
+ }
+ },
+ {
+ id: "effect-easing",
+ frames: { opacity: [1, 0] },
+ timing: {
+ easing: "steps(2)",
+ duration: DURATION
+ }
+ },
+ {
+ id: "keyframe-easing",
+ frames: [
+ {
+ offset: 0,
+ easing: "steps(2)",
+ opacity: 1
+ },
+ {
+ offset: 1,
+ opacity: 0
+ }
+ ],
+ timing: {
+ duration: DURATION,
+ }
+ },
+ {
+ id: "both-easing",
+ frames: [
+ {
+ offset: 0,
+ easing: "steps(2)",
+ opacity: 1
+ },
+ {
+ offset: 0,
+ easing: "steps(5)",
+ marginLeft: "0px",
+ marginTop: "0px"
+ },
+ {
+ offset: 1,
+ opacity: 0,
+ marginLeft: "100px",
+ marginTop: "100px"
+ },
+ ],
+ timing: {
+ easing: "steps(10)",
+ duration: DURATION,
+ }
+ },
+ ].forEach(({ id, frames, timing }) => {
+ const target = document.createElement("div");
+ document.body.appendChild(target);
+ const effect = new KeyframeEffect(target, frames, timing);
+ const animation = new Animation(effect, document.timeline);
+ animation.id = id;
+ animation.play();
+ });
+
+ </script>
+</body>
+</html>
--- a/devtools/client/animationinspector/test/head.js
+++ b/devtools/client/animationinspector/test/head.js
@@ -490,8 +490,103 @@ function* setStyle(animation, panel, nam
yield onAnimationChanged;
// Also wait for the target node previews to be loaded if the panel got
// refreshed as a result of this animation mutation.
yield waitForAllAnimationTargets(panel);
// And wait for animation timeline rendering.
yield waitForAnimationTimelineRendering(panel);
}
+
+/**
+ * Graph shapes of summary and detail are constructed by <path> element.
+ * This function checks the vertex of path segments.
+ * Also, if needed, checks the color for <stop> element.
+ * @param pathEl - <path> element.
+ * @param duration - float as duration which pathEl represetns.
+ * @param hasClosePath - set true if the path shoud be closing.
+ * @param expectedValues - JSON object format. We can test the vertex and color.
+ * e.g.
+ * [
+ * // Test the vertex (x=0, y=0) should be passing through.
+ * { x: 0, y: 0 },
+ * { x: 0, y: 1 },
+ * // If we have to test the color as well,
+ * // we can write as following.
+ * { x: 500, y: 1, color: "rgb(0, 0, 255)" },
+ * { x: 1000, y: 1 }
+ * ]
+ */
+function assertPathSegments(pathEl, duration, hasClosePath, expectedValues) {
+ const pathSegList = pathEl.pathSegList;
+ ok(pathSegList, "The tested element should have pathSegList");
+
+ expectedValues.forEach(expectedValue => {
+ ok(isPassingThrough(pathSegList, expectedValue.x, expectedValue.y),
+ `The path segment of x ${ expectedValue.x }, y ${ expectedValue.y } `
+ + `should be passing through`);
+
+ if (expectedValue.color) {
+ assertColor(pathEl.closest("svg"), expectedValue.x / duration, expectedValue.color);
+ }
+ });
+
+ if (hasClosePath) {
+ const closePathSeg = pathSegList.getItem(pathSegList.numberOfItems - 1);
+ is(closePathSeg.pathSegType, closePathSeg.PATHSEG_CLOSEPATH,
+ "The last segment should be close path");
+ }
+}
+
+/**
+ * Check the color for <stop> element.
+ * @param svgEl - <svg> element which has <stop> element.
+ * @param offset - float which represents the "offset" attribute of <stop>.
+ * @param expectedColor - e.g. rgb(0, 0, 255)
+ */
+function assertColor(svgEl, offset, expectedColor) {
+ const stopEl = findStopElement(svgEl, offset);
+ ok(stopEl, `stop element at offset ${ offset } should exist`);
+ is(stopEl.getAttribute("stop-color"), expectedColor,
+ `stop-color of stop element at offset ${ offset } should be ${ expectedColor }`);
+}
+
+/**
+ * Check whether the given vertex is passing throug on the path.
+ * @param pathSegList - pathSegList of <path> element.
+ * @param x - float x of vertex.
+ * @param y - float y of vertex.
+ * @return true: passing through, false: no on the path.
+ */
+function isPassingThrough(pathSegList, x, y) {
+ let previousPathSeg = pathSegList.getItem(0);
+ for (let i = 0; i < pathSegList.numberOfItems; i++) {
+ const pathSeg = pathSegList.getItem(i);
+ if (pathSeg.x === undefined) {
+ continue;
+ }
+ const currentX = parseFloat(pathSeg.x.toFixed(3));
+ const currentY = parseFloat(pathSeg.y.toFixed(6));
+ if (currentX === x && currentY === y) {
+ return true;
+ }
+ const previousX = parseFloat(previousPathSeg.x.toFixed(3));
+ const previousY = parseFloat(previousPathSeg.y.toFixed(6));
+ if (previousX <= x && x <= currentX &&
+ Math.min(previousY, currentY) <= y && y <= Math.max(previousY, currentY)) {
+ return true;
+ }
+ previousPathSeg = pathSeg;
+ }
+ return false;
+}
+
+/**
+ * Find <stop> element which has given offset from given <svg> element.
+ * @param svgEl - <svg> element which has <stop> element.
+ * @param offset - float which represents the "offset" attribute of <stop>.
+ * @return <stop> element.
+ */
+function findStopElement(svgEl, offset) {
+ return [...svgEl.querySelectorAll("stop")].find(stopEl => {
+ return stopEl.getAttribute("offset") == offset;
+ });
+}