mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1120899 - Extract the animationplayer meta-data UI to a refreshable component; r=bgrins
This commit is contained in:
parent
c159c9c250
commit
97e5971148
@ -138,6 +138,8 @@ function PlayerWidget(player, containerEl) {
|
||||
|
||||
this.onStateChanged = this.onStateChanged.bind(this);
|
||||
this.onPlayPauseBtnClick = this.onPlayPauseBtnClick.bind(this);
|
||||
|
||||
this.metaDataComponent = new PlayerMetaDataHeader();
|
||||
}
|
||||
|
||||
PlayerWidget.prototype = {
|
||||
@ -159,6 +161,7 @@ PlayerWidget.prototype = {
|
||||
|
||||
this.stopTimelineAnimation();
|
||||
this.stopListeners();
|
||||
this.metaDataComponent.destroy();
|
||||
|
||||
this.el.remove();
|
||||
this.playPauseBtnEl = this.currentTimeEl = this.timeDisplayEl = null;
|
||||
@ -184,45 +187,8 @@ PlayerWidget.prototype = {
|
||||
}
|
||||
});
|
||||
|
||||
// Animation header
|
||||
let titleEl = createNode({
|
||||
parent: this.el,
|
||||
attributes: {
|
||||
"class": "animation-title"
|
||||
}
|
||||
});
|
||||
let titleHTML = "";
|
||||
|
||||
// Name.
|
||||
if (state.name) {
|
||||
// Css animations have names.
|
||||
titleHTML += L10N.getStr("player.animationNameLabel");
|
||||
titleHTML += "<strong>" + state.name + "</strong>";
|
||||
} else {
|
||||
// Css transitions don't.
|
||||
titleHTML += L10N.getStr("player.transitionNameLabel");
|
||||
}
|
||||
|
||||
// Duration, delay and iteration count.
|
||||
titleHTML += "<span class='meta-data'>";
|
||||
titleHTML += L10N.getStr("player.animationDurationLabel");
|
||||
titleHTML += "<strong>" + L10N.getFormatStr("player.timeLabel",
|
||||
this.getFormattedTime(state.duration)) + "</strong>";
|
||||
|
||||
if (state.delay) {
|
||||
titleHTML += L10N.getStr("player.animationDelayLabel");
|
||||
titleHTML += "<strong>" + L10N.getFormatStr("player.timeLabel",
|
||||
this.getFormattedTime(state.delay)) + "</strong>";
|
||||
}
|
||||
|
||||
if (state.iterationCount !== 1) {
|
||||
titleHTML += L10N.getStr("player.animationIterationCountLabel");
|
||||
let count = state.iterationCount || L10N.getStr("player.infiniteIterationCount");
|
||||
titleHTML += "<strong>" + count + "</strong>";
|
||||
}
|
||||
|
||||
titleHTML += "</span>";
|
||||
titleEl.innerHTML = titleHTML;
|
||||
this.metaDataComponent.createMarkup(this.el);
|
||||
this.metaDataComponent.render(state);
|
||||
|
||||
// Timeline widget.
|
||||
let timelineEl = createNode({
|
||||
@ -296,18 +262,6 @@ PlayerWidget.prototype = {
|
||||
this.displayTime(state.currentTime);
|
||||
},
|
||||
|
||||
/**
|
||||
* Format time as a string.
|
||||
* @param {Number} time Defaults to the player's currentTime.
|
||||
* @return {String} The formatted time, e.g. "10.55"
|
||||
*/
|
||||
getFormattedTime: function(time) {
|
||||
return (time/1000).toLocaleString(undefined, {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Executed when the playPause button is clicked.
|
||||
* Note that tests may want to call this callback directly rather than
|
||||
@ -328,7 +282,8 @@ PlayerWidget.prototype = {
|
||||
*/
|
||||
onStateChanged: function() {
|
||||
let state = this.player.state;
|
||||
this.updateWidgetState(state.playState);
|
||||
this.updateWidgetState(state);
|
||||
this.metaDataComponent.render(state);
|
||||
|
||||
switch (state.playState) {
|
||||
case "finished":
|
||||
@ -354,7 +309,7 @@ PlayerWidget.prototype = {
|
||||
pause: function() {
|
||||
// Switch to the right className on the element right away to avoid waiting
|
||||
// for the next state update to change the playPause icon.
|
||||
this.updateWidgetState("paused");
|
||||
this.updateWidgetState({playState: "paused"});
|
||||
return this.player.pause().then(() => {
|
||||
this.stopTimelineAnimation();
|
||||
});
|
||||
@ -368,12 +323,12 @@ PlayerWidget.prototype = {
|
||||
play: function() {
|
||||
// Switch to the right className on the element right away to avoid waiting
|
||||
// for the next state update to change the playPause icon.
|
||||
this.updateWidgetState("running");
|
||||
this.updateWidgetState({playState: "running"});
|
||||
this.startTimelineAnimation();
|
||||
return this.player.play();
|
||||
},
|
||||
|
||||
updateWidgetState: function(playState) {
|
||||
updateWidgetState: function({playState}) {
|
||||
this.el.className = "player-widget " + playState;
|
||||
},
|
||||
|
||||
@ -417,7 +372,7 @@ PlayerWidget.prototype = {
|
||||
|
||||
// Set the time label value.
|
||||
this.timeDisplayEl.textContent = L10N.getFormatStr("player.timeLabel",
|
||||
this.getFormattedTime(time));
|
||||
L10N.numberWithDecimals(time / 1000, 2));
|
||||
|
||||
// Set the timeline slider value.
|
||||
if (!state.iterationCount && time !== state.duration) {
|
||||
@ -437,6 +392,162 @@ PlayerWidget.prototype = {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* UI component responsible for displaying and updating the player meta-data:
|
||||
* name, duration, iterations, delay.
|
||||
* The parent UI component for this should drive its updates by calling
|
||||
* render(state) whenever it wants the component to update.
|
||||
*/
|
||||
function PlayerMetaDataHeader() {
|
||||
// Store the various state pieces we need to only refresh the UI when things
|
||||
// change.
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
PlayerMetaDataHeader.prototype = {
|
||||
createMarkup: function(containerEl) {
|
||||
// The main title element.
|
||||
this.el = createNode({
|
||||
parent: containerEl,
|
||||
attributes: {
|
||||
"class": "animation-title"
|
||||
}
|
||||
});
|
||||
|
||||
// Animation name (value hidden by default since transitions don't have names).
|
||||
this.nameLabel = createNode({
|
||||
parent: this.el,
|
||||
nodeType: "span"
|
||||
});
|
||||
|
||||
this.nameValue = createNode({
|
||||
parent: this.el,
|
||||
nodeType: "strong",
|
||||
attributes: {
|
||||
"style": "display:none;"
|
||||
}
|
||||
});
|
||||
|
||||
// Animation duration, delay and iteration container.
|
||||
let metaData = createNode({
|
||||
parent: this.el,
|
||||
nodeType: "span",
|
||||
attributes: {
|
||||
"class": "meta-data"
|
||||
}
|
||||
});
|
||||
|
||||
// Animation duration.
|
||||
this.durationLabel = createNode({
|
||||
parent: metaData,
|
||||
nodeType: "span"
|
||||
});
|
||||
this.durationLabel.textContent = L10N.getStr("player.animationDurationLabel");
|
||||
|
||||
this.durationValue = createNode({
|
||||
parent: metaData,
|
||||
nodeType: "strong"
|
||||
});
|
||||
|
||||
// Animation delay (hidden by default since there may not be a delay).
|
||||
this.delayLabel = createNode({
|
||||
parent: metaData,
|
||||
nodeType: "span",
|
||||
attributes: {
|
||||
"style": "display:none;"
|
||||
}
|
||||
});
|
||||
this.delayLabel.textContent = L10N.getStr("player.animationDelayLabel");
|
||||
|
||||
this.delayValue = createNode({
|
||||
parent: metaData,
|
||||
nodeType: "strong"
|
||||
});
|
||||
|
||||
// Animation iteration count (also hidden by default since we don't display
|
||||
// single iterations).
|
||||
this.iterationLabel = createNode({
|
||||
parent: metaData,
|
||||
nodeType: "span",
|
||||
attributes: {
|
||||
"style": "display:none;"
|
||||
}
|
||||
});
|
||||
this.iterationLabel.textContent = L10N.getStr("player.animationIterationCountLabel");
|
||||
|
||||
this.iterationValue = createNode({
|
||||
parent: metaData,
|
||||
nodeType: "strong",
|
||||
attributes: {
|
||||
"style": "display:none;"
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
this.state = null;
|
||||
this.el.remove();
|
||||
this.el = null;
|
||||
this.nameLabel = this.nameValue = null;
|
||||
this.durationLabel = this.durationValue = null;
|
||||
this.delayLabel = this.delayValue = null;
|
||||
this.iterationLabel = this.iterationValue = null;
|
||||
},
|
||||
|
||||
render: function(state) {
|
||||
// Update the name if needed.
|
||||
if (state.name !== this.state.name) {
|
||||
if (state.name) {
|
||||
// Css animations have names.
|
||||
this.nameLabel.textContent = L10N.getStr("player.animationNameLabel");
|
||||
this.nameValue.style.display = "inline";
|
||||
this.nameValue.textContent = state.name;
|
||||
} else {
|
||||
// Css transitions don't.
|
||||
this.nameLabel.textContent = L10N.getStr("player.transitionNameLabel");
|
||||
this.nameValue.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
// update the duration value if needed.
|
||||
if (state.duration !== this.state.duration) {
|
||||
this.durationValue.textContent = L10N.getFormatStr("player.timeLabel",
|
||||
L10N.numberWithDecimals(state.duration / 1000, 2));
|
||||
}
|
||||
|
||||
// Update the delay if needed.
|
||||
if (state.delay !== this.state.delay) {
|
||||
if (state.delay) {
|
||||
this.delayLabel.style.display = "inline";
|
||||
this.delayValue.style.display = "inline";
|
||||
this.delayValue.textContent = L10N.getFormatStr("player.timeLabel",
|
||||
L10N.numberWithDecimals(state.delay / 1000, 2));
|
||||
} else {
|
||||
// Hide the delay elements if there is no delay defined.
|
||||
this.delayLabel.style.display = "none";
|
||||
this.delayValue.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
// Update the iterationCount if needed.
|
||||
if (state.iterationCount !== this.state.iterationCount) {
|
||||
if (state.iterationCount !== 1) {
|
||||
this.iterationLabel.style.display = "inline";
|
||||
this.iterationValue.style.display = "inline";
|
||||
let count = state.iterationCount ||
|
||||
L10N.getStr("player.infiniteIterationCount");
|
||||
this.iterationValue.innerHTML = count;
|
||||
} else {
|
||||
// Hide the iteration elements if iteration is 1.
|
||||
this.iterationLabel.style.display = "none";
|
||||
this.iterationValue.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
this.state = state;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* DOM node creation helper function.
|
||||
* @param {Object} Options to customize the node to be created.
|
||||
|
@ -22,3 +22,4 @@ support-files =
|
||||
[browser_animation_timeline_animates.js]
|
||||
[browser_animation_timeline_waits_for_delay.js]
|
||||
[browser_animation_ui_updates_when_animation_changes.js]
|
||||
[browser_animation_ui_updates_when_animation_data_changes.js]
|
||||
|
@ -13,12 +13,22 @@ add_task(function*() {
|
||||
info("Selecting a node with an animation that doesn't repeat");
|
||||
yield selectNode(".long", inspector);
|
||||
let widget = panel.playerWidgets[0];
|
||||
let metaDataLabels = widget.el.querySelectorAll(".animation-title .meta-data strong");
|
||||
is(metaDataLabels.length, 1, "Only the duration is shown");
|
||||
|
||||
ok(isNodeVisible(widget.metaDataComponent.durationValue),
|
||||
"The duration value is shown");
|
||||
ok(!isNodeVisible(widget.metaDataComponent.delayValue),
|
||||
"The delay value is hidden");
|
||||
ok(!isNodeVisible(widget.metaDataComponent.iterationValue),
|
||||
"The iteration count is hidden");
|
||||
|
||||
info("Selecting a node with an animation that repeats several times");
|
||||
yield selectNode(".delayed", inspector);
|
||||
widget = panel.playerWidgets[0];
|
||||
let iterationLabel = widget.el.querySelectorAll(".animation-title .meta-data strong")[2];
|
||||
is(iterationLabel.textContent, "10", "The iteration is shown");
|
||||
|
||||
ok(isNodeVisible(widget.metaDataComponent.durationValue),
|
||||
"The duration value is shown");
|
||||
ok(isNodeVisible(widget.metaDataComponent.delayValue),
|
||||
"The delay value is shown");
|
||||
ok(isNodeVisible(widget.metaDataComponent.iterationValue),
|
||||
"The iteration count is shown");
|
||||
});
|
||||
|
@ -8,6 +8,8 @@
|
||||
// slider don't show values bigger than the animation duration (which would
|
||||
// happen if the local requestAnimationFrame loop didn't stop correctly).
|
||||
|
||||
let L10N = new ViewHelpers.L10N();
|
||||
|
||||
add_task(function*() {
|
||||
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
|
||||
let {inspector, panel} = yield openAnimationInspector();
|
||||
@ -35,6 +37,6 @@ add_task(function*() {
|
||||
is(widget.currentTimeEl.value, front.state.duration,
|
||||
"The timeline slider has the right value");
|
||||
is(widget.timeDisplayEl.textContent,
|
||||
widget.getFormattedTime(front.state.duration) + "s",
|
||||
L10N.numberWithDecimals(front.state.duration / 1000, 2) + "s",
|
||||
"The timeline slider has the right value");
|
||||
});
|
||||
|
@ -26,9 +26,11 @@ add_task(function*() {
|
||||
ok(metaDataEl, "The meta-data element exists");
|
||||
|
||||
let metaDataEls = metaDataEl.querySelectorAll("strong");
|
||||
is(metaDataEls.length, 2, "2 meta-data elements were found");
|
||||
is(metaDataEls[0].textContent, "2.00s",
|
||||
is(metaDataEls.length, 3, "3 meta-data elements were found");
|
||||
is(metaDataEls[0].textContent, "2s",
|
||||
"The first meta-data is the duration, and is correct");
|
||||
ok(!isNodeVisible(metaDataEls[1]),
|
||||
"The second meta-data is hidden, since there's no delay on the animation");
|
||||
|
||||
info("Select the node with the delayed animation");
|
||||
yield selectNode(".delayed", inspector);
|
||||
@ -40,10 +42,13 @@ add_task(function*() {
|
||||
metaDataEls = titleEl.querySelectorAll(".meta-data strong");
|
||||
is(metaDataEls.length, 3,
|
||||
"3 meta-data elements were found for the delayed animation");
|
||||
is(metaDataEls[0].textContent, "3.00s",
|
||||
is(metaDataEls[0].textContent, "3s",
|
||||
"The first meta-data is the duration, and is correct");
|
||||
is(metaDataEls[1].textContent, "60.00s",
|
||||
ok(isNodeVisible(metaDataEls[0]), "The duration is shown");
|
||||
is(metaDataEls[1].textContent, "60s",
|
||||
"The second meta-data is the delay, and is correct");
|
||||
ok(isNodeVisible(metaDataEls[1]), "The delay is shown");
|
||||
is(metaDataEls[2].textContent, "10",
|
||||
"The third meta-data is the iteration count, and is correct");
|
||||
ok(isNodeVisible(metaDataEls[2]), "The iteration count is shown");
|
||||
});
|
||||
|
@ -7,6 +7,8 @@
|
||||
// Test that once an animation is paused and its widget is refreshed, the right
|
||||
// initial time is displayed.
|
||||
|
||||
let L10N = new ViewHelpers.L10N();
|
||||
|
||||
add_task(function*() {
|
||||
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
|
||||
let {inspector, panel} = yield openAnimationInspector();
|
||||
@ -25,6 +27,7 @@ add_task(function*() {
|
||||
widget = panel.playerWidgets[0];
|
||||
ok(widget.el.classList.contains("paused"), "The widget is still in paused mode");
|
||||
is(widget.timeDisplayEl.textContent,
|
||||
widget.getFormattedTime(widget.player.state.currentTime) + "s",
|
||||
L10N.numberWithDecimals(widget.player.state.currentTime / 1000, 2) + "s",
|
||||
"The initial time has been set to the player's");
|
||||
});
|
||||
|
||||
|
@ -20,5 +20,5 @@ add_task(function*() {
|
||||
is(timeline.value, 0, "The timeline is at 0 since the animation hasn't started");
|
||||
|
||||
let timeLabel = widget.timeDisplayEl;
|
||||
is(timeLabel.textContent, "0.00s", "The current time is 0");
|
||||
is(timeLabel.textContent, "0s", "The current time is 0");
|
||||
});
|
||||
|
@ -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";
|
||||
|
||||
// Verify that if the animation's duration, iterations or delay change in
|
||||
// content, then the widget reflects the changes.
|
||||
|
||||
add_task(function*() {
|
||||
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
|
||||
let {panel, inspector} = yield openAnimationInspector();
|
||||
|
||||
info("Select the test node");
|
||||
yield selectNode(".animated", inspector);
|
||||
|
||||
info("Get the player widget");
|
||||
let widget = panel.playerWidgets[0];
|
||||
|
||||
yield setStyle(widget, "animationDuration", "5.5s");
|
||||
is(widget.metaDataComponent.durationValue.textContent, "5.50s",
|
||||
"The widget shows the new duration");
|
||||
|
||||
yield setStyle(widget, "animationIterationCount", "300");
|
||||
is(widget.metaDataComponent.iterationValue.textContent, "300",
|
||||
"The widget shows the new iteration count");
|
||||
|
||||
yield setStyle(widget, "animationDelay", "45s");
|
||||
is(widget.metaDataComponent.delayValue.textContent, "45s",
|
||||
"The widget shows the new delay");
|
||||
});
|
||||
|
||||
function* setStyle(widget, name, value) {
|
||||
info("Change the animation style via the content DOM. Setting " +
|
||||
name + " to " + value);
|
||||
yield executeInContent("Test:SetNodeStyle", {
|
||||
propertyName: name,
|
||||
propertyValue: value
|
||||
}, {
|
||||
node: getNode(".animated")
|
||||
});
|
||||
|
||||
info("Wait for the next state update");
|
||||
yield widget.player.once(widget.player.AUTO_REFRESH_EVENT);
|
||||
}
|
@ -26,3 +26,21 @@ addMessageListener("Test:ToggleAnimationPlayer", function(msg) {
|
||||
|
||||
sendAsyncMessage("Test:ToggleAnimationPlayer");
|
||||
});
|
||||
|
||||
/**
|
||||
* Set a given style property value on a node. This is useful to dynamically
|
||||
* change an animation's duration or delay for instance.
|
||||
* @param {Object} data
|
||||
* - {String} propertyName The name of the property to set.
|
||||
* - {String} propertyValue The value for the property.
|
||||
* @param {Object} objects
|
||||
* - {DOMNode} node The node to use
|
||||
*/
|
||||
addMessageListener("Test:SetNodeStyle", function(msg) {
|
||||
let {propertyName, propertyValue} = msg.data;
|
||||
let {node} = msg.objects;
|
||||
|
||||
node.style[propertyName] = propertyValue;
|
||||
|
||||
sendAsyncMessage("Test:SetNodeStyle");
|
||||
});
|
||||
|
@ -54,7 +54,7 @@
|
||||
left: 10px;
|
||||
background: red;
|
||||
|
||||
animation: simple-animation 2s
|
||||
animation: simple-animation 2s;
|
||||
}
|
||||
|
||||
.long {
|
||||
@ -62,7 +62,7 @@
|
||||
left: 10px;
|
||||
background: blue;
|
||||
|
||||
animation: simple-animation 120s
|
||||
animation: simple-animation 120s;
|
||||
}
|
||||
|
||||
@keyframes simple-animation {
|
||||
|
@ -5,11 +5,12 @@
|
||||
"use strict";
|
||||
|
||||
const Cu = Components.utils;
|
||||
let {gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
|
||||
let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||
let {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
|
||||
let TargetFactory = devtools.TargetFactory;
|
||||
let {console} = Components.utils.import("resource://gre/modules/devtools/Console.jsm", {});
|
||||
const {gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
|
||||
const {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||
const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
|
||||
const TargetFactory = devtools.TargetFactory;
|
||||
const {console} = Components.utils.import("resource://gre/modules/devtools/Console.jsm", {});
|
||||
const {ViewHelpers} = Cu.import("resource:///modules/devtools/ViewHelpers.jsm", {});
|
||||
|
||||
// All tests are asynchronous
|
||||
waitForExplicitFinish();
|
||||
@ -263,12 +264,30 @@ function executeInContent(name, data={}, objects={}, expectResponse=true) {
|
||||
* Simulate a click on the playPause button of a playerWidget.
|
||||
*/
|
||||
let togglePlayPauseButton = Task.async(function*(widget) {
|
||||
let nextState = widget.player.state.playState === "running" ? "paused" : "running";
|
||||
|
||||
// Note that instead of simulating a real event here, the callback is just
|
||||
// called. This is better because the callback returns a promise, so we know
|
||||
// when the player is paused, and we don't really care to test that simulating
|
||||
// a DOM event actually works.
|
||||
yield widget.onPlayPauseBtnClick();
|
||||
let onClicked = widget.onPlayPauseBtnClick();
|
||||
|
||||
// Verify that the button's state is changed immediately, even if it will be
|
||||
// changed anyway with the next auto-refresh.
|
||||
ok(widget.el.classList.contains(nextState),
|
||||
"The button's state was changed in the UI before the request was sent");
|
||||
|
||||
yield onClicked;
|
||||
|
||||
// Wait for the next sate change event to make sure the state is updated
|
||||
yield widget.player.once(widget.player.AUTO_REFRESH_EVENT);
|
||||
});
|
||||
|
||||
/**
|
||||
* Is the given node visible in the page (rendered in the frame tree).
|
||||
* @param {DOMNode}
|
||||
* @return {Boolean}
|
||||
*/
|
||||
function isNodeVisible(node) {
|
||||
return !!node.getClientRects().length;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user