--- a/devtools/client/inspector/animation/test/browser.ini
+++ b/devtools/client/inspector/animation/test/browser.ini
@@ -1,12 +1,13 @@
[DEFAULT]
tags = devtools
subsuite = devtools
support-files =
+ doc_custom_playback_rate.html
doc_multi_easings.html
doc_multi_keyframes.html
doc_multi_timings.html
doc_simple_animation.html
head.js
!/devtools/client/framework/test/shared-head.js
!/devtools/client/inspector/test/head.js
!/devtools/client/inspector/test/shared-head.js
@@ -16,21 +17,29 @@ support-files =
[browser_animation_animated-property-list.js]
[browser_animation_animated-property-name.js]
[browser_animation_animation-detail_close-button.js]
[browser_animation_animation-detail_title.js]
[browser_animation_animation-detail_visibility.js]
[browser_animation_animation-list.js]
[browser_animation_animation-target.js]
[browser_animation_animation-timeline-tick.js]
+[browser_animation_current-time-label.js]
+[browser_animation_current-time-scrubber.js]
[browser_animation_empty_on_invalid_nodes.js]
[browser_animation_inspector_exists.js]
[browser_animation_keyframes-graph_computed-value-path.js]
[browser_animation_keyframes-graph_computed-value-path_easing-hint.js]
[browser_animation_keyframes-graph_keyframe-marker.js]
+[browser_animation_keyframes-progress-bar.js]
+[browser_animation_logic_auto-stop.js]
+[browser_animation_pause-resume-button.js]
+[browser_animation_pause-resume-button_spacebar.js]
+[browser_animation_playback-rate-selector.js]
+[browser_animation_rewind-button.js]
[browser_animation_summary-graph_animation-name.js]
[browser_animation_summary-graph_compositor.js]
[browser_animation_summary-graph_computed-timing-path.js]
[browser_animation_summary-graph_delay-sign.js]
[browser_animation_summary-graph_end-delay-sign.js]
[browser_animation_summary-graph_effect-timing-path.js]
[browser_animation_summary-graph_negative-delay-path.js]
[browser_animation_summary-graph_negative-end-delay-path.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/test/browser_animation_current-time-label.js
@@ -0,0 +1,61 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for following CurrentTimeLabel component:
+// * element existence
+// * label content at plural timing
+
+add_task(async function () {
+ await addTab(URL_ROOT + "doc_multi_timings.html");
+ const { animationInspector, inspector, panel } = await openAnimationInspector();
+
+ info("Checking current time label existence");
+ const labelEl = panel.querySelector(".current-time-label");
+ ok(labelEl, "current time label should exist");
+
+ info("Checking current time label content");
+ await selectNodeAndWaitForAnimations(".keyframes-easing-step", inspector);
+ await clickOnCurrentTimeScrubberController(animationInspector, panel, 0.5);
+ assertLabelContent(labelEl, animationInspector.state.animations[0].state.currentTime);
+ await clickOnCurrentTimeScrubberController(animationInspector, panel, 0.2);
+ assertLabelContent(labelEl, animationInspector.state.animations[0].state.currentTime);
+
+ info("Checking current time label content during running");
+ // Resume
+ await clickOnPauseResumeButton(animationInspector, panel);
+ const previousContent = labelEl.textContent;
+ await wait(1000);
+ const currentContent = labelEl.textContent;
+ isnot(previousContent, currentContent, "Current time label should change");
+});
+
+function assertLabelContent(labelEl, time) {
+ const expected = formatStopwatchTime(time);
+ is(labelEl.textContent, expected, `Content of label should be ${ expected }`);
+}
+
+function formatStopwatchTime(time) {
+ // Format falsy values as 0
+ if (!time) {
+ return "00:00.000";
+ }
+
+ let milliseconds = parseInt(time % 1000, 10);
+ let seconds = parseInt((time / 1000) % 60, 10);
+ let minutes = parseInt((time / (1000 * 60)), 10);
+
+ let pad = (nb, max) => {
+ if (nb < max) {
+ return new Array((max + "").length - (nb + "").length + 1).join("0") + nb;
+ }
+ return nb;
+ };
+
+ minutes = pad(minutes, 10);
+ seconds = pad(seconds, 10);
+ milliseconds = pad(milliseconds, 100);
+
+ return `${minutes}:${seconds}.${milliseconds}`;
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/test/browser_animation_current-time-scrubber.js
@@ -0,0 +1,68 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for following CurrentTimeScrubber and CurrentTimeScrubberController components:
+// * element existence
+// * scrubber position validity
+// * make animations currentTime to change by click on the controller
+// * mouse drag on the scrubber
+
+add_task(async function () {
+ await addTab(URL_ROOT + "doc_multi_timings.html");
+ const { animationInspector, inspector, panel } = await openAnimationInspector();
+
+ info("Checking scrubber controller existence");
+ const controllerEl = panel.querySelector(".current-time-scrubber-controller");
+ ok(controllerEl, "scrubber controller should exist");
+
+ info("Checking scrubber existence");
+ const scrubberEl = controllerEl.querySelector(".current-time-scrubber");
+ ok(scrubberEl, "scrubber should exist");
+
+ info("Checking scrubber changes current time of animation and the position");
+ await selectNodeAndWaitForAnimations(".enddelay-with-iterations-infinity", inspector);
+ const duration = animationInspector.state.timeScale.getDuration();
+ await clickOnCurrentTimeScrubberController(animationInspector, panel, 0);
+ assertAnimationsCurrentTime(animationInspector, 0);
+ assertPosition(scrubberEl, controllerEl, 0, animationInspector);
+
+ await clickOnCurrentTimeScrubberController(animationInspector, panel, 1);
+ assertAnimationsCurrentTime(animationInspector, duration);
+ assertPosition(scrubberEl, controllerEl, duration, animationInspector);
+
+ await clickOnCurrentTimeScrubberController(animationInspector, panel, 0.5);
+ assertAnimationsCurrentTime(animationInspector, duration * 0.5);
+ assertPosition(scrubberEl, controllerEl, duration * 0.5, animationInspector);
+
+ info("Checking current time scrubber position during running");
+ // Running again
+ await clickOnPauseResumeButton(animationInspector, panel);
+ let previousX = scrubberEl.getBoundingClientRect().x;
+ await wait(100);
+ let currentX = scrubberEl.getBoundingClientRect().x;
+ isnot(previousX, currentX, "Scrubber should be moved");
+
+ info("Checking draggable on scrubber over animation list");
+ await clickOnPauseResumeButton(animationInspector, panel);
+ previousX = scrubberEl.getBoundingClientRect().x;
+ await dragOnCurrentTimeScrubber(animationInspector, panel, 0.5, 2, 30);
+ currentX = scrubberEl.getBoundingClientRect().x;
+ isnot(previousX, currentX, "Scrubber should be draggable");
+
+ info("Checking a behavior which mouse out from animation inspector area " +
+ "during dragging from controller");
+ await dragOnCurrentTimeScrubberController(animationInspector, panel, 0.5, 2);
+ ok(!panel.querySelector(".animation-list-container")
+ .classList.contains("active-scrubber"), "Click and DnD should be inactive");
+});
+
+function assertPosition(scrubberEl, controllerEl, time, animationInspector) {
+ const controllerBounds = controllerEl.getBoundingClientRect();
+ const scrubberBounds = scrubberEl.getBoundingClientRect();
+ const scrubberX = scrubberBounds.x + scrubberBounds.width / 2 - controllerBounds.x;
+ const timeScale = animationInspector.state.timeScale;
+ const expected = Math.round(time / timeScale.getDuration() * controllerBounds.width);
+ is(scrubberX, expected, `Position should be ${ expected } at ${ time }ms`);
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/test/browser_animation_keyframes-progress-bar.js
@@ -0,0 +1,91 @@
+/* Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for following KeyframesProgressBar:
+// * element existence
+// * progress bar position in multi effect timings
+// * progress bar position after changing playback rate
+// * progress bar position when select another animation
+
+const POSITION_TESTCASES = [
+ {
+ targetClassName: "cssanimation-linear",
+ scrubberPositions: [0, 0.25, 0.5, 0.75, 1],
+ expectedPositions: [0, 0.25, 0.5, 0.75, 0],
+ },
+ {
+ targetClassName: "easing-step",
+ scrubberPositions: [0, 0.49, 0.5, 0.99],
+ expectedPositions: [0, 0, 0.5, 0.5],
+ },
+ {
+ targetClassName: "delay-positive",
+ scrubberPositions: [0, 0.33, 0.5],
+ expectedPositions: [0, 0, 0.25],
+ },
+ {
+ targetClassName: "delay-negative",
+ scrubberPositions: [0, 0.49, 0.5, 0.75],
+ expectedPositions: [0, 0, 0.5, 0.75],
+ },
+ {
+ targetClassName: "enddelay-positive",
+ scrubberPositions: [0, 0.66, 0.67, 0.99],
+ expectedPositions: [0, 0.99, 0, 0],
+ },
+ {
+ targetClassName: "enddelay-negative",
+ scrubberPositions: [0, 0.49, 0.5, 0.99],
+ expectedPositions: [0, 0.49, 0, 0],
+ },
+ {
+ targetClassName: "direction-reverse-with-iterations-infinity",
+ scrubberPositions: [0, 0.25, 0.5, 0.75, 1],
+ expectedPositions: [1, 0.75, 0.5, 0.25, 1],
+ },
+ {
+ targetClassName: "fill-both-width-delay-iterationstart",
+ scrubberPositions: [0, 0.33, 0.66, 0.833, 1],
+ expectedPositions: [0.5, 0.5, 0.99, 0.25, 0.5],
+ },
+];
+
+add_task(async function () {
+ await addTab(URL_ROOT + "doc_multi_timings.html");
+ const { animationInspector, inspector, panel } = await openAnimationInspector();
+
+ info("Checking progress bar position in multi effect timings");
+ await clickOnPauseResumeButton(animationInspector, panel);
+
+ for (const testcase of POSITION_TESTCASES) {
+ info(`Checking progress bar position for ${ testcase.targetClassName }`);
+ await selectNodeAndWaitForAnimations(`.${ testcase.targetClassName }`, inspector);
+
+ info("Checking progress bar existence");
+ const areaEl = panel.querySelector(".keyframes-progress-bar-area");
+ ok(areaEl, "progress bar area should exist");
+ const barEl = areaEl.querySelector(".keyframes-progress-bar");
+ ok(barEl, "progress bar should exist");
+
+ const scrubberPositions = testcase.scrubberPositions;
+ const expectedPositions = testcase.expectedPositions;
+
+ for (let i = 0; i < scrubberPositions.length; i++) {
+ info(`Scrubber position is ${ scrubberPositions[i] }`);
+ await clickOnCurrentTimeScrubberController(animationInspector,
+ panel, scrubberPositions[i]);
+ assertPosition(barEl, areaEl, expectedPositions[i], animationInspector);
+ }
+ }
+});
+
+function assertPosition(barEl, areaEl, expectedRate, animationInspector) {
+ const controllerBounds = areaEl.getBoundingClientRect();
+ const barBounds = barEl.getBoundingClientRect();
+ const barX = barBounds.x + barBounds.width / 2 - controllerBounds.x;
+ const expected = controllerBounds.width * expectedRate;
+ ok(expected - 1 < barX && barX < expected + 1,
+ `Position should apploximately be ${ expected } (x of bar is ${ barX })`);
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/test/browser_animation_logic_auto-stop.js
@@ -0,0 +1,63 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Animation inspector makes the current time to stop
+// after end of animation duration except iterations infinity.
+// Test followings:
+// * state of animations and UI components after end of animation duration
+// * state of animations and UI components after end of animation duration
+// but iteration count is infinity
+
+add_task(async function () {
+ await addTab(URL_ROOT + "doc_multi_timings.html");
+ const { animationInspector, inspector, panel } = await openAnimationInspector();
+
+ info("Checking state after end of animation duration");
+ await selectNodeAndWaitForAnimations(".easing-step", inspector);
+ const pixelsData = getDurationAndRate(animationInspector, panel, 5);
+ await clickOnCurrentTimeScrubberController(animationInspector,
+ panel, 1 - pixelsData.rate);
+ await clickOnPauseResumeButton(animationInspector, panel);
+ // Must be able to catch rendering event after stopping the animation.
+ await waitForSummaryAndDetail(animationInspector);
+ await assertStates(animationInspector, panel, false);
+
+ info("Checking state after end of animation duration and infinity iterations");
+ await clickOnPauseResumeButton(animationInspector, panel);
+ await selectNodeAndWaitForAnimations(".enddelay-with-iterations-infinity", inspector);
+ await clickOnCurrentTimeScrubberController(animationInspector, panel, 1);
+ await clickOnPauseResumeButton(animationInspector, panel);
+ await assertStates(animationInspector, panel, true);
+});
+
+async function assertStates(animationInspector, panel, shouldRunning) {
+ const buttonEl = panel.querySelector(".pause-resume-button");
+ const labelEl = panel.querySelector(".current-time-label");
+ const scrubberEl = panel.querySelector(".current-time-scrubber");
+
+ const previousLabelContent = labelEl.textContent;
+ const previousScrubberX = scrubberEl.getBoundingClientRect().x;
+ await wait(100);
+ const currentLabelContent = labelEl.textContent;
+ const currentScrubberX = scrubberEl.getBoundingClientRect().x;
+
+ if (shouldRunning) {
+ isnot(previousLabelContent, currentLabelContent,
+ "Current time label content should change");
+ isnot(previousScrubberX, currentScrubberX,
+ "Current time scrubber position should change");
+ ok(!buttonEl.classList.contains("paused"),
+ "State of button should be running");
+ assertAnimationsRunning(animationInspector, panel);
+ } else {
+ is(previousLabelContent, currentLabelContent,
+ "Current time label Content should not change");
+ is(previousScrubberX, currentScrubberX,
+ "Current time scrubber position should not change");
+ ok(buttonEl.classList.contains("paused"),
+ "State of button should be paused");
+ assertAnimationsPausing(animationInspector, panel);
+ }
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/test/browser_animation_pause-resume-button.js
@@ -0,0 +1,33 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for following PauseResumeButton component:
+// * element existence
+// * state during running animations
+// * state during pausing animations
+// * make animations to pause by push button
+// * make animations to resume by push button
+
+add_task(async function () {
+ await addTab(URL_ROOT + "doc_custom_playback_rate.html");
+ const { animationInspector, panel } = await openAnimationInspector();
+
+ info("Checking pause/resume button existence");
+ const buttonEl = panel.querySelector(".pause-resume-button");
+ ok(buttonEl, "pause/resume button should exist");
+
+ info("Checking state during running animations");
+ ok(!buttonEl.classList.contains("paused"), "State of button should be running");
+
+ info("Checking button makes animations to pause");
+ await clickOnPauseResumeButton(animationInspector, panel);
+ assertAnimationsPausing(animationInspector, panel);
+ ok(buttonEl.classList.contains("paused"), "State of button should be paused");
+
+ info("Checking button makes animations to resume");
+ await clickOnPauseResumeButton(animationInspector, panel);
+ assertAnimationsRunning(animationInspector, panel);
+ ok(!buttonEl.classList.contains("paused"), "State of button should be resumed");
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/test/browser_animation_pause-resume-button_spacebar.js
@@ -0,0 +1,33 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for following PauseResumeButton component with spacebar:
+// * make animations to pause/resume by spacebar
+// * combination with other UI components
+
+add_task(async function () {
+ await addTab(URL_ROOT + "doc_custom_playback_rate.html");
+ const { animationInspector, panel } = await openAnimationInspector();
+
+ info("Checking spacebar makes animations to pause");
+ await sendSpaceKeyEvent(animationInspector, panel);
+ assertAnimationsPausing(animationInspector, panel);
+ await sendSpaceKeyEvent(animationInspector, panel);
+ assertAnimationsRunning(animationInspector, panel);
+
+ info("Checking spacebar works with other UI components");
+ // To pause
+ await clickOnPauseResumeButton(animationInspector, panel);
+ // To resume
+ await sendSpaceKeyEvent(animationInspector, panel);
+ assertAnimationsRunning(animationInspector, panel);
+ // To pause
+ await clickOnCurrentTimeScrubberController(animationInspector, panel, 0.5);
+ // To resume
+ await clickOnPauseResumeButton(animationInspector, panel);
+ // To pause
+ await sendSpaceKeyEvent(animationInspector, panel);
+ assertAnimationsPausing(animationInspector, panel);
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/test/browser_animation_playback-rate-selector.js
@@ -0,0 +1,54 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for following PlaybackRateSelector component:
+// * element existence
+// * make playback rate of animations by the selector
+// * in case of animations have mixed playback rate
+// * in case of animations have playback rate which is not default selectable value
+
+add_task(async function () {
+ await addTab(URL_ROOT + "doc_custom_playback_rate.html");
+ const { animationInspector, inspector, panel } = await openAnimationInspector();
+
+ info("Checking playback rate selector existence");
+ const selectEl = panel.querySelector(".playback-rate-selector");
+ ok(selectEl, "scrubber controller should exist");
+
+ info("Checking playback rate existence which includes custom rate of animations");
+ const selectableRates = [0.1, 0.25, 0.5, 1, 1.5, 2, 5, 10];
+ is(selectEl.options.length, selectableRates.length,
+ `Length of options should be ${ selectableRates.length }`);
+ for (let i = 0; i < selectEl.options.length; i++) {
+ const optionEl = selectEl.options[i];
+ const selectableRate = selectableRates[i];
+ is(Number(optionEl.value), selectableRate,
+ `Option of index[${ i }] should be ${ selectableRate }`);
+ }
+
+ info("Checking selected playback rate");
+ is(Number(selectEl.value), 1.5, "Selected option should be 1.5");
+
+ info("Checking playback rate of animations");
+ await clickOnPlaybackRateSelector(animationInspector, panel, 0.5);
+ assertPlaybackRate(animationInspector, 0.5);
+
+ info("Checking mixed playback rate");
+ await selectNodeAndWaitForAnimations("div", inspector);
+ await clickOnPlaybackRateSelector(animationInspector, panel, 2);
+ assertPlaybackRate(animationInspector, 2);
+ await selectNodeAndWaitForAnimations("body", inspector);
+ is(selectEl.value, "", "Selected option should be empty");
+
+ info("Checking playback rate after re-setting");
+ await clickOnPlaybackRateSelector(animationInspector, panel, 1);
+ assertPlaybackRate(animationInspector, 1);
+});
+
+async function assertPlaybackRate(animationInspector, rate) {
+ const isRateEqual =
+ animationInspector.state.animations.every(({state}) => state.playbackRate === rate);
+ ok(isRateEqual, `Playback rate of animations should be ${ rate }`);
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/test/browser_animation_rewind-button.js
@@ -0,0 +1,28 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for following RewindButton component:
+// * element existence
+// * make animations to rewind to zero
+// * the state should be always paused after rewinding
+
+add_task(async function () {
+ await addTab(URL_ROOT + "doc_custom_playback_rate.html");
+ const { animationInspector, panel } = await openAnimationInspector();
+
+ info("Checking button existence");
+ ok(panel.querySelector(".rewind-button"), "Rewind button should exist");
+
+ info("Checking rewind button makes animations to rewind to zero");
+ await clickOnRewindButton(animationInspector, panel);
+ assertAnimationsCurrentTime(animationInspector, 0);
+ assertAnimationsPausing(animationInspector, panel);
+
+ info("Checking rewind button makes animations after clicking scrubber");
+ await clickOnCurrentTimeScrubberController(animationInspector, panel, 0.5);
+ await clickOnRewindButton(animationInspector, panel);
+ assertAnimationsCurrentTime(animationInspector, 0);
+ assertAnimationsPausing(animationInspector, panel);
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/test/doc_custom_playback_rate.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="UTF-8">
+ <style>
+ div {
+ background-color: lime;
+ height: 100px;
+ }
+ </style>
+ </head>
+ <body>
+ <script>
+ "use strict";
+
+ const duration = 100000;
+
+ function createAnimation() {
+ const div = document.createElement("div");
+ document.body.appendChild(div);
+ const animation = div.animate([{ opacity: 0 }], duration);
+ animation.playbackRate = 1.5;
+ }
+
+ createAnimation();
+ createAnimation();
+ </script>
+ </body>
+</html>
--- a/devtools/client/inspector/animation/test/head.js
+++ b/devtools/client/inspector/animation/test/head.js
@@ -119,16 +119,188 @@ const clickOnDetailCloseButton = functio
const buttonEl = panel.querySelector(".animation-detail-close-button");
const bounds = buttonEl.getBoundingClientRect();
const x = bounds.width / 2;
const y = bounds.height / 2;
EventUtils.synthesizeMouse(buttonEl, x, y, {}, buttonEl.ownerGlobal);
};
/**
+ * Click on pause/resume button.
+ *
+ * @param {AnimationInspector} animationInspector
+ * @param {AnimationsPanel} panel
+ * The panel instance.
+ */
+const clickOnPauseResumeButton = async function (animationInspector, panel) {
+ info("Click on pause/resume button");
+ const buttonEl = panel.querySelector(".pause-resume-button");
+ const bounds = buttonEl.getBoundingClientRect();
+ const x = bounds.width / 2;
+ const y = bounds.height / 2;
+ EventUtils.synthesizeMouse(buttonEl, x, y, {}, buttonEl.ownerGlobal);
+ await waitForSummaryAndDetail(animationInspector);
+};
+
+/**
+ * Click on rewind button.
+ *
+ * @param {AnimationInspector} animationInspector
+ * @param {AnimationsPanel} panel
+ * The panel instance.
+ */
+const clickOnRewindButton = async function (animationInspector, panel) {
+ info("Click on rewind button");
+ const buttonEl = panel.querySelector(".rewind-button");
+ const bounds = buttonEl.getBoundingClientRect();
+ const x = bounds.width / 2;
+ const y = bounds.height / 2;
+ EventUtils.synthesizeMouse(buttonEl, x, y, {}, buttonEl.ownerGlobal);
+ await waitForSummaryAndDetail(animationInspector);
+};
+
+/**
+ * Click on the scrubber controller pane to update the animation current time.
+ *
+ * @param {AnimationsPanel} panel
+ * @param {Number} mouseDownPosition
+ * rate on scrubber controller pane.
+ * This method calculates
+ * `mouseDownPosition * offsetWidth + offsetLeft of scrubber controller pane`
+ * as the clientX of MouseEvent.
+ */
+const clickOnCurrentTimeScrubberController = async function (animationInspector,
+ panel,
+ mouseDownPosition,
+ mouseMovePosition) {
+ const controllerEl = panel.querySelector(".current-time-scrubber-controller");
+ const bounds = controllerEl.getBoundingClientRect();
+ const mousedonwX = bounds.width * mouseDownPosition;
+
+ info(`Click ${ mousedonwX } on scrubber controller`);
+ EventUtils.synthesizeMouse(controllerEl, mousedonwX, 0, {}, controllerEl.ownerGlobal);
+ await waitForSummaryAndDetail(animationInspector);
+};
+
+/**
+ * Click on playback rate selector to select given rate.
+ *
+ * @param {AnimationInspector} animationInspector
+ * @param {AnimationsPanel} panel
+ * @param {Number} rate
+ */
+const clickOnPlaybackRateSelector = async function (animationInspector, panel, rate) {
+ info(`Click on playback rate selector to select ${rate}`);
+ const selectEl = panel.querySelector(".playback-rate-selector");
+ const optionEl = [...selectEl.options].filter(o => Number(o.value) === rate)[0];
+
+ if (!optionEl) {
+ ok(false, `Could not find an option for rate ${ rate } in the rate selector. ` +
+ `Values are: ${ [...selectEl.options].map(o => o.value) }`);
+ return;
+ }
+
+ const win = selectEl.ownerGlobal;
+ EventUtils.synthesizeMouseAtCenter(selectEl, { type: "mousedown" }, win);
+ EventUtils.synthesizeMouseAtCenter(optionEl, { type: "mouseup" }, win);
+ await waitForSummaryAndDetail(animationInspector);
+};
+
+/**
+ * Drag on the scrubber to update the animation current time.
+ *
+ * @param {AnimationsPanel} panel
+ * @param {Number} mouseDownPosition
+ * rate on scrubber controller pane.
+ * This method calculates
+ * `mouseDownPosition * offsetWidth + offsetLeft of scrubber controller pane`
+ * as the clientX of MouseEvent.
+ * @param {Number} mouseMovePosition
+ * Dispatch mousemove event with mouseMovePosition after mousedown.
+ * Calculation for clinetX is same to above.
+ * @param {Number} mouseYPixel
+ * Y of mouse in pixel.
+ */
+const dragOnCurrentTimeScrubber = async function (animationInspector,
+ panel,
+ mouseDownPosition,
+ mouseMovePosition,
+ mouseYPixel) {
+ const controllerEl = panel.querySelector(".current-time-scrubber");
+ const bounds = controllerEl.getBoundingClientRect();
+ const mousedonwX = bounds.width * mouseDownPosition;
+ const mousemoveX = bounds.width * mouseMovePosition;
+
+ info(`Drag on scrubber from ${ mousedonwX } to ${ mousemoveX }`);
+ EventUtils.synthesizeMouse(controllerEl, mousedonwX, mouseYPixel,
+ { type: "mousedown" }, controllerEl.ownerGlobal);
+ await waitForSummaryAndDetail(animationInspector);
+ EventUtils.synthesizeMouse(controllerEl, mousemoveX, mouseYPixel,
+ { type: "mousemove" }, controllerEl.ownerGlobal);
+ EventUtils.synthesizeMouse(controllerEl, mousemoveX, mouseYPixel,
+ { type: "mouseup" }, controllerEl.ownerGlobal);
+ await waitForSummaryAndDetail(animationInspector);
+};
+
+/**
+ * Drag on the scrubber controller pane to update the animation current time.
+ *
+ * @param {AnimationsPanel} panel
+ * @param {Number} mouseDownPosition
+ * rate on scrubber controller pane.
+ * This method calculates
+ * `mouseDownPosition * offsetWidth + offsetLeft of scrubber controller pane`
+ * as the clientX of MouseEvent.
+ * @param {Number} mouseMovePosition
+ * Dispatch mousemove event with mouseMovePosition after mousedown.
+ * Calculation for clinetX is same to above.
+ */
+const dragOnCurrentTimeScrubberController = async function (animationInspector,
+ panel,
+ mouseDownPosition,
+ mouseMovePosition) {
+ const controllerEl = panel.querySelector(".current-time-scrubber-controller");
+ const bounds = controllerEl.getBoundingClientRect();
+ const mousedonwX = bounds.width * mouseDownPosition;
+ const mousemoveX = bounds.width * mouseMovePosition;
+
+ info(`Drag on scrubber controller from ${ mousedonwX } to ${ mousemoveX }`);
+ EventUtils.synthesizeMouse(controllerEl, mousedonwX, 0,
+ { type: "mousedown" }, controllerEl.ownerGlobal);
+ await waitForSummaryAndDetail(animationInspector);
+ EventUtils.synthesizeMouse(controllerEl, mousemoveX, 0,
+ { type: "mousemove" }, controllerEl.ownerGlobal);
+ EventUtils.synthesizeMouse(controllerEl, mousemoveX, 0,
+ { type: "mouseup" }, controllerEl.ownerGlobal);
+ await waitForSummaryAndDetail(animationInspector);
+};
+
+/**
+ * Get current animation duration and rate of
+ * clickOrDragOnCurrentTimeScrubberController in given pixels.
+ *
+ * @param {AnimationInspector} animationInspector
+ * @param {AnimationsPanel} panel
+ * @param {Number} pixels
+ * @return {Object}
+ * {
+ * duration,
+ * rate,
+ * }
+ */
+const getDurationAndRate = function (animationInspector, panel, pixels) {
+ const controllerEl = panel.querySelector(".current-time-scrubber-controller");
+ const bounds = controllerEl.getBoundingClientRect();
+ const duration =
+ animationInspector.state.timeScale.getDuration() / bounds.width * pixels;
+ const rate = 1 / bounds.width * pixels;
+ return { duration, rate };
+};
+
+/**
* Set the inspector's current selection to a node or to the first match of the
* given css selector and wait for the animations to be displayed
*
* @param {String|NodeFront}
* data The node to select
* @param {InspectorPanel} inspector
* The instance of InspectorPanel currently loaded in the toolbox
* @param {String} reason
@@ -142,16 +314,28 @@ const selectNodeAndWaitForAnimations = a
// be properly displayed (wait for all target DOM nodes to be previewed).
const onUpdated = inspector.once("inspector-updated");
await selectNode(data, inspector, reason);
await onUpdated;
await waitForRendering(inspector.animationinspector);
};
/**
+ * Send keyboard event of space to given panel.
+ *
+ * @param {AnimationInspector} animationInspector
+ * @param {AnimationsPanel} panel
+ */
+const sendSpaceKeyEvent = async function (animationInspector, panel) {
+ panel.focus();
+ EventUtils.sendKey("SPACE", panel.ownerGlobal);
+ await waitForSummaryAndDetail(animationInspector);
+};
+
+/**
* Set the sidebar width by given parameter.
*
* @param {String} width
* Change sidebar width by given parameter.
* @param {InspectorPanel} inspector
* The instance of InspectorPanel currently loaded in the toolbox
* @return {Promise} Resolves when the sidebar size changed.
*/
@@ -175,17 +359,18 @@ const waitForRendering = async function
};
/**
* Wait for rendering of animation keyframes.
*
* @param {AnimationInspector} inspector
*/
const waitForAnimationDetail = async function (animationInspector) {
- if (animationInspector.state.animations.length === 1) {
+ if (animationInspector.state.selectedAnimation &&
+ animationInspector.state.detailVisibility) {
await animationInspector.once("animation-keyframes-rendered");
}
};
/**
* Wait for all AnimationTarget components to be fully loaded
* (fetched their related actor and rendered).
*
@@ -204,16 +389,79 @@ const waitForAllAnimationTargets = async
*/
const waitForAllSummaryGraph = async function (animationInspector) {
for (let i = 0; i < animationInspector.state.animations.length; i++) {
await animationInspector.once("animation-summary-graph-rendered");
}
};
/**
+ * Wait for rendering of all summary graph and detail.
+ *
+ * @param {AnimationInspector} inspector
+ */
+const waitForSummaryAndDetail = async function (animationInspector) {
+ await Promise.all([
+ waitForAllSummaryGraph(animationInspector),
+ waitForAnimationDetail(animationInspector),
+ ]);
+};
+
+/**
+ * Check whether current time of all animations and UI are given specified time.
+ *
+ * @param {AnimationInspector} animationInspector
+ * @param {AnimationsPanel} panel
+ * @param {Number} time
+ */
+function assertAnimationsCurrentTime(animationInspector, time) {
+ const isTimeEqual =
+ animationInspector.state.animations.every(({state}) => state.currentTime === time);
+ ok(isTimeEqual, `Current time of animations should be ${ time }`);
+}
+
+/**
+ * Check whether the animations are pausing.
+ *
+ * @param {AnimationInspector} animationInspector
+ * @param {AnimationsPanel} panel
+ */
+function assertAnimationsPausing(animationInspector, panel) {
+ assertAnimationsPausingOrRunning(animationInspector, panel, true);
+}
+
+/**
+ * Check whether the animations are pausing/running.
+ *
+ * @param {AnimationInspector} animationInspector
+ * @param {AnimationsPanel} panel
+ * @param {boolean} shouldPause
+ */
+function assertAnimationsPausingOrRunning(animationInspector, panel, shouldPause) {
+ const hasRunningAnimation =
+ animationInspector.state.animations.some(({state}) => state.playState === "running");
+
+ if (shouldPause) {
+ is(hasRunningAnimation, false, "All animations should be paused");
+ } else {
+ is(hasRunningAnimation, true, "Animations should be running at least one");
+ }
+}
+
+/**
+ * Check whether the animations are running.
+ *
+ * @param {AnimationInspector} animationInspector
+ * @param {AnimationsPanel} panel
+ */
+function assertAnimationsRunning(animationInspector, panel) {
+ assertAnimationsPausingOrRunning(animationInspector, panel, false);
+}
+
+/**
* Check the <stop> element in the given linearGradientEl for the correct offset
* and color attributes.
*
* @param {Element} linearGradientEl
<linearGradient> element which has <stop> element.
* @param {Number} offset
* float which represents the "offset" attribute of <stop>.
* @param {String} expectedColor