import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo

This commit is contained in:
Roy Tam 2018-01-19 03:59:58 +08:00
commit dcd9973243
150858 changed files with 23884658 additions and 0 deletions

View file

@ -0,0 +1,6 @@
"use strict";
module.exports = {
// Extend from the shared list of defined globals for mochitests.
"extends": "../../../.eslintrc.mochitests.js"
};

View file

@ -0,0 +1,71 @@
[DEFAULT]
tags = devtools
subsuite = devtools
support-files =
doc_body_animation.html
doc_end_delay.html
doc_frame_script.js
doc_keyframes.html
doc_modify_playbackRate.html
doc_negative_animation.html
doc_pseudo_elements.html
doc_script_animation.html
doc_simple_animation.html
doc_multiple_animation_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
!/devtools/client/shared/test/test-actor.js
[browser_animation_animated_properties_displayed.js]
[browser_animation_click_selects_animation.js]
[browser_animation_controller_exposes_document_currentTime.js]
skip-if = os == "linux" && !debug # Bug 1234567
[browser_animation_empty_on_invalid_nodes.js]
[browser_animation_keyframe_click_to_set_time.js]
[browser_animation_keyframe_markers.js]
[browser_animation_mutations_with_same_names.js]
[browser_animation_panel_exists.js]
[browser_animation_participate_in_inspector_update.js]
[browser_animation_playerFronts_are_refreshed.js]
[browser_animation_playerWidgets_appear_on_panel_init.js]
[browser_animation_playerWidgets_target_nodes.js]
[browser_animation_pseudo_elements.js]
[browser_animation_refresh_on_added_animation.js]
[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_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]
[browser_animation_timeline_rate_selector.js]
[browser_animation_timeline_rewind_button.js]
[browser_animation_timeline_scrubber_exists.js]
[browser_animation_timeline_scrubber_movable.js]
[browser_animation_timeline_scrubber_moves.js]
[browser_animation_timeline_setCurrentTime.js]
[browser_animation_timeline_shows_delay.js]
[browser_animation_timeline_shows_endDelay.js]
[browser_animation_timeline_shows_iterations.js]
[browser_animation_timeline_shows_name_label.js]
[browser_animation_timeline_shows_time_info.js]
[browser_animation_timeline_takes_rate_into_account.js]
[browser_animation_timeline_ui.js]
[browser_animation_toggle_button_resets_on_navigate.js]
[browser_animation_toggle_button_toggles_animations.js]
[browser_animation_toolbar_exists.js]
[browser_animation_ui_updates_when_animation_data_changes.js]

View file

@ -0,0 +1,91 @@
/* 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";
const LAYOUT_ERRORS_L10N =
new LocalizationHelper("toolkit/locales/layout_errors.properties");
// Test that when an animation is selected, its list of animated properties is
// displayed below it.
const EXPECTED_PROPERTIES = [
"background-attachment",
"background-clip",
"background-color",
"background-image",
"background-origin",
"background-position-x",
"background-position-y",
"background-repeat",
"background-size",
"border-bottom-left-radius",
"border-bottom-right-radius",
"border-top-left-radius",
"border-top-right-radius",
"filter",
"height",
"transform",
"width"
].sort();
add_task(function* () {
yield addTab(URL_ROOT + "doc_keyframes.html");
let {panel} = yield openAnimationInspector();
let timeline = panel.animationsTimelineComponent;
let propertiesList = timeline.rootWrapperEl
.querySelector(".animated-properties");
ok(!isNodeVisible(propertiesList),
"The list of properties panel is hidden by default");
info("Click to select the animation");
yield clickOnAnimation(panel, 0);
ok(isNodeVisible(propertiesList),
"The list of properties panel is shown");
ok(propertiesList.querySelectorAll(".property").length,
"The list of properties panel actually contains properties");
ok(hasExpectedProperties(propertiesList),
"The list of properties panel contains the right properties");
ok(hasExpectedWarnings(propertiesList),
"The list of properties panel contains the right warnings");
info("Click to unselect the animation");
yield clickOnAnimation(panel, 0, true);
ok(!isNodeVisible(propertiesList),
"The list of properties panel is hidden again");
});
function hasExpectedProperties(containerEl) {
let names = [...containerEl.querySelectorAll(".property .name")]
.map(n => n.textContent)
.sort();
if (names.length !== EXPECTED_PROPERTIES.length) {
return false;
}
for (let i = 0; i < names.length; i++) {
if (names[i] !== EXPECTED_PROPERTIES[i]) {
return false;
}
}
return true;
}
function hasExpectedWarnings(containerEl) {
let warnings = [...containerEl.querySelectorAll(".warning")];
for (let warning of warnings) {
let warningID =
"CompositorAnimationWarningTransformWithGeometricProperties";
if (warning.getAttribute("title") == LAYOUT_ERRORS_L10N.getStr(warningID)) {
return true;
}
}
return false;
}

View file

@ -0,0 +1,44 @@
/* 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";
// Check that animations displayed in the timeline can be selected by clicking
// them, and that this emits the right events and adds the right classes.
add_task(function* () {
yield addTab(URL_ROOT + "doc_simple_animation.html");
let {panel} = yield openAnimationInspector();
let timeline = panel.animationsTimelineComponent;
let selected = timeline.rootWrapperEl.querySelectorAll(".animation.selected");
ok(!selected.length, "There are no animations selected by default");
info("Click on the first animation, expect the right event and right class");
let animation0 = yield clickOnAnimation(panel, 0);
is(animation0, timeline.animations[0],
"The selected event was emitted with the right animation");
ok(isTimeBlockSelected(timeline, 0),
"The time block has the right selected class");
info("Click on the second animation, expect it to be selected too");
let animation1 = yield clickOnAnimation(panel, 1);
is(animation1, timeline.animations[1],
"The selected event was emitted with the right animation");
ok(isTimeBlockSelected(timeline, 1),
"The second time block has the right selected class");
info("Click again on the first animation and check if it unselects");
yield clickOnAnimation(panel, 0, true);
ok(!isTimeBlockSelected(timeline, 0),
"The first time block has been unselected");
});
function isTimeBlockSelected(timeline, index) {
let animation = timeline.rootWrapperEl.querySelectorAll(".animation")[index];
let animatedProperties = timeline.rootWrapperEl.querySelectorAll(
".animated-properties")[index];
return animation.classList.contains("selected") &&
animatedProperties.classList.contains("selected");
}

View file

@ -0,0 +1,43 @@
/* 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";
// Test that the controller provides the document.timeline currentTime (at least
// the last known version since new animations were added).
add_task(function* () {
yield addTab(URL_ROOT + "doc_simple_animation.html");
let {panel, controller} = yield openAnimationInspector();
ok(controller.documentCurrentTime, "The documentCurrentTime getter exists");
checkDocumentTimeIsCorrect(controller);
let time1 = controller.documentCurrentTime;
yield startNewAnimation(controller, panel);
checkDocumentTimeIsCorrect(controller);
let time2 = controller.documentCurrentTime;
ok(time2 > time1, "The new documentCurrentTime is higher than the old one");
});
function checkDocumentTimeIsCorrect(controller) {
let time = 0;
for (let {state} of controller.animationPlayers) {
time = Math.max(time, state.documentCurrentTime);
}
is(controller.documentCurrentTime, time,
"The documentCurrentTime is correct");
}
function* startNewAnimation(controller, panel) {
info("Add a new animation to the page and check the time again");
let onPlayerAdded = controller.once(controller.PLAYERS_UPDATED_EVENT);
yield executeInContent("devtools:test:setAttribute", {
selector: ".still",
attributeName: "class",
attributeValue: "ball still short"
});
yield onPlayerAdded;
yield waitForAllAnimationTargets(panel);
}

View file

@ -0,0 +1,42 @@
/* 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 that the panel shows no animation data for invalid or not animated nodes
add_task(function* () {
yield addTab(URL_ROOT + "doc_simple_animation.html");
let {inspector, panel, window} = yield openAnimationInspector();
let {document} = window;
info("Select node .still and check that the panel is empty");
let stillNode = yield getNodeFront(".still", inspector);
let onUpdated = panel.once(panel.UI_UPDATED_EVENT);
yield selectNodeAndWaitForAnimations(stillNode, inspector);
yield onUpdated;
is(panel.animationsTimelineComponent.animations.length, 0,
"No animation players stored in the timeline component for a still node");
is(panel.animationsTimelineComponent.animationsEl.childNodes.length, 0,
"No animation displayed in the timeline component for a still node");
is(document.querySelector("#error-type").textContent,
ANIMATION_L10N.getStr("panel.invalidElementSelected"),
"The correct error message is displayed");
info("Select the comment text node and check that the panel is empty");
let commentNode = yield inspector.walker.previousSibling(stillNode);
onUpdated = panel.once(panel.UI_UPDATED_EVENT);
yield selectNodeAndWaitForAnimations(commentNode, inspector);
yield onUpdated;
is(panel.animationsTimelineComponent.animations.length, 0,
"No animation players stored in the timeline component for a text node");
is(panel.animationsTimelineComponent.animationsEl.childNodes.length, 0,
"No animation displayed in the timeline component for a text node");
is(document.querySelector("#error-type").textContent,
ANIMATION_L10N.getStr("panel.invalidElementSelected"),
"The correct error message is displayed");
});

View file

@ -0,0 +1,52 @@
/* 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";
// Test that animated properties' keyframes can be clicked, and that doing so
// sets the current time in the timeline.
add_task(function* () {
yield addTab(URL_ROOT + "doc_keyframes.html");
let {panel} = yield openAnimationInspector();
let timeline = panel.animationsTimelineComponent;
let {scrubberEl} = timeline;
// XXX: The scrollbar is placed in the timeline in such a way that it causes
// the animations to be slightly offset with the header when it appears.
// So for now, let's hide the scrollbar. Bug 1229340 should fix this.
timeline.animationsEl.style.overflow = "hidden";
info("Expand the animation");
yield clickOnAnimation(panel, 0);
info("Click on the first keyframe of the first animated property");
yield clickKeyframe(panel, 0, "background-color", 0);
info("Make sure the scrubber stopped moving and is at the right position");
yield assertScrubberMoving(panel, false);
checkScrubberPos(scrubberEl, 0);
info("Click on a keyframe in the middle");
yield clickKeyframe(panel, 0, "transform", 2);
info("Make sure the scrubber is at the right position");
checkScrubberPos(scrubberEl, 50);
});
function* clickKeyframe(panel, animIndex, property, index) {
let keyframeComponent = getKeyframeComponent(panel, animIndex, property);
let keyframeEl = getKeyframeEl(panel, animIndex, property, index);
let onSelect = keyframeComponent.once("frame-selected");
EventUtils.sendMouseEvent({type: "click"}, keyframeEl,
keyframeEl.ownerDocument.defaultView);
yield onSelect;
}
function checkScrubberPos(scrubberEl, pos) {
let newPos = Math.round(parseFloat(scrubberEl.style.left));
let expectedPos = Math.round(pos);
is(newPos, expectedPos, `The scrubber is at ${pos}%`);
}

View file

@ -0,0 +1,74 @@
/* 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";
// Test that when an animation is selected and its list of properties is shown,
// there are keyframes markers next to each property being animated.
const EXPECTED_PROPERTIES = [
"backgroundColor",
"backgroundPosition",
"backgroundSize",
"borderBottomLeftRadius",
"borderBottomRightRadius",
"borderTopLeftRadius",
"borderTopRightRadius",
"filter",
"height",
"transform",
"width"
];
add_task(function* () {
yield addTab(URL_ROOT + "doc_keyframes.html");
let {panel} = yield openAnimationInspector();
let timeline = panel.animationsTimelineComponent;
info("Expand the animation");
yield clickOnAnimation(panel, 0);
ok(timeline.rootWrapperEl.querySelectorAll(".frames .keyframes").length,
"There are container elements for displaying keyframes");
let data = yield getExpectedKeyframesData(timeline.animations[0]);
for (let propertyName in data) {
info("Check the keyframe markers for " + propertyName);
let widthMarkerSelector = ".frame[data-property=" + propertyName + "]";
let markers = timeline.rootWrapperEl.querySelectorAll(widthMarkerSelector);
is(markers.length, data[propertyName].length,
"The right number of keyframes was found for " + propertyName);
let offsets = [...markers].map(m => parseFloat(m.dataset.offset));
let values = [...markers].map(m => m.dataset.value);
for (let i = 0; i < markers.length; i++) {
is(markers[i].dataset.offset, offsets[i],
"Marker " + i + " for " + propertyName + " has the right offset");
is(markers[i].dataset.value, values[i],
"Marker " + i + " for " + propertyName + " has the right value");
}
}
});
function* getExpectedKeyframesData(animation) {
// We're testing the UI state here, so it's fine to get the list of expected
// properties from the animation actor.
let properties = yield animation.getProperties();
let data = {};
for (let expectedProperty of EXPECTED_PROPERTIES) {
data[expectedProperty] = [];
for (let {name, values} of properties) {
if (name !== expectedProperty) {
continue;
}
for (let {offset, value} of values) {
data[expectedProperty].push({offset, value});
}
}
}
return data;
}

View file

@ -0,0 +1,31 @@
/* 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";
// Check that when animations are added later (through animation mutations) and
// if these animations have the same names, then all of them are still being
// displayed (which should be true as long as these animations apply to
// different nodes).
add_task(function* () {
yield addTab(URL_ROOT + "doc_negative_animation.html");
let {controller, panel} = yield openAnimationInspector();
info("Wait until all animations have been added " +
"(they're added with setTimeout)");
while (controller.animationPlayers.length < 3) {
yield controller.once(controller.PLAYERS_UPDATED_EVENT);
}
yield waitForAllAnimationTargets(panel);
is(panel.animationsTimelineComponent.animations.length, 3,
"The timeline shows 3 animations too");
// Reduce the known nodeFronts to a set to make them unique.
let nodeFronts = new Set(panel.animationsTimelineComponent
.targetNodes.map(n => n.previewer.nodeFront));
is(nodeFronts.size, 3,
"The animations are applied to 3 different node fronts");
});

View file

@ -0,0 +1,23 @@
/* 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";
// Test that the animation panel sidebar exists
add_task(function* () {
yield addTab("data:text/html;charset=utf-8,welcome to the animation panel");
let {panel, controller} = yield openAnimationInspector();
ok(controller,
"The animation controller exists");
ok(controller.animationsFront,
"The animation controller has been initialized");
ok(panel,
"The animation panel exists");
ok(panel.playersEl,
"The animation panel has been initialized");
ok(panel.animationsTimelineComponent,
"The animation panel has been initialized");
});

View file

@ -0,0 +1,46 @@
/* 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 that the update of the animation panel participate in the
// inspector-updated event. This means that the test verifies that the
// inspector-updated event is emitted *after* the animation panel is ready.
add_task(function* () {
yield addTab(URL_ROOT + "doc_simple_animation.html");
let {inspector, panel, controller} = yield openAnimationInspector();
info("Listen for the players-updated, ui-updated and " +
"inspector-updated events");
let receivedEvents = [];
controller.once(controller.PLAYERS_UPDATED_EVENT, () => {
receivedEvents.push(controller.PLAYERS_UPDATED_EVENT);
});
panel.once(panel.UI_UPDATED_EVENT, () => {
receivedEvents.push(panel.UI_UPDATED_EVENT);
});
inspector.once("inspector-updated", () => {
receivedEvents.push("inspector-updated");
});
info("Selecting an animated node");
let node = yield getNodeFront(".animated", inspector);
yield selectNodeAndWaitForAnimations(node, inspector);
info("Check that all events were received");
// Only assert that the inspector-updated event is last, the order of the
// first 2 events is irrelevant.
is(receivedEvents.length, 3, "3 events were received");
is(receivedEvents[2], "inspector-updated",
"The third event received was the inspector-updated event");
ok(receivedEvents.indexOf(controller.PLAYERS_UPDATED_EVENT) !== -1,
"The players-updated event was received");
ok(receivedEvents.indexOf(panel.UI_UPDATED_EVENT) !== -1,
"The ui-updated event was received");
});

View file

@ -0,0 +1,36 @@
/* 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);
// Check that the AnimationPlayerFront objects lifecycle is managed by the
// AnimationController.
add_task(function* () {
yield addTab(URL_ROOT + "doc_simple_animation.html");
let {controller, inspector} = yield openAnimationInspector();
info("Selecting an animated node");
// selectNode waits for the inspector-updated event before resolving, which
// means the controller.PLAYERS_UPDATED_EVENT event has been emitted before
// and players are ready.
yield selectNodeAndWaitForAnimations(".animated", inspector);
is(controller.animationPlayers.length, 1,
"One AnimationPlayerFront has been created");
info("Selecting a node with mutliple animations");
yield selectNodeAndWaitForAnimations(".multi", inspector);
is(controller.animationPlayers.length, 2,
"2 AnimationPlayerFronts have been created");
info("Selecting a node with no animations");
yield selectNodeAndWaitForAnimations(".still", inspector);
is(controller.animationPlayers.length, 0,
"There are no more AnimationPlayerFront objects");
});

View file

@ -0,0 +1,41 @@
/* 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";
// Test that player widgets are displayed right when the animation panel is
// initialized, if the selected node (<body> by default) is animated.
const { ANIMATION_TYPES } = require("devtools/server/actors/animation");
add_task(function* () {
yield addTab(URL_ROOT + "doc_multiple_animation_types.html");
let {panel} = yield openAnimationInspector();
is(panel.animationsTimelineComponent.animations.length, 3,
"Three animations are handled by the timeline after init");
assertAnimationsDisplayed(panel, 3,
"Three animations are displayed after init");
is(
panel.animationsTimelineComponent
.animationsEl
.querySelectorAll(`.animation.${ANIMATION_TYPES.SCRIPT_ANIMATION}`)
.length,
1,
"One script-generated animation is displayed");
is(
panel.animationsTimelineComponent
.animationsEl
.querySelectorAll(`.animation.${ANIMATION_TYPES.CSS_ANIMATION}`)
.length,
1,
"One CSS animation is displayed");
is(
panel.animationsTimelineComponent
.animationsEl
.querySelectorAll(`.animation.${ANIMATION_TYPES.CSS_TRANSITION}`)
.length,
1,
"One CSS transition is displayed");
});

View file

@ -0,0 +1,33 @@
/* 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 that player widgets display information about target nodes
add_task(function* () {
yield addTab(URL_ROOT + "doc_simple_animation.html");
let {inspector, panel} = yield openAnimationInspector();
info("Select the simple animated node");
yield selectNodeAndWaitForAnimations(".animated", inspector);
let targetNodeComponent = panel.animationsTimelineComponent.targetNodes[0];
let {previewer} = targetNodeComponent;
// Make sure to wait for the target-retrieved event if the nodeFront hasn't
// yet been retrieved by the TargetNodeComponent.
if (!previewer.nodeFront) {
yield targetNodeComponent.once("target-retrieved");
}
is(previewer.el.textContent, "div#.ball.animated",
"The target element's content is correct");
let highlighterEl = previewer.el.querySelector(".node-highlighter");
ok(highlighterEl,
"The icon to highlight the target element in the page exists");
});

View file

@ -0,0 +1,49 @@
/* 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";
// Test that animated pseudo-elements do show in the timeline.
add_task(function* () {
yield addTab(URL_ROOT + "doc_pseudo_elements.html");
let {inspector, panel} = yield openAnimationInspector();
let timeline = panel.animationsTimelineComponent;
info("With <body> selected by default check the content of the timeline");
is(timeline.timeBlocks.length, 3, "There are 3 animations in the timeline");
let getTargetNodeText = index => {
let el = timeline.targetNodes[index].previewer.previewEl;
return [...el.childNodes]
.map(n => n.style.display === "none" ? "" : n.textContent)
.join("");
};
is(getTargetNodeText(0), "body", "The first animated node is <body>");
is(getTargetNodeText(1), "::before", "The second animated node is ::before");
is(getTargetNodeText(2), "::after", "The third animated node is ::after");
info("Getting the before and after nodeFronts");
let bodyContainer = yield getContainerForSelector("body", inspector);
let getBodyChildNodeFront = index => {
return bodyContainer.elt.children[1].childNodes[index].container.node;
};
let beforeNode = getBodyChildNodeFront(0);
let afterNode = getBodyChildNodeFront(1);
info("Select the ::before pseudo-element in the inspector");
yield selectNode(beforeNode, inspector);
is(timeline.timeBlocks.length, 1, "There is 1 animation in the timeline");
is(timeline.targetNodes[0].previewer.nodeFront,
inspector.selection.nodeFront,
"The right node front is displayed in the timeline");
info("Select the ::after pseudo-element in the inspector");
yield selectNode(afterNode, inspector);
is(timeline.timeBlocks.length, 1, "There is 1 animation in the timeline");
is(timeline.targetNodes[0].previewer.nodeFront,
inspector.selection.nodeFront,
"The right node front is displayed in the timeline");
});

View file

@ -0,0 +1,47 @@
/* 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 that the panel content refreshes when new animations are added.
add_task(function* () {
yield addTab(URL_ROOT + "doc_simple_animation.html");
let {inspector, panel} = yield openAnimationInspector();
info("Select a non animated node");
yield selectNodeAndWaitForAnimations(".still", inspector);
assertAnimationsDisplayed(panel, 0);
info("Start an animation on the node");
yield changeElementAndWait({
selector: ".still",
attributeName: "class",
attributeValue: "ball animated"
}, panel, inspector);
assertAnimationsDisplayed(panel, 1);
info("Remove the animation class on the node");
yield changeElementAndWait({
selector: ".ball.animated",
attributeName: "class",
attributeValue: "ball still"
}, panel, inspector);
assertAnimationsDisplayed(panel, 0);
});
function* changeElementAndWait(options, panel, inspector) {
let onPanelUpdated = panel.once(panel.UI_UPDATED_EVENT);
let onInspectorUpdated = inspector.once("inspector-updated");
yield executeInContent("devtools:test:setAttribute", options);
yield promise.all([
onInspectorUpdated, onPanelUpdated, waitForAllAnimationTargets(panel)]);
}

View file

@ -0,0 +1,50 @@
/* 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 that the panel content refreshes when animations are removed.
add_task(function* () {
yield addTab(URL_ROOT + "doc_simple_animation.html");
let {inspector, panel} = yield openAnimationInspector();
yield testRefreshOnRemove(inspector, panel);
});
function* testRefreshOnRemove(inspector, panel) {
info("Select a animated node");
yield selectNodeAndWaitForAnimations(".animated", inspector);
assertAnimationsDisplayed(panel, 1);
info("Listen to the next UI update event");
let onPanelUpdated = panel.once(panel.UI_UPDATED_EVENT);
info("Remove the animation on the node by removing the class");
yield executeInContent("devtools:test:setAttribute", {
selector: ".animated",
attributeName: "class",
attributeValue: "ball still test-node"
});
yield onPanelUpdated;
ok(true, "The panel update event was fired");
assertAnimationsDisplayed(panel, 0);
info("Add an finite animation on the node again, and wait for it to appear");
onPanelUpdated = panel.once(panel.UI_UPDATED_EVENT);
yield executeInContent("devtools:test:setAttribute", {
selector: ".test-node",
attributeName: "class",
attributeValue: "ball short test-node"
});
yield onPanelUpdated;
yield waitForAllAnimationTargets(panel);
assertAnimationsDisplayed(panel, 1);
}

View file

@ -0,0 +1,53 @@
/* 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 that the panel only refreshes when it is visible in the sidebar.
add_task(function* () {
yield addTab(URL_ROOT + "doc_simple_animation.html");
let {inspector, panel} = yield openAnimationInspector();
yield testRefresh(inspector, panel);
});
function* testRefresh(inspector, panel) {
info("Select a non animated node");
yield selectNodeAndWaitForAnimations(".still", inspector);
info("Switch to the rule-view panel");
inspector.sidebar.select("ruleview");
info("Select the animated node now");
yield selectNodeAndWaitForAnimations(".animated", inspector);
assertAnimationsDisplayed(panel, 0,
"The panel doesn't show the animation data while inactive");
info("Switch to the animation panel");
inspector.sidebar.select("animationinspector");
yield panel.once(panel.UI_UPDATED_EVENT);
assertAnimationsDisplayed(panel, 1,
"The panel shows the animation data after selecting it");
info("Switch again to the rule-view");
inspector.sidebar.select("ruleview");
info("Select the non animated node again");
yield selectNodeAndWaitForAnimations(".still", inspector);
assertAnimationsDisplayed(panel, 1,
"The panel still shows the previous animation data since it is inactive");
info("Switch to the animation panel again");
inspector.sidebar.select("animationinspector");
yield panel.once(panel.UI_UPDATED_EVENT);
assertAnimationsDisplayed(panel, 0,
"The panel is now empty after refreshing");
}

View file

@ -0,0 +1,57 @@
/* 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 that when animations displayed in the timeline are running on the
// compositor, they get a special icon and information in the tooltip.
add_task(function* () {
yield addTab(URL_ROOT + "doc_simple_animation.html");
let {inspector, panel} = yield openAnimationInspector();
let timeline = panel.animationsTimelineComponent;
info("Select a test node we know has an animation running on the compositor");
yield selectNodeAndWaitForAnimations(".animated", inspector);
let animationEl = timeline.animationsEl.querySelector(".animation");
ok(animationEl.classList.contains("fast-track"),
"The animation element has the fast-track css class");
ok(hasTooltip(animationEl,
ANIMATION_L10N.getStr("player.allPropertiesOnCompositorTooltip")),
"The animation element has the right tooltip content");
info("Select a node we know doesn't have an animation on the compositor");
yield selectNodeAndWaitForAnimations(".no-compositor", inspector);
animationEl = timeline.animationsEl.querySelector(".animation");
ok(!animationEl.classList.contains("fast-track"),
"The animation element does not have the fast-track css class");
ok(!hasTooltip(animationEl,
ANIMATION_L10N.getStr("player.allPropertiesOnCompositorTooltip")),
"The animation element does not have oncompositor tooltip content");
ok(!hasTooltip(animationEl,
ANIMATION_L10N.getStr("player.somePropertiesOnCompositorTooltip")),
"The animation element does not have oncompositor tooltip content");
info("Select a node we know has animation on the compositor and not on the" +
" compositor");
yield selectNodeAndWaitForAnimations(".compositor-notall", inspector);
animationEl = timeline.animationsEl.querySelector(".animation");
ok(animationEl.classList.contains("fast-track"),
"The animation element has the fast-track css class");
ok(hasTooltip(animationEl,
ANIMATION_L10N.getStr("player.somePropertiesOnCompositorTooltip")),
"The animation element has the right tooltip content");
});
function hasTooltip(animationEl, expected) {
let el = animationEl.querySelector(".name");
let tooltip = el.getAttribute("title");
return tooltip.indexOf(expected) !== -1;
}

View file

@ -0,0 +1,23 @@
/* 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);
// Check that when playerFronts are updated, the same number of playerWidgets
// are created in the panel.
add_task(function* () {
yield addTab(URL_ROOT + "doc_simple_animation.html");
let {inspector, panel, controller} = yield openAnimationInspector();
let timeline = panel.animationsTimelineComponent;
info("Selecting the test animated node again");
yield selectNodeAndWaitForAnimations(".multi", inspector);
is(controller.animationPlayers.length,
timeline.animationsEl.querySelectorAll(".animation").length,
"As many timeline elements were created as there are playerFronts");
});

View file

@ -0,0 +1,21 @@
/* 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 that the panel shows an animation player when an animated node is
// selected.
add_task(function* () {
yield addTab(URL_ROOT + "doc_simple_animation.html");
let {inspector, panel} = yield openAnimationInspector();
info("Select node .animated and check that the panel is not empty");
let node = yield getNodeFront(".animated", inspector);
yield selectNodeAndWaitForAnimations(node, inspector);
assertAnimationsDisplayed(panel, 1);
});

View file

@ -0,0 +1,49 @@
/* 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";
add_task(function* setup() {
yield SpecialPowers.pushPrefEnv({
set: [["dom.ipc.processCount", 1]]
});
});
// Test that the spacebar key press toggles the toggleAll button state
// when a node with no animation is selected.
// This test doesn't need to test if animations actually pause/resume
// because there's an other test that does this :
// browser_animation_toggle_button_toggles_animation.js
add_task(function* () {
yield addTab(URL_ROOT + "doc_simple_animation.html");
let {panel, inspector, window, controller} = yield openAnimationInspector();
let {toggleAllButtonEl} = panel;
// select a node without animations
yield selectNodeAndWaitForAnimations(".still", inspector);
// ensure the focus is on the animation panel
window.focus();
info("Simulate spacebar stroke and check toggleAll button" +
" is in paused state");
// sending the key will lead to a ALL_ANIMATIONS_TOGGLED_EVENT
let onToggled = once(controller, controller.ALL_ANIMATIONS_TOGGLED_EVENT);
EventUtils.sendKey("SPACE", window);
yield onToggled;
ok(toggleAllButtonEl.classList.contains("paused"),
"The toggle all button is in its paused state");
info("Simulate spacebar stroke and check toggleAll button" +
" is in playing state");
// sending the key will lead to a ALL_ANIMATIONS_TOGGLED_EVENT
onToggled = once(controller, controller.ALL_ANIMATIONS_TOGGLED_EVENT);
EventUtils.sendKey("SPACE", window);
yield onToggled;
ok(!toggleAllButtonEl.classList.contains("paused"),
"The toggle all button is in its playing state again");
});

View file

@ -0,0 +1,45 @@
/* 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";
// Test that the spacebar key press toggles the play/resume button state.
// This test doesn't need to test if animations actually pause/resume
// because there's an other test that does this.
// There are animations in the test page and since, by default, the <body> node
// is selected, animations will be displayed in the timeline, so the timeline
// play/resume button will be displayed
requestLongerTimeout(2);
add_task(function* () {
requestLongerTimeout(2);
yield addTab(URL_ROOT + "doc_simple_animation.html");
let {panel, window} = yield openAnimationInspector();
let {playTimelineButtonEl} = panel;
// ensure the focus is on the animation panel
window.focus();
info("Simulate spacebar stroke and check playResume button" +
" is in paused state");
// sending the key will lead to a UI_UPDATE_EVENT
let onUpdated = panel.once(panel.UI_UPDATED_EVENT);
EventUtils.sendKey("SPACE", window);
yield onUpdated;
ok(playTimelineButtonEl.classList.contains("paused"),
"The play/resume button is in its paused state");
info("Simulate spacebar stroke and check playResume button" +
" is in playing state");
// sending the key will lead to a UI_UPDATE_EVENT
onUpdated = panel.once(panel.UI_UPDATED_EVENT);
EventUtils.sendKey("SPACE", window);
yield onUpdated;
ok(!playTimelineButtonEl.classList.contains("paused"),
"The play/resume button is in its play state again");
});

View file

@ -0,0 +1,73 @@
/* 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 that the DOM element targets displayed in animation player widgets can
// be used to highlight elements in the DOM and select them in the inspector.
add_task(function* () {
yield addTab(URL_ROOT + "doc_simple_animation.html");
let {toolbox, inspector, panel} = yield openAnimationInspector();
info("Select the simple animated node");
let onPanelUpdated = panel.once(panel.UI_UPDATED_EVENT);
yield selectNodeAndWaitForAnimations(".animated", inspector);
yield onPanelUpdated;
let targets = yield waitForAllAnimationTargets(panel);
// Arbitrary select the first one
let targetNodeComponent = targets[0];
info("Retrieve the part of the widget that highlights the node on hover");
let highlightingEl = targetNodeComponent.previewer.previewEl;
info("Listen to node-highlight event and mouse over the widget");
let onHighlight = toolbox.once("node-highlight");
EventUtils.synthesizeMouse(highlightingEl, 10, 5, {type: "mouseover"},
highlightingEl.ownerDocument.defaultView);
let nodeFront = yield onHighlight;
// Do not forget to mouseout, otherwise we get random mouseover event
// when selecting another node, which triggers some requests in animation
// inspector.
EventUtils.synthesizeMouse(highlightingEl, 10, 5, {type: "mouseout"},
highlightingEl.ownerDocument.defaultView);
ok(true, "The node-highlight event was fired");
is(targetNodeComponent.previewer.nodeFront, nodeFront,
"The highlighted node is the one stored on the animation widget");
is(nodeFront.tagName, "DIV",
"The highlighted node has the correct tagName");
is(nodeFront.attributes[0].name, "class",
"The highlighted node has the correct attributes");
is(nodeFront.attributes[0].value, "ball animated",
"The highlighted node has the correct class");
info("Select the body node in order to have the list of all animations");
onPanelUpdated = panel.once(panel.UI_UPDATED_EVENT);
yield selectNodeAndWaitForAnimations("body", inspector);
yield onPanelUpdated;
targets = yield waitForAllAnimationTargets(panel);
targetNodeComponent = targets[0];
info("Click on the first animated node component and wait for the " +
"selection to change");
let onSelection = inspector.selection.once("new-node-front");
onPanelUpdated = panel.once(panel.UI_UPDATED_EVENT);
let nodeEl = targetNodeComponent.previewer.previewEl;
EventUtils.sendMouseEvent({type: "click"}, nodeEl,
nodeEl.ownerDocument.defaultView);
yield onSelection;
is(inspector.selection.nodeFront, targetNodeComponent.previewer.nodeFront,
"The selected node is the one stored on the animation widget");
yield onPanelUpdated;
yield waitForAllAnimationTargets(panel);
});

View file

@ -0,0 +1,54 @@
/* 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 that the DOM element targets displayed in animation player widgets can
// be used to highlight elements in the DOM and select them in the inspector.
add_task(function* () {
yield addTab(URL_ROOT + "doc_simple_animation.html");
let {panel} = yield openAnimationInspector();
let targets = panel.animationsTimelineComponent.targetNodes;
info("Click on the highlighter icon for the first animated node");
let domNodePreview1 = targets[0].previewer;
yield lockHighlighterOn(domNodePreview1);
ok(domNodePreview1.highlightNodeEl.classList.contains("selected"),
"The highlighter icon is selected");
info("Click on the highlighter icon for the second animated node");
let domNodePreview2 = targets[1].previewer;
yield lockHighlighterOn(domNodePreview2);
ok(domNodePreview2.highlightNodeEl.classList.contains("selected"),
"The highlighter icon is selected");
ok(!domNodePreview1.highlightNodeEl.classList.contains("selected"),
"The highlighter icon for the first node is unselected");
info("Click again to unhighlight");
yield unlockHighlighterOn(domNodePreview2);
ok(!domNodePreview2.highlightNodeEl.classList.contains("selected"),
"The highlighter icon for the second node is unselected");
});
function* lockHighlighterOn(domNodePreview) {
let onLocked = domNodePreview.once("target-highlighter-locked");
clickOnHighlighterIcon(domNodePreview);
yield onLocked;
}
function* unlockHighlighterOn(domNodePreview) {
let onUnlocked = domNodePreview.once("target-highlighter-unlocked");
clickOnHighlighterIcon(domNodePreview);
yield onUnlocked;
}
function clickOnHighlighterIcon(domNodePreview) {
let lockEl = domNodePreview.highlightNodeEl;
EventUtils.sendMouseEvent({type: "click"}, lockEl,
lockEl.ownerDocument.defaultView);
}

View file

@ -0,0 +1,48 @@
/* 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);
// Check that the timeline toolbar displays the current time, and that it
// changes when animations are playing, gets back to 0 when animations are
// rewound, and stops when animations are paused.
add_task(function* () {
yield addTab(URL_ROOT + "doc_simple_animation.html");
let {panel} = yield openAnimationInspector();
let label = panel.timelineCurrentTimeEl;
ok(label, "The current time label exists");
// On page load animations are playing so the time shoud change, although we
// don't want to test the exact value of the time displayed, just that it
// actually changes.
info("Make sure the time displayed actually changes");
yield isCurrentTimeLabelChanging(panel, true);
info("Pause the animations and check that the time stops changing");
yield clickTimelinePlayPauseButton(panel);
yield isCurrentTimeLabelChanging(panel, false);
info("Rewind the animations and check that the time stops changing");
yield clickTimelineRewindButton(panel);
yield isCurrentTimeLabelChanging(panel, false);
is(label.textContent, "00:00.000");
});
function* isCurrentTimeLabelChanging(panel, isChanging) {
let label = panel.timelineCurrentTimeEl;
let time1 = label.textContent;
yield new Promise(r => setTimeout(r, 200));
let time2 = label.textContent;
if (isChanging) {
ok(time1 !== time2, "The text displayed in the label changes with time");
} else {
is(time1, time2, "The text displayed in the label doesn't change");
}
}

View file

@ -0,0 +1,59 @@
/* 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);
// Check that the timeline shows correct time graduations in the header.
const {findOptimalTimeInterval, TimeScale} = require("devtools/client/animationinspector/utils");
// Should be kept in sync with TIME_GRADUATION_MIN_SPACING in
// animation-timeline.js
const TIME_GRADUATION_MIN_SPACING = 40;
add_task(function* () {
yield addTab(URL_ROOT + "doc_simple_animation.html");
// System scrollbar is enabled by default on our testing envionment and it
// would shrink width of inspector and affect number of time-ticks causing
// unexpected results. So, we set it wider to avoid this kind of edge case.
yield pushPref("devtools.toolsidebar-width.inspector", 350);
let {panel} = yield openAnimationInspector();
let timeline = panel.animationsTimelineComponent;
let headerEl = timeline.timeHeaderEl;
info("Find out how many time graduations should there be");
let width = headerEl.offsetWidth;
let animationDuration = TimeScale.maxEndTime - TimeScale.minStartTime;
let minTimeInterval = TIME_GRADUATION_MIN_SPACING * animationDuration / width;
// Note that findOptimalTimeInterval is tested separately in xpcshell test
// test_findOptimalTimeInterval.js, so we assume that it works here.
let interval = findOptimalTimeInterval(minTimeInterval);
let nb = Math.ceil(animationDuration / interval);
is(headerEl.querySelectorAll(".header-item").length, nb,
"The expected number of time ticks were found");
info("Make sure graduations are evenly distributed and show the right times");
[...headerEl.querySelectorAll(".time-tick")].forEach((tick, i) => {
let left = parseFloat(tick.style.left);
let expectedPos = i * interval * 100 / animationDuration;
is(Math.round(left), Math.round(expectedPos),
`Graduation ${i} is positioned correctly`);
// Note that the distancetoRelativeTime and formatTime functions are tested
// separately in xpcshell test test_timeScale.js, so we assume that they
// work here.
let formattedTime = TimeScale.formatTime(
TimeScale.distanceToRelativeTime(expectedPos, width));
is(tick.textContent, formattedTime,
`Graduation ${i} has the right text content`);
});
});

View file

@ -0,0 +1,71 @@
/* 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";
// Check that the iteration start is displayed correctly in time blocks.
add_task(function* () {
yield addTab(URL_ROOT + "doc_script_animation.html");
let {panel} = yield openAnimationInspector();
let timelineComponent = panel.animationsTimelineComponent;
let timeBlockComponents = timelineComponent.timeBlocks;
let detailsComponents = timelineComponent.details;
for (let i = 0; i < timeBlockComponents.length; i++) {
info(`Expand time block ${i} so its keyframes are visible`);
yield clickOnAnimation(panel, i);
info(`Check the state of time block ${i}`);
let {containerEl, animation: {state}} = timeBlockComponents[i];
checkAnimationTooltip(containerEl, state);
checkProgressAtStartingTime(containerEl, state);
// Get the first set of keyframes (there's only one animated property
// anyway), and the first frame element from there, we're only interested in
// its offset.
let keyframeComponent = detailsComponents[i].keyframeComponents[0];
let frameEl = keyframeComponent.keyframesEl.querySelector(".frame");
checkKeyframeOffset(containerEl, frameEl, state);
}
});
function checkAnimationTooltip(el, {iterationStart, duration}) {
info("Check an animation's iterationStart data in its tooltip");
let title = el.querySelector(".name").getAttribute("title");
let iterationStartTime = iterationStart * duration / 1000;
let iterationStartTimeString = iterationStartTime.toLocaleString(undefined, {
maximumFractionDigits: 2,
minimumFractionDigits: 2
}).replace(".", "\\.");
let iterationStartString = iterationStart.toString().replace(".", "\\.");
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 }`);
}
function checkKeyframeOffset(timeBlockEl, frameEl, {iterationStart}) {
info("Check that the first keyframe is offset correctly");
let start = getIterationStartFromLeft(frameEl);
is(start, iterationStart % 1, "The frame offset for iteration start");
}
function getIterationStartFromLeft(el) {
let left = 100 - parseFloat(/(\d+)%/.exec(el.style.left)[1]);
return left / 100;
}

View file

@ -0,0 +1,34 @@
/* 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);
// Check that the timeline toolbar contains a pause button and that this pause button can
// be clicked. Check that when it is, the button changes state and the scrubber stops and
// resumes.
add_task(function* () {
yield addTab(URL_ROOT + "doc_simple_animation.html");
let {panel} = yield openAnimationInspector();
let btn = panel.playTimelineButtonEl;
ok(btn, "The play/pause button exists");
ok(!btn.classList.contains("paused"), "The play/pause button is in its playing state");
info("Click on the button to pause all timeline animations");
yield clickTimelinePlayPauseButton(panel);
ok(btn.classList.contains("paused"), "The play/pause button is in its paused state");
yield assertScrubberMoving(panel, false);
info("Click again on the button to play all timeline animations");
yield clickTimelinePlayPauseButton(panel);
ok(!btn.classList.contains("paused"),
"The play/pause button is in its playing state again");
yield assertScrubberMoving(panel, true);
});

View file

@ -0,0 +1,48 @@
/* 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);
// Checks that the play/pause button goes to the right state when the scrubber has reached
// the end of the timeline but there are infinite animations playing.
add_task(function* () {
yield addTab(URL_ROOT + "doc_simple_animation.html");
let {panel, inspector} = yield openAnimationInspector();
let timeline = panel.animationsTimelineComponent;
let btn = panel.playTimelineButtonEl;
info("Select an infinite animation and wait for the scrubber to reach the end");
yield selectNodeAndWaitForAnimations(".multi", inspector);
yield waitForOutOfBoundScrubber(timeline);
ok(!btn.classList.contains("paused"),
"The button is in its playing state still, animations are infinite.");
yield assertScrubberMoving(panel, true);
info("Click on the button after the scrubber has moved out of bounds");
yield clickTimelinePlayPauseButton(panel);
ok(btn.classList.contains("paused"),
"The button can be paused after the scrubber has moved out of bounds");
yield assertScrubberMoving(panel, false);
});
function waitForOutOfBoundScrubber({win, scrubberEl}) {
return new Promise(resolve => {
function check() {
let pos = scrubberEl.getBoxQuads()[0].bounds.right;
let width = win.document.documentElement.offsetWidth;
if (pos >= width) {
setTimeout(resolve, 50);
} else {
setTimeout(check, 50);
}
}
check();
});
}

View file

@ -0,0 +1,60 @@
/* 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);
// Also checks that the button goes to the right state when the scrubber has
// reached the end of the timeline: continues to be in playing mode for infinite
// animations, goes to paused mode otherwise.
// And test that clicking the button once the scrubber has reached the end of
// the timeline does the right thing.
add_task(function* () {
yield addTab(URL_ROOT + "doc_simple_animation.html");
let {panel, controller, inspector} = yield openAnimationInspector();
let btn = panel.playTimelineButtonEl;
// For a finite animation, once the scrubber reaches the end of the timeline, the pause
// button should go back to paused mode.
info("Select a finite animation and wait for the animation to complete");
yield selectNodeAndWaitForAnimations(".negative-delay", inspector);
let onButtonPaused = waitForButtonPaused(btn);
let onTimelineUpdated = controller.once(controller.PLAYERS_UPDATED_EVENT);
// The page is reloaded to avoid missing the animation.
yield reloadTab(inspector);
yield onTimelineUpdated;
yield onButtonPaused;
ok(btn.classList.contains("paused"),
"The button is in paused state once finite animations are done");
yield assertScrubberMoving(panel, false);
info("Click again on the button to play the animation from the start again");
yield clickTimelinePlayPauseButton(panel);
ok(!btn.classList.contains("paused"),
"Clicking the button once finite animations are done should restart them");
yield assertScrubberMoving(panel, true);
});
function waitForButtonPaused(btn) {
return new Promise(resolve => {
let observer = new btn.ownerDocument.defaultView.MutationObserver(mutations => {
for (let mutation of mutations) {
if (mutation.type === "attributes" &&
mutation.attributeName === "class" &&
!mutation.oldValue.includes("paused") &&
btn.classList.contains("paused")) {
observer.disconnect();
resolve();
}
}
});
observer.observe(btn, { attributes: true, attributeOldValue: true });
});
}

View file

@ -0,0 +1,56 @@
/* 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);
// Check that the timeline toolbar contains a playback rate selector UI and that
// it can be used to change the playback rate of animations in the timeline.
// Also check that it displays the rate of the current animations in case they
// all have the same rate, or that it displays the empty value in case they
// have mixed rates.
add_task(function* () {
yield addTab(URL_ROOT + "doc_simple_animation.html");
let {panel, controller, inspector, toolbox} = yield openAnimationInspector();
// In this test, we disable the highlighter on purpose because of the way
// events are simulated to select an option in the playbackRate <select>.
// Indeed, this may cause mousemove events to be triggered on the nodes that
// are underneath the <select>, and these are AnimationTargetNode instances.
// Simulating mouse events on them will cause the highlighter to emit requests
// and this might cause the test to fail if they happen after it has ended.
disableHighlighter(toolbox);
let select = panel.rateSelectorEl.firstChild;
ok(select, "The rate selector exists");
info("Change all of the current animations' rates to 0.5");
yield changeTimelinePlaybackRate(panel, .5);
checkAllAnimationsRatesChanged(controller, select, .5);
info("Select just one animated node and change its rate only");
yield selectNodeAndWaitForAnimations(".animated", inspector);
yield changeTimelinePlaybackRate(panel, 2);
checkAllAnimationsRatesChanged(controller, select, 2);
info("Select the <body> again, it should now have mixed-rates animations");
yield selectNodeAndWaitForAnimations("body", inspector);
is(select.value, "", "The selected rate is empty");
info("Change the rate for these mixed-rate animations");
yield changeTimelinePlaybackRate(panel, 1);
checkAllAnimationsRatesChanged(controller, select, 1);
});
function checkAllAnimationsRatesChanged({animationPlayers}, select, rate) {
ok(animationPlayers.every(({state}) => state.playbackRate === rate),
"All animations' rates have been set to " + rate);
is(select.value, rate, "The right value is displayed in the select");
}

View file

@ -0,0 +1,51 @@
/* 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);
// Check that the timeline toolbar contains a rewind button and that it can be
// clicked. Check that when it is, the current animations displayed in the
// timeline get their playstates changed to paused, and their currentTimes
// reset to 0, and that the scrubber stops moving and is positioned to the
// start.
add_task(function* () {
yield addTab(URL_ROOT + "doc_simple_animation.html");
let {panel, controller} = yield openAnimationInspector();
let players = controller.animationPlayers;
let btn = panel.rewindTimelineButtonEl;
ok(btn, "The rewind button exists");
info("Click on the button to rewind all timeline animations");
yield clickTimelineRewindButton(panel);
info("Check that the scrubber has stopped moving");
yield assertScrubberMoving(panel, false);
ok(players.every(({state}) => state.currentTime === 0),
"All animations' currentTimes have been set to 0");
ok(players.every(({state}) => state.playState === "paused"),
"All animations have been paused");
info("Play the animations again");
yield clickTimelinePlayPauseButton(panel);
info("And pause them after a short while");
yield new Promise(r => setTimeout(r, 200));
info("Check that rewinding when animations are paused works too");
yield clickTimelineRewindButton(panel);
info("Check that the scrubber has stopped moving");
yield assertScrubberMoving(panel, false);
ok(players.every(({state}) => state.currentTime === 0),
"All animations' currentTimes have been set to 0");
ok(players.every(({state}) => state.playState === "paused"),
"All animations have been paused");
});

View file

@ -0,0 +1,20 @@
/* 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);
// Check that the timeline does have a scrubber element.
add_task(function* () {
yield addTab(URL_ROOT + "doc_simple_animation.html");
let {panel} = yield openAnimationInspector();
let timeline = panel.animationsTimelineComponent;
let scrubberEl = timeline.scrubberEl;
ok(scrubberEl, "The scrubber element exists");
ok(scrubberEl.classList.contains("scrubber"), "It has the right classname");
});

View file

@ -0,0 +1,70 @@
/* 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);
// Check that the scrubber in the timeline can be moved by clicking & dragging
// in the header area.
// Also check that doing so changes the timeline's play/pause button to paused
// state.
// Finally, also check that the scrubber can be moved using the scrubber handle.
add_task(function* () {
yield addTab(URL_ROOT + "doc_simple_animation.html");
let {panel} = yield openAnimationInspector();
let timeline = panel.animationsTimelineComponent;
let {win, timeHeaderEl, scrubberEl, scrubberHandleEl} = timeline;
let playTimelineButtonEl = panel.playTimelineButtonEl;
ok(!playTimelineButtonEl.classList.contains("paused"),
"The timeline play button is in its playing state by default");
info("Mousedown in the header to move the scrubber");
yield synthesizeInHeaderAndWaitForChange(timeline, 50, 1, "mousedown");
checkScrubberIsAt(scrubberEl, timeHeaderEl, 50);
ok(playTimelineButtonEl.classList.contains("paused"),
"The timeline play button is in its paused state after mousedown");
info("Continue moving the mouse and verify that the scrubber tracks it");
yield synthesizeInHeaderAndWaitForChange(timeline, 100, 1, "mousemove");
checkScrubberIsAt(scrubberEl, timeHeaderEl, 100);
ok(playTimelineButtonEl.classList.contains("paused"),
"The timeline play button is in its paused state after mousemove");
info("Release the mouse and move again and verify that the scrubber stays");
EventUtils.synthesizeMouse(timeHeaderEl, 100, 1, {type: "mouseup"}, win);
EventUtils.synthesizeMouse(timeHeaderEl, 200, 1, {type: "mousemove"}, win);
checkScrubberIsAt(scrubberEl, timeHeaderEl, 100);
info("Try to drag the scrubber handle and check that the scrubber moves");
let onDataChanged = timeline.once("timeline-data-changed");
EventUtils.synthesizeMouse(scrubberHandleEl, 1, 20, {type: "mousedown"}, win);
EventUtils.synthesizeMouse(timeHeaderEl, 0, 0, {type: "mousemove"}, win);
EventUtils.synthesizeMouse(timeHeaderEl, 0, 0, {type: "mouseup"}, win);
yield onDataChanged;
checkScrubberIsAt(scrubberEl, timeHeaderEl, 0);
});
function* synthesizeInHeaderAndWaitForChange(timeline, x, y, type) {
let onDataChanged = timeline.once("timeline-data-changed");
EventUtils.synthesizeMouse(timeline.timeHeaderEl, x, y, {type}, timeline.win);
yield onDataChanged;
}
function getPositionPercentage(pos, headerEl) {
return pos * 100 / headerEl.offsetWidth;
}
function checkScrubberIsAt(scrubberEl, timeHeaderEl, pos) {
let newPos = Math.round(parseFloat(scrubberEl.style.left));
let expectedPos = Math.round(getPositionPercentage(pos, timeHeaderEl));
is(newPos, expectedPos,
`The scrubber is at position ${pos} (${expectedPos}%)`);
}

View file

@ -0,0 +1,28 @@
/* 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);
// Check that the scrubber in the timeline moves when animations are playing.
// The animations in the test page last for a very long time, so the test just
// measures the position of the scrubber once, then waits for some time to pass
// and measures its position again.
add_task(function* () {
yield addTab(URL_ROOT + "doc_simple_animation.html");
let {panel} = yield openAnimationInspector();
let timeline = panel.animationsTimelineComponent;
let scrubberEl = timeline.scrubberEl;
let startPos = scrubberEl.getBoundingClientRect().left;
info("Wait for some time to check that the scrubber moves");
yield new Promise(r => setTimeout(r, 2000));
let endPos = scrubberEl.getBoundingClientRect().left;
ok(endPos > startPos, "The scrubber has moved");
});

View file

@ -0,0 +1,88 @@
/* 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);
// Animation.currentTime ignores neagtive delay and positive/negative endDelay
// during fill-mode, even if they are set.
// For example, when the animation timing is
// { duration: 1000, iterations: 1, endDelay: -500, easing: linear },
// the animation progress is 0.5 at 700ms because the progress stops as 0.5 at
// 500ms in original animation. However, if you set as
// animation.currentTime = 700 manually, the progress will be 0.7.
// So we modify setCurrentTime method since
// AnimationInspector should re-produce same as original animation.
// In these tests,
// we confirm the behavior of setCurrentTime by delay and endDelay.
add_task(function* () {
yield addTab(URL_ROOT + "doc_timing_combination_animation.html");
const { panel, controller } = yield openAnimationInspector();
yield clickTimelinePlayPauseButton(panel);
const timelineComponent = panel.animationsTimelineComponent;
const timeBlockComponents = timelineComponent.timeBlocks;
// Test -5000ms.
let time = -5000;
yield controller.setCurrentTimeAll(time, true);
for (let i = 0; i < timeBlockComponents.length; i++) {
yield timeBlockComponents[i].animation.refreshState();
const state = yield timeBlockComponents[i].animation.state;
info(`Check the state at ${ time }ms with `
+ `delay:${ state.delay } and endDelay:${ state.endDelay }`);
is(state.currentTime, 0,
`The currentTime should be 0 at setCurrentTime(${ time })`);
}
// Test 10000ms.
time = 10000;
yield controller.setCurrentTimeAll(time, true);
for (let i = 0; i < timeBlockComponents.length; i++) {
yield timeBlockComponents[i].animation.refreshState();
const state = yield timeBlockComponents[i].animation.state;
info(`Check the state at ${ time }ms with `
+ `delay:${ state.delay } and endDelay:${ state.endDelay }`);
const expected = state.delay < 0 ? 0 : time;
is(state.currentTime, expected,
`The currentTime should be ${ expected } at setCurrentTime(${ time }).`
+ ` delay: ${ state.delay } and endDelay: ${ state.endDelay }`);
}
// Test 60000ms.
time = 60000;
yield controller.setCurrentTimeAll(time, true);
for (let i = 0; i < timeBlockComponents.length; i++) {
yield timeBlockComponents[i].animation.refreshState();
const state = yield timeBlockComponents[i].animation.state;
info(`Check the state at ${ time }ms with `
+ `delay:${ state.delay } and endDelay:${ state.endDelay }`);
const expected = state.delay < 0 ? time + state.delay : time;
is(state.currentTime, expected,
`The currentTime should be ${ expected } at setCurrentTime(${ time }).`
+ ` delay: ${ state.delay } and endDelay: ${ state.endDelay }`);
}
// Test 150000ms.
time = 150000;
yield controller.setCurrentTimeAll(time, true);
for (let i = 0; i < timeBlockComponents.length; i++) {
yield timeBlockComponents[i].animation.refreshState();
const state = yield timeBlockComponents[i].animation.state;
info(`Check the state at ${ time }ms with `
+ `delay:${ state.delay } and endDelay:${ state.endDelay }`);
const currentTime = state.delay < 0 ? time + state.delay : time;
const endTime =
state.delay + state.iterationCount * state.duration + state.endDelay;
const expected =
state.endDelay < 0 && state.fill === "both" && currentTime > endTime
? endTime : currentTime;
is(state.currentTime, expected,
`The currentTime should be ${ expected } at setCurrentTime(${ time }).`
+ ` delay: ${ state.delay } and endDelay: ${ state.endDelay }`);
}
});

View file

@ -0,0 +1,96 @@
/* 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);
// Check that animation delay is visualized in the timeline when the animation
// is delayed.
// Also check that negative delays do not overflow the UI, and are shown like
// positive delays.
add_task(function* () {
yield addTab(URL_ROOT + "doc_simple_animation.html");
let {inspector, panel} = yield openAnimationInspector();
info("Selecting a delayed animated node");
yield selectNodeAndWaitForAnimations(".delayed", inspector);
let timelineEl = panel.animationsTimelineComponent.rootWrapperEl;
checkDelayAndName(timelineEl, true);
let animationEl = timelineEl.querySelector(".animation");
let state = panel.animationsTimelineComponent.timeBlocks[0].animation.state;
checkPath(animationEl, state);
info("Selecting a no-delay animated node");
yield selectNodeAndWaitForAnimations(".animated", inspector);
checkDelayAndName(timelineEl, false);
animationEl = timelineEl.querySelector(".animation");
state = panel.animationsTimelineComponent.timeBlocks[0].animation.state;
checkPath(animationEl, state);
info("Selecting a negative-delay animated node");
yield selectNodeAndWaitForAnimations(".negative-delay", inspector);
checkDelayAndName(timelineEl, true);
animationEl = timelineEl.querySelector(".animation");
state = panel.animationsTimelineComponent.timeBlocks[0].animation.state;
checkPath(animationEl, state);
});
function checkDelayAndName(timelineEl, hasDelay) {
let delay = timelineEl.querySelector(".delay");
is(!!delay, hasDelay, "The timeline " +
(hasDelay ? "contains" : "does not contain") +
" a delay element, as expected");
if (hasDelay) {
let targetNode = timelineEl.querySelector(".target");
// Check that the delay element does not cause the timeline to overflow.
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");
// 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 }`);
}
}

View file

@ -0,0 +1,78 @@
/* 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);
// Check that animation endDelay is visualized in the timeline when the
// animation is delayed.
// Also check that negative endDelays do not overflow the UI, and are shown
// like positive endDelays.
add_task(function* () {
yield addTab(URL_ROOT + "doc_end_delay.html");
let {inspector, panel} = yield openAnimationInspector();
let selectors = ["#target1", "#target2", "#target3", "#target4"];
for (let i = 0; i < selectors.length; i++) {
let selector = selectors[i];
yield selectNode(selector, inspector);
let timelineEl = panel.animationsTimelineComponent.rootWrapperEl;
let animationEl = timelineEl.querySelector(".animation");
checkEndDelayAndName(animationEl);
const state =
panel.animationsTimelineComponent.timeBlocks[0].animation.state;
checkPath(animationEl, state);
}
});
function checkEndDelayAndName(animationEl) {
let endDelay = animationEl.querySelector(".end-delay");
let name = animationEl.querySelector(".name");
let targetNode = animationEl.querySelector(".target");
// Check that the endDelay element does not cause the timeline to overflow.
let endDelayLeft = Math.round(endDelay.getBoundingClientRect().x);
let sidebarWidth = Math.round(targetNode.getBoundingClientRect().width);
ok(endDelayLeft >= sidebarWidth,
"The endDelay element isn't displayed over the sidebar");
// 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");
// 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 }`);
}
}

View file

@ -0,0 +1,47 @@
/* 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);
// Check that the timeline is displays as many iteration elements as there are
// iterations in an animation.
add_task(function* () {
yield addTab(URL_ROOT + "doc_simple_animation.html");
let {inspector, panel} = yield openAnimationInspector();
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(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(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;
}

View file

@ -0,0 +1,46 @@
/* 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);
// Check the text content and width of name label.
add_task(function* () {
yield addTab(URL_ROOT + "doc_simple_animation.html");
let {inspector, panel} = yield openAnimationInspector();
info("Selecting 'simple-animation' animation which is running on compositor");
yield selectNodeAndWaitForAnimations(".animated", inspector);
checkNameLabel(panel.animationsTimelineComponent.rootWrapperEl, "simple-animation");
info("Selecting 'no-compositor' animation which is not running on compositor");
yield selectNodeAndWaitForAnimations(".no-compositor", inspector);
checkNameLabel(panel.animationsTimelineComponent.rootWrapperEl, "no-compositor");
});
function checkNameLabel(rootWrapperEl, expectedLabelContent) {
const timeblockEl = rootWrapperEl.querySelector(".time-block");
const labelEl = rootWrapperEl.querySelector(".name div");
is(labelEl.textContent, expectedLabelContent,
`Text content of labelEl sould be ${ expectedLabelContent }`);
// Expand timeblockEl to avoid max-width of the label.
timeblockEl.style.width = "10000px";
const originalLabelWidth = labelEl.clientWidth;
ok(originalLabelWidth < timeblockEl.clientWidth / 2,
"Label width should be less than 50%");
// Set timeblockEl width to double of original label width.
timeblockEl.style.width = `${ originalLabelWidth * 2 }px`;
is(labelEl.clientWidth + labelEl.offsetLeft, originalLabelWidth,
`Label width + offsetLeft should be ${ originalLabelWidth }px`);
// Shrink timeblockEl to enable max-width.
timeblockEl.style.width = `${ originalLabelWidth }px`;
is(labelEl.clientWidth + labelEl.offsetLeft,
Math.round(timeblockEl.clientWidth / 2),
"Label width + offsetLeft should be half of timeblockEl");
}

View file

@ -0,0 +1,50 @@
/* 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);
// Check that the timeline displays animations' duration, delay iteration
// counts and iteration start in tooltips.
add_task(function* () {
yield addTab(URL_ROOT + "doc_simple_animation.html");
let {panel, controller} = yield openAnimationInspector();
info("Getting the animation element from the panel");
let timelineEl = panel.animationsTimelineComponent.rootWrapperEl;
let timeBlockNameEls = timelineEl.querySelectorAll(".time-block .name");
// Verify that each time-block's name element has a tooltip that looks sort of
// ok. We don't need to test the actual content.
[...timeBlockNameEls].forEach((el, i) => {
ok(el.hasAttribute("title"), "The tooltip is defined for animation " + i);
let title = el.getAttribute("title");
if (controller.animationPlayers[i].state.delay) {
ok(title.match(/Delay: [\d.-]+s/), "The tooltip shows the delay");
}
ok(title.match(/Duration: [\d.]+s/), "The tooltip shows the duration");
if (controller.animationPlayers[i].state.endDelay) {
ok(title.match(/End delay: [\d.-]+s/), "The tooltip shows the endDelay");
}
if (controller.animationPlayers[i].state.iterationCount !== 1) {
ok(title.match(/Repeats: /), "The tooltip shows the iterations");
} else {
ok(!title.match(/Repeats: /), "The tooltip doesn't show the iterations");
}
if (controller.animationPlayers[i].state.easing) {
ok(title.match(/Easing: /), "The tooltip shows the easing");
}
if (controller.animationPlayers[i].state.fill) {
ok(title.match(/Fill: /), "The tooltip shows the fill");
}
if (controller.animationPlayers[i].state.direction) {
ok(title.match(/Direction: /), "The tooltip shows the direction");
}
ok(!title.match(/Iteration start:/),
"The tooltip doesn't show the iteration start");
});
});

View file

@ -0,0 +1,81 @@
/* 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";
// Check that if an animation has had its playbackRate changed via the DOM, then
// the timeline UI shows the right delay and duration.
// Indeed, the header in the timeline UI always shows the unaltered time,
// because there might be multiple animations displayed at the same time, some
// of which may have a different rate than others. Those that have had their
// rate changed have a delay = delay/rate and a duration = duration/rate.
add_task(function* () {
yield addTab(URL_ROOT + "doc_modify_playbackRate.html");
let {panel} = yield openAnimationInspector();
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);
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);
// 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");
});
function getDuration(pathEl) {
const pathSegList = pathEl.pathSegList;
// Find the index of starting iterations.
let startingIterationIndex = 0;
const firstPathSeg = pathSegList.getItem(1);
for (let i = 2, n = pathSegList.numberOfItems - 2; i < n; i++) {
// Changing point of the progress acceleration is the time.
const pathSeg = pathSegList.getItem(i);
if (firstPathSeg.y != pathSeg.y) {
startingIterationIndex = i;
break;
}
}
// Find the index of ending iterations.
let endingIterationIndex = 0;
let previousPathSegment = pathSegList.getItem(startingIterationIndex);
for (let i = startingIterationIndex + 1, n = pathSegList.numberOfItems - 2;
i < n; i++) {
// Find forwards fill-mode.
const pathSeg = pathSegList.getItem(i);
if (previousPathSegment.y == pathSeg.y) {
endingIterationIndex = i;
break;
}
previousPathSegment = pathSeg;
}
if (endingIterationIndex) {
// Not forwards fill-mode
endingIterationIndex = pathSegList.numberOfItems - 2;
}
// Return the distance of starting and ending
const startingIterationPathSegment =
pathSegList.getItem(startingIterationIndex);
const endingIterationPathSegment =
pathSegList.getItem(startingIterationIndex);
return endingIterationPathSegment.x - startingIterationPathSegment.x;
}

View file

@ -0,0 +1,43 @@
/* 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);
// Check that the timeline contains the right elements.
add_task(function* () {
yield addTab(URL_ROOT + "doc_simple_animation.html");
let {panel} = yield openAnimationInspector();
let timeline = panel.animationsTimelineComponent;
let el = timeline.rootWrapperEl;
ok(el.querySelector(".time-header"),
"The header element is in the DOM of the timeline");
ok(el.querySelectorAll(".time-header .header-item").length,
"The header has some time graduations");
ok(el.querySelector(".animations"),
"The animations container is in the DOM of the timeline");
is(el.querySelectorAll(".animations .animation").length,
timeline.animations.length,
"The number of animations displayed matches the number of animations");
for (let i = 0; i < timeline.animations.length; i++) {
let animation = timeline.animations[i];
let animationEl = el.querySelectorAll(".animations .animation")[i];
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"),
"The timeline has svg and path element as summary graph");
}
});

View file

@ -0,0 +1,31 @@
/* 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 that a page navigation resets the state of the global toggle button.
add_task(function* () {
yield addTab(URL_ROOT + "doc_simple_animation.html");
let {inspector, panel} = yield openAnimationInspector();
info("Select the non-animated test node");
yield selectNodeAndWaitForAnimations(".still", inspector);
ok(!panel.toggleAllButtonEl.classList.contains("paused"),
"The toggle button is in its running state by default");
info("Toggle all animations, so that they pause");
yield panel.toggleAll();
ok(panel.toggleAllButtonEl.classList.contains("paused"),
"The toggle button now is in its paused state");
info("Reloading the page");
yield reloadTab(inspector);
ok(!panel.toggleAllButtonEl.classList.contains("paused"),
"The toggle button is back in its running state");
});

View file

@ -0,0 +1,32 @@
/* 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 that the main toggle button actually toggles animations.
// This test doesn't need to be extra careful about checking that *all*
// animations have been paused (including inside iframes) because there's an
// actor test in /devtools/server/tests/browser/ that does this.
add_task(function* () {
yield addTab(URL_ROOT + "doc_simple_animation.html");
let {panel} = yield openAnimationInspector();
info("Click the toggle button");
yield panel.toggleAll();
yield checkState("paused");
info("Click again the toggle button");
yield panel.toggleAll();
yield checkState("running");
});
function* checkState(state) {
for (let selector of [".animated", ".multi", ".long"]) {
let playState = yield getAnimationPlayerState(selector);
is(playState, state, "The animation on node " + selector + " is " + state);
}
}

View file

@ -0,0 +1,36 @@
/* 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 that the animation panel has a top toolbar that contains the play/pause
// button and that is displayed at all times.
// Also test that this toolbar gets replaced by the timeline toolbar when there
// are animations to be displayed.
add_task(function* () {
yield addTab(URL_ROOT + "doc_simple_animation.html");
let {inspector, window} = yield openAnimationInspector();
let doc = window.document;
let toolbar = doc.querySelector("#global-toolbar");
ok(toolbar, "The panel contains the toolbar element with the new UI");
ok(!isNodeVisible(toolbar),
"The toolbar is hidden while there are animations");
let timelineToolbar = doc.querySelector("#timeline-toolbar");
ok(timelineToolbar, "The panel contains a timeline toolbar element");
ok(isNodeVisible(timelineToolbar),
"The timeline toolbar is visible when there are animations");
info("Select a node that has no animations");
yield selectNodeAndWaitForAnimations(".still", inspector);
ok(isNodeVisible(toolbar),
"The toolbar is shown when there are no animations");
ok(!isNodeVisible(timelineToolbar),
"The timeline toolbar is hidden when there are no animations");
});

View file

@ -0,0 +1,53 @@
/* 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);
// Verify that if the animation's duration, iterations or delay change in
// content, then the widget reflects the changes.
add_task(function* () {
yield addTab(URL_ROOT + "doc_simple_animation.html");
let {panel, controller, inspector} = yield openAnimationInspector();
info("Select the test node");
yield selectNodeAndWaitForAnimations(".animated", inspector);
let animation = controller.animationPlayers[0];
yield setStyle(animation, panel, "animationDuration", "5.5s");
yield setStyle(animation, panel, "animationIterationCount", "300");
yield setStyle(animation, panel, "animationDelay", "45s");
let animationsEl = panel.animationsTimelineComponent.animationsEl;
let timeBlockEl = animationsEl.querySelector(".time-block");
// 45s delay + (300 * 5.5)s duration
let expectedTotalDuration = 1695 * 1000;
// XXX: the nb and size of each iteration cannot be tested easily (displayed
// using a linear-gradient background and capped at 2px wide). They should
// be tested in bug 1173761.
let delayWidth = parseFloat(timeBlockEl.querySelector(".delay").style.width);
is(Math.round(delayWidth * expectedTotalDuration / 100), 45 * 1000,
"The timeline has the right delay");
});
function* setStyle(animation, panel, name, value) {
info("Change the animation style via the content DOM. Setting " +
name + " to " + value);
let onAnimationChanged = once(animation, "changed");
yield executeInContent("devtools:test:setStyle", {
selector: ".animated",
propertyName: name,
propertyValue: value
});
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);
}

View file

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<style>
body {
background-color: white;
color: black;
animation: change-background-color 3s infinite alternate;
}
@keyframes change-background-color {
to {
background-color: black;
color: white;
}
}
</style>
</head>
<body>
<h1>Animated body element</h1>
</body>
</html>

View file

@ -0,0 +1,69 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<style>
.target {
width: 50px;
height: 50px;
background: blue;
}
</style>
</head>
<body>
<div id="target1" class="target"></div>
<div id="target2" class="target"></div>
<div id="target3" class="target"></div>
<div id="target4" class="target"></div>
<script>
/* globals KeyframeEffect, Animation */
"use strict";
let animations = [{
id: "target1",
frames: [{ opacity: 0, offset: 0 }, { opacity: 1, offset: 1 }],
timing: {
id: "endDelay_animation1",
duration: 1000000,
endDelay: 500000,
fill: "none"
}
}, {
id: "target2",
frames: [{ opacity: 0, offset: 0 }, { opacity: 1, offset: 1 }],
timing: {
id: "endDelay_animation2",
duration: 1000000,
endDelay: -500000,
fill: "none"
}
}, {
id: "target3",
frames: [{ opacity: 0, offset: 0 }, { opacity: 1, offset: 1 }],
timing: {
id: "endDelay_animation3",
duration: 1000000,
endDelay: -1500000,
fill: "forwards"
}
}, {
id: "target4",
frames: [{ opacity: 0, offset: 0 }, { opacity: 1, offset: 1 }],
timing: {
id: "endDelay_animation4",
duration: 100000,
delay: 100000,
endDelay: -1500000,
fill: "forwards"
}
}];
for (let {id, frames, timing} of animations) {
let effect = new KeyframeEffect(document.getElementById(id),
frames, timing);
let animation = new Animation(effect, document.timeline);
animation.play();
}
</script>
</body>
</html>

View file

@ -0,0 +1,122 @@
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* globals addMessageListener, sendAsyncMessage */
"use strict";
// A helper frame-script for brower/devtools/animationinspector tests.
/**
* Toggle (play or pause) one of the animation players of a given node.
* @param {Object} data
* - {String} selector The CSS selector to get the node (can be a "super"
* selector).
* - {Number} animationIndex The index of the node's animationPlayers to play
* or pause
* - {Boolean} pause True to pause the animation, false to play.
*/
addMessageListener("Test:ToggleAnimationPlayer", function (msg) {
let {selector, animationIndex, pause} = msg.data;
let node = superQuerySelector(selector);
if (!node) {
return;
}
let animation = node.getAnimations()[animationIndex];
if (pause) {
animation.pause();
} else {
animation.play();
}
sendAsyncMessage("Test:ToggleAnimationPlayer");
});
/**
* Change the currentTime of one of the animation players of a given node.
* @param {Object} data
* - {String} selector The CSS selector to get the node (can be a "super"
* selector).
* - {Number} animationIndex The index of the node's animationPlayers to change.
* - {Number} currentTime The current time to set.
*/
addMessageListener("Test:SetAnimationPlayerCurrentTime", function (msg) {
let {selector, animationIndex, currentTime} = msg.data;
let node = superQuerySelector(selector);
if (!node) {
return;
}
let animation = node.getAnimations()[animationIndex];
animation.currentTime = currentTime;
sendAsyncMessage("Test:SetAnimationPlayerCurrentTime");
});
/**
* Change the playbackRate of one of the animation players of a given node.
* @param {Object} data
* - {String} selector The CSS selector to get the node (can be a "super"
* selector).
* - {Number} animationIndex The index of the node's animationPlayers to change.
* - {Number} playbackRate The rate to set.
*/
addMessageListener("Test:SetAnimationPlayerPlaybackRate", function (msg) {
let {selector, animationIndex, playbackRate} = msg.data;
let node = superQuerySelector(selector);
if (!node) {
return;
}
let player = node.getAnimations()[animationIndex];
player.playbackRate = playbackRate;
sendAsyncMessage("Test:SetAnimationPlayerPlaybackRate");
});
/**
* Get the current playState of an animation player on a given node.
* @param {Object} data
* - {String} selector The CSS selector to get the node (can be a "super"
* selector).
* - {Number} animationIndex The index of the node's animationPlayers to check
*/
addMessageListener("Test:GetAnimationPlayerState", function (msg) {
let {selector, animationIndex} = msg.data;
let node = superQuerySelector(selector);
if (!node) {
return;
}
let animation = node.getAnimations()[animationIndex];
animation.ready.then(() => {
sendAsyncMessage("Test:GetAnimationPlayerState", animation.playState);
});
});
/**
* Like document.querySelector but can go into iframes too.
* ".container iframe || .sub-container div" will first try to find the node
* matched by ".container iframe" in the root document, then try to get the
* content document inside it, and then try to match ".sub-container div" inside
* this document.
* Any selector coming before the || separator *MUST* match a frame node.
* @param {String} superSelector.
* @return {DOMNode} The node, or null if not found.
*/
function superQuerySelector(superSelector, root = content.document) {
let frameIndex = superSelector.indexOf("||");
if (frameIndex === -1) {
return root.querySelector(superSelector);
}
let rootSelector = superSelector.substring(0, frameIndex).trim();
let childSelector = superSelector.substring(frameIndex + 2).trim();
root = root.querySelector(rootSelector);
if (!root || !root.contentWindow) {
return null;
}
return superQuerySelector(childSelector, root.contentWindow.document);
}

View file

@ -0,0 +1,55 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Yay! Keyframes!</title>
<style>
div {
animation: wow 100s forwards;
}
@keyframes wow {
0% {
width: 100px;
height: 100px;
border-radius: 0px;
background: #f06;
}
10% {
border-radius: 2px;
}
20% {
transform: rotate(13deg);
}
30% {
background: gold;
}
40% {
filter: blur(40px);
}
50% {
transform: rotate(720deg) translateX(300px) skew(-13deg);
}
60% {
width: 200px;
height: 200px;
}
70% {
border-radius: 10px;
}
80% {
background: #333;
}
90% {
border-radius: 50%;
}
100% {
width: 500px;
height: 500px;
}
}
</style>
</head>
<body>
<div></div>
</body>
</html>

View file

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<style>
div {
width: 50px;
height: 50px;
background: blue;
animation: move 20s 20s linear;
animation-fill-mode: forwards;
}
@keyframes move {
to {
margin-left: 200px;
}
}
</style>
</head>
<body>
<div></div>
<div class="rate"></div>
<script>
"use strict";
var el = document.querySelector(".rate");
var ani = el.getAnimations()[0];
ani.playbackRate = 2;
</script>
</body>
</html>

View file

@ -0,0 +1,61 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<style>
.ball {
width: 80px;
height: 80px;
border-radius: 50%;
}
.script-animation {
background: #f06;
}
.css-transition {
background: #006;
transition: background-color 20s;
}
.css-animation {
background: #a06;
animation: flash 10s forwards;
}
@keyframes flash {
0% {
opacity: 1;
}
50% {
opacity: 0;
}
100% {
opacity: 1;
}
}
</style>
</head>
<body>
<div class="ball script-animation"></div>
<div class="ball css-animation"></div>
<div class="ball css-transition"></div>
<script>
/* globals KeyframeEffect, Animation */
"use strict";
setTimeout(function () {
document.querySelector(".css-transition").style.backgroundColor = "yellow";
}, 0);
let effect = new KeyframeEffect(
document.querySelector(".script-animation"), [
{opacity: 1, offset: 0},
{opacity: .1, offset: 1}
], { duration: 10000, fill: "forwards" });
let animation = new Animation(effect, document.timeline);
animation.play();
</script>
</body>
</html>

View file

@ -0,0 +1,66 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<style>
html, body {
margin: 0;
height: 100%;
overflow: hidden;
}
div {
position: absolute;
top: 0;
left: -500px;
height: 20px;
width: 500px;
color: red;
background: linear-gradient(to left, currentColor, currentColor 2px, transparent);
}
.zero {
color: blue;
top: 20px;
}
.positive {
color: green;
top: 40px;
}
.negative.move { animation: 5s -1s move linear forwards; }
.zero.move { animation: 5s 0s move linear forwards; }
.positive.move { animation: 5s 1s move linear forwards; }
@keyframes move {
to {
transform: translateX(500px);
}
}
</style>
</head>
<body>
<div class="negative"></div>
<div class="zero"></div>
<div class="positive"></div>
<script>
"use strict";
var negative = document.querySelector(".negative");
var zero = document.querySelector(".zero");
var positive = document.querySelector(".positive");
// The non-delayed animation starts now.
zero.classList.add("move");
// The negative-delayed animation starts in 1 second.
setTimeout(function () {
negative.classList.add("move");
}, 1000);
// The positive-delayed animation starts in 200 ms.
setTimeout(function () {
positive.classList.add("move");
}, 200);
</script>
</body>
</html>

View file

@ -0,0 +1,61 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Animated pseudo elements</title>
<style>
html, body {
margin: 0;
height: 100%;
width: 100%;
overflow: hidden;
display: flex;
justify-content: center;
align-items: flex-end;
}
body {
animation: color 2s linear infinite;
background: #333;
}
@keyframes color {
to {
filter: hue-rotate(360deg);
}
}
body::before,
body::after {
content: "";
flex-grow: 1;
height: 100%;
animation: grow 1s linear infinite alternate;
}
body::before {
background: hsl(120, 80%, 80%);
}
body::after {
background: hsl(240, 80%, 80%);
animation-delay: -.5s;
}
@keyframes grow {
0% {height: 100%; animation-timing-function: ease-in-out;}
10% {height: 80%; animation-timing-function: ease-in-out;}
20% {height: 60%; animation-timing-function: ease-in-out;}
30% {height: 70%; animation-timing-function: ease-in-out;}
40% {height: 50%; animation-timing-function: ease-in-out;}
50% {height: 30%; animation-timing-function: ease-in-out;}
60% {height: 80%; animation-timing-function: ease-in-out;}
70% {height: 90%; animation-timing-function: ease-in-out;}
80% {height: 70%; animation-timing-function: ease-in-out;}
90% {height: 60%; animation-timing-function: ease-in-out;}
100% {height: 100%; animation-timing-function: ease-in-out;}
}
</style>
</head>
<body>
</body>
</html>

View file

@ -0,0 +1,71 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<style>
#target1 {
width: 50px;
height: 50px;
background: red;
}
#target2 {
width: 50px;
height: 50px;
background: green;
}
#target3 {
width: 50px;
height: 50px;
background: blue;
}
</style>
</head>
<body>
<div id="target1"></div>
<div id="target2"></div>
<div id="target3"></div>
<script>
/* globals KeyframeEffect, Animation */
"use strict";
let animations = [{
id: "target1",
frames: [{ opacity: 0, offset: 0 }, { opacity: 1, offset: 1 }],
timing: {
duration: 100,
iterations: 2,
iterationStart: 0.25,
fill: "both"
}
}, {
id: "target2",
frames: [{ opacity: 0, offset: 0 }, { opacity: 1, offset: 1 }],
timing: {
duration: 100,
iterations: 1,
iterationStart: 0.25,
fill: "both"
}
}, {
id: "target3",
frames: [{ opacity: 0, offset: 0 }, { opacity: 1, offset: 1 }],
timing: {
duration: 100,
iterations: 1.5,
iterationStart: 2.5,
fill: "both"
}
}];
for (let {id, frames, timing} of animations) {
let effect = new KeyframeEffect(document.getElementById(id),
frames, timing);
let animation = new Animation(effect, document.timeline);
animation.play();
}
</script>
</body>
</html>

View file

@ -0,0 +1,147 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<style>
.ball {
width: 80px;
height: 80px;
border-radius: 50%;
background: #f06;
position: absolute;
}
.still {
top: 0;
left: 10px;
}
.animated {
top: 100px;
left: 10px;
animation: simple-animation 2s infinite alternate;
}
.multi {
top: 200px;
left: 10px;
animation: simple-animation 2s infinite alternate,
other-animation 5s infinite alternate;
}
.delayed {
top: 300px;
left: 10px;
background: rebeccapurple;
animation: simple-animation 3s 60s 10;
}
.multi-finite {
top: 400px;
left: 10px;
background: yellow;
animation: simple-animation 3s,
other-animation 4s;
}
.short {
top: 500px;
left: 10px;
background: red;
animation: simple-animation 2s;
}
.long {
top: 600px;
left: 10px;
background: blue;
animation: simple-animation 120s;
}
.negative-delay {
top: 700px;
left: 10px;
background: gray;
animation: simple-animation 15s -10s;
animation-fill-mode: forwards;
}
.no-compositor {
top: 0;
right: 10px;
background: gold;
animation: no-compositor 10s cubic-bezier(.57,-0.02,1,.31) forwards;
}
.compositor-notall {
animation: compositor-notall 2s infinite;
}
@keyframes simple-animation {
100% {
transform: translateX(300px);
}
}
@keyframes other-animation {
100% {
background: blue;
}
}
@keyframes no-compositor {
100% {
margin-right: 600px;
}
}
@keyframes compositor-notall {
from {
opacity: 0;
width: 0px;
transform: translate(0px);
}
to {
opacity: 1;
width: 100px;
transform: translate(100px);
}
}
</style>
</head>
<body>
<!-- Comment node -->
<div class="ball still"></div>
<div class="ball animated"></div>
<div class="ball multi"></div>
<div class="ball delayed"></div>
<div class="ball multi-finite"></div>
<div class="ball short"></div>
<div class="ball long"></div>
<div class="ball negative-delay"></div>
<div class="ball no-compositor"></div>
<div class="ball" id="endDelayed"></div>
<div class="ball compositor-notall"></div>
<script>
/* globals KeyframeEffect, Animation */
"use strict";
var el = document.getElementById("endDelayed");
let effect = new KeyframeEffect(el, [
{ opacity: 0, offset: 0 },
{ opacity: 1, offset: 1 }
], { duration: 1000000, endDelay: 500000, fill: "none" });
let animation = new Animation(effect, document.timeline);
animation.play();
</script>
</body>
</html>

View file

@ -0,0 +1,35 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<style>
div {
display: inline-block;
width: 100px;
height: 100px;
background-color: lime;
}
</style>
</head>
<body>
<script>
"use strict";
const delayList = [0, 50000, -50000];
const endDelayList = [0, 50000, -50000];
delayList.forEach(delay => {
endDelayList.forEach(endDelay => {
const el = document.createElement("div");
document.body.appendChild(el);
el.animate({ opacity: [0, 1] },
{ duration: 200000,
iterations: 1,
fill: "both",
delay: delay,
endDelay: endDelay });
});
});
</script>
</body>
</html>

View file

@ -0,0 +1,426 @@
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* eslint no-unused-vars: [2, {"vars": "local", "args": "none"}] */
"use strict";
/* import-globals-from ../../inspector/test/head.js */
// Import the inspector's head.js first (which itself imports shared-head.js).
Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/devtools/client/inspector/test/head.js",
this);
const FRAME_SCRIPT_URL = CHROME_URL_ROOT + "doc_frame_script.js";
const COMMON_FRAME_SCRIPT_URL = "chrome://devtools/content/shared/frame-script-utils.js";
const TAB_NAME = "animationinspector";
const ANIMATION_L10N =
new LocalizationHelper("devtools/client/locales/animationinspector.properties");
// Auto clean-up when a test ends
registerCleanupFunction(function* () {
yield closeAnimationInspector();
while (gBrowser.tabs.length > 1) {
gBrowser.removeCurrentTab();
}
});
// Clean-up all prefs that might have been changed during a test run
// (safer here because if the test fails, then the pref is never reverted)
registerCleanupFunction(() => {
Services.prefs.clearUserPref("devtools.debugger.log");
});
// WebAnimations API is not enabled by default in all release channels yet, see
// Bug 1264101.
function enableWebAnimationsAPI() {
return new Promise(resolve => {
SpecialPowers.pushPrefEnv({"set": [
["dom.animations-api.core.enabled", true]
]}, resolve);
});
}
/**
* Add a new test tab in the browser and load the given url.
* @param {String} url The url to be loaded in the new tab
* @return a promise that resolves to the tab object when the url is loaded
*/
var _addTab = addTab;
addTab = function (url) {
return enableWebAnimationsAPI().then(() => _addTab(url)).then(tab => {
let browser = tab.linkedBrowser;
info("Loading the helper frame script " + FRAME_SCRIPT_URL);
browser.messageManager.loadFrameScript(FRAME_SCRIPT_URL, false);
info("Loading the helper frame script " + COMMON_FRAME_SCRIPT_URL);
browser.messageManager.loadFrameScript(COMMON_FRAME_SCRIPT_URL, false);
return tab;
});
};
/**
* Reload the current tab location.
* @param {InspectorPanel} inspector The instance of InspectorPanel currently
* loaded in the toolbox
*/
function* reloadTab(inspector) {
let onNewRoot = inspector.once("new-root");
yield executeInContent("devtools:test:reload", {}, {}, false);
yield onNewRoot;
yield inspector.once("inspector-updated");
}
/*
* 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
* Defaults to "test" which instructs the inspector not
* to highlight the node upon selection
* @return {Promise} Resolves when the inspector is updated with the new node
and animations of its subtree are properly displayed.
*/
var selectNodeAndWaitForAnimations = Task.async(
function* (data, inspector, reason = "test") {
yield selectNode(data, inspector, reason);
// We want to make sure the rest of the test waits for the animations to
// be properly displayed (wait for all target DOM nodes to be previewed).
let {AnimationsPanel} = inspector.sidebar.getWindowForTab(TAB_NAME);
yield waitForAllAnimationTargets(AnimationsPanel);
}
);
/**
* Check if there are the expected number of animations being displayed in the
* panel right now.
* @param {AnimationsPanel} panel
* @param {Number} nbAnimations The expected number of animations.
* @param {String} msg An optional string to be used as the assertion message.
*/
function assertAnimationsDisplayed(panel, nbAnimations, msg = "") {
msg = msg || `There are ${nbAnimations} animations in the panel`;
is(panel.animationsTimelineComponent
.animationsEl
.querySelectorAll(".animation").length, nbAnimations, msg);
}
/**
* Takes an Inspector panel that was just created, and waits
* for a "inspector-updated" event as well as the animation inspector
* sidebar to be ready. Returns a promise once these are completed.
*
* @param {InspectorPanel} inspector
* @return {Promise}
*/
var waitForAnimationInspectorReady = Task.async(function* (inspector) {
let win = inspector.sidebar.getWindowForTab(TAB_NAME);
let updated = inspector.once("inspector-updated");
// In e10s, if we wait for underlying toolbox actors to
// load (by setting DevToolsUtils.testing to true), we miss the
// "animationinspector-ready" event on the sidebar, so check to see if the
// iframe is already loaded.
let tabReady = win.document.readyState === "complete" ?
promise.resolve() :
inspector.sidebar.once("animationinspector-ready");
return promise.all([updated, tabReady]);
});
/**
* Open the toolbox, with the inspector tool visible and the animationinspector
* sidebar selected.
* @return a promise that resolves when the inspector is ready.
*/
var openAnimationInspector = Task.async(function* () {
let {inspector, toolbox} = yield openInspectorSidebarTab(TAB_NAME);
info("Waiting for the inspector and sidebar to be ready");
yield waitForAnimationInspectorReady(inspector);
let win = inspector.sidebar.getWindowForTab(TAB_NAME);
let {AnimationsController, AnimationsPanel} = win;
info("Waiting for the animation controller and panel to be ready");
if (AnimationsPanel.initialized) {
yield AnimationsPanel.initialized;
} else {
yield AnimationsPanel.once(AnimationsPanel.PANEL_INITIALIZED);
}
// Make sure we wait for all animations to be loaded (especially their target
// nodes to be lazily displayed). This is safe to do even if there are no
// animations displayed.
yield waitForAllAnimationTargets(AnimationsPanel);
return {
toolbox: toolbox,
inspector: inspector,
controller: AnimationsController,
panel: AnimationsPanel,
window: win
};
});
/**
* Close the toolbox.
* @return a promise that resolves when the toolbox has closed.
*/
var closeAnimationInspector = Task.async(function* () {
let target = TargetFactory.forTab(gBrowser.selectedTab);
yield gDevTools.closeToolbox(target);
});
/**
* Wait for a content -> chrome message on the message manager (the window
* messagemanager is used).
* @param {String} name The message name
* @return {Promise} A promise that resolves to the response data when the
* message has been received
*/
function waitForContentMessage(name) {
info("Expecting message " + name + " from content");
let mm = gBrowser.selectedBrowser.messageManager;
return new Promise(resolve => {
mm.addMessageListener(name, function onMessage(msg) {
mm.removeMessageListener(name, onMessage);
resolve(msg.data);
});
});
}
/**
* Send an async message to the frame script (chrome -> content) and wait for a
* response message with the same name (content -> chrome).
* @param {String} name The message name. Should be one of the messages defined
* in doc_frame_script.js
* @param {Object} data Optional data to send along
* @param {Object} objects Optional CPOW objects to send along
* @param {Boolean} expectResponse If set to false, don't wait for a response
* with the same name from the content script. Defaults to true.
* @return {Promise} Resolves to the response data if a response is expected,
* immediately resolves otherwise
*/
function executeInContent(name, data = {}, objects = {},
expectResponse = true) {
info("Sending message " + name + " to content");
let mm = gBrowser.selectedBrowser.messageManager;
mm.sendAsyncMessage(name, data, objects);
if (expectResponse) {
return waitForContentMessage(name);
}
return promise.resolve();
}
/**
* Get the current playState of an animation player on a given node.
*/
var getAnimationPlayerState = Task.async(function* (selector,
animationIndex = 0) {
let playState = yield executeInContent("Test:GetAnimationPlayerState",
{selector, animationIndex});
return playState;
});
/**
* Is the given node visible in the page (rendered in the frame tree).
* @param {DOMNode}
* @return {Boolean}
*/
function isNodeVisible(node) {
return !!node.getClientRects().length;
}
/**
* Wait for all AnimationTargetNode instances to be fully loaded
* (fetched their related actor and rendered), and return them.
* @param {AnimationsPanel} panel
* @return {Array} all AnimationTargetNode instances
*/
var waitForAllAnimationTargets = Task.async(function* (panel) {
let targets = panel.animationsTimelineComponent.targetNodes;
yield promise.all(targets.map(t => {
if (!t.previewer.nodeFront) {
return t.once("target-retrieved");
}
return false;
}));
return targets;
});
/**
* Check the scrubber element in the timeline is moving.
* @param {AnimationPanel} panel
* @param {Boolean} isMoving
*/
function* assertScrubberMoving(panel, isMoving) {
let timeline = panel.animationsTimelineComponent;
if (isMoving) {
// If we expect the scrubber to move, just wait for a couple of
// timeline-data-changed events and compare times.
let {time: time1} = yield timeline.once("timeline-data-changed");
let {time: time2} = yield timeline.once("timeline-data-changed");
ok(time2 > time1, "The scrubber is moving");
} else {
// If instead we expect the scrubber to remain at its position, just wait
// for some time and make sure timeline-data-changed isn't emitted.
let hasMoved = false;
timeline.once("timeline-data-changed", () => {
hasMoved = true;
});
yield new Promise(r => setTimeout(r, 500));
ok(!hasMoved, "The scrubber is not moving");
}
}
/**
* Click the play/pause button in the timeline toolbar and wait for animations
* to update.
* @param {AnimationsPanel} panel
*/
function* clickTimelinePlayPauseButton(panel) {
let onUiUpdated = panel.once(panel.UI_UPDATED_EVENT);
let btn = panel.playTimelineButtonEl;
let win = btn.ownerDocument.defaultView;
EventUtils.sendMouseEvent({type: "click"}, btn, win);
yield onUiUpdated;
yield waitForAllAnimationTargets(panel);
}
/**
* Click the rewind button in the timeline toolbar and wait for animations to
* update.
* @param {AnimationsPanel} panel
*/
function* clickTimelineRewindButton(panel) {
let onUiUpdated = panel.once(panel.UI_UPDATED_EVENT);
let btn = panel.rewindTimelineButtonEl;
let win = btn.ownerDocument.defaultView;
EventUtils.sendMouseEvent({type: "click"}, btn, win);
yield onUiUpdated;
yield waitForAllAnimationTargets(panel);
}
/**
* Select a rate inside the playback rate selector in the timeline toolbar and
* wait for animations to update.
* @param {AnimationsPanel} panel
* @param {Number} rate The new rate value to be selected
*/
function* changeTimelinePlaybackRate(panel, rate) {
let onUiUpdated = panel.once(panel.UI_UPDATED_EVENT);
let select = panel.rateSelectorEl.firstChild;
let win = select.ownerDocument.defaultView;
// Get the right option.
let option = [...select.options].filter(o => o.value === rate + "")[0];
if (!option) {
ok(false,
"Could not find an option for rate " + rate + " in the rate selector. " +
"Values are: " + [...select.options].map(o => o.value));
return;
}
// Simulate the right events to select the option in the drop-down.
EventUtils.synthesizeMouseAtCenter(select, {type: "mousedown"}, win);
EventUtils.synthesizeMouseAtCenter(option, {type: "mouseup"}, win);
yield onUiUpdated;
yield waitForAllAnimationTargets(panel);
// Simulate a mousemove outside of the rate selector area to avoid subsequent
// tests from failing because of unwanted mouseover events.
EventUtils.synthesizeMouseAtCenter(
win.document.querySelector("#timeline-toolbar"), {type: "mousemove"}, win);
}
/**
* Prevent the toolbox common highlighter from making backend requests.
* @param {Toolbox} toolbox
*/
function disableHighlighter(toolbox) {
toolbox._highlighter = {
showBoxModel: () => new Promise(r => r()),
hideBoxModel: () => new Promise(r => r()),
pick: () => new Promise(r => r()),
cancelPick: () => new Promise(r => r()),
destroy: () => {},
traits: {}
};
}
/**
* Click on an animation in the timeline to select/unselect it.
* @param {AnimationsPanel} panel The panel instance.
* @param {Number} index The index of the animation to click on.
* @param {Boolean} shouldClose Set to true if clicking should close the
* animation.
* @return {Promise} resolves to the animation whose state has changed.
*/
function* clickOnAnimation(panel, index, shouldClose) {
let timeline = panel.animationsTimelineComponent;
// Expect a selection event.
let onSelectionChanged = timeline.once(shouldClose
? "animation-unselected"
: "animation-selected");
// If we're opening the animation, also wait for the keyframes-retrieved
// event.
let onReady = shouldClose
? Promise.resolve()
: timeline.details[index].once("keyframes-retrieved");
info("Click on animation " + index + " in the timeline");
let timeBlock = timeline.rootWrapperEl.querySelectorAll(".time-block")[index];
EventUtils.sendMouseEvent({type: "click"}, timeBlock,
timeBlock.ownerDocument.defaultView);
yield onReady;
return yield onSelectionChanged;
}
/**
* Get an instance of the Keyframes component from the timeline.
* @param {AnimationsPanel} panel The panel instance.
* @param {Number} animationIndex The index of the animation in the timeline.
* @param {String} propertyName The name of the animated property.
* @return {Keyframes} The Keyframes component instance.
*/
function getKeyframeComponent(panel, animationIndex, propertyName) {
let timeline = panel.animationsTimelineComponent;
let detailsComponent = timeline.details[animationIndex];
return detailsComponent.keyframeComponents
.find(c => c.propertyName === propertyName);
}
/**
* Get a keyframe element from the timeline.
* @param {AnimationsPanel} panel The panel instance.
* @param {Number} animationIndex The index of the animation in the timeline.
* @param {String} propertyName The name of the animated property.
* @param {Index} keyframeIndex The index of the keyframe.
* @return {DOMNode} The keyframe element.
*/
function getKeyframeEl(panel, animationIndex, propertyName, keyframeIndex) {
let keyframeComponent = getKeyframeComponent(panel, animationIndex,
propertyName);
return keyframeComponent.keyframesEl
.querySelectorAll(".frame")[keyframeIndex];
}

View file

@ -0,0 +1,6 @@
"use strict";
module.exports = {
// Extend from the common devtools xpcshell eslintrc config.
"extends": "../../../../.eslintrc.xpcshell.js"
};

View file

@ -0,0 +1,81 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* eslint no-eval:0 */
"use strict";
var Cu = Components.utils;
const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
const {findOptimalTimeInterval} = require("devtools/client/animationinspector/utils");
// This test array contains objects that are used to test the
// findOptimalTimeInterval function. Each object should have the following
// properties:
// - desc: an optional string that will be printed out
// - minTimeInterval: a number that represents the minimum time in ms
// that should be displayed in one interval
// - expectedInterval: a number that you expect the findOptimalTimeInterval
// function to return as a result.
// Optionally you can pass a string where `interval` is the calculated
// interval, this string will be eval'd and tested to be truthy.
const TEST_DATA = [{
desc: "With no minTimeInterval, expect the interval to be 0",
minTimeInterval: null,
expectedInterval: 0
}, {
desc: "With a minTimeInterval of 0 ms, expect the interval to be 0",
minTimeInterval: 0,
expectedInterval: 0
}, {
desc: "With a minInterval of 1ms, expect the interval to be the 1ms too",
minTimeInterval: 1,
expectedInterval: 1
}, {
desc: "With a very small minTimeInterval, expect the interval to be 1ms",
minTimeInterval: 1e-31,
expectedInterval: 1
}, {
desc: "With a minInterval of 2.5ms, expect the interval to be 2.5ms too",
minTimeInterval: 2.5,
expectedInterval: 2.5
}, {
desc: "With a minInterval of 5ms, expect the interval to be 5ms too",
minTimeInterval: 5,
expectedInterval: 5
}, {
desc: "With a minInterval of 7ms, expect the interval to be the next " +
"multiple of 5",
minTimeInterval: 7,
expectedInterval: 10
}, {
minTimeInterval: 20,
expectedInterval: 25
}, {
minTimeInterval: 33,
expectedInterval: 50
}, {
minTimeInterval: 987,
expectedInterval: 1000
}, {
minTimeInterval: 1234,
expectedInterval: 2500
}, {
minTimeInterval: 9800,
expectedInterval: 10000
}];
function run_test() {
for (let {minTimeInterval, desc, expectedInterval} of TEST_DATA) {
do_print(`Testing minTimeInterval: ${minTimeInterval}.
Expecting ${expectedInterval}.`);
let interval = findOptimalTimeInterval(minTimeInterval);
if (typeof expectedInterval == "string") {
ok(eval(expectedInterval), desc);
} else {
equal(interval, expectedInterval, desc);
}
}
}

View file

@ -0,0 +1,62 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* 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";
var Cu = Components.utils;
const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
const {formatStopwatchTime} = require("devtools/client/animationinspector/utils");
const TEST_DATA = [{
desc: "Formatting 0",
time: 0,
expected: "00:00.000"
}, {
desc: "Formatting null",
time: null,
expected: "00:00.000"
}, {
desc: "Formatting undefined",
time: undefined,
expected: "00:00.000"
}, {
desc: "Formatting a small number of ms",
time: 13,
expected: "00:00.013"
}, {
desc: "Formatting a slightly larger number of ms",
time: 500,
expected: "00:00.500"
}, {
desc: "Formatting 1 second",
time: 1000,
expected: "00:01.000"
}, {
desc: "Formatting a number of seconds",
time: 1532,
expected: "00:01.532"
}, {
desc: "Formatting a big number of seconds",
time: 58450,
expected: "00:58.450"
}, {
desc: "Formatting 1 minute",
time: 60000,
expected: "01:00.000"
}, {
desc: "Formatting a number of minutes",
time: 263567,
expected: "04:23.567"
}, {
desc: "Formatting a large number of minutes",
time: 1000 * 60 * 60 * 3,
expected: "180:00.000"
}];
function run_test() {
for (let {desc, time, expected} of TEST_DATA) {
equal(formatStopwatchTime(time), expected, desc);
}
}

View file

@ -0,0 +1,27 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* 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";
var Cu = Components.utils;
const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
const {getCssPropertyName} = require("devtools/client/animationinspector/components/animation-details");
const TEST_DATA = [{
jsName: "alllowercase",
cssName: "alllowercase"
}, {
jsName: "borderWidth",
cssName: "border-width"
}, {
jsName: "borderTopRightRadius",
cssName: "border-top-right-radius"
}];
function run_test() {
for (let {jsName, cssName} of TEST_DATA) {
equal(getCssPropertyName(jsName), cssName);
}
}

View file

@ -0,0 +1,207 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* 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";
var Cu = Components.utils;
const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
const {TimeScale} = require("devtools/client/animationinspector/utils");
const TEST_ANIMATIONS = [{
desc: "Testing a few standard animations",
animations: [{
previousStartTime: 500,
delay: 0,
duration: 1000,
iterationCount: 1,
playbackRate: 1
}, {
previousStartTime: 400,
delay: 100,
duration: 10,
iterationCount: 100,
playbackRate: 1
}, {
previousStartTime: 50,
delay: 1000,
duration: 100,
iterationCount: 20,
playbackRate: 1
}],
expectedMinStart: 50,
expectedMaxEnd: 3050
}, {
desc: "Testing a single negative-delay animation",
animations: [{
previousStartTime: 100,
delay: -100,
duration: 100,
iterationCount: 1,
playbackRate: 1
}],
expectedMinStart: 0,
expectedMaxEnd: 100
}, {
desc: "Testing a single negative-delay animation with a different rate",
animations: [{
previousStartTime: 3500,
delay: -1000,
duration: 10000,
iterationCount: 2,
playbackRate: 2
}],
expectedMinStart: 3000,
expectedMaxEnd: 13000
}];
const TEST_STARTTIME_TO_DISTANCE = [{
time: 50,
expectedDistance: 0
}, {
time: 50,
expectedDistance: 0
}, {
time: 3050,
expectedDistance: 100
}, {
time: 1550,
expectedDistance: 50
}];
const TEST_DURATION_TO_DISTANCE = [{
time: 3000,
expectedDistance: 100
}, {
time: 0,
expectedDistance: 0
}];
const TEST_DISTANCE_TO_TIME = [{
distance: 100,
expectedTime: 3050
}, {
distance: 0,
expectedTime: 50
}, {
distance: 25,
expectedTime: 800
}];
const TEST_DISTANCE_TO_RELATIVE_TIME = [{
distance: 100,
expectedTime: 3000
}, {
distance: 0,
expectedTime: 0
}, {
distance: 25,
expectedTime: 750
}];
const TEST_FORMAT_TIME_MS = [{
time: 0,
expectedFormattedTime: "0ms"
}, {
time: 3540.341,
expectedFormattedTime: "3540ms"
}, {
time: 1.99,
expectedFormattedTime: "2ms"
}, {
time: 4000,
expectedFormattedTime: "4000ms"
}];
const TEST_FORMAT_TIME_S = [{
time: 0,
expectedFormattedTime: "0.0s"
}, {
time: 3540.341,
expectedFormattedTime: "3.5s"
}, {
time: 1.99,
expectedFormattedTime: "0.0s"
}, {
time: 4000,
expectedFormattedTime: "4.0s"
}, {
time: 102540,
expectedFormattedTime: "102.5s"
}, {
time: 102940,
expectedFormattedTime: "102.9s"
}];
function run_test() {
do_print("Check the default min/max range values");
equal(TimeScale.minStartTime, Infinity);
equal(TimeScale.maxEndTime, 0);
for (let {desc, animations, expectedMinStart, expectedMaxEnd} of
TEST_ANIMATIONS) {
do_print("Test adding a few animations: " + desc);
for (let state of animations) {
TimeScale.addAnimation(state);
}
do_print("Checking the time scale range");
equal(TimeScale.minStartTime, expectedMinStart);
equal(TimeScale.maxEndTime, expectedMaxEnd);
do_print("Test reseting the animations");
TimeScale.reset();
equal(TimeScale.minStartTime, Infinity);
equal(TimeScale.maxEndTime, 0);
}
do_print("Add a set of animations again");
for (let state of TEST_ANIMATIONS[0].animations) {
TimeScale.addAnimation(state);
}
do_print("Test converting start times to distances");
for (let {time, expectedDistance} of TEST_STARTTIME_TO_DISTANCE) {
let distance = TimeScale.startTimeToDistance(time);
equal(distance, expectedDistance);
}
do_print("Test converting durations to distances");
for (let {time, expectedDistance} of TEST_DURATION_TO_DISTANCE) {
let distance = TimeScale.durationToDistance(time);
equal(distance, expectedDistance);
}
do_print("Test converting distances to times");
for (let {distance, expectedTime} of TEST_DISTANCE_TO_TIME) {
let time = TimeScale.distanceToTime(distance);
equal(time, expectedTime);
}
do_print("Test converting distances to relative times");
for (let {distance, expectedTime} of TEST_DISTANCE_TO_RELATIVE_TIME) {
let time = TimeScale.distanceToRelativeTime(distance);
equal(time, expectedTime);
}
do_print("Test formatting times (millis)");
for (let {time, expectedFormattedTime} of TEST_FORMAT_TIME_MS) {
let formattedTime = TimeScale.formatTime(time);
equal(formattedTime, expectedFormattedTime);
}
// Add 1 more animation to increase the range and test more time formatting
// cases.
TimeScale.addAnimation({
startTime: 3000,
duration: 5000,
delay: 0,
iterationCount: 1
});
do_print("Test formatting times (seconds)");
for (let {time, expectedFormattedTime} of TEST_FORMAT_TIME_S) {
let formattedTime = TimeScale.formatTime(time);
equal(formattedTime, expectedFormattedTime);
}
}

View file

@ -0,0 +1,54 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
https://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const Cu = Components.utils;
const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
const {TimeScale} = require("devtools/client/animationinspector/utils");
const TEST_ENDDELAY_X = [{
desc: "Testing positive-endDelay animations",
animations: [{
previousStartTime: 0,
duration: 500,
playbackRate: 1,
iterationCount: 3,
delay: 500,
endDelay: 500
}],
expectedEndDelayX: 80
}, {
desc: "Testing negative-endDelay animations",
animations: [{
previousStartTime: 0,
duration: 500,
playbackRate: 1,
iterationCount: 9,
delay: 500,
endDelay: -500
}],
expectedEndDelayX: 90
}];
function run_test() {
do_print("Test calculating endDelayX");
// Be independent of possible prior tests
TimeScale.reset();
for (let {desc, animations, expectedEndDelayX} of TEST_ENDDELAY_X) {
do_print(`Adding animations: ${desc}`);
for (let state of animations) {
TimeScale.addAnimation(state);
let {endDelayX} = TimeScale.getAnimationDimensions({state});
equal(endDelayX, expectedEndDelayX);
TimeScale.reset();
}
}
}

View file

@ -0,0 +1,12 @@
[DEFAULT]
tags = devtools
head =
tail =
firefox-appdir = browser
skip-if = toolkit == 'android'
[test_findOptimalTimeInterval.js]
[test_formatStopwatchTime.js]
[test_getCssPropertyName.js]
[test_timeScale.js]
[test_timeScale_dimensions.js]