diff --git a/toolkit/devtools/server/actors/animation.js b/toolkit/devtools/server/actors/animation.js index 5b1b63439675..93c19a4abf7c 100644 --- a/toolkit/devtools/server/actors/animation.js +++ b/toolkit/devtools/server/actors/animation.js @@ -24,10 +24,16 @@ * /dom/webidl/Animation*.webidl */ +const {Cu} = require("chrome"); +const {Task} = Cu.import("resource://gre/modules/Task.jsm", {}); +const {setInterval, clearInterval} = require("sdk/timers"); const {ActorClass, Actor, FrontClass, Front, Arg, method, RetVal} = require("devtools/server/protocol"); const {NodeActor} = require("devtools/server/actors/inspector"); +const EventEmitter = require("devtools/toolkit/event-emitter"); + +const PLAYER_DEFAULT_AUTO_REFRESH_TIMEOUT = 500; // ms /** * The AnimationPlayerActor provides information about a given animation: its @@ -179,8 +185,13 @@ let AnimationPlayerActor = ActorClass({ }); let AnimationPlayerFront = FrontClass(AnimationPlayerActor, { + AUTO_REFRESH_EVENT: "updated-state", + initialize: function(conn, form, detail, ctx) { + EventEmitter.decorate(this); Front.prototype.initialize.call(this, conn, form, detail, ctx); + + this.state = {}; }, form: function(form, detail) { @@ -189,9 +200,11 @@ let AnimationPlayerFront = FrontClass(AnimationPlayerActor, { return; } this._form = form; + this.state = this.initialState; }, destroy: function() { + this.stopAutoRefresh(); Front.prototype.destroy.call(this); }, @@ -209,7 +222,75 @@ let AnimationPlayerFront = FrontClass(AnimationPlayerActor, { iterationCount: this._form.iterationCount, isRunningOnCompositor: this._form.isRunningOnCompositor } - } + }, + + // About auto-refresh: + // + // The AnimationPlayerFront is capable of automatically refreshing its state + // by calling the getCurrentState method at regular intervals. This allows + // consumers to update their knowledge of the player's currentTime, playState, + // ... dynamically. + // + // Calling startAutoRefresh will start the automatic refreshing of the state, + // and calling stopAutoRefresh will stop it. + // Once the automatic refresh has been started, the AnimationPlayerFront emits + // "updated-state" events everytime the state changes. + // + // Note that given the time-related nature of animations, the actual state + // changes a lot more often than "updated-state" events are emitted. This is + // to avoid making many protocol requests. + + /** + * Start auto-refreshing this player's state. + * @param {Number} interval Optional auto-refresh timer interval to override + * the default value. + */ + startAutoRefresh: function(interval=PLAYER_DEFAULT_AUTO_REFRESH_TIMEOUT) { + if (this.autoRefreshTimer) { + return; + } + + this.autoRefreshTimer = setInterval(this.refreshState.bind(this), interval); + }, + + /** + * Stop auto-refreshing this player's state. + */ + stopAutoRefresh: function() { + if (!this.autoRefreshTimer) { + return; + } + + clearInterval(this.autoRefreshTimer); + this.autoRefreshTimer = null; + }, + + /** + * Called automatically when auto-refresh is on. Doesn't return anything, but + * emits the "updated-state" event. + */ + refreshState: Task.async(function*() { + let data = yield this.getCurrentState(); + + // By the time the new state is received, auto-refresh might be stopped. + if (!this.autoRefreshTimer) { + return; + } + + // Check if something has changed + let hasChanged = false; + for (let key in data) { + if (this.state[key] !== data[key]) { + hasChanged = true; + break; + } + } + + if (hasChanged) { + this.state = data; + this.emit(this.AUTO_REFRESH_EVENT, this.state); + } + }) }); /** diff --git a/toolkit/devtools/server/tests/browser/browser.ini b/toolkit/devtools/server/tests/browser/browser.ini index 080d536dd1aa..64f125acc668 100644 --- a/toolkit/devtools/server/tests/browser/browser.ini +++ b/toolkit/devtools/server/tests/browser/browser.ini @@ -18,6 +18,7 @@ support-files = [browser_animation_actors_02.js] [browser_animation_actors_03.js] [browser_animation_actors_04.js] +[browser_animation_actors_05.js] [browser_navigateEvents.js] [browser_storage_dynamic_windows.js] [browser_storage_listings.js] diff --git a/toolkit/devtools/server/tests/browser/browser_animation_actors_05.js b/toolkit/devtools/server/tests/browser/browser_animation_actors_05.js new file mode 100644 index 000000000000..59f91a6e185c --- /dev/null +++ b/toolkit/devtools/server/tests/browser/browser_animation_actors_05.js @@ -0,0 +1,57 @@ +/* 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 AnimationPlayers can auto-refresh their states. + +const {AnimationsFront} = require("devtools/server/actors/animation"); +const {InspectorFront} = require("devtools/server/actors/inspector"); + +add_task(function*() { + let doc = yield addTab(MAIN_DOMAIN + "animation.html"); + + 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); + + let node = yield walker.querySelector(walker.rootNode, ".simple-animation"); + let [player] = yield front.getAnimationPlayersForNode(node); + + ok(player.startAutoRefresh, "The startAutoRefresh function is available"); + ok(player.stopAutoRefresh, "The stopAutoRefresh function is available"); + ok(player.state, "The current state is stored on the player"); + + info("Subscribe to the refresh event, start the auto-refresh and wait for " + + "a few events to be received"); + + player.startAutoRefresh(); + + let onAllEventsReceived = new Promise(resolve => { + let expected = 5; + let previousState = player.initialState; + let onNewState = (e, state) => { + ok(state.currentTime !== previousState.currentTime, + "The time has changed since the last update"); + expected --; + previousState = state; + if (expected === 0) { + player.off(player.AUTO_REFRESH_EVENT, onNewState); + resolve(); + } + }; + player.on(player.AUTO_REFRESH_EVENT, onNewState); + }); + + yield onAllEventsReceived; + + info("Stop the auto-refresh"); + player.stopAutoRefresh(); + + yield closeDebuggerClient(client); + gBrowser.removeCurrentTab(); +});