Bug 1122766 - Fix canvas debugger to continue recording frames until one with a draw call. r=vp

This commit is contained in:
Jordan Santell 2015-02-21 11:56:00 -05:00
parent 862950c9f9
commit c0e7a6e6a0
6 changed files with 117 additions and 2 deletions

View File

@ -1,6 +1,7 @@
[DEFAULT]
subsuite = devtools
support-files =
doc_raf-begin.html
doc_simple-canvas.html
doc_simple-canvas-bitmasks.html
doc_simple-canvas-deep-stack.html
@ -34,6 +35,7 @@ skip-if = e10s # bug 1102301 - leaks while running as a standalone directory in
[browser_canvas-frontend-record-01.js]
[browser_canvas-frontend-record-02.js]
[browser_canvas-frontend-record-03.js]
[browser_canvas-frontend-record-04.js]
[browser_canvas-frontend-reload-01.js]
[browser_canvas-frontend-reload-02.js]
[browser_canvas-frontend-slider-01.js]

View File

@ -22,7 +22,7 @@ function ifTestingSupported() {
"A snapshot actor was sent after recording.");
let animationOverview = yield snapshotActor.getOverview();
ok(snapshotActor,
ok(animationOverview,
"An animation overview could be retrieved after recording.");
let thumbnails = animationOverview.thumbnails;

View File

@ -0,0 +1,34 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Bug 1122766
* Tests that the canvas actor correctly returns from recordAnimationFrame
* in the scenario where a loop starts with rAF and has rAF in the beginning
* of its loop, when the recording starts before the rAFs start.
*/
function ifTestingSupported() {
let { target, panel } = yield initCanvasDebuggerFrontend(RAF_BEGIN_URL);
let { window, EVENTS, gFront, SnapshotsListView } = panel.panelWin;
loadFrameScripts();
yield reload(target);
let recordingFinished = once(window, EVENTS.SNAPSHOT_RECORDING_FINISHED);
SnapshotsListView._onRecordButtonClick();
// Wait until after the recording started to trigger the content.
// Use the gFront method rather than the SNAPSHOT_RECORDING_STARTED event
// which triggers before the underlying actor call
yield waitUntil(function*() { return !(yield gFront.isRecording()); });
// Start animation in content
evalInDebuggee("start();");
yield recordingFinished;
ok(true, "Finished recording a snapshot of the animation loop.");
yield removeTab(target.tab);
finish();
}

View File

@ -0,0 +1,36 @@
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<title>Canvas inspector test page</title>
</head>
<body>
<canvas width="128" height="128"></canvas>
<script type="text/javascript;version=1.8">
"use strict";
var ctx = document.querySelector("canvas").getContext("2d");
function drawRect(fill, size) {
ctx.fillStyle = fill;
ctx.fillRect(size[0], size[1], size[2], size[3]);
}
function drawScene() {
window.requestAnimationFrame(drawScene);
ctx.clearRect(0, 0, 128, 128);
drawRect("rgb(192, 192, 192)", [0, 0, 128, 128]);
drawRect("rgba(0, 0, 192, 0.5)", [30, 30, 55, 50]);
drawRect("rgba(192, 0, 0, 0.5)", [10, 10, 55, 50]);
}
function start () { window.requestAnimationFrame(drawScene); }
</script>
</body>
</html>

View File

@ -20,6 +20,7 @@ let { DebuggerServer } = Cu.import("resource://gre/modules/devtools/dbg-server.j
let { DebuggerClient } = Cu.import("resource://gre/modules/devtools/dbg-client.jsm", {});
let { CallWatcherFront } = devtools.require("devtools/server/actors/call-watcher");
let { CanvasFront } = devtools.require("devtools/server/actors/canvas");
let { setTimeout } = devtools.require("sdk/timers");
let TiltGL = devtools.require("devtools/tilt/tilt-gl");
let TargetFactory = devtools.TargetFactory;
let Toolbox = devtools.Toolbox;
@ -33,6 +34,7 @@ const SIMPLE_CANVAS_TRANSPARENT_URL = EXAMPLE_URL + "doc_simple-canvas-transpare
const SIMPLE_CANVAS_DEEP_STACK_URL = EXAMPLE_URL + "doc_simple-canvas-deep-stack.html";
const WEBGL_ENUM_URL = EXAMPLE_URL + "doc_webgl-enum.html";
const WEBGL_BINDINGS_URL = EXAMPLE_URL + "doc_webgl-bindings.html";
const RAF_BEGIN_URL = EXAMPLE_URL + "doc_raf-begin.html";
// All tests are asynchronous.
waitForExplicitFinish();
@ -275,3 +277,22 @@ function getSourceActor(aSources, aURL) {
let item = aSources.getItemForAttachment(a => a.source.url === aURL);
return item ? item.value : null;
}
/**
* Waits until a predicate returns true.
*
* @param function predicate
* Invoked once in a while until it returns true.
* @param number interval [optional]
* How often the predicate is invoked, in milliseconds.
*/
function *waitUntil (predicate, interval = 10) {
if (yield predicate()) {
return Promise.resolve(true);
}
let deferred = Promise.defer();
setTimeout(function() {
waitUntil(predicate).then(() => deferred.resolve(true));
}, interval);
return deferred.promise;
}

View File

@ -229,6 +229,10 @@ let FrameSnapshotFront = protocol.FrontClass(FrameSnapshotActor, {
* made when drawing frame inside an animation loop.
*/
let CanvasActor = exports.CanvasActor = protocol.ActorClass({
// Reset for each recording, boolean indicating whether or not
// any draw calls were called for a recording.
_animationContainsDrawCall: false,
typeName: "canvas",
initialize: function(conn, tabActor) {
protocol.Actor.prototype.initialize.call(this, conn);
@ -286,6 +290,16 @@ let CanvasActor = exports.CanvasActor = protocol.ActorClass({
response: { initialized: RetVal("boolean") }
}),
/**
* Returns whether or not the CanvasActor is recording an animation.
* Used in tests.
*/
isRecording: method(function() {
return !!this._callWatcher.isRecording();
}, {
response: { recording: RetVal("boolean") }
}),
/**
* Records a snapshot of all the calls made during the next animation frame.
* The animation should be implemented via the de-facto requestAnimationFrame
@ -300,6 +314,7 @@ let CanvasActor = exports.CanvasActor = protocol.ActorClass({
return this._currentAnimationFrameSnapshot.promise;
}
this._recordingContainsDrawCall = false;
this._callWatcher.eraseRecording();
this._callWatcher.resumeRecording();
@ -340,7 +355,11 @@ let CanvasActor = exports.CanvasActor = protocol.ActorClass({
_handleAnimationFrame: function(functionCall) {
if (!this._animationStarted) {
this._handleAnimationFrameBegin();
} else {
}
// Check to see if draw calls occurred yet, as it could be future frames,
// like in the scenario where requestAnimationFrame is called to trigger an animation,
// and rAF is at the beginning of the animate loop.
else if (this._animationContainsDrawCall) {
this._handleAnimationFrameEnd(functionCall);
}
},
@ -362,6 +381,7 @@ let CanvasActor = exports.CanvasActor = protocol.ActorClass({
// previously recorded calls.
let functionCalls = this._callWatcher.pauseRecording();
this._callWatcher.eraseRecording();
this._animationContainsDrawCall = false;
// Since the animation frame finished, get a hold of the (already retrieved)
// canvas pixels to conveniently create a screenshot of the final rendering.
@ -410,6 +430,8 @@ let CanvasActor = exports.CanvasActor = protocol.ActorClass({
let dimensions = CanvasFront.THUMBNAIL_SIZE;
let thumbnail;
this._animationContainsDrawCall = true;
// Create a thumbnail on every draw call on the canvas context, to augment
// the respective function call actor with this additional data.
if (global == CallWatcherFront.CANVAS_WEBGL_CONTEXT) {