mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-10 02:08:38 +09:00
import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo
This commit is contained in:
commit
dcd9973243
150858 changed files with 23884658 additions and 0 deletions
6
devtools/client/animationinspector/test/.eslintrc.js
Normal file
6
devtools/client/animationinspector/test/.eslintrc.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
// Extend from the shared list of defined globals for mochitests.
|
||||
"extends": "../../../.eslintrc.mochitests.js"
|
||||
};
|
||||
71
devtools/client/animationinspector/test/browser.ini
Normal file
71
devtools/client/animationinspector/test/browser.ini
Normal 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]
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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");
|
||||
});
|
||||
|
|
@ -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}%`);
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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");
|
||||
});
|
||||
|
|
@ -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");
|
||||
});
|
||||
|
|
@ -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");
|
||||
});
|
||||
|
|
@ -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");
|
||||
});
|
||||
|
|
@ -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");
|
||||
});
|
||||
|
|
@ -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");
|
||||
});
|
||||
|
|
@ -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");
|
||||
});
|
||||
|
|
@ -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)]);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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");
|
||||
});
|
||||
|
|
@ -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);
|
||||
});
|
||||
|
|
@ -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");
|
||||
});
|
||||
|
|
@ -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");
|
||||
});
|
||||
|
|
@ -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);
|
||||
});
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
@ -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`);
|
||||
});
|
||||
});
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
});
|
||||
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
|
|
@ -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 });
|
||||
});
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
|
|
@ -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");
|
||||
});
|
||||
|
|
@ -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");
|
||||
});
|
||||
|
|
@ -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}%)`);
|
||||
}
|
||||
|
|
@ -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");
|
||||
});
|
||||
|
|
@ -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 }`);
|
||||
}
|
||||
});
|
||||
|
|
@ -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 }`);
|
||||
}
|
||||
}
|
||||
|
|
@ -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 }`);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
|
|
@ -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");
|
||||
});
|
||||
});
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
});
|
||||
|
|
@ -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");
|
||||
});
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
});
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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>
|
||||
69
devtools/client/animationinspector/test/doc_end_delay.html
Normal file
69
devtools/client/animationinspector/test/doc_end_delay.html
Normal 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>
|
||||
122
devtools/client/animationinspector/test/doc_frame_script.js
Normal file
122
devtools/client/animationinspector/test/doc_frame_script.js
Normal 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);
|
||||
}
|
||||
55
devtools/client/animationinspector/test/doc_keyframes.html
Normal file
55
devtools/client/animationinspector/test/doc_keyframes.html
Normal 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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
426
devtools/client/animationinspector/test/head.js
Normal file
426
devtools/client/animationinspector/test/head.js
Normal 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];
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
// Extend from the common devtools xpcshell eslintrc config.
|
||||
"extends": "../../../../.eslintrc.xpcshell.js"
|
||||
};
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
207
devtools/client/animationinspector/test/unit/test_timeScale.js
Normal file
207
devtools/client/animationinspector/test/unit/test_timeScale.js
Normal 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
12
devtools/client/animationinspector/test/unit/xpcshell.ini
Normal file
12
devtools/client/animationinspector/test/unit/xpcshell.ini
Normal 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]
|
||||
Loading…
Add table
Add a link
Reference in a new issue