Bug 1155651 - Return all players below a node in getAnimationPlayersForNode; r=miker

Now when the front requests the list of AnimationPlayers for a node,
all players for this node + the nodes in its subtree are returned.
Also the mutationobserver starts observing all mutations in the node
and its subtree.

--HG--
extra : rebase_source : c419bd2f397617317fa9c9d0b7fd22f07abc12cf
This commit is contained in:
Patrick Brosset 2015-04-21 09:22:30 +02:00
parent b2a2f73211
commit b2a9538957
8 changed files with 106 additions and 35 deletions

View File

@ -11,9 +11,6 @@ add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html"); yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {controller, inspector} = yield openAnimationInspector(); let {controller, inspector} = yield openAnimationInspector();
is(controller.animationPlayers.length, 0,
"There are no AnimationPlayerFront objects at first");
info("Selecting an animated node"); info("Selecting an animated node");
// selectNode waits for the inspector-updated event before resolving, which // selectNode waits for the inspector-updated event before resolving, which
// means the controller.PLAYERS_UPDATED_EVENT event has been emitted before // means the controller.PLAYERS_UPDATED_EVENT event has been emitted before

View File

@ -10,15 +10,17 @@ add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html"); yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {inspector, panel, controller} = yield openAnimationInspector(); let {inspector, panel, controller} = yield openAnimationInspector();
info("Apply 2 finite animations to the test node"); info("Select the test node");
yield selectNode(".still", inspector);
info("Apply 2 finite animations to the test node and wait for the widgets to appear");
let onUiUpdated = panel.once(panel.UI_UPDATED_EVENT);
yield executeInContent("devtools:test:setAttribute", { yield executeInContent("devtools:test:setAttribute", {
selector: ".still", selector: ".still",
attributeName: "class", attributeName: "class",
attributeValue: "ball still multi-finite" attributeValue: "ball still multi-finite"
}); });
yield onUiUpdated;
info("Select the test node");
yield selectNode(".still", inspector);
is(controller.animationPlayers.length, 2, "2 animation players exist"); is(controller.animationPlayers.length, 2, "2 animation players exist");

View File

@ -14,15 +14,17 @@ add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html"); yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {inspector, panel} = yield openAnimationInspector(); let {inspector, panel} = yield openAnimationInspector();
info("Start an animation on the test node"); info("Select the test node");
yield selectNode(".still", inspector);
info("Start an animation on the test node and wait for the widget to appear");
let onUiUpdated = panel.once(panel.UI_UPDATED_EVENT);
yield executeInContent("devtools:test:setAttribute", { yield executeInContent("devtools:test:setAttribute", {
selector: ".still", selector: ".still",
attributeName: "class", attributeName: "class",
attributeValue: "ball still short" attributeValue: "ball still short"
}); });
yield onUiUpdated;
info("Select the node");
yield selectNode(".still", inspector);
info("Wait until the animation ends"); info("Wait until the animation ends");
let widget = panel.playerWidgets[0]; let widget = panel.playerWidgets[0];

View File

@ -31,7 +31,8 @@ add_task(function*() {
info("Faking an older server version by setting " + info("Faking an older server version by setting " +
"AnimationsController.hasSetCurrentTime to false"); "AnimationsController.hasSetCurrentTime to false");
yield selectNode("body", inspector); // Selecting <div.still> to make sure no widgets are displayed in the panel.
yield selectNode(".still", inspector);
controller.hasSetCurrentTime = false; controller.hasSetCurrentTime = false;
info("Selecting the animated node again"); info("Selecting the animated node again");
@ -46,13 +47,12 @@ add_task(function*() {
ok(container.children[0].classList.contains("toggle"), ok(container.children[0].classList.contains("toggle"),
"The first button is the play/pause button"); "The first button is the play/pause button");
yield selectNode("body", inspector); yield selectNode(".still", inspector);
controller.hasSetCurrentTime = true; controller.hasSetCurrentTime = true;
info("Faking an older server version by setting " + info("Faking an older server version by setting " +
"AnimationsController.hasSetPlaybackRate to false"); "AnimationsController.hasSetPlaybackRate to false");
yield selectNode("body", inspector);
controller.hasSetPlaybackRate = false; controller.hasSetPlaybackRate = false;
info("Selecting the animated node again"); info("Selecting the animated node again");
@ -65,6 +65,6 @@ add_task(function*() {
ok(!container.querySelector("select"), ok(!container.querySelector("select"),
"The playback rate select does not exist"); "The playback rate select does not exist");
yield selectNode("body", inspector); yield selectNode(".still", inspector);
controller.hasSetPlaybackRate = true; controller.hasSetPlaybackRate = true;
}); });

View File

@ -10,6 +10,9 @@ add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html"); yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {inspector, panel} = yield openAnimationInspector(); let {inspector, panel} = yield openAnimationInspector();
info("Select the non-animated test node");
yield selectNode(".still", inspector);
ok(!panel.toggleAllButtonEl.classList.contains("paused"), ok(!panel.toggleAllButtonEl.classList.contains("paused"),
"The toggle button is in its running state by default"); "The toggle button is in its running state by default");

View File

@ -486,13 +486,16 @@ let AnimationsActor = exports.AnimationsActor = ActorClass({
}, },
/** /**
* Retrieve the list of AnimationPlayerActor actors corresponding to * Retrieve the list of AnimationPlayerActor actors for currently running
* currently running animations for a given node. * animations on a node and its descendants.
* @param {NodeActor} nodeActor The NodeActor type is defined in * @param {NodeActor} nodeActor The NodeActor as defined in
* /toolkit/devtools/server/actors/inspector * /toolkit/devtools/server/actors/inspector
*/ */
getAnimationPlayersForNode: method(function(nodeActor) { getAnimationPlayersForNode: method(function(nodeActor) {
let animations = nodeActor.rawNode.getAnimations(); let animations = [
...nodeActor.rawNode.getAnimations(),
...this.getAllAnimations(nodeActor.rawNode)
];
// No care is taken here to destroy the previously stored actors because it // No care is taken here to destroy the previously stored actors because it
// is assumed that the client is responsible for lifetimes of actors. // is assumed that the client is responsible for lifetimes of actors.
@ -511,7 +514,10 @@ let AnimationsActor = exports.AnimationsActor = ActorClass({
this.stopAnimationPlayerUpdates(); this.stopAnimationPlayerUpdates();
let win = nodeActor.rawNode.ownerDocument.defaultView; let win = nodeActor.rawNode.ownerDocument.defaultView;
this.observer = new win.MutationObserver(this.onAnimationMutation); this.observer = new win.MutationObserver(this.onAnimationMutation);
this.observer.observe(nodeActor.rawNode, {animations: true}); this.observer.observe(nodeActor.rawNode, {
animations: true,
subtree: true
});
return this.actors; return this.actors;
}, { }, {
@ -596,23 +602,31 @@ let AnimationsActor = exports.AnimationsActor = ActorClass({
}), }),
/** /**
* Iterates through all nodes in all of the tabActor's window documents and * Iterates through all nodes below a given rootNode (optionally also in
* finds all existing animation players. * nested frames) and finds all existing animation players.
* This is currently used to allow playing/pausing all animations at once * @param {DOMNode} rootNode The root node to start iterating at. Animation
* until the WebAnimations API provides a way to play/pause via the document * players will *not* be reported for this node.
* timeline (alternatively, when bug 1123524 is fixed, we will be able to * @param {Boolean} traverseFrames Whether we should iterate through nested
* only iterate once and then listen for changes). * frames too.
* @return {Array} An array of AnimationPlayer objects.
*/ */
getAllAnimationPlayers: function() { getAllAnimations: function(rootNode, traverseFrames) {
let animations = []; let animations = [];
// These loops shouldn't be as bad as they look. // These loops shouldn't be as bad as they look.
// Typically, there will be very few windows, and getElementsByTagName is // Typically, there will be very few nested frames, and getElementsByTagName
// really fast even on large DOM trees. // is really fast even on large DOM trees.
for (let window of this.tabActor.windows) { for (let element of rootNode.getElementsByTagNameNS("*", "*")) {
let root = window.document.body || window.document; if (traverseFrames && element.contentWindow) {
for (let element of root.getElementsByTagNameNS("*", "*")) { animations = [
animations = [...animations, ...element.getAnimations()]; ...animations,
...this.getAllAnimations(element.contentWindow.document, traverseFrames)
];
} else {
animations = [
...animations,
...element.getAnimations()
];
} }
} }
@ -636,7 +650,10 @@ let AnimationsActor = exports.AnimationsActor = ActorClass({
*/ */
pauseAll: method(function() { pauseAll: method(function() {
let readyPromises = []; let readyPromises = [];
for (let player of this.getAllAnimationPlayers()) { // Until the WebAnimations API provides a way to play/pause via the document
// timeline, we have to iterate through the whole DOM to find all players.
for (let player of
this.getAllAnimations(this.tabActor.window.document, true)) {
player.pause(); player.pause();
readyPromises.push(player.ready); readyPromises.push(player.ready);
} }
@ -653,7 +670,10 @@ let AnimationsActor = exports.AnimationsActor = ActorClass({
*/ */
playAll: method(function() { playAll: method(function() {
let readyPromises = []; let readyPromises = [];
for (let player of this.getAllAnimationPlayers()) { // Until the WebAnimations API provides a way to play/pause via the document
// timeline, we have to iterate through the whole DOM to find all players.
for (let player of
this.getAllAnimations(this.tabActor.window.document, true)) {
player.play(); player.play();
readyPromises.push(player.ready); readyPromises.push(player.ready);
} }

View File

@ -31,6 +31,7 @@ support-files =
[browser_animation_actors_13.js] [browser_animation_actors_13.js]
[browser_animation_actors_14.js] [browser_animation_actors_14.js]
[browser_animation_actors_15.js] [browser_animation_actors_15.js]
[browser_animation_actors_16.js]
[browser_canvasframe_helper_01.js] [browser_canvasframe_helper_01.js]
[browser_canvasframe_helper_02.js] [browser_canvasframe_helper_02.js]
[browser_canvasframe_helper_03.js] [browser_canvasframe_helper_03.js]

View File

@ -0,0 +1,46 @@
/* 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/ */
"use strict";
// Check that the AnimationsActor can retrieve all animations inside a node's
// subtree (but not going into iframes).
const {AnimationsFront} = require("devtools/server/actors/animation");
const {InspectorFront} = require("devtools/server/actors/inspector");
const URL = MAIN_DOMAIN + "animation.html";
add_task(function*() {
info("Creating a test document with 2 iframes containing animated nodes");
let doc = yield addTab("data:text/html;charset=utf-8," +
"<iframe id='iframe' src='" + URL + "'></iframe>");
initDebuggerServer();
let client = new DebuggerClient(DebuggerServer.connectPipe());
let form = yield connectDebuggerClient(client);
let inspector = InspectorFront(client, form);
let walker = yield inspector.getWalker();
let front = AnimationsFront(client, form);
info("Try retrieving all animations from the root doc's <body> node");
let rootBody = yield walker.querySelector(walker.rootNode, "body");
let players = yield front.getAnimationPlayersForNode(rootBody);
is(players.length, 0, "The node has no animation players");
info("Retrieve all animations from the iframe's <body> node");
let iframe = yield walker.querySelector(walker.rootNode, "#iframe");
let {nodes} = yield walker.children(iframe);
let frameBody = yield walker.querySelector(nodes[0], "body");
players = yield front.getAnimationPlayersForNode(frameBody);
// Testing for a hard-coded number of animations here would intermittently
// fail depending on how fast or slow the test is (indeed, the test page
// contains short transitions, and delayed animations). So just make sure we
// at least have the infinitely running animations.
ok(players.length >= 4, "All subtree animations were retrieved");
yield closeDebuggerClient(client);
gBrowser.removeCurrentTab();
});