Bug 1247243 - Animations are shown only every 2 reloads. r=pbrosset

MozReview-Commit-ID: 71XsHc9WXHw
This commit is contained in:
Nicolas Chevobbe 2016-02-23 00:15:04 +01:00
Родитель f64e21907a
Коммит ca059bd532
9 изменённых файлов: 270 добавлений и 71 удалений

Просмотреть файл

@ -148,13 +148,6 @@ var AnimationsController = {
// Expose actor capabilities. // Expose actor capabilities.
this.traits = yield getServerTraits(target); this.traits = yield getServerTraits(target);
// We want to handle animation mutation events synchronously to avoid race
// conditions when there are many rapid mutations. So when a mutation occurs
// and animations are removed, we don't release the corresponding actors
// in a blocking way, we just release asynchronously and don't wait for
// completion, but instead store the promise in this array.
this.nonBlockingPlayerReleases = [];
if (this.destroyed) { if (this.destroyed) {
console.warn("Could not fully initialize the AnimationsController"); console.warn("Could not fully initialize the AnimationsController");
return; return;
@ -184,12 +177,9 @@ var AnimationsController = {
this.destroyed = promise.defer(); this.destroyed = promise.defer();
this.stopListeners(); this.stopListeners();
yield this.destroyAnimationPlayers(); this.destroyAnimationPlayers();
this.nodeFront = null; this.nodeFront = null;
// Finish releasing players that haven't been released yet.
yield promise.all(this.nonBlockingPlayerReleases);
if (this.animationsFront) { if (this.animationsFront) {
this.animationsFront.destroy(); this.animationsFront.destroy();
this.animationsFront = null; this.animationsFront = null;
@ -238,7 +228,7 @@ var AnimationsController = {
if (!gInspector.selection.isConnected() || if (!gInspector.selection.isConnected() ||
!gInspector.selection.isElementNode()) { !gInspector.selection.isElementNode()) {
yield this.destroyAnimationPlayers(); this.destroyAnimationPlayers();
this.emit(this.PLAYERS_UPDATED_EVENT); this.emit(this.PLAYERS_UPDATED_EVENT);
done(); done();
return; return;
@ -334,7 +324,7 @@ var AnimationsController = {
animationPlayers: [], animationPlayers: [],
refreshAnimationPlayers: Task.async(function*(nodeFront) { refreshAnimationPlayers: Task.async(function*(nodeFront) {
yield this.destroyAnimationPlayers(); this.destroyAnimationPlayers();
this.animationPlayers = yield this.animationsFront this.animationPlayers = yield this.animationsFront
.getAnimationPlayersForNode(nodeFront); .getAnimationPlayersForNode(nodeFront);
@ -356,8 +346,6 @@ var AnimationsController = {
} }
if (type === "removed") { if (type === "removed") {
// Don't wait for the release request to complete, we can do that later.
this.nonBlockingPlayerReleases.push(player.release());
let index = this.animationPlayers.indexOf(player); let index = this.animationPlayers.indexOf(player);
this.animationPlayers.splice(index, 1); this.animationPlayers.splice(index, 1);
} }
@ -386,19 +374,9 @@ var AnimationsController = {
return time; return time;
}, },
destroyAnimationPlayers: Task.async(function*() { destroyAnimationPlayers: function() {
// Let the server know that we're not interested in receiving updates about
// players for the current node. We're either being destroyed or a new node
// has been selected.
if (this.traits.hasMutationEvents) {
yield this.animationsFront.stopAnimationPlayerUpdates();
}
for (let front of this.animationPlayers) {
yield front.release();
}
this.animationPlayers = []; this.animationPlayers = [];
}) }
}; };
EventEmitter.decorate(AnimationsController); EventEmitter.decorate(AnimationsController);

Просмотреть файл

@ -154,6 +154,7 @@ AnimationsTimeline.prototype = {
for (let animation of this.animations) { for (let animation of this.animations) {
animation.off("changed", this.onAnimationStateChanged); animation.off("changed", this.onAnimationStateChanged);
} }
this.stopAnimatingScrubber();
TimeScale.reset(); TimeScale.reset();
this.destroySubComponents("targetNodes"); this.destroySubComponents("targetNodes");
this.destroySubComponents("timeBlocks"); this.destroySubComponents("timeBlocks");

Просмотреть файл

@ -28,24 +28,9 @@ add_task(function*() {
is(controller.animationPlayers.length, 2, is(controller.animationPlayers.length, 2,
"2 AnimationPlayerFronts have been created"); "2 AnimationPlayerFronts have been created");
// Hold on to one of the AnimationPlayerFront objects and mock its release
// method to test that it is released correctly and that its auto-refresh is
// stopped.
let retainedFront = controller.animationPlayers[0];
let oldRelease = retainedFront.release;
let releaseCalled = false;
retainedFront.release = () => {
releaseCalled = true;
};
info("Selecting a node with no animations"); info("Selecting a node with no animations");
yield selectNode(".still", inspector); yield selectNode(".still", inspector);
is(controller.animationPlayers.length, 0, is(controller.animationPlayers.length, 0,
"There are no more AnimationPlayerFront objects"); "There are no more AnimationPlayerFront objects");
info("Checking the destroyed AnimationPlayerFront object");
ok(releaseCalled, "The AnimationPlayerFront has been released");
yield oldRelease.call(retainedFront);
}); });

Просмотреть файл

@ -561,6 +561,10 @@ var AnimationsActor = exports.AnimationsActor = ActorClass({
/** /**
* Retrieve the list of AnimationPlayerActor actors for currently running * Retrieve the list of AnimationPlayerActor actors for currently running
* animations on a node and its descendants. * animations on a node and its descendants.
* Note that calling this method a second time will destroy all previously
* retrieved AnimationPlayerActors. Indeed, the lifecycle of these actors
* is managed here on the server and tied to getAnimationPlayersForNode
* being called.
* @param {NodeActor} nodeActor The NodeActor as defined in * @param {NodeActor} nodeActor The NodeActor as defined in
* /devtools/server/actors/inspector * /devtools/server/actors/inspector
*/ */
@ -570,9 +574,12 @@ var AnimationsActor = exports.AnimationsActor = ActorClass({
...this.getAllAnimations(nodeActor.rawNode) ...this.getAllAnimations(nodeActor.rawNode)
]; ];
// No care is taken here to destroy the previously stored actors because it // Destroy previously stored actors
// is assumed that the client is responsible for lifetimes of actors. if (this.actors) {
this.actors.forEach(actor => actor.destroy());
}
this.actors = []; this.actors = [];
for (let i = 0; i < animations.length; i++) { for (let i = 0; i < animations.length; i++) {
let actor = AnimationPlayerActor(this, animations[i]); let actor = AnimationPlayerActor(this, animations[i]);
this.actors.push(actor); this.actors.push(actor);

Просмотреть файл

@ -14,8 +14,6 @@
// runs. // runs.
const ALL_ANIMATED_NODES = [".simple-animation", ".multiple-animations", const ALL_ANIMATED_NODES = [".simple-animation", ".multiple-animations",
".delayed-animation"]; ".delayed-animation"];
// List of selectors that match some animated nodes in the test page only.
const SOME_ANIMATED_NODES = [".simple-animation", ".delayed-animation"];
add_task(function*() { add_task(function*() {
let {client, walker, animations} = let {client, walker, animations} =
@ -37,19 +35,34 @@ add_task(function*() {
yield animations.toggleAll(); yield animations.toggleAll();
yield checkStates(walker, animations, ALL_ANIMATED_NODES, "running"); yield checkStates(walker, animations, ALL_ANIMATED_NODES, "running");
info("Pause a given list of animations only"); info("Play all animations from multiple animated node using toggleSeveral");
let players = []; let players = yield getPlayersFor(walker, animations,
for (let selector of SOME_ANIMATED_NODES) { [".multiple-animations"]);
let [player] = yield getPlayersFor(walker, animations, selector); is(players.length, 2, "Node has 2 animation players");
players.push(player);
}
yield animations.toggleSeveral(players, true);
yield checkStates(walker, animations, SOME_ANIMATED_NODES, "paused");
yield checkStates(walker, animations, [".multiple-animations"], "running");
info("Play the same list of animations");
yield animations.toggleSeveral(players, false); yield animations.toggleSeveral(players, false);
yield checkStates(walker, animations, ALL_ANIMATED_NODES, "running"); let state1 = yield players[0].getCurrentState();
is(state1.playState, "running",
"The playState of the first player is running");
let state2 = yield players[1].getCurrentState();
is(state2.playState, "running",
"The playState of the second player is running");
info("Pause one animation from a multiple animated node using toggleSeveral");
yield animations.toggleSeveral([players[0]], true);
state1 = yield players[0].getCurrentState();
is(state1.playState, "paused", "The playState of the first player is paused");
state2 = yield players[1].getCurrentState();
is(state2.playState, "running",
"The playState of the second player is running");
info("Play the same animation");
yield animations.toggleSeveral([players[0]], false);
state1 = yield players[0].getCurrentState();
is(state1.playState, "running",
"The playState of the first player is running");
state2 = yield players[1].getCurrentState();
is(state2.playState, "running",
"The playState of the second player is running");
yield closeDebuggerClient(client); yield closeDebuggerClient(client);
gBrowser.removeCurrentTab(); gBrowser.removeCurrentTab();

Просмотреть файл

@ -54,20 +54,21 @@ function* testSetCurrentTime(walker, animations) {
function* testSetCurrentTimes(walker, animations) { function* testSetCurrentTimes(walker, animations) {
ok(animations.setCurrentTimes, "The AnimationsActor has the right method"); ok(animations.setCurrentTimes, "The AnimationsActor has the right method");
info("Retrieve multiple animated nodes and their animation players"); info("Retrieve multiple animated node and its animation players");
let node1 = yield walker.querySelector(walker.rootNode, ".simple-animation");
let player1 = (yield animations.getAnimationPlayersForNode(node1))[0]; let nodeMulti = yield walker.querySelector(walker.rootNode,
let node2 = yield walker.querySelector(walker.rootNode, ".delayed-animation"); ".multiple-animations");
let player2 = (yield animations.getAnimationPlayersForNode(node2))[0]; let players = (yield animations.getAnimationPlayersForNode(nodeMulti));
ok(players.length > 1, "Node has more than 1 animation player");
info("Try to set multiple current times at once"); info("Try to set multiple current times at once");
yield animations.setCurrentTimes([player1, player2], 500, true); yield animations.setCurrentTimes(players, 500, true);
info("Get the states of both players and verify their correctness"); info("Get the states of players and verify their correctness");
let state1 = yield player1.getCurrentState(); for (let i = 0; i < players.length; i++) {
let state2 = yield player2.getCurrentState(); let state = yield players[i].getCurrentState();
is(state1.playState, "paused", "Player 1 is paused"); is(state.playState, "paused", `Player ${i + 1} is paused`);
is(state2.playState, "paused", "Player 2 is paused"); is(state.currentTime, 500, `Player ${i + 1} has the right currentTime`);
is(state1.currentTime, 500, "Player 1 has the right currentTime"); }
is(state2.currentTime, 500, "Player 2 has the right currentTime");
} }

Просмотреть файл

@ -0,0 +1,120 @@
<html>
<head>
<meta charset="UTF-8">
<title>Animation Test Data</title>
<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;
}
@keyframes simple-animation {
100% {
transform: translateX(300px);
}
}
@keyframes other-animation {
100% {
background: blue;
}
}
@keyframes no-compositor {
100% {
margin-right: 600px;
}
}
</style>
<script type="text/javascript">
window.onload = function() {
window.opener.postMessage('ready', '*');
};
</script>
</head>
</body>
<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>
</body>
</html>

Просмотреть файл

@ -2,6 +2,7 @@
tags = devtools tags = devtools
skip-if = buildapp == 'b2g' || os == 'android' skip-if = buildapp == 'b2g' || os == 'android'
support-files = support-files =
animation-data.html
Debugger.Source.prototype.element.js Debugger.Source.prototype.element.js
Debugger.Source.prototype.element-2.js Debugger.Source.prototype.element-2.js
Debugger.Source.prototype.element.html Debugger.Source.prototype.element.html
@ -22,6 +23,7 @@ support-files =
setup-in-child.js setup-in-child.js
setup-in-parent.js setup-in-parent.js
[test_animation_actor-lifetime.html]
[test_connection-manager.html] [test_connection-manager.html]
skip-if = buildapp == 'mulet' skip-if = buildapp == 'mulet'
[test_connectToChild.html] [test_connectToChild.html]

Просмотреть файл

@ -0,0 +1,92 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1247243
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 1247243</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
<script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
<script type="application/javascript;version=1.8">
window.onload = function() {
const Ci = Components.interfaces;
const {AnimationsFront} = require("devtools/server/actors/animation");
const {InspectorFront} = require("devtools/server/actors/inspector");
SimpleTest.waitForExplicitFinish();
let gWalker = null;
let gClient = null;
let animationsFront = null;
addTest(function setup() {
info ("Setting up inspector and animation actors.");
let url = document.getElementById("animationContent").href;
attachURL(url, function(err, client, tab, doc) {
let inspector = InspectorFront(client, tab);
animationsFront = new AnimationsFront(client, tab);
promiseDone(inspector.getWalker().then(walker => {
ok(walker, "getWalker() should return an actor.");
gClient = client;
gWalker = walker;
}).then(runNextTest));
});
});
addAsyncTest(function* testActorLifetime() {
info ("Testing animated node actor");
let animatedNodeActor = yield gWalker.querySelector(gWalker.rootNode,
".animated");
yield animationsFront.getAnimationPlayersForNode(animatedNodeActor);
let serverConnection = animationsFront.conn._transport._serverConnection;
let animationsActor = serverConnection.getActor(animationsFront.actorID);
is(animationsActor.actors.length, 1,
"AnimationActor have 1 AnimationPlayerActors");
info ("Testing AnimationPlayerActors release");
let stillNodeActor = yield gWalker.querySelector(gWalker.rootNode,
".still");
yield animationsFront.getAnimationPlayersForNode(stillNodeActor);
is(animationsActor.actors.length, 0,
"AnimationActor does not have any AnimationPlayerActors anymore");
info ("Testing multi animated node actor");
let multiNodeActor = yield gWalker.querySelector(gWalker.rootNode,
".multi");
yield animationsFront.getAnimationPlayersForNode(multiNodeActor);
is(animationsActor.actors.length, 2,
"AnimationActor has now 2 AnimationPlayerActors");
info ("Testing single animated node actor");
yield animationsFront.getAnimationPlayersForNode(animatedNodeActor);
is(animationsActor.actors.length, 1,
"AnimationActor has only one AnimationPlayerActors");
info ("Testing AnimationPlayerActors release again");
yield animationsFront.getAnimationPlayersForNode(stillNodeActor);
is(animationsActor.actors.length, 0,
"AnimationActor does not have any AnimationPlayerActors anymore");
runNextTest();
});
runNextTest();
};
</script>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1247243">Mozilla Bug 1247243</a>
<a id="animationContent" target="_blank" href="animation-data.html">Test Document</a>
</body>
</html>