summaryrefslogtreecommitdiffstats
path: root/devtools/client/canvasdebugger/test
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/canvasdebugger/test')
-rw-r--r--devtools/client/canvasdebugger/test/.eslintrc.js6
-rw-r--r--devtools/client/canvasdebugger/test/browser.ini61
-rw-r--r--devtools/client/canvasdebugger/test/browser_canvas-actor-test-01.js17
-rw-r--r--devtools/client/canvasdebugger/test/browser_canvas-actor-test-02.js78
-rw-r--r--devtools/client/canvasdebugger/test/browser_canvas-actor-test-03.js75
-rw-r--r--devtools/client/canvasdebugger/test/browser_canvas-actor-test-04.js85
-rw-r--r--devtools/client/canvasdebugger/test/browser_canvas-actor-test-05.js50
-rw-r--r--devtools/client/canvasdebugger/test/browser_canvas-actor-test-06.js100
-rw-r--r--devtools/client/canvasdebugger/test/browser_canvas-actor-test-07.js94
-rw-r--r--devtools/client/canvasdebugger/test/browser_canvas-actor-test-08.js36
-rw-r--r--devtools/client/canvasdebugger/test/browser_canvas-actor-test-09.js36
-rw-r--r--devtools/client/canvasdebugger/test/browser_canvas-actor-test-10.js107
-rw-r--r--devtools/client/canvasdebugger/test/browser_canvas-actor-test-11.js138
-rw-r--r--devtools/client/canvasdebugger/test/browser_canvas-actor-test-12.js29
-rw-r--r--devtools/client/canvasdebugger/test/browser_canvas-frontend-call-highlight.js41
-rw-r--r--devtools/client/canvasdebugger/test/browser_canvas-frontend-call-list.js70
-rw-r--r--devtools/client/canvasdebugger/test/browser_canvas-frontend-call-search.js72
-rw-r--r--devtools/client/canvasdebugger/test/browser_canvas-frontend-call-stack-01.js82
-rw-r--r--devtools/client/canvasdebugger/test/browser_canvas-frontend-call-stack-02.js57
-rw-r--r--devtools/client/canvasdebugger/test/browser_canvas-frontend-call-stack-03.js65
-rw-r--r--devtools/client/canvasdebugger/test/browser_canvas-frontend-clear.js43
-rw-r--r--devtools/client/canvasdebugger/test/browser_canvas-frontend-img-screenshots.js34
-rw-r--r--devtools/client/canvasdebugger/test/browser_canvas-frontend-img-thumbnails-01.js65
-rw-r--r--devtools/client/canvasdebugger/test/browser_canvas-frontend-img-thumbnails-02.js67
-rw-r--r--devtools/client/canvasdebugger/test/browser_canvas-frontend-open.js41
-rw-r--r--devtools/client/canvasdebugger/test/browser_canvas-frontend-record-01.js60
-rw-r--r--devtools/client/canvasdebugger/test/browser_canvas-frontend-record-02.js73
-rw-r--r--devtools/client/canvasdebugger/test/browser_canvas-frontend-record-03.js37
-rw-r--r--devtools/client/canvasdebugger/test/browser_canvas-frontend-record-04.js34
-rw-r--r--devtools/client/canvasdebugger/test/browser_canvas-frontend-reload-01.js55
-rw-r--r--devtools/client/canvasdebugger/test/browser_canvas-frontend-reload-02.js70
-rw-r--r--devtools/client/canvasdebugger/test/browser_canvas-frontend-slider-01.js39
-rw-r--r--devtools/client/canvasdebugger/test/browser_canvas-frontend-slider-02.js97
-rw-r--r--devtools/client/canvasdebugger/test/browser_canvas-frontend-snapshot-select-01.js93
-rw-r--r--devtools/client/canvasdebugger/test/browser_canvas-frontend-snapshot-select-02.js30
-rw-r--r--devtools/client/canvasdebugger/test/browser_canvas-frontend-stepping.js76
-rw-r--r--devtools/client/canvasdebugger/test/browser_canvas-frontend-stop-01.js36
-rw-r--r--devtools/client/canvasdebugger/test/browser_canvas-frontend-stop-02.js35
-rw-r--r--devtools/client/canvasdebugger/test/browser_canvas-frontend-stop-03.js36
-rw-r--r--devtools/client/canvasdebugger/test/browser_profiling-canvas.js45
-rw-r--r--devtools/client/canvasdebugger/test/browser_profiling-webgl.js91
-rw-r--r--devtools/client/canvasdebugger/test/doc_no-canvas.html14
-rw-r--r--devtools/client/canvasdebugger/test/doc_raf-begin.html36
-rw-r--r--devtools/client/canvasdebugger/test/doc_raf-no-canvas.html18
-rw-r--r--devtools/client/canvasdebugger/test/doc_settimeout.html37
-rw-r--r--devtools/client/canvasdebugger/test/doc_simple-canvas-bitmasks.html34
-rw-r--r--devtools/client/canvasdebugger/test/doc_simple-canvas-deep-stack.html46
-rw-r--r--devtools/client/canvasdebugger/test/doc_simple-canvas-transparent.html37
-rw-r--r--devtools/client/canvasdebugger/test/doc_simple-canvas.html37
-rw-r--r--devtools/client/canvasdebugger/test/doc_webgl-bindings.html61
-rw-r--r--devtools/client/canvasdebugger/test/doc_webgl-drawArrays.html187
-rw-r--r--devtools/client/canvasdebugger/test/doc_webgl-drawElements.html225
-rw-r--r--devtools/client/canvasdebugger/test/doc_webgl-enum.html34
-rw-r--r--devtools/client/canvasdebugger/test/head.js305
54 files changed, 3527 insertions, 0 deletions
diff --git a/devtools/client/canvasdebugger/test/.eslintrc.js b/devtools/client/canvasdebugger/test/.eslintrc.js
new file mode 100644
index 000000000..8d15a76d9
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/.eslintrc.js
@@ -0,0 +1,6 @@
+"use strict";
+
+module.exports = {
+ // Extend from the shared list of defined globals for mochitests.
+ "extends": "../../../.eslintrc.mochitests.js"
+};
diff --git a/devtools/client/canvasdebugger/test/browser.ini b/devtools/client/canvasdebugger/test/browser.ini
new file mode 100644
index 000000000..65c81c32f
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/browser.ini
@@ -0,0 +1,61 @@
+[DEFAULT]
+tags = devtools
+subsuite = devtools
+support-files =
+ doc_raf-begin.html
+ doc_settimeout.html
+ doc_no-canvas.html
+ doc_raf-no-canvas.html
+ doc_simple-canvas.html
+ doc_simple-canvas-bitmasks.html
+ doc_simple-canvas-deep-stack.html
+ doc_simple-canvas-transparent.html
+ doc_webgl-bindings.html
+ doc_webgl-enum.html
+ doc_webgl-drawArrays.html
+ doc_webgl-drawElements.html
+ head.js
+
+[browser_canvas-actor-test-01.js]
+[browser_canvas-actor-test-02.js]
+[browser_canvas-actor-test-03.js]
+[browser_canvas-actor-test-04.js]
+[browser_canvas-actor-test-05.js]
+[browser_canvas-actor-test-06.js]
+[browser_canvas-actor-test-07.js]
+[browser_canvas-actor-test-08.js]
+[browser_canvas-actor-test-09.js]
+subsuite = gpu
+[browser_canvas-actor-test-10.js]
+subsuite = gpu
+[browser_canvas-actor-test-11.js]
+subsuite = gpu
+[browser_canvas-actor-test-12.js]
+[browser_canvas-frontend-call-highlight.js]
+[browser_canvas-frontend-call-list.js]
+[browser_canvas-frontend-call-search.js]
+[browser_canvas-frontend-call-stack-01.js]
+[browser_canvas-frontend-call-stack-02.js]
+[browser_canvas-frontend-call-stack-03.js]
+[browser_canvas-frontend-clear.js]
+[browser_canvas-frontend-img-screenshots.js]
+[browser_canvas-frontend-img-thumbnails-01.js]
+[browser_canvas-frontend-img-thumbnails-02.js]
+[browser_canvas-frontend-open.js]
+[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]
+[browser_canvas-frontend-slider-02.js]
+[browser_canvas-frontend-snapshot-select-01.js]
+[browser_canvas-frontend-snapshot-select-02.js]
+[browser_canvas-frontend-stepping.js]
+[browser_canvas-frontend-stop-01.js]
+[browser_canvas-frontend-stop-02.js]
+[browser_canvas-frontend-stop-03.js]
+[browser_profiling-canvas.js]
+[browser_profiling-webgl.js]
+subsuite = gpu
diff --git a/devtools/client/canvasdebugger/test/browser_canvas-actor-test-01.js b/devtools/client/canvasdebugger/test/browser_canvas-actor-test-01.js
new file mode 100644
index 000000000..9b6ee4e4f
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/browser_canvas-actor-test-01.js
@@ -0,0 +1,17 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if the canvas debugger leaks on initialization and sudden destruction.
+ * You can also use this initialization format as a template for other tests.
+ */
+
+function* ifTestingSupported() {
+ let { target, front } = yield initCallWatcherBackend(SIMPLE_CANVAS_URL);
+
+ ok(target, "Should have a target available.");
+ ok(front, "Should have a protocol front available.");
+
+ yield removeTab(target.tab);
+ finish();
+}
diff --git a/devtools/client/canvasdebugger/test/browser_canvas-actor-test-02.js b/devtools/client/canvasdebugger/test/browser_canvas-actor-test-02.js
new file mode 100644
index 000000000..eb8a8f5f7
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/browser_canvas-actor-test-02.js
@@ -0,0 +1,78 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if functions calls are recorded and stored for a canvas context,
+ * and that their stack is successfully retrieved.
+ */
+
+function* ifTestingSupported() {
+ let { target, front } = yield initCallWatcherBackend(SIMPLE_CANVAS_URL);
+
+ let navigated = once(target, "navigate");
+
+ yield front.setup({
+ tracedGlobals: ["CanvasRenderingContext2D", "WebGLRenderingContext"],
+ startRecording: true,
+ performReload: true,
+ storeCalls: true
+ });
+ ok(true, "The front was setup up successfully.");
+
+ yield navigated;
+ ok(true, "Target automatically navigated when the front was set up.");
+
+ // Allow the content to execute some functions.
+ yield waitForTick();
+
+ let functionCalls = yield front.pauseRecording();
+ ok(functionCalls,
+ "An array of function call actors was sent after reloading.");
+ ok(functionCalls.length > 0,
+ "There's at least one function call actor available.");
+
+ is(functionCalls[0].type, CallWatcherFront.METHOD_FUNCTION,
+ "The called function is correctly identified as a method.");
+ is(functionCalls[0].name, "clearRect",
+ "The called function's name is correct.");
+ is(functionCalls[0].file, SIMPLE_CANVAS_URL,
+ "The called function's file is correct.");
+ is(functionCalls[0].line, 25,
+ "The called function's line is correct.");
+
+ is(functionCalls[0].callerPreview, "Object",
+ "The called function's caller preview is correct.");
+ is(functionCalls[0].argsPreview, "0, 0, 128, 128",
+ "The called function's args preview is correct.");
+
+ let details = yield functionCalls[1].getDetails();
+ ok(details,
+ "The first called function has some details available.");
+
+ is(details.stack.length, 3,
+ "The called function's stack depth is correct.");
+
+ is(details.stack[0].name, "fillStyle",
+ "The called function's stack is correct (1.1).");
+ is(details.stack[0].file, SIMPLE_CANVAS_URL,
+ "The called function's stack is correct (1.2).");
+ is(details.stack[0].line, 20,
+ "The called function's stack is correct (1.3).");
+
+ is(details.stack[1].name, "drawRect",
+ "The called function's stack is correct (2.1).");
+ is(details.stack[1].file, SIMPLE_CANVAS_URL,
+ "The called function's stack is correct (2.2).");
+ is(details.stack[1].line, 26,
+ "The called function's stack is correct (2.3).");
+
+ is(details.stack[2].name, "drawScene",
+ "The called function's stack is correct (3.1).");
+ is(details.stack[2].file, SIMPLE_CANVAS_URL,
+ "The called function's stack is correct (3.2).");
+ is(details.stack[2].line, 33,
+ "The called function's stack is correct (3.3).");
+
+ yield removeTab(target.tab);
+ finish();
+}
diff --git a/devtools/client/canvasdebugger/test/browser_canvas-actor-test-03.js b/devtools/client/canvasdebugger/test/browser_canvas-actor-test-03.js
new file mode 100644
index 000000000..8a8a63780
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/browser_canvas-actor-test-03.js
@@ -0,0 +1,75 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if functions inside a single animation frame are recorded and stored
+ * for a canvas context.
+ */
+
+function* ifTestingSupported() {
+ let { target, front } = yield initCanvasDebuggerBackend(SIMPLE_CANVAS_URL);
+
+ let navigated = once(target, "navigate");
+
+ yield front.setup({ reload: true });
+ ok(true, "The front was setup up successfully.");
+
+ yield navigated;
+ ok(true, "Target automatically navigated when the front was set up.");
+
+ let snapshotActor = yield front.recordAnimationFrame();
+ ok(snapshotActor,
+ "A snapshot actor was sent after recording.");
+
+ let animationOverview = yield snapshotActor.getOverview();
+ ok(snapshotActor,
+ "An animation overview could be retrieved after recording.");
+
+ let functionCalls = animationOverview.calls;
+ ok(functionCalls,
+ "An array of function call actors was sent after recording.");
+ is(functionCalls.length, 8,
+ "The number of function call actors is correct.");
+
+ is(functionCalls[0].type, CallWatcherFront.METHOD_FUNCTION,
+ "The first called function is correctly identified as a method.");
+ is(functionCalls[0].name, "clearRect",
+ "The first called function's name is correct.");
+ is(functionCalls[0].file, SIMPLE_CANVAS_URL,
+ "The first called function's file is correct.");
+ is(functionCalls[0].line, 25,
+ "The first called function's line is correct.");
+ is(functionCalls[0].argsPreview, "0, 0, 128, 128",
+ "The first called function's args preview is correct.");
+ is(functionCalls[0].callerPreview, "Object",
+ "The first called function's caller preview is correct.");
+
+ is(functionCalls[6].type, CallWatcherFront.METHOD_FUNCTION,
+ "The penultimate called function is correctly identified as a method.");
+ is(functionCalls[6].name, "fillRect",
+ "The penultimate called function's name is correct.");
+ is(functionCalls[6].file, SIMPLE_CANVAS_URL,
+ "The penultimate called function's file is correct.");
+ is(functionCalls[6].line, 21,
+ "The penultimate called function's line is correct.");
+ is(functionCalls[6].argsPreview, "10, 10, 55, 50",
+ "The penultimate called function's args preview is correct.");
+ is(functionCalls[6].callerPreview, "Object",
+ "The penultimate called function's caller preview is correct.");
+
+ is(functionCalls[7].type, CallWatcherFront.METHOD_FUNCTION,
+ "The last called function is correctly identified as a method.");
+ is(functionCalls[7].name, "requestAnimationFrame",
+ "The last called function's name is correct.");
+ is(functionCalls[7].file, SIMPLE_CANVAS_URL,
+ "The last called function's file is correct.");
+ is(functionCalls[7].line, 30,
+ "The last called function's line is correct.");
+ ok(functionCalls[7].argsPreview.includes("Function"),
+ "The last called function's args preview is correct.");
+ is(functionCalls[7].callerPreview, "Object",
+ "The last called function's caller preview is correct.");
+
+ yield removeTab(target.tab);
+ finish();
+}
diff --git a/devtools/client/canvasdebugger/test/browser_canvas-actor-test-04.js b/devtools/client/canvasdebugger/test/browser_canvas-actor-test-04.js
new file mode 100644
index 000000000..d3c7d7661
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/browser_canvas-actor-test-04.js
@@ -0,0 +1,85 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if draw calls inside a single animation frame generate and retrieve
+ * the correct thumbnails.
+ */
+
+function* ifTestingSupported() {
+ let { target, front } = yield initCanvasDebuggerBackend(SIMPLE_CANVAS_URL);
+
+ let navigated = once(target, "navigate");
+
+ yield front.setup({ reload: true });
+ ok(true, "The front was setup up successfully.");
+
+ yield navigated;
+ ok(true, "Target automatically navigated when the front was set up.");
+
+ let snapshotActor = yield front.recordAnimationFrame();
+ ok(snapshotActor,
+ "A snapshot actor was sent after recording.");
+
+ let animationOverview = yield snapshotActor.getOverview();
+ ok(animationOverview,
+ "An animation overview could be retrieved after recording.");
+
+ let thumbnails = animationOverview.thumbnails;
+ ok(thumbnails,
+ "An array of thumbnails was sent after recording.");
+ is(thumbnails.length, 4,
+ "The number of thumbnails is correct.");
+
+ is(thumbnails[0].index, 0,
+ "The first thumbnail's index is correct.");
+ is(thumbnails[0].width, 50,
+ "The first thumbnail's width is correct.");
+ is(thumbnails[0].height, 50,
+ "The first thumbnail's height is correct.");
+ is(thumbnails[0].flipped, false,
+ "The first thumbnail's flipped flag is correct.");
+ is([].find.call(Uint32(thumbnails[0].pixels), e => e > 0), undefined,
+ "The first thumbnail's pixels seem to be completely transparent.");
+
+ is(thumbnails[1].index, 2,
+ "The second thumbnail's index is correct.");
+ is(thumbnails[1].width, 50,
+ "The second thumbnail's width is correct.");
+ is(thumbnails[1].height, 50,
+ "The second thumbnail's height is correct.");
+ is(thumbnails[1].flipped, false,
+ "The second thumbnail's flipped flag is correct.");
+ is([].find.call(Uint32(thumbnails[1].pixels), e => e > 0), 4290822336,
+ "The second thumbnail's pixels seem to not be completely transparent.");
+
+ is(thumbnails[2].index, 4,
+ "The third thumbnail's index is correct.");
+ is(thumbnails[2].width, 50,
+ "The third thumbnail's width is correct.");
+ is(thumbnails[2].height, 50,
+ "The third thumbnail's height is correct.");
+ is(thumbnails[2].flipped, false,
+ "The third thumbnail's flipped flag is correct.");
+ is([].find.call(Uint32(thumbnails[2].pixels), e => e > 0), 4290822336,
+ "The third thumbnail's pixels seem to not be completely transparent.");
+
+ is(thumbnails[3].index, 6,
+ "The fourth thumbnail's index is correct.");
+ is(thumbnails[3].width, 50,
+ "The fourth thumbnail's width is correct.");
+ is(thumbnails[3].height, 50,
+ "The fourth thumbnail's height is correct.");
+ is(thumbnails[3].flipped, false,
+ "The fourth thumbnail's flipped flag is correct.");
+ is([].find.call(Uint32(thumbnails[3].pixels), e => e > 0), 4290822336,
+ "The fourth thumbnail's pixels seem to not be completely transparent.");
+
+ yield removeTab(target.tab);
+ finish();
+}
+
+function Uint32(src) {
+ let charView = new Uint8Array(src);
+ return new Uint32Array(charView.buffer);
+}
diff --git a/devtools/client/canvasdebugger/test/browser_canvas-actor-test-05.js b/devtools/client/canvasdebugger/test/browser_canvas-actor-test-05.js
new file mode 100644
index 000000000..e13dab9a4
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/browser_canvas-actor-test-05.js
@@ -0,0 +1,50 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if draw calls inside a single animation frame generate and retrieve
+ * the correct "end result" screenshot.
+ */
+
+function* ifTestingSupported() {
+ let { target, front } = yield initCanvasDebuggerBackend(SIMPLE_CANVAS_URL);
+
+ let navigated = once(target, "navigate");
+
+ yield front.setup({ reload: true });
+ ok(true, "The front was setup up successfully.");
+
+ yield navigated;
+ ok(true, "Target automatically navigated when the front was set up.");
+
+ let snapshotActor = yield front.recordAnimationFrame();
+ ok(snapshotActor,
+ "A snapshot actor was sent after recording.");
+
+ let animationOverview = yield snapshotActor.getOverview();
+ ok(snapshotActor,
+ "An animation overview could be retrieved after recording.");
+
+ let screenshot = animationOverview.screenshot;
+ ok(screenshot,
+ "A screenshot was sent after recording.");
+
+ is(screenshot.index, 6,
+ "The screenshot's index is correct.");
+ is(screenshot.width, 128,
+ "The screenshot's width is correct.");
+ is(screenshot.height, 128,
+ "The screenshot's height is correct.");
+ is(screenshot.flipped, false,
+ "The screenshot's flipped flag is correct.");
+ is([].find.call(Uint32(screenshot.pixels), e => e > 0), 4290822336,
+ "The screenshot's pixels seem to not be completely transparent.");
+
+ yield removeTab(target.tab);
+ finish();
+}
+
+function Uint32(src) {
+ let charView = new Uint8Array(src);
+ return new Uint32Array(charView.buffer);
+}
diff --git a/devtools/client/canvasdebugger/test/browser_canvas-actor-test-06.js b/devtools/client/canvasdebugger/test/browser_canvas-actor-test-06.js
new file mode 100644
index 000000000..511db6667
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/browser_canvas-actor-test-06.js
@@ -0,0 +1,100 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if screenshots for arbitrary draw calls are generated properly.
+ */
+
+function* ifTestingSupported() {
+ let { target, front } = yield initCanvasDebuggerBackend(SIMPLE_CANVAS_TRANSPARENT_URL);
+
+ let navigated = once(target, "navigate");
+
+ yield front.setup({ reload: true });
+ ok(true, "The front was setup up successfully.");
+
+ yield navigated;
+ ok(true, "Target automatically navigated when the front was set up.");
+
+ let snapshotActor = yield front.recordAnimationFrame();
+ let animationOverview = yield snapshotActor.getOverview();
+
+ let functionCalls = animationOverview.calls;
+ ok(functionCalls,
+ "An array of function call actors was sent after recording.");
+ is(functionCalls.length, 8,
+ "The number of function call actors is correct.");
+
+ is(functionCalls[0].name, "clearRect",
+ "The first called function's name is correct.");
+ is(functionCalls[2].name, "fillRect",
+ "The second called function's name is correct.");
+ is(functionCalls[4].name, "fillRect",
+ "The third called function's name is correct.");
+ is(functionCalls[6].name, "fillRect",
+ "The fourth called function's name is correct.");
+
+ let firstDrawCallScreenshot = yield snapshotActor.generateScreenshotFor(functionCalls[0]);
+ let secondDrawCallScreenshot = yield snapshotActor.generateScreenshotFor(functionCalls[2]);
+ let thirdDrawCallScreenshot = yield snapshotActor.generateScreenshotFor(functionCalls[4]);
+ let fourthDrawCallScreenshot = yield snapshotActor.generateScreenshotFor(functionCalls[6]);
+
+ ok(firstDrawCallScreenshot,
+ "The first draw call has a screenshot attached.");
+ is(firstDrawCallScreenshot.index, 0,
+ "The first draw call has the correct screenshot index.");
+ is(firstDrawCallScreenshot.width, 128,
+ "The first draw call has the correct screenshot width.");
+ is(firstDrawCallScreenshot.height, 128,
+ "The first draw call has the correct screenshot height.");
+ is([].find.call(Uint32(firstDrawCallScreenshot.pixels), e => e > 0), undefined,
+ "The first draw call's screenshot's pixels seems to be completely transparent.");
+
+ ok(secondDrawCallScreenshot,
+ "The second draw call has a screenshot attached.");
+ is(secondDrawCallScreenshot.index, 2,
+ "The second draw call has the correct screenshot index.");
+ is(secondDrawCallScreenshot.width, 128,
+ "The second draw call has the correct screenshot width.");
+ is(secondDrawCallScreenshot.height, 128,
+ "The second draw call has the correct screenshot height.");
+ is([].find.call(Uint32(firstDrawCallScreenshot.pixels), e => e > 0), undefined,
+ "The second draw call's screenshot's pixels seems to be completely transparent.");
+
+ ok(thirdDrawCallScreenshot,
+ "The third draw call has a screenshot attached.");
+ is(thirdDrawCallScreenshot.index, 4,
+ "The third draw call has the correct screenshot index.");
+ is(thirdDrawCallScreenshot.width, 128,
+ "The third draw call has the correct screenshot width.");
+ is(thirdDrawCallScreenshot.height, 128,
+ "The third draw call has the correct screenshot height.");
+ is([].find.call(Uint32(thirdDrawCallScreenshot.pixels), e => e > 0), 2160001024,
+ "The third draw call's screenshot's pixels seems to not be completely transparent.");
+
+ ok(fourthDrawCallScreenshot,
+ "The fourth draw call has a screenshot attached.");
+ is(fourthDrawCallScreenshot.index, 6,
+ "The fourth draw call has the correct screenshot index.");
+ is(fourthDrawCallScreenshot.width, 128,
+ "The fourth draw call has the correct screenshot width.");
+ is(fourthDrawCallScreenshot.height, 128,
+ "The fourth draw call has the correct screenshot height.");
+ is([].find.call(Uint32(fourthDrawCallScreenshot.pixels), e => e > 0), 2147483839,
+ "The fourth draw call's screenshot's pixels seems to not be completely transparent.");
+
+ isnot(firstDrawCallScreenshot.pixels, secondDrawCallScreenshot.pixels,
+ "The screenshots taken on consecutive draw calls are different (1).");
+ isnot(secondDrawCallScreenshot.pixels, thirdDrawCallScreenshot.pixels,
+ "The screenshots taken on consecutive draw calls are different (2).");
+ isnot(thirdDrawCallScreenshot.pixels, fourthDrawCallScreenshot.pixels,
+ "The screenshots taken on consecutive draw calls are different (3).");
+
+ yield removeTab(target.tab);
+ finish();
+}
+
+function Uint32(src) {
+ let charView = new Uint8Array(src);
+ return new Uint32Array(charView.buffer);
+}
diff --git a/devtools/client/canvasdebugger/test/browser_canvas-actor-test-07.js b/devtools/client/canvasdebugger/test/browser_canvas-actor-test-07.js
new file mode 100644
index 000000000..8e6c8c25a
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/browser_canvas-actor-test-07.js
@@ -0,0 +1,94 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if screenshots for non-draw calls can still be retrieved properly,
+ * by deferring the the most recent previous draw-call.
+ */
+
+function* ifTestingSupported() {
+ let { target, front } = yield initCanvasDebuggerBackend(SIMPLE_CANVAS_URL);
+
+ let navigated = once(target, "navigate");
+
+ yield front.setup({ reload: true });
+ ok(true, "The front was setup up successfully.");
+
+ yield navigated;
+ ok(true, "Target automatically navigated when the front was set up.");
+
+ let snapshotActor = yield front.recordAnimationFrame();
+ let animationOverview = yield snapshotActor.getOverview();
+
+ let functionCalls = animationOverview.calls;
+ ok(functionCalls,
+ "An array of function call actors was sent after recording.");
+ is(functionCalls.length, 8,
+ "The number of function call actors is correct.");
+
+ let firstNonDrawCall = yield functionCalls[1].getDetails();
+ let secondNonDrawCall = yield functionCalls[3].getDetails();
+ let lastNonDrawCall = yield functionCalls[7].getDetails();
+
+ is(firstNonDrawCall.name, "fillStyle",
+ "The first non-draw function's name is correct.");
+ is(secondNonDrawCall.name, "fillStyle",
+ "The second non-draw function's name is correct.");
+ is(lastNonDrawCall.name, "requestAnimationFrame",
+ "The last non-draw function's name is correct.");
+
+ let firstScreenshot = yield snapshotActor.generateScreenshotFor(functionCalls[1]);
+ let secondScreenshot = yield snapshotActor.generateScreenshotFor(functionCalls[3]);
+ let lastScreenshot = yield snapshotActor.generateScreenshotFor(functionCalls[7]);
+
+ ok(firstScreenshot,
+ "A screenshot was successfully retrieved for the first non-draw function.");
+ ok(secondScreenshot,
+ "A screenshot was successfully retrieved for the second non-draw function.");
+ ok(lastScreenshot,
+ "A screenshot was successfully retrieved for the last non-draw function.");
+
+ let firstActualScreenshot = yield snapshotActor.generateScreenshotFor(functionCalls[0]);
+ ok(sameArray(firstScreenshot.pixels, firstActualScreenshot.pixels),
+ "The screenshot for the first non-draw function is correct.");
+ is(firstScreenshot.width, 128,
+ "The screenshot for the first non-draw function has the correct width.");
+ is(firstScreenshot.height, 128,
+ "The screenshot for the first non-draw function has the correct height.");
+
+ let secondActualScreenshot = yield snapshotActor.generateScreenshotFor(functionCalls[2]);
+ ok(sameArray(secondScreenshot.pixels, secondActualScreenshot.pixels),
+ "The screenshot for the second non-draw function is correct.");
+ is(secondScreenshot.width, 128,
+ "The screenshot for the second non-draw function has the correct width.");
+ is(secondScreenshot.height, 128,
+ "The screenshot for the second non-draw function has the correct height.");
+
+ let lastActualScreenshot = yield snapshotActor.generateScreenshotFor(functionCalls[6]);
+ ok(sameArray(lastScreenshot.pixels, lastActualScreenshot.pixels),
+ "The screenshot for the last non-draw function is correct.");
+ is(lastScreenshot.width, 128,
+ "The screenshot for the last non-draw function has the correct width.");
+ is(lastScreenshot.height, 128,
+ "The screenshot for the last non-draw function has the correct height.");
+
+ ok(!sameArray(firstScreenshot.pixels, secondScreenshot.pixels),
+ "The screenshots taken on consecutive draw calls are different (1).");
+ ok(!sameArray(secondScreenshot.pixels, lastScreenshot.pixels),
+ "The screenshots taken on consecutive draw calls are different (2).");
+
+ yield removeTab(target.tab);
+ finish();
+}
+
+function sameArray(a, b) {
+ if (a.length != b.length) {
+ return false;
+ }
+ for (let i = 0; i < a.length; i++) {
+ if (a[i] !== b[i]) {
+ return false;
+ }
+ }
+ return true;
+}
diff --git a/devtools/client/canvasdebugger/test/browser_canvas-actor-test-08.js b/devtools/client/canvasdebugger/test/browser_canvas-actor-test-08.js
new file mode 100644
index 000000000..f3aeda1a9
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/browser_canvas-actor-test-08.js
@@ -0,0 +1,36 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that integers used in arguments are not cast to their constant, enum value
+ * forms if the method's signature does not expect an enum. Bug 999687.
+ */
+
+function* ifTestingSupported() {
+ let { target, front } = yield initCanvasDebuggerBackend(SIMPLE_BITMASKS_URL);
+
+ let navigated = once(target, "navigate");
+
+ yield front.setup({ reload: true });
+ ok(true, "The front was setup up successfully.");
+
+ yield navigated;
+ ok(true, "Target automatically navigated when the front was set up.");
+
+ let snapshotActor = yield front.recordAnimationFrame();
+ let animationOverview = yield snapshotActor.getOverview();
+ let functionCalls = animationOverview.calls;
+
+ is(functionCalls[0].name, "clearRect",
+ "The first called function's name is correct.");
+ is(functionCalls[0].argsPreview, "0, 0, 4, 4",
+ "The first called function's args preview is not cast to enums.");
+
+ is(functionCalls[2].name, "fillRect",
+ "The fillRect called function's name is correct.");
+ is(functionCalls[2].argsPreview, "0, 0, 1, 1",
+ "The fillRect called function's args preview is not casted to enums.");
+
+ yield removeTab(target.tab);
+ finish();
+}
diff --git a/devtools/client/canvasdebugger/test/browser_canvas-actor-test-09.js b/devtools/client/canvasdebugger/test/browser_canvas-actor-test-09.js
new file mode 100644
index 000000000..d123e3319
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/browser_canvas-actor-test-09.js
@@ -0,0 +1,36 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that integers used in arguments are not cast to their constant, enum value
+ * forms if the method's signature does not expect an enum. Bug 999687.
+ */
+
+function* ifTestingSupported() {
+ let { target, front } = yield initCanvasDebuggerBackend(WEBGL_ENUM_URL);
+
+ let navigated = once(target, "navigate");
+
+ yield front.setup({ reload: true });
+ ok(true, "The front was setup up successfully.");
+
+ yield navigated;
+ ok(true, "Target automatically navigated when the front was set up.");
+
+ let snapshotActor = yield front.recordAnimationFrame();
+ let animationOverview = yield snapshotActor.getOverview();
+ let functionCalls = animationOverview.calls;
+
+ is(functionCalls[0].name, "clear",
+ "The function's name is correct.");
+ is(functionCalls[0].argsPreview, "DEPTH_BUFFER_BIT | STENCIL_BUFFER_BIT | COLOR_BUFFER_BIT",
+ "The bits passed into `gl.clear` have been cast to their enum values.");
+
+ is(functionCalls[1].name, "bindTexture",
+ "The function's name is correct.");
+ is(functionCalls[1].argsPreview, "TEXTURE_2D, null",
+ "The bits passed into `gl.bindTexture` have been cast to their enum values.");
+
+ yield removeTab(target.tab);
+ finish();
+}
diff --git a/devtools/client/canvasdebugger/test/browser_canvas-actor-test-10.js b/devtools/client/canvasdebugger/test/browser_canvas-actor-test-10.js
new file mode 100644
index 000000000..672ef9662
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/browser_canvas-actor-test-10.js
@@ -0,0 +1,107 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that the correct framebuffer, renderbuffer and textures are re-bound
+ * after generating screenshots using the actor.
+ */
+
+function* ifTestingSupported() {
+ let { target, front } = yield initCanvasDebuggerBackend(WEBGL_BINDINGS_URL);
+ loadFrameScripts();
+
+ let navigated = once(target, "navigate");
+
+ yield front.setup({ reload: true });
+ ok(true, "The front was setup up successfully.");
+
+ yield navigated;
+ ok(true, "Target automatically navigated when the front was set up.");
+
+ let snapshotActor = yield front.recordAnimationFrame();
+ let animationOverview = yield snapshotActor.getOverview();
+ let functionCalls = animationOverview.calls;
+
+ let firstScreenshot = yield snapshotActor.generateScreenshotFor(functionCalls[0]);
+ is(firstScreenshot.index, -1,
+ "The first screenshot didn't encounter any draw call.");
+ is(firstScreenshot.scaling, 0.25,
+ "The first screenshot has the correct scaling.");
+ is(firstScreenshot.width, CanvasFront.WEBGL_SCREENSHOT_MAX_HEIGHT,
+ "The first screenshot has the correct width.");
+ is(firstScreenshot.height, CanvasFront.WEBGL_SCREENSHOT_MAX_HEIGHT,
+ "The first screenshot has the correct height.");
+ is(firstScreenshot.flipped, true,
+ "The first screenshot has the correct 'flipped' flag.");
+ is(firstScreenshot.pixels.length, 0,
+ "The first screenshot should be empty.");
+
+ is((yield evalInDebuggee("gl.getParameter(gl.FRAMEBUFFER_BINDING) === customFramebuffer")),
+ true,
+ "The debuggee's gl context framebuffer wasn't changed.");
+ is((yield evalInDebuggee("gl.getParameter(gl.RENDERBUFFER_BINDING) === customRenderbuffer")),
+ true,
+ "The debuggee's gl context renderbuffer wasn't changed.");
+ is((yield evalInDebuggee("gl.getParameter(gl.TEXTURE_BINDING_2D) === customTexture")),
+ true,
+ "The debuggee's gl context texture binding wasn't changed.");
+ is((yield evalInDebuggee("gl.getParameter(gl.VIEWPORT)[0]")),
+ 128,
+ "The debuggee's gl context viewport's left coord. wasn't changed.");
+ is((yield evalInDebuggee("gl.getParameter(gl.VIEWPORT)[1]")),
+ 256,
+ "The debuggee's gl context viewport's left coord. wasn't changed.");
+ is((yield evalInDebuggee("gl.getParameter(gl.VIEWPORT)[2]")),
+ 384,
+ "The debuggee's gl context viewport's left coord. wasn't changed.");
+ is((yield evalInDebuggee("gl.getParameter(gl.VIEWPORT)[3]")),
+ 512,
+ "The debuggee's gl context viewport's left coord. wasn't changed.");
+
+ let secondScreenshot = yield snapshotActor.generateScreenshotFor(functionCalls[1]);
+ is(secondScreenshot.index, 1,
+ "The second screenshot has the correct index.");
+ is(secondScreenshot.width, CanvasFront.WEBGL_SCREENSHOT_MAX_HEIGHT,
+ "The second screenshot has the correct width.");
+ is(secondScreenshot.height, CanvasFront.WEBGL_SCREENSHOT_MAX_HEIGHT,
+ "The second screenshot has the correct height.");
+ is(secondScreenshot.scaling, 0.25,
+ "The second screenshot has the correct scaling.");
+ is(secondScreenshot.flipped, true,
+ "The second screenshot has the correct 'flipped' flag.");
+ is(secondScreenshot.pixels.length, Math.pow(CanvasFront.WEBGL_SCREENSHOT_MAX_HEIGHT, 2) * 4,
+ "The second screenshot should not be empty.");
+ is(secondScreenshot.pixels[0], 0,
+ "The second screenshot has the correct red component.");
+ is(secondScreenshot.pixels[1], 0,
+ "The second screenshot has the correct green component.");
+ is(secondScreenshot.pixels[2], 255,
+ "The second screenshot has the correct blue component.");
+ is(secondScreenshot.pixels[3], 255,
+ "The second screenshot has the correct alpha component.");
+
+ is((yield evalInDebuggee("gl.getParameter(gl.FRAMEBUFFER_BINDING) === customFramebuffer")),
+ true,
+ "The debuggee's gl context framebuffer still wasn't changed.");
+ is((yield evalInDebuggee("gl.getParameter(gl.RENDERBUFFER_BINDING) === customRenderbuffer")),
+ true,
+ "The debuggee's gl context renderbuffer still wasn't changed.");
+ is((yield evalInDebuggee("gl.getParameter(gl.TEXTURE_BINDING_2D) === customTexture")),
+ true,
+ "The debuggee's gl context texture binding still wasn't changed.");
+ is((yield evalInDebuggee("gl.getParameter(gl.VIEWPORT)[0]")),
+ 128,
+ "The debuggee's gl context viewport's left coord. still wasn't changed.");
+ is((yield evalInDebuggee("gl.getParameter(gl.VIEWPORT)[1]")),
+ 256,
+ "The debuggee's gl context viewport's left coord. still wasn't changed.");
+ is((yield evalInDebuggee("gl.getParameter(gl.VIEWPORT)[2]")),
+ 384,
+ "The debuggee's gl context viewport's left coord. still wasn't changed.");
+ is((yield evalInDebuggee("gl.getParameter(gl.VIEWPORT)[3]")),
+ 512,
+ "The debuggee's gl context viewport's left coord. still wasn't changed.");
+
+ yield removeTab(target.tab);
+ finish();
+}
diff --git a/devtools/client/canvasdebugger/test/browser_canvas-actor-test-11.js b/devtools/client/canvasdebugger/test/browser_canvas-actor-test-11.js
new file mode 100644
index 000000000..a1e5010b6
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/browser_canvas-actor-test-11.js
@@ -0,0 +1,138 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that loops using setTimeout are recorded and stored
+ * for a canvas context, and that the generated screenshots are correct.
+ */
+
+function* ifTestingSupported() {
+ let { target, front } = yield initCanvasDebuggerBackend(SET_TIMEOUT_URL);
+
+ let navigated = once(target, "navigate");
+
+ yield front.setup({ reload: true });
+ ok(true, "The front was setup up successfully.");
+
+ yield navigated;
+ ok(true, "Target automatically navigated when the front was set up.");
+
+ let snapshotActor = yield front.recordAnimationFrame();
+ ok(snapshotActor,
+ "A snapshot actor was sent after recording.");
+
+ let animationOverview = yield snapshotActor.getOverview();
+ ok(snapshotActor,
+ "An animation overview could be retrieved after recording.");
+
+ let functionCalls = animationOverview.calls;
+ ok(functionCalls,
+ "An array of function call actors was sent after recording.");
+ is(functionCalls.length, 8,
+ "The number of function call actors is correct.");
+
+ is(functionCalls[0].type, CallWatcherFront.METHOD_FUNCTION,
+ "The first called function is correctly identified as a method.");
+ is(functionCalls[0].name, "clearRect",
+ "The first called function's name is correct.");
+ is(functionCalls[0].file, SET_TIMEOUT_URL,
+ "The first called function's file is correct.");
+ is(functionCalls[0].line, 25,
+ "The first called function's line is correct.");
+ is(functionCalls[0].argsPreview, "0, 0, 128, 128",
+ "The first called function's args preview is correct.");
+ is(functionCalls[0].callerPreview, "Object",
+ "The first called function's caller preview is correct.");
+
+ is(functionCalls[6].type, CallWatcherFront.METHOD_FUNCTION,
+ "The penultimate called function is correctly identified as a method.");
+ is(functionCalls[6].name, "fillRect",
+ "The penultimate called function's name is correct.");
+ is(functionCalls[6].file, SET_TIMEOUT_URL,
+ "The penultimate called function's file is correct.");
+ is(functionCalls[6].line, 21,
+ "The penultimate called function's line is correct.");
+ is(functionCalls[6].argsPreview, "10, 10, 55, 50",
+ "The penultimate called function's args preview is correct.");
+ is(functionCalls[6].callerPreview, "Object",
+ "The penultimate called function's caller preview is correct.");
+
+ is(functionCalls[7].type, CallWatcherFront.METHOD_FUNCTION,
+ "The last called function is correctly identified as a method.");
+ is(functionCalls[7].name, "setTimeout",
+ "The last called function's name is correct.");
+ is(functionCalls[7].file, SET_TIMEOUT_URL,
+ "The last called function's file is correct.");
+ is(functionCalls[7].line, 30,
+ "The last called function's line is correct.");
+ ok(functionCalls[7].argsPreview.includes("Function"),
+ "The last called function's args preview is correct.");
+ is(functionCalls[7].callerPreview, "Object",
+ "The last called function's caller preview is correct.");
+
+ let firstNonDrawCall = yield functionCalls[1].getDetails();
+ let secondNonDrawCall = yield functionCalls[3].getDetails();
+ let lastNonDrawCall = yield functionCalls[7].getDetails();
+
+ is(firstNonDrawCall.name, "fillStyle",
+ "The first non-draw function's name is correct.");
+ is(secondNonDrawCall.name, "fillStyle",
+ "The second non-draw function's name is correct.");
+ is(lastNonDrawCall.name, "setTimeout",
+ "The last non-draw function's name is correct.");
+
+ let firstScreenshot = yield snapshotActor.generateScreenshotFor(functionCalls[1]);
+ let secondScreenshot = yield snapshotActor.generateScreenshotFor(functionCalls[3]);
+ let lastScreenshot = yield snapshotActor.generateScreenshotFor(functionCalls[7]);
+
+ ok(firstScreenshot,
+ "A screenshot was successfully retrieved for the first non-draw function.");
+ ok(secondScreenshot,
+ "A screenshot was successfully retrieved for the second non-draw function.");
+ ok(lastScreenshot,
+ "A screenshot was successfully retrieved for the last non-draw function.");
+
+ let firstActualScreenshot = yield snapshotActor.generateScreenshotFor(functionCalls[0]);
+ ok(sameArray(firstScreenshot.pixels, firstActualScreenshot.pixels),
+ "The screenshot for the first non-draw function is correct.");
+ is(firstScreenshot.width, 128,
+ "The screenshot for the first non-draw function has the correct width.");
+ is(firstScreenshot.height, 128,
+ "The screenshot for the first non-draw function has the correct height.");
+
+ let secondActualScreenshot = yield snapshotActor.generateScreenshotFor(functionCalls[2]);
+ ok(sameArray(secondScreenshot.pixels, secondActualScreenshot.pixels),
+ "The screenshot for the second non-draw function is correct.");
+ is(secondScreenshot.width, 128,
+ "The screenshot for the second non-draw function has the correct width.");
+ is(secondScreenshot.height, 128,
+ "The screenshot for the second non-draw function has the correct height.");
+
+ let lastActualScreenshot = yield snapshotActor.generateScreenshotFor(functionCalls[6]);
+ ok(sameArray(lastScreenshot.pixels, lastActualScreenshot.pixels),
+ "The screenshot for the last non-draw function is correct.");
+ is(lastScreenshot.width, 128,
+ "The screenshot for the last non-draw function has the correct width.");
+ is(lastScreenshot.height, 128,
+ "The screenshot for the last non-draw function has the correct height.");
+
+ ok(!sameArray(firstScreenshot.pixels, secondScreenshot.pixels),
+ "The screenshots taken on consecutive draw calls are different (1).");
+ ok(!sameArray(secondScreenshot.pixels, lastScreenshot.pixels),
+ "The screenshots taken on consecutive draw calls are different (2).");
+
+ yield removeTab(target.tab);
+ finish();
+}
+
+function sameArray(a, b) {
+ if (a.length != b.length) {
+ return false;
+ }
+ for (let i = 0; i < a.length; i++) {
+ if (a[i] !== b[i]) {
+ return false;
+ }
+ }
+ return true;
+}
diff --git a/devtools/client/canvasdebugger/test/browser_canvas-actor-test-12.js b/devtools/client/canvasdebugger/test/browser_canvas-actor-test-12.js
new file mode 100644
index 000000000..86e51931e
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/browser_canvas-actor-test-12.js
@@ -0,0 +1,29 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that the recording can be disabled via stopRecordingAnimationFrame
+ * in the event no rAF loop is found.
+ */
+
+function* ifTestingSupported() {
+ let { target, front } = yield initCanvasDebuggerBackend(NO_CANVAS_URL);
+ loadFrameScripts();
+
+ let navigated = once(target, "navigate");
+
+ yield front.setup({ reload: true });
+ ok(true, "The front was setup up successfully.");
+
+ yield navigated;
+ ok(true, "Target automatically navigated when the front was set up.");
+
+ let startRecording = front.recordAnimationFrame();
+ yield front.stopRecordingAnimationFrame();
+
+ ok(!(yield startRecording),
+ "recordAnimationFrame() does not return a SnapshotActor when cancelled.");
+
+ yield removeTab(target.tab);
+ finish();
+}
diff --git a/devtools/client/canvasdebugger/test/browser_canvas-frontend-call-highlight.js b/devtools/client/canvasdebugger/test/browser_canvas-frontend-call-highlight.js
new file mode 100644
index 000000000..2270f0ccf
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/browser_canvas-frontend-call-highlight.js
@@ -0,0 +1,41 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if certain function calls are properly highlighted in the UI.
+ */
+
+function* ifTestingSupported() {
+ let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
+ let { window, $, EVENTS, SnapshotsListView, CallsListView } = panel.panelWin;
+
+ yield reload(target);
+
+ let recordingFinished = once(window, EVENTS.SNAPSHOT_RECORDING_FINISHED);
+ let callListPopulated = once(window, EVENTS.CALL_LIST_POPULATED);
+ SnapshotsListView._onRecordButtonClick();
+ yield promise.all([recordingFinished, callListPopulated]);
+
+ is(CallsListView.itemCount, 8,
+ "All the function calls should now be displayed in the UI.");
+
+ is($(".call-item-view", CallsListView.getItemAtIndex(0).target).hasAttribute("draw-call"), true,
+ "The first item's node should have a draw-call attribute.");
+ is($(".call-item-view", CallsListView.getItemAtIndex(1).target).hasAttribute("draw-call"), false,
+ "The second item's node should not have a draw-call attribute.");
+ is($(".call-item-view", CallsListView.getItemAtIndex(2).target).hasAttribute("draw-call"), true,
+ "The third item's node should have a draw-call attribute.");
+ is($(".call-item-view", CallsListView.getItemAtIndex(3).target).hasAttribute("draw-call"), false,
+ "The fourth item's node should not have a draw-call attribute.");
+ is($(".call-item-view", CallsListView.getItemAtIndex(4).target).hasAttribute("draw-call"), true,
+ "The fifth item's node should have a draw-call attribute.");
+ is($(".call-item-view", CallsListView.getItemAtIndex(5).target).hasAttribute("draw-call"), false,
+ "The sixth item's node should not have a draw-call attribute.");
+ is($(".call-item-view", CallsListView.getItemAtIndex(6).target).hasAttribute("draw-call"), true,
+ "The seventh item's node should have a draw-call attribute.");
+ is($(".call-item-view", CallsListView.getItemAtIndex(7).target).hasAttribute("draw-call"), false,
+ "The eigth item's node should not have a draw-call attribute.");
+
+ yield teardown(panel);
+ finish();
+}
diff --git a/devtools/client/canvasdebugger/test/browser_canvas-frontend-call-list.js b/devtools/client/canvasdebugger/test/browser_canvas-frontend-call-list.js
new file mode 100644
index 000000000..5f9ce876f
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/browser_canvas-frontend-call-list.js
@@ -0,0 +1,70 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if all the function calls associated with an animation frame snapshot
+ * are properly displayed in the UI.
+ */
+
+function* ifTestingSupported() {
+ let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
+ let { window, $, EVENTS, SnapshotsListView, CallsListView } = panel.panelWin;
+
+ yield reload(target);
+
+ let recordingFinished = once(window, EVENTS.SNAPSHOT_RECORDING_FINISHED);
+ let callListPopulated = once(window, EVENTS.CALL_LIST_POPULATED);
+ SnapshotsListView._onRecordButtonClick();
+ yield promise.all([recordingFinished, callListPopulated]);
+
+ is(CallsListView.itemCount, 8,
+ "All the function calls should now be displayed in the UI.");
+
+ testItem(CallsListView.getItemAtIndex(0),
+ "1", "Object", "clearRect", "(0, 0, 128, 128)", "doc_simple-canvas.html:25");
+
+ testItem(CallsListView.getItemAtIndex(1),
+ "2", "Object", "fillStyle", " = rgb(192, 192, 192)", "doc_simple-canvas.html:20");
+ testItem(CallsListView.getItemAtIndex(2),
+ "3", "Object", "fillRect", "(0, 0, 128, 128)", "doc_simple-canvas.html:21");
+
+ testItem(CallsListView.getItemAtIndex(3),
+ "4", "Object", "fillStyle", " = rgba(0, 0, 192, 0.5)", "doc_simple-canvas.html:20");
+ testItem(CallsListView.getItemAtIndex(4),
+ "5", "Object", "fillRect", "(30, 30, 55, 50)", "doc_simple-canvas.html:21");
+
+ testItem(CallsListView.getItemAtIndex(5),
+ "6", "Object", "fillStyle", " = rgba(192, 0, 0, 0.5)", "doc_simple-canvas.html:20");
+ testItem(CallsListView.getItemAtIndex(6),
+ "7", "Object", "fillRect", "(10, 10, 55, 50)", "doc_simple-canvas.html:21");
+
+ testItem(CallsListView.getItemAtIndex(7),
+ "8", "", "requestAnimationFrame", "(Function)", "doc_simple-canvas.html:30");
+
+ function testItem(item, index, context, name, args, location) {
+ let i = CallsListView.indexOfItem(item);
+ is(i, index - 1,
+ "The item at index " + index + " is correctly displayed in the UI.");
+
+ is($(".call-item-index", item.target).getAttribute("value"), index,
+ "The item's gutter label has the correct text.");
+
+ if (context) {
+ is($(".call-item-context", item.target).getAttribute("value"), context,
+ "The item's context label has the correct text.");
+ } else {
+ is($(".call-item-context", item.target) + "", "[object XULElement]",
+ "The item's context label should not be available.");
+ }
+
+ is($(".call-item-name", item.target).getAttribute("value"), name,
+ "The item's name label has the correct text.");
+ is($(".call-item-args", item.target).getAttribute("value"), args,
+ "The item's args label has the correct text.");
+ is($(".call-item-location", item.target).getAttribute("value"), location,
+ "The item's location label has the correct text.");
+ }
+
+ yield teardown(panel);
+ finish();
+}
diff --git a/devtools/client/canvasdebugger/test/browser_canvas-frontend-call-search.js b/devtools/client/canvasdebugger/test/browser_canvas-frontend-call-search.js
new file mode 100644
index 000000000..e865df391
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/browser_canvas-frontend-call-search.js
@@ -0,0 +1,72 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if filtering the items in the call list works properly.
+ */
+
+function* ifTestingSupported() {
+ let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
+ let { window, $, EVENTS, SnapshotsListView, CallsListView } = panel.panelWin;
+ let searchbox = $("#calls-searchbox");
+
+ yield reload(target);
+
+ let firstRecordingFinished = once(window, EVENTS.SNAPSHOT_RECORDING_FINISHED);
+ let callListPopulated = once(window, EVENTS.CALL_LIST_POPULATED);
+ SnapshotsListView._onRecordButtonClick();
+ yield promise.all([firstRecordingFinished, callListPopulated]);
+
+ is(searchbox.value, "",
+ "The searchbox should be initially empty.");
+ is(CallsListView.visibleItems.length, 8,
+ "All the items should be initially visible in the calls list.");
+
+ searchbox.focus();
+ EventUtils.sendString("clear", window);
+
+ is(searchbox.value, "clear",
+ "The searchbox should now contain the 'clear' string.");
+ is(CallsListView.visibleItems.length, 1,
+ "Only one item should now be visible in the calls list.");
+
+ is(CallsListView.visibleItems[0].attachment.actor.type, CallWatcherFront.METHOD_FUNCTION,
+ "The visible item's type has the expected value.");
+ is(CallsListView.visibleItems[0].attachment.actor.name, "clearRect",
+ "The visible item's name has the expected value.");
+ is(CallsListView.visibleItems[0].attachment.actor.file, SIMPLE_CANVAS_URL,
+ "The visible item's file has the expected value.");
+ is(CallsListView.visibleItems[0].attachment.actor.line, 25,
+ "The visible item's line has the expected value.");
+ is(CallsListView.visibleItems[0].attachment.actor.argsPreview, "0, 0, 128, 128",
+ "The visible item's args have the expected value.");
+ is(CallsListView.visibleItems[0].attachment.actor.callerPreview, "Object",
+ "The visible item's caller has the expected value.");
+
+ let secondRecordingFinished = once(window, EVENTS.SNAPSHOT_RECORDING_FINISHED);
+ callListPopulated = once(window, EVENTS.CALL_LIST_POPULATED);
+
+ SnapshotsListView._onRecordButtonClick();
+ yield secondRecordingFinished;
+
+ SnapshotsListView.selectedIndex = 1;
+ yield callListPopulated;
+
+ is(searchbox.value, "clear",
+ "The searchbox should still contain the 'clear' string.");
+ is(CallsListView.visibleItems.length, 1,
+ "Only one item should still be visible in the calls list.");
+
+ for (let i = 0; i < 5; i++) {
+ searchbox.focus();
+ EventUtils.sendKey("BACK_SPACE", window);
+ }
+
+ is(searchbox.value, "",
+ "The searchbox should now be emptied.");
+ is(CallsListView.visibleItems.length, 8,
+ "All the items should be initially visible again in the calls list.");
+
+ yield teardown(panel);
+ finish();
+}
diff --git a/devtools/client/canvasdebugger/test/browser_canvas-frontend-call-stack-01.js b/devtools/client/canvasdebugger/test/browser_canvas-frontend-call-stack-01.js
new file mode 100644
index 000000000..964683c84
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/browser_canvas-frontend-call-stack-01.js
@@ -0,0 +1,82 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if the a function call's stack is properly displayed in the UI.
+ */
+
+// Force the old debugger UI since it's directly used (see Bug 1301705)
+Services.prefs.setBoolPref("devtools.debugger.new-debugger-frontend", false);
+registerCleanupFunction(function* () {
+ Services.prefs.clearUserPref("devtools.debugger.new-debugger-frontend");
+});
+
+function* ifTestingSupported() {
+ let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_DEEP_STACK_URL);
+ let { window, $, $all, EVENTS, SnapshotsListView, CallsListView } = panel.panelWin;
+
+ yield reload(target);
+
+ let recordingFinished = once(window, EVENTS.SNAPSHOT_RECORDING_FINISHED);
+ let callListPopulated = once(window, EVENTS.CALL_LIST_POPULATED);
+ SnapshotsListView._onRecordButtonClick();
+ yield promise.all([recordingFinished, callListPopulated]);
+
+ let callItem = CallsListView.getItemAtIndex(2);
+ let locationLink = $(".call-item-location", callItem.target);
+
+ is($(".call-item-stack", callItem.target), null,
+ "There should be no stack container available yet for the draw call.");
+
+ let callStackDisplayed = once(window, EVENTS.CALL_STACK_DISPLAYED);
+ EventUtils.sendMouseEvent({ type: "mousedown" }, locationLink, window);
+ yield callStackDisplayed;
+
+ isnot($(".call-item-stack", callItem.target), null,
+ "There should be a stack container available now for the draw call.");
+ // We may have more than 4 functions, depending on whether async
+ // stacks are available.
+ ok($all(".call-item-stack-fn", callItem.target).length >= 4,
+ "There should be at least 4 functions on the stack for the draw call.");
+
+ ok($all(".call-item-stack-fn-name", callItem.target)[0].getAttribute("value")
+ .includes("C()"),
+ "The first function on the stack has the correct name.");
+ ok($all(".call-item-stack-fn-name", callItem.target)[1].getAttribute("value")
+ .includes("B()"),
+ "The second function on the stack has the correct name.");
+ ok($all(".call-item-stack-fn-name", callItem.target)[2].getAttribute("value")
+ .includes("A()"),
+ "The third function on the stack has the correct name.");
+ ok($all(".call-item-stack-fn-name", callItem.target)[3].getAttribute("value")
+ .includes("drawRect()"),
+ "The fourth function on the stack has the correct name.");
+
+ is($all(".call-item-stack-fn-location", callItem.target)[0].getAttribute("value"),
+ "doc_simple-canvas-deep-stack.html:26",
+ "The first function on the stack has the correct location.");
+ is($all(".call-item-stack-fn-location", callItem.target)[1].getAttribute("value"),
+ "doc_simple-canvas-deep-stack.html:28",
+ "The second function on the stack has the correct location.");
+ is($all(".call-item-stack-fn-location", callItem.target)[2].getAttribute("value"),
+ "doc_simple-canvas-deep-stack.html:30",
+ "The third function on the stack has the correct location.");
+ is($all(".call-item-stack-fn-location", callItem.target)[3].getAttribute("value"),
+ "doc_simple-canvas-deep-stack.html:35",
+ "The fourth function on the stack has the correct location.");
+
+ let jumpedToSource = once(window, EVENTS.SOURCE_SHOWN_IN_JS_DEBUGGER);
+ EventUtils.sendMouseEvent({ type: "mousedown" }, $(".call-item-stack-fn-location", callItem.target));
+ yield jumpedToSource;
+
+ let toolbox = yield gDevTools.getToolbox(target);
+ let { panelWin: { DebuggerView: view } } = toolbox.getPanel("jsdebugger");
+
+ is(view.Sources.selectedValue, getSourceActor(view.Sources, SIMPLE_CANVAS_DEEP_STACK_URL),
+ "The expected source was shown in the debugger.");
+ is(view.editor.getCursor().line, 25,
+ "The expected source line is highlighted in the debugger.");
+
+ yield teardown(panel);
+ finish();
+}
diff --git a/devtools/client/canvasdebugger/test/browser_canvas-frontend-call-stack-02.js b/devtools/client/canvasdebugger/test/browser_canvas-frontend-call-stack-02.js
new file mode 100644
index 000000000..9b5c65839
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/browser_canvas-frontend-call-stack-02.js
@@ -0,0 +1,57 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if the a function call's stack is properly displayed in the UI
+ * and jumping to source in the debugger for the topmost call item works.
+ */
+
+// Force the old debugger UI since it's directly used (see Bug 1301705)
+Services.prefs.setBoolPref("devtools.debugger.new-debugger-frontend", false);
+registerCleanupFunction(function* () {
+ Services.prefs.clearUserPref("devtools.debugger.new-debugger-frontend");
+});
+
+function* ifTestingSupported() {
+ let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_DEEP_STACK_URL);
+ let { window, $, $all, EVENTS, SnapshotsListView, CallsListView } = panel.panelWin;
+
+ yield reload(target);
+
+ let recordingFinished = once(window, EVENTS.SNAPSHOT_RECORDING_FINISHED);
+ let callListPopulated = once(window, EVENTS.CALL_LIST_POPULATED);
+ SnapshotsListView._onRecordButtonClick();
+ yield promise.all([recordingFinished, callListPopulated]);
+
+ let callItem = CallsListView.getItemAtIndex(2);
+ let locationLink = $(".call-item-location", callItem.target);
+
+ is($(".call-item-stack", callItem.target), null,
+ "There should be no stack container available yet for the draw call.");
+
+ let callStackDisplayed = once(window, EVENTS.CALL_STACK_DISPLAYED);
+ EventUtils.sendMouseEvent({ type: "mousedown" }, locationLink, window);
+ yield callStackDisplayed;
+
+ isnot($(".call-item-stack", callItem.target), null,
+ "There should be a stack container available now for the draw call.");
+ // We may have more than 4 functions, depending on whether async
+ // stacks are available.
+ ok($all(".call-item-stack-fn", callItem.target).length >= 4,
+ "There should be at least 4 functions on the stack for the draw call.");
+
+ let jumpedToSource = once(window, EVENTS.SOURCE_SHOWN_IN_JS_DEBUGGER);
+ EventUtils.sendMouseEvent({ type: "mousedown" }, $(".call-item-location", callItem.target));
+ yield jumpedToSource;
+
+ let toolbox = yield gDevTools.getToolbox(target);
+ let { panelWin: { DebuggerView: view } } = toolbox.getPanel("jsdebugger");
+
+ is(view.Sources.selectedValue, getSourceActor(view.Sources, SIMPLE_CANVAS_DEEP_STACK_URL),
+ "The expected source was shown in the debugger.");
+ is(view.editor.getCursor().line, 23,
+ "The expected source line is highlighted in the debugger.");
+
+ yield teardown(panel);
+ finish();
+}
diff --git a/devtools/client/canvasdebugger/test/browser_canvas-frontend-call-stack-03.js b/devtools/client/canvasdebugger/test/browser_canvas-frontend-call-stack-03.js
new file mode 100644
index 000000000..24780c566
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/browser_canvas-frontend-call-stack-03.js
@@ -0,0 +1,65 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if the a function call's stack can be shown/hidden by double-clicking
+ * on a function call item.
+ */
+
+function* ifTestingSupported() {
+ let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_DEEP_STACK_URL);
+ let { window, $, $all, EVENTS, SnapshotsListView, CallsListView } = panel.panelWin;
+
+ yield reload(target);
+
+ let recordingFinished = once(window, EVENTS.SNAPSHOT_RECORDING_FINISHED);
+ let callListPopulated = once(window, EVENTS.CALL_LIST_POPULATED);
+ SnapshotsListView._onRecordButtonClick();
+ yield promise.all([recordingFinished, callListPopulated]);
+
+ let callItem = CallsListView.getItemAtIndex(2);
+ let view = $(".call-item-view", callItem.target);
+ let contents = $(".call-item-contents", callItem.target);
+
+ is(view.hasAttribute("call-stack-populated"), false,
+ "The call item's view should not have the stack populated yet.");
+ is(view.hasAttribute("call-stack-expanded"), false,
+ "The call item's view should not have the stack populated yet.");
+ is($(".call-item-stack", callItem.target), null,
+ "There should be no stack container available yet for the draw call.");
+
+ let callStackDisplayed = once(window, EVENTS.CALL_STACK_DISPLAYED);
+ EventUtils.sendMouseEvent({ type: "dblclick" }, contents, window);
+ yield callStackDisplayed;
+
+ is(view.hasAttribute("call-stack-populated"), true,
+ "The call item's view should have the stack populated now.");
+ is(view.getAttribute("call-stack-expanded"), "true",
+ "The call item's view should have the stack expanded now.");
+ isnot($(".call-item-stack", callItem.target), null,
+ "There should be a stack container available now for the draw call.");
+ is($(".call-item-stack", callItem.target).hidden, false,
+ "The stack container should now be visible.");
+ // We may have more than 4 functions, depending on whether async
+ // stacks are available.
+ ok($all(".call-item-stack-fn", callItem.target).length >= 4,
+ "There should be at least 4 functions on the stack for the draw call.");
+
+ EventUtils.sendMouseEvent({ type: "dblclick" }, contents, window);
+
+ is(view.hasAttribute("call-stack-populated"), true,
+ "The call item's view should still have the stack populated.");
+ is(view.getAttribute("call-stack-expanded"), "false",
+ "The call item's view should not have the stack expanded anymore.");
+ isnot($(".call-item-stack", callItem.target), null,
+ "There should still be a stack container available for the draw call.");
+ is($(".call-item-stack", callItem.target).hidden, true,
+ "The stack container should now be hidden.");
+ // We may have more than 4 functions, depending on whether async
+ // stacks are available.
+ ok($all(".call-item-stack-fn", callItem.target).length >= 4,
+ "There should still be at least 4 functions on the stack for the draw call.");
+
+ yield teardown(panel);
+ finish();
+}
diff --git a/devtools/client/canvasdebugger/test/browser_canvas-frontend-clear.js b/devtools/client/canvasdebugger/test/browser_canvas-frontend-clear.js
new file mode 100644
index 000000000..c80082046
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/browser_canvas-frontend-clear.js
@@ -0,0 +1,43 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if clearing the snapshots list works as expected.
+ */
+
+function* ifTestingSupported() {
+ let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
+ let { window, EVENTS, SnapshotsListView } = panel.panelWin;
+
+ yield reload(target);
+
+ let firstRecordingFinished = once(window, EVENTS.SNAPSHOT_RECORDING_FINISHED);
+ SnapshotsListView._onRecordButtonClick();
+
+ yield firstRecordingFinished;
+ ok(true, "Finished recording a snapshot of the animation loop.");
+
+ is(SnapshotsListView.itemCount, 1,
+ "There should be one item available in the snapshots list.");
+
+ let secondRecordingFinished = once(window, EVENTS.SNAPSHOT_RECORDING_FINISHED);
+ SnapshotsListView._onRecordButtonClick();
+
+ yield secondRecordingFinished;
+ ok(true, "Finished recording another snapshot of the animation loop.");
+
+ is(SnapshotsListView.itemCount, 2,
+ "There should be two items available in the snapshots list.");
+
+ let clearingFinished = once(window, EVENTS.SNAPSHOTS_LIST_CLEARED);
+ SnapshotsListView._onClearButtonClick();
+
+ yield clearingFinished;
+ ok(true, "Finished recording all snapshots.");
+
+ is(SnapshotsListView.itemCount, 0,
+ "There should be no items available in the snapshots list.");
+
+ yield teardown(panel);
+ finish();
+}
diff --git a/devtools/client/canvasdebugger/test/browser_canvas-frontend-img-screenshots.js b/devtools/client/canvasdebugger/test/browser_canvas-frontend-img-screenshots.js
new file mode 100644
index 000000000..e96543e10
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/browser_canvas-frontend-img-screenshots.js
@@ -0,0 +1,34 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if screenshots are properly displayed in the UI.
+ */
+
+function* ifTestingSupported() {
+ let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
+ let { window, $, EVENTS, SnapshotsListView } = panel.panelWin;
+
+ yield reload(target);
+
+ let recordingFinished = once(window, EVENTS.SNAPSHOT_RECORDING_FINISHED);
+ let callListPopulated = once(window, EVENTS.CALL_LIST_POPULATED);
+ let screenshotDisplayed = once(window, EVENTS.CALL_SCREENSHOT_DISPLAYED);
+ SnapshotsListView._onRecordButtonClick();
+ yield promise.all([recordingFinished, callListPopulated, screenshotDisplayed]);
+
+ is($("#screenshot-container").hidden, false,
+ "The screenshot container should now be visible.");
+
+ is($("#screenshot-dimensions").getAttribute("value"), "128" + "\u00D7" + "128",
+ "The screenshot dimensions label has the expected value.");
+
+ is($("#screenshot-image").getAttribute("flipped"), "false",
+ "The screenshot element should not be flipped vertically.");
+
+ ok(window.getComputedStyle($("#screenshot-image")).backgroundImage.includes("#screenshot-rendering"),
+ "The screenshot element should have an offscreen canvas element as a background.");
+
+ yield teardown(panel);
+ finish();
+}
diff --git a/devtools/client/canvasdebugger/test/browser_canvas-frontend-img-thumbnails-01.js b/devtools/client/canvasdebugger/test/browser_canvas-frontend-img-thumbnails-01.js
new file mode 100644
index 000000000..41e8f7383
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/browser_canvas-frontend-img-thumbnails-01.js
@@ -0,0 +1,65 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if thumbnails are properly displayed in the UI.
+ */
+
+function* ifTestingSupported() {
+ let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
+ let { window, $, $all, EVENTS, SnapshotsListView } = panel.panelWin;
+
+ yield reload(target);
+
+ let recordingFinished = once(window, EVENTS.SNAPSHOT_RECORDING_FINISHED);
+ let callListPopulated = once(window, EVENTS.CALL_LIST_POPULATED);
+ let thumbnailsDisplayed = once(window, EVENTS.THUMBNAILS_DISPLAYED);
+ SnapshotsListView._onRecordButtonClick();
+ yield promise.all([recordingFinished, callListPopulated, thumbnailsDisplayed]);
+
+ is($all(".filmstrip-thumbnail").length, 4,
+ "There should be 4 thumbnails displayed in the UI.");
+
+ let firstThumbnail = $(".filmstrip-thumbnail[index='0']");
+ ok(firstThumbnail,
+ "The first thumbnail element should be for the function call at index 0.");
+ is(firstThumbnail.width, 50,
+ "The first thumbnail's width is correct.");
+ is(firstThumbnail.height, 50,
+ "The first thumbnail's height is correct.");
+ is(firstThumbnail.getAttribute("flipped"), "false",
+ "The first thumbnail should not be flipped vertically.");
+
+ let secondThumbnail = $(".filmstrip-thumbnail[index='2']");
+ ok(secondThumbnail,
+ "The second thumbnail element should be for the function call at index 2.");
+ is(secondThumbnail.width, 50,
+ "The second thumbnail's width is correct.");
+ is(secondThumbnail.height, 50,
+ "The second thumbnail's height is correct.");
+ is(secondThumbnail.getAttribute("flipped"), "false",
+ "The second thumbnail should not be flipped vertically.");
+
+ let thirdThumbnail = $(".filmstrip-thumbnail[index='4']");
+ ok(thirdThumbnail,
+ "The third thumbnail element should be for the function call at index 4.");
+ is(thirdThumbnail.width, 50,
+ "The third thumbnail's width is correct.");
+ is(thirdThumbnail.height, 50,
+ "The third thumbnail's height is correct.");
+ is(thirdThumbnail.getAttribute("flipped"), "false",
+ "The third thumbnail should not be flipped vertically.");
+
+ let fourthThumbnail = $(".filmstrip-thumbnail[index='6']");
+ ok(fourthThumbnail,
+ "The fourth thumbnail element should be for the function call at index 6.");
+ is(fourthThumbnail.width, 50,
+ "The fourth thumbnail's width is correct.");
+ is(fourthThumbnail.height, 50,
+ "The fourth thumbnail's height is correct.");
+ is(fourthThumbnail.getAttribute("flipped"), "false",
+ "The fourth thumbnail should not be flipped vertically.");
+
+ yield teardown(panel);
+ finish();
+}
diff --git a/devtools/client/canvasdebugger/test/browser_canvas-frontend-img-thumbnails-02.js b/devtools/client/canvasdebugger/test/browser_canvas-frontend-img-thumbnails-02.js
new file mode 100644
index 000000000..798bc090b
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/browser_canvas-frontend-img-thumbnails-02.js
@@ -0,0 +1,67 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if thumbnails are correctly linked with other UI elements like
+ * function call items and their respective screenshots.
+ */
+
+function* ifTestingSupported() {
+ let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
+ let { window, $, $all, EVENTS, SnapshotsListView, CallsListView } = panel.panelWin;
+
+ yield reload(target);
+
+ let recordingFinished = once(window, EVENTS.SNAPSHOT_RECORDING_FINISHED);
+ let callListPopulated = once(window, EVENTS.CALL_LIST_POPULATED);
+ let thumbnailsDisplayed = once(window, EVENTS.THUMBNAILS_DISPLAYED);
+ let screenshotDisplayed = once(window, EVENTS.CALL_SCREENSHOT_DISPLAYED);
+ SnapshotsListView._onRecordButtonClick();
+ yield promise.all([
+ recordingFinished,
+ callListPopulated,
+ thumbnailsDisplayed,
+ screenshotDisplayed
+ ]);
+
+ is($all(".filmstrip-thumbnail[highlighted]").length, 0,
+ "There should be no highlighted thumbnail available yet.");
+ is(CallsListView.selectedIndex, -1,
+ "There should be no selected item in the calls list view.");
+
+ EventUtils.sendMouseEvent({ type: "mousedown" }, $all(".filmstrip-thumbnail")[0], window);
+ yield once(window, EVENTS.CALL_SCREENSHOT_DISPLAYED);
+ info("The first draw call was selected, by clicking the first thumbnail.");
+
+ isnot($(".filmstrip-thumbnail[highlighted][index='0']"), null,
+ "There should be a highlighted thumbnail available now, for the first draw call.");
+ is($all(".filmstrip-thumbnail[highlighted]").length, 1,
+ "There should be only one highlighted thumbnail available now.");
+ is(CallsListView.selectedIndex, 0,
+ "The first draw call should be selected in the calls list view.");
+
+ EventUtils.sendMouseEvent({ type: "mousedown" }, $all(".call-item-view")[1], window);
+ yield once(window, EVENTS.CALL_SCREENSHOT_DISPLAYED);
+ info("The second context call was selected, by clicking the second call item.");
+
+ isnot($(".filmstrip-thumbnail[highlighted][index='0']"), null,
+ "There should be a highlighted thumbnail available, for the first draw call.");
+ is($all(".filmstrip-thumbnail[highlighted]").length, 1,
+ "There should be only one highlighted thumbnail available.");
+ is(CallsListView.selectedIndex, 1,
+ "The second draw call should be selected in the calls list view.");
+
+ EventUtils.sendMouseEvent({ type: "mousedown" }, $all(".call-item-view")[2], window);
+ yield once(window, EVENTS.CALL_SCREENSHOT_DISPLAYED);
+ info("The second draw call was selected, by clicking the third call item.");
+
+ isnot($(".filmstrip-thumbnail[highlighted][index='2']"), null,
+ "There should be a highlighted thumbnail available, for the second draw call.");
+ is($all(".filmstrip-thumbnail[highlighted]").length, 1,
+ "There should be only one highlighted thumbnail available.");
+ is(CallsListView.selectedIndex, 2,
+ "The second draw call should be selected in the calls list view.");
+
+ yield teardown(panel);
+ finish();
+}
diff --git a/devtools/client/canvasdebugger/test/browser_canvas-frontend-open.js b/devtools/client/canvasdebugger/test/browser_canvas-frontend-open.js
new file mode 100644
index 000000000..59c4d4cfb
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/browser_canvas-frontend-open.js
@@ -0,0 +1,41 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that the frontend UI is properly configured when opening the tool.
+ */
+
+function* ifTestingSupported() {
+ let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
+ let { $ } = panel.panelWin;
+
+ is($("#snapshots-pane").hasAttribute("hidden"), false,
+ "The snapshots pane should initially be visible.");
+ is($("#debugging-pane").hasAttribute("hidden"), false,
+ "The debugging pane should initially be visible.");
+
+ is($("#record-snapshot").getAttribute("hidden"), "true",
+ "The 'record snapshot' button should initially be hidden.");
+ is($("#import-snapshot").hasAttribute("hidden"), false,
+ "The 'import snapshot' button should initially be visible.");
+ is($("#clear-snapshots").hasAttribute("hidden"), false,
+ "The 'clear snapshots' button should initially be visible.");
+
+ is($("#reload-notice").hasAttribute("hidden"), false,
+ "The reload notice should initially be visible.");
+ is($("#empty-notice").getAttribute("hidden"), "true",
+ "The empty notice should initially be hidden.");
+ is($("#waiting-notice").getAttribute("hidden"), "true",
+ "The waiting notice should initially be hidden.");
+
+ is($("#screenshot-container").getAttribute("hidden"), "true",
+ "The screenshot container should initially be hidden.");
+ is($("#snapshot-filmstrip").getAttribute("hidden"), "true",
+ "The snapshot filmstrip should initially be hidden.");
+
+ is($("#debugging-pane-contents").getAttribute("hidden"), "true",
+ "The rest of the UI should initially be hidden.");
+
+ yield teardown(panel);
+ finish();
+}
diff --git a/devtools/client/canvasdebugger/test/browser_canvas-frontend-record-01.js b/devtools/client/canvasdebugger/test/browser_canvas-frontend-record-01.js
new file mode 100644
index 000000000..cd0358d3c
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/browser_canvas-frontend-record-01.js
@@ -0,0 +1,60 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests whether the frontend behaves correctly while reording a snapshot.
+ */
+
+function* ifTestingSupported() {
+ let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
+ let { window, EVENTS, $, SnapshotsListView } = panel.panelWin;
+
+ yield reload(target);
+
+ is($("#record-snapshot").hasAttribute("checked"), false,
+ "The 'record snapshot' button should initially be unchecked.");
+ is($("#record-snapshot").hasAttribute("disabled"), false,
+ "The 'record snapshot' button should initially be enabled.");
+ is($("#record-snapshot").hasAttribute("hidden"), false,
+ "The 'record snapshot' button should now be visible.");
+
+ is(SnapshotsListView.itemCount, 0,
+ "There should be no items available in the snapshots list view.");
+ is(SnapshotsListView.selectedIndex, -1,
+ "There should be no selected item in the snapshots list view.");
+
+ let recordingStarted = once(window, EVENTS.SNAPSHOT_RECORDING_STARTED);
+ let recordingFinished = once(window, EVENTS.SNAPSHOT_RECORDING_FINISHED);
+ SnapshotsListView._onRecordButtonClick();
+
+ yield recordingStarted;
+ ok(true, "Started recording a snapshot of the animation loop.");
+
+ is($("#record-snapshot").getAttribute("checked"), "true",
+ "The 'record snapshot' button should now be checked.");
+ is($("#record-snapshot").hasAttribute("hidden"), false,
+ "The 'record snapshot' button should still be visible.");
+
+ is(SnapshotsListView.itemCount, 1,
+ "There should be one item available in the snapshots list view now.");
+ is(SnapshotsListView.selectedIndex, -1,
+ "There should be no selected item in the snapshots list view yet.");
+
+ yield recordingFinished;
+ ok(true, "Finished recording a snapshot of the animation loop.");
+
+ is($("#record-snapshot").hasAttribute("checked"), false,
+ "The 'record snapshot' button should now be unchecked.");
+ is($("#record-snapshot").hasAttribute("disabled"), false,
+ "The 'record snapshot' button should now be re-enabled.");
+ is($("#record-snapshot").hasAttribute("hidden"), false,
+ "The 'record snapshot' button should still be visible.");
+
+ is(SnapshotsListView.itemCount, 1,
+ "There should still be only one item available in the snapshots list view.");
+ is(SnapshotsListView.selectedIndex, 0,
+ "There should be one selected item in the snapshots list view now.");
+
+ yield teardown(panel);
+ finish();
+}
diff --git a/devtools/client/canvasdebugger/test/browser_canvas-frontend-record-02.js b/devtools/client/canvasdebugger/test/browser_canvas-frontend-record-02.js
new file mode 100644
index 000000000..aee63a574
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/browser_canvas-frontend-record-02.js
@@ -0,0 +1,73 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests whether the frontend displays a placeholder snapshot while recording.
+ */
+
+function* ifTestingSupported() {
+ let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
+ let { window, EVENTS, L10N, $, SnapshotsListView } = panel.panelWin;
+
+ yield reload(target);
+
+ let recordingStarted = once(window, EVENTS.SNAPSHOT_RECORDING_STARTED);
+ let recordingFinished = once(window, EVENTS.SNAPSHOT_RECORDING_FINISHED);
+ let recordingSelected = once(window, EVENTS.SNAPSHOT_RECORDING_SELECTED);
+ SnapshotsListView._onRecordButtonClick();
+
+ yield recordingStarted;
+ ok(true, "Started recording a snapshot of the animation loop.");
+
+ let item = SnapshotsListView.getItemAtIndex(0);
+
+ is($(".snapshot-item-title", item.target).getAttribute("value"),
+ L10N.getFormatStr("snapshotsList.itemLabel", 1),
+ "The placeholder item's title label is correct.");
+
+ is($(".snapshot-item-calls", item.target).getAttribute("value"),
+ L10N.getStr("snapshotsList.loadingLabel"),
+ "The placeholder item's calls label is correct.");
+
+ is($(".snapshot-item-save", item.target).getAttribute("value"), "",
+ "The placeholder item's save label should not have a value yet.");
+
+ is($("#reload-notice").getAttribute("hidden"), "true",
+ "The reload notice should now be hidden.");
+ is($("#empty-notice").getAttribute("hidden"), "true",
+ "The empty notice should now be hidden.");
+ is($("#waiting-notice").hasAttribute("hidden"), false,
+ "The waiting notice should now be visible.");
+
+ is($("#screenshot-container").getAttribute("hidden"), "true",
+ "The screenshot container should still be hidden.");
+ is($("#snapshot-filmstrip").getAttribute("hidden"), "true",
+ "The snapshot filmstrip should still be hidden.");
+
+ is($("#debugging-pane-contents").getAttribute("hidden"), "true",
+ "The rest of the UI should still be hidden.");
+
+ yield recordingFinished;
+ ok(true, "Finished recording a snapshot of the animation loop.");
+
+ yield recordingSelected;
+ ok(true, "Finished selecting a snapshot of the animation loop.");
+
+ is($("#reload-notice").getAttribute("hidden"), "true",
+ "The reload notice should now be hidden.");
+ is($("#empty-notice").getAttribute("hidden"), "true",
+ "The empty notice should now be hidden.");
+ is($("#waiting-notice").getAttribute("hidden"), "true",
+ "The waiting notice should now be hidden.");
+
+ is($("#screenshot-container").hasAttribute("hidden"), false,
+ "The screenshot container should now be visible.");
+ is($("#snapshot-filmstrip").hasAttribute("hidden"), false,
+ "The snapshot filmstrip should now be visible.");
+
+ is($("#debugging-pane-contents").hasAttribute("hidden"), false,
+ "The rest of the UI should now be visible.");
+
+ yield teardown(panel);
+ finish();
+}
diff --git a/devtools/client/canvasdebugger/test/browser_canvas-frontend-record-03.js b/devtools/client/canvasdebugger/test/browser_canvas-frontend-record-03.js
new file mode 100644
index 000000000..c3638610e
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/browser_canvas-frontend-record-03.js
@@ -0,0 +1,37 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests whether the frontend displays the correct info for a snapshot
+ * after finishing recording.
+ */
+
+function* ifTestingSupported() {
+ let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
+ let { window, EVENTS, $, SnapshotsListView } = panel.panelWin;
+
+ yield reload(target);
+
+ let recordingFinished = once(window, EVENTS.SNAPSHOT_RECORDING_FINISHED);
+ SnapshotsListView._onRecordButtonClick();
+
+ yield recordingFinished;
+ ok(true, "Finished recording a snapshot of the animation loop.");
+
+ let item = SnapshotsListView.getItemAtIndex(0);
+
+ is(SnapshotsListView.selectedItem, item,
+ "The first item should now be selected in the snapshots list view (1).");
+ is(SnapshotsListView.selectedIndex, 0,
+ "The first item should now be selected in the snapshots list view (2).");
+
+ is($(".snapshot-item-calls", item.target).getAttribute("value"), "4 draws, 8 calls",
+ "The placeholder item's calls label is correct.");
+ is($(".snapshot-item-save", item.target).getAttribute("value"), "Save",
+ "The placeholder item's save label is correct.");
+ is($(".snapshot-item-save", item.target).getAttribute("disabled"), "false",
+ "The placeholder item's save label should be clickable.");
+
+ yield teardown(panel);
+ finish();
+}
diff --git a/devtools/client/canvasdebugger/test/browser_canvas-frontend-record-04.js b/devtools/client/canvasdebugger/test/browser_canvas-frontend-record-04.js
new file mode 100644
index 000000000..fde8501e6
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/browser_canvas-frontend-record-04.js
@@ -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();
+}
diff --git a/devtools/client/canvasdebugger/test/browser_canvas-frontend-reload-01.js b/devtools/client/canvasdebugger/test/browser_canvas-frontend-reload-01.js
new file mode 100644
index 000000000..cf353aa27
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/browser_canvas-frontend-reload-01.js
@@ -0,0 +1,55 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that the frontend UI is properly reconfigured after reloading.
+ */
+
+function* ifTestingSupported() {
+ let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
+ let { window, $, EVENTS } = panel.panelWin;
+
+ let reset = once(window, EVENTS.UI_RESET);
+ let navigated = reload(target);
+
+ yield reset;
+ ok(true, "The UI was reset after the refresh button was clicked.");
+
+ yield navigated;
+ ok(true, "The target finished reloading.");
+
+ is($("#snapshots-pane").hasAttribute("hidden"), false,
+ "The snapshots pane should still be visible.");
+ is($("#debugging-pane").hasAttribute("hidden"), false,
+ "The debugging pane should still be visible.");
+
+ is($("#record-snapshot").hasAttribute("checked"), false,
+ "The 'record snapshot' button should not be checked.");
+ is($("#record-snapshot").hasAttribute("disabled"), false,
+ "The 'record snapshot' button should not be disabled.");
+
+ is($("#record-snapshot").hasAttribute("hidden"), false,
+ "The 'record snapshot' button should now be visible.");
+ is($("#import-snapshot").hasAttribute("hidden"), false,
+ "The 'import snapshot' button should still be visible.");
+ is($("#clear-snapshots").hasAttribute("hidden"), false,
+ "The 'clear snapshots' button should still be visible.");
+
+ is($("#reload-notice").getAttribute("hidden"), "true",
+ "The reload notice should now be hidden.");
+ is($("#empty-notice").hasAttribute("hidden"), false,
+ "The empty notice should now be visible.");
+ is($("#waiting-notice").getAttribute("hidden"), "true",
+ "The waiting notice should now be hidden.");
+
+ is($("#snapshot-filmstrip").getAttribute("hidden"), "true",
+ "The snapshot filmstrip should still be hidden.");
+ is($("#screenshot-container").getAttribute("hidden"), "true",
+ "The screenshot container should still be hidden.");
+
+ is($("#debugging-pane-contents").getAttribute("hidden"), "true",
+ "The rest of the UI should still be hidden.");
+
+ yield teardown(panel);
+ finish();
+}
diff --git a/devtools/client/canvasdebugger/test/browser_canvas-frontend-reload-02.js b/devtools/client/canvasdebugger/test/browser_canvas-frontend-reload-02.js
new file mode 100644
index 000000000..2747fd13f
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/browser_canvas-frontend-reload-02.js
@@ -0,0 +1,70 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that the frontend UI is properly reconfigured after reloading.
+ */
+
+function* ifTestingSupported() {
+ let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
+ let { window, $, $all, EVENTS, SnapshotsListView, CallsListView } = panel.panelWin;
+
+ is(SnapshotsListView.itemCount, 0,
+ "There should be no snapshots initially displayed in the UI.");
+ is(CallsListView.itemCount, 0,
+ "There should be no function calls initially displayed in the UI.");
+
+ is($("#screenshot-container").hidden, true,
+ "The screenshot should not be initially displayed in the UI.");
+ is($("#snapshot-filmstrip").hidden, true,
+ "There should be no thumbnails initially displayed in the UI (1).");
+ is($all(".filmstrip-thumbnail").length, 0,
+ "There should be no thumbnails initially displayed in the UI (2).");
+
+ yield reload(target);
+
+ let recordingFinished = once(window, EVENTS.SNAPSHOT_RECORDING_FINISHED);
+ let callListPopulated = once(window, EVENTS.CALL_LIST_POPULATED);
+ let thumbnailsDisplayed = once(window, EVENTS.THUMBNAILS_DISPLAYED);
+ let screenshotDisplayed = once(window, EVENTS.CALL_SCREENSHOT_DISPLAYED);
+ SnapshotsListView._onRecordButtonClick();
+ yield promise.all([
+ recordingFinished,
+ callListPopulated,
+ thumbnailsDisplayed,
+ screenshotDisplayed
+ ]);
+
+ is(SnapshotsListView.itemCount, 1,
+ "There should be one snapshot displayed in the UI.");
+ is(CallsListView.itemCount, 8,
+ "All the function calls should now be displayed in the UI.");
+
+ is($("#screenshot-container").hidden, false,
+ "The screenshot should now be displayed in the UI.");
+ is($("#snapshot-filmstrip").hidden, false,
+ "All the thumbnails should now be displayed in the UI (1).");
+ is($all(".filmstrip-thumbnail").length, 4,
+ "All the thumbnails should now be displayed in the UI (2).");
+
+ let reset = once(window, EVENTS.UI_RESET);
+ let navigated = reload(target);
+
+ yield reset;
+ ok(true, "The UI was reset after the refresh button was clicked.");
+
+ is(SnapshotsListView.itemCount, 0,
+ "There should be no snapshots displayed in the UI after navigating.");
+ is(CallsListView.itemCount, 0,
+ "There should be no function calls displayed in the UI after navigating.");
+ is($("#snapshot-filmstrip").hidden, true,
+ "There should be no thumbnails displayed in the UI after navigating.");
+ is($("#screenshot-container").hidden, true,
+ "The screenshot should not be displayed in the UI after navigating.");
+
+ yield navigated;
+ ok(true, "The target finished reloading.");
+
+ yield teardown(panel);
+ finish();
+}
diff --git a/devtools/client/canvasdebugger/test/browser_canvas-frontend-slider-01.js b/devtools/client/canvasdebugger/test/browser_canvas-frontend-slider-01.js
new file mode 100644
index 000000000..cdce00bd1
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/browser_canvas-frontend-slider-01.js
@@ -0,0 +1,39 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if the slider in the calls list view works as advertised.
+ */
+
+function* ifTestingSupported() {
+ let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
+ let { window, $, EVENTS, SnapshotsListView, CallsListView } = panel.panelWin;
+
+ yield reload(target);
+
+ let recordingFinished = once(window, EVENTS.SNAPSHOT_RECORDING_FINISHED);
+ let callListPopulated = once(window, EVENTS.CALL_LIST_POPULATED);
+ SnapshotsListView._onRecordButtonClick();
+ yield promise.all([recordingFinished, callListPopulated]);
+
+ is(CallsListView.selectedIndex, -1,
+ "No item in the function calls list should be initially selected.");
+
+ is($("#calls-slider").value, 0,
+ "The slider should be moved all the way to the start.");
+ is($("#calls-slider").min, 0,
+ "The slider minimum value should be 0.");
+ is($("#calls-slider").max, 7,
+ "The slider maximum value should be 7.");
+
+ CallsListView.selectedIndex = 1;
+ is($("#calls-slider").value, 1,
+ "The slider should be changed according to the current selection.");
+
+ $("#calls-slider").value = 2;
+ is(CallsListView.selectedIndex, 2,
+ "The calls selection should be changed according to the current slider value.");
+
+ yield teardown(panel);
+ finish();
+}
diff --git a/devtools/client/canvasdebugger/test/browser_canvas-frontend-slider-02.js b/devtools/client/canvasdebugger/test/browser_canvas-frontend-slider-02.js
new file mode 100644
index 000000000..5074ab206
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/browser_canvas-frontend-slider-02.js
@@ -0,0 +1,97 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if the slider in the calls list view works as advertised.
+ */
+
+function* ifTestingSupported() {
+ let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
+ let { window, $, EVENTS, gFront, SnapshotsListView, CallsListView } = panel.panelWin;
+
+ yield reload(target);
+
+ let recordingFinished = once(window, EVENTS.SNAPSHOT_RECORDING_FINISHED);
+ let callListPopulated = once(window, EVENTS.CALL_LIST_POPULATED);
+ let thumbnailsDisplayed = once(window, EVENTS.THUMBNAILS_DISPLAYED);
+ SnapshotsListView._onRecordButtonClick();
+ yield promise.all([recordingFinished, callListPopulated, thumbnailsDisplayed]);
+
+ let firstSnapshot = SnapshotsListView.getItemAtIndex(0);
+ let firstSnapshotOverview = yield firstSnapshot.attachment.actor.getOverview();
+
+ let thumbnails = firstSnapshotOverview.thumbnails;
+ is(thumbnails.length, 4,
+ "There should be 4 thumbnails cached for the snapshot item.");
+
+ let thumbnailImageElementSet = waitForMozSetImageElement(window);
+ $("#calls-slider").value = 1;
+ let thumbnailPixels = yield thumbnailImageElementSet;
+
+ ok(sameArray(thumbnailPixels, thumbnails[0].pixels),
+ "The screenshot element should have a thumbnail as an immediate background.");
+
+ yield once(window, EVENTS.CALL_SCREENSHOT_DISPLAYED);
+ ok(true, "The full-sized screenshot was displayed for the item at index 1.");
+
+ thumbnailImageElementSet = waitForMozSetImageElement(window);
+ $("#calls-slider").value = 2;
+ thumbnailPixels = yield thumbnailImageElementSet;
+
+ ok(sameArray(thumbnailPixels, thumbnails[1].pixels),
+ "The screenshot element should have a thumbnail as an immediate background.");
+
+ yield once(window, EVENTS.CALL_SCREENSHOT_DISPLAYED);
+ ok(true, "The full-sized screenshot was displayed for the item at index 2.");
+
+ thumbnailImageElementSet = waitForMozSetImageElement(window);
+ $("#calls-slider").value = 7;
+ thumbnailPixels = yield thumbnailImageElementSet;
+
+ ok(sameArray(thumbnailPixels, thumbnails[3].pixels),
+ "The screenshot element should have a thumbnail as an immediate background.");
+
+ yield once(window, EVENTS.CALL_SCREENSHOT_DISPLAYED);
+ ok(true, "The full-sized screenshot was displayed for the item at index 7.");
+
+ thumbnailImageElementSet = waitForMozSetImageElement(window);
+ $("#calls-slider").value = 4;
+ thumbnailPixels = yield thumbnailImageElementSet;
+
+ ok(sameArray(thumbnailPixels, thumbnails[2].pixels),
+ "The screenshot element should have a thumbnail as an immediate background.");
+
+ yield once(window, EVENTS.CALL_SCREENSHOT_DISPLAYED);
+ ok(true, "The full-sized screenshot was displayed for the item at index 4.");
+
+ thumbnailImageElementSet = waitForMozSetImageElement(window);
+ $("#calls-slider").value = 0;
+ thumbnailPixels = yield thumbnailImageElementSet;
+
+ ok(sameArray(thumbnailPixels, thumbnails[0].pixels),
+ "The screenshot element should have a thumbnail as an immediate background.");
+
+ yield once(window, EVENTS.CALL_SCREENSHOT_DISPLAYED);
+ ok(true, "The full-sized screenshot was displayed for the item at index 0.");
+
+ yield teardown(panel);
+ finish();
+}
+
+function waitForMozSetImageElement(panel) {
+ let deferred = promise.defer();
+ panel._onMozSetImageElement = deferred.resolve;
+ return deferred.promise;
+}
+
+function sameArray(a, b) {
+ if (a.length != b.length) {
+ return false;
+ }
+ for (let i = 0; i < a.length; i++) {
+ if (a[i] !== b[i]) {
+ return false;
+ }
+ }
+ return true;
+}
diff --git a/devtools/client/canvasdebugger/test/browser_canvas-frontend-snapshot-select-01.js b/devtools/client/canvasdebugger/test/browser_canvas-frontend-snapshot-select-01.js
new file mode 100644
index 000000000..4dc275282
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/browser_canvas-frontend-snapshot-select-01.js
@@ -0,0 +1,93 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if selecting snapshots in the frontend displays the appropriate data
+ * respective to their recorded animation frame.
+ */
+
+function* ifTestingSupported() {
+ let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
+ let { window, $, EVENTS, SnapshotsListView, CallsListView } = panel.panelWin;
+
+ yield reload(target);
+
+ yield recordAndWaitForFirstSnapshot();
+ info("First snapshot recorded.");
+
+ is(SnapshotsListView.selectedIndex, 0,
+ "A snapshot should be automatically selected after first recording.");
+ is(CallsListView.selectedIndex, -1,
+ "There should be no call item automatically selected in the snapshot.");
+
+ yield recordAndWaitForAnotherSnapshot();
+ info("Second snapshot recorded.");
+
+ is(SnapshotsListView.selectedIndex, 0,
+ "A snapshot should not be automatically selected after another recording.");
+ is(CallsListView.selectedIndex, -1,
+ "There should still be no call item automatically selected in the snapshot.");
+
+ let secondSnapshotTarget = SnapshotsListView.getItemAtIndex(1).target;
+ let snapshotSelected = waitForSnapshotSelection();
+ EventUtils.sendMouseEvent({ type: "mousedown" }, secondSnapshotTarget, window);
+
+ yield snapshotSelected;
+ info("Second snapshot selected.");
+
+ is(SnapshotsListView.selectedIndex, 1,
+ "The second snapshot should now be selected.");
+ is(CallsListView.selectedIndex, -1,
+ "There should still be no call item automatically selected in the snapshot.");
+
+ let firstDrawCallContents = $(".call-item-contents", CallsListView.getItemAtIndex(2).target);
+ let screenshotDisplayed = once(window, EVENTS.CALL_SCREENSHOT_DISPLAYED);
+ EventUtils.sendMouseEvent({ type: "mousedown" }, firstDrawCallContents, window);
+
+ yield screenshotDisplayed;
+ info("First draw call in the second snapshot selected.");
+
+ is(SnapshotsListView.selectedIndex, 1,
+ "The second snapshot should still be selected.");
+ is(CallsListView.selectedIndex, 2,
+ "The first draw call should now be selected in the snapshot.");
+
+ let firstSnapshotTarget = SnapshotsListView.getItemAtIndex(0).target;
+ snapshotSelected = waitForSnapshotSelection();
+ EventUtils.sendMouseEvent({ type: "mousedown" }, firstSnapshotTarget, window);
+
+ yield snapshotSelected;
+ info("First snapshot re-selected.");
+
+ is(SnapshotsListView.selectedIndex, 0,
+ "The first snapshot should now be re-selected.");
+ is(CallsListView.selectedIndex, -1,
+ "There should still be no call item automatically selected in the snapshot.");
+
+ function recordAndWaitForFirstSnapshot() {
+ let recordingFinished = once(window, EVENTS.SNAPSHOT_RECORDING_FINISHED);
+ let snapshotSelected = waitForSnapshotSelection();
+ SnapshotsListView._onRecordButtonClick();
+ return promise.all([recordingFinished, snapshotSelected]);
+ }
+
+ function recordAndWaitForAnotherSnapshot() {
+ let recordingFinished = once(window, EVENTS.SNAPSHOT_RECORDING_FINISHED);
+ SnapshotsListView._onRecordButtonClick();
+ return recordingFinished;
+ }
+
+ function waitForSnapshotSelection() {
+ let callListPopulated = once(window, EVENTS.CALL_LIST_POPULATED);
+ let thumbnailsDisplayed = once(window, EVENTS.THUMBNAILS_DISPLAYED);
+ let screenshotDisplayed = once(window, EVENTS.CALL_SCREENSHOT_DISPLAYED);
+ return promise.all([
+ callListPopulated,
+ thumbnailsDisplayed,
+ screenshotDisplayed
+ ]);
+ }
+
+ yield teardown(panel);
+ finish();
+}
diff --git a/devtools/client/canvasdebugger/test/browser_canvas-frontend-snapshot-select-02.js b/devtools/client/canvasdebugger/test/browser_canvas-frontend-snapshot-select-02.js
new file mode 100644
index 000000000..27a03fb51
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/browser_canvas-frontend-snapshot-select-02.js
@@ -0,0 +1,30 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if selecting snapshots in the frontend displays the appropriate data
+ * respective to their recorded animation frame.
+ */
+
+function* ifTestingSupported() {
+ let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
+ let { window, $, EVENTS, SnapshotsListView, CallsListView } = panel.panelWin;
+
+ yield reload(target);
+
+ SnapshotsListView._onRecordButtonClick();
+ let snapshotTarget = SnapshotsListView.getItemAtIndex(0).target;
+
+ EventUtils.sendMouseEvent({ type: "mousedown" }, snapshotTarget, window);
+ EventUtils.sendMouseEvent({ type: "mousedown" }, snapshotTarget, window);
+ EventUtils.sendMouseEvent({ type: "mousedown" }, snapshotTarget, window);
+
+ ok(true, "clicking in-progress snapshot does not fail");
+
+ let finished = once(window, EVENTS.SNAPSHOT_RECORDING_FINISHED);
+ SnapshotsListView._onRecordButtonClick();
+ yield finished;
+
+ yield teardown(panel);
+ finish();
+}
diff --git a/devtools/client/canvasdebugger/test/browser_canvas-frontend-stepping.js b/devtools/client/canvasdebugger/test/browser_canvas-frontend-stepping.js
new file mode 100644
index 000000000..d76449b91
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/browser_canvas-frontend-stepping.js
@@ -0,0 +1,76 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if the stepping buttons in the call list toolbar work as advertised.
+ */
+
+function* ifTestingSupported() {
+ let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
+ let { window, $, EVENTS, SnapshotsListView, CallsListView } = panel.panelWin;
+
+ yield reload(target);
+
+ let recordingFinished = once(window, EVENTS.SNAPSHOT_RECORDING_FINISHED);
+ let callListPopulated = once(window, EVENTS.CALL_LIST_POPULATED);
+ SnapshotsListView._onRecordButtonClick();
+ yield promise.all([recordingFinished, callListPopulated]);
+
+ checkSteppingButtons(1, 1, 1, 1);
+ is(CallsListView.selectedIndex, -1,
+ "There should be no selected item in the calls list view initially.");
+
+ CallsListView._onResume();
+ checkSteppingButtons(1, 1, 1, 1);
+ is(CallsListView.selectedIndex, 0,
+ "The first draw call should now be selected.");
+
+ CallsListView._onResume();
+ checkSteppingButtons(1, 1, 1, 1);
+ is(CallsListView.selectedIndex, 2,
+ "The second draw call should now be selected.");
+
+ CallsListView._onStepOver();
+ checkSteppingButtons(1, 1, 1, 1);
+ is(CallsListView.selectedIndex, 3,
+ "The next context call should now be selected.");
+
+ CallsListView._onStepOut();
+ checkSteppingButtons(0, 0, 1, 0);
+ is(CallsListView.selectedIndex, 7,
+ "The last context call should now be selected.");
+
+ function checkSteppingButtons(resume, stepOver, stepIn, stepOut) {
+ if (!resume) {
+ is($("#resume").getAttribute("disabled"), "true",
+ "The resume button doesn't have the expected disabled state.");
+ } else {
+ is($("#resume").hasAttribute("disabled"), false,
+ "The resume button doesn't have the expected enabled state.");
+ }
+ if (!stepOver) {
+ is($("#step-over").getAttribute("disabled"), "true",
+ "The stepOver button doesn't have the expected disabled state.");
+ } else {
+ is($("#step-over").hasAttribute("disabled"), false,
+ "The stepOver button doesn't have the expected enabled state.");
+ }
+ if (!stepIn) {
+ is($("#step-in").getAttribute("disabled"), "true",
+ "The stepIn button doesn't have the expected disabled state.");
+ } else {
+ is($("#step-in").hasAttribute("disabled"), false,
+ "The stepIn button doesn't have the expected enabled state.");
+ }
+ if (!stepOut) {
+ is($("#step-out").getAttribute("disabled"), "true",
+ "The stepOut button doesn't have the expected disabled state.");
+ } else {
+ is($("#step-out").hasAttribute("disabled"), false,
+ "The stepOut button doesn't have the expected enabled state.");
+ }
+ }
+
+ yield teardown(panel);
+ finish();
+}
diff --git a/devtools/client/canvasdebugger/test/browser_canvas-frontend-stop-01.js b/devtools/client/canvasdebugger/test/browser_canvas-frontend-stop-01.js
new file mode 100644
index 000000000..3a74e4b44
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/browser_canvas-frontend-stop-01.js
@@ -0,0 +1,36 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that you can stop a recording that does not have a rAF cycle.
+ */
+
+function* ifTestingSupported() {
+ let { target, panel } = yield initCanvasDebuggerFrontend(NO_CANVAS_URL);
+ let { window, EVENTS, $, SnapshotsListView } = panel.panelWin;
+
+ yield reload(target);
+
+ let recordingStarted = once(window, EVENTS.SNAPSHOT_RECORDING_STARTED);
+ SnapshotsListView._onRecordButtonClick();
+
+ yield recordingStarted;
+
+ is($("#empty-notice").hidden, true, "Empty notice not shown");
+ is($("#waiting-notice").hidden, false, "Waiting notice shown");
+
+ let recordingFinished = once(window, EVENTS.SNAPSHOT_RECORDING_FINISHED);
+ let recordingCancelled = once(window, EVENTS.SNAPSHOT_RECORDING_CANCELLED);
+ SnapshotsListView._onRecordButtonClick();
+
+ yield promise.all([recordingFinished, recordingCancelled]);
+
+ ok(true, "Recording stopped and was considered failed.");
+
+ is(SnapshotsListView.itemCount, 0, "No snapshots in the list.");
+ is($("#empty-notice").hidden, false, "Empty notice shown");
+ is($("#waiting-notice").hidden, true, "Waiting notice not shown");
+
+ yield teardown(panel);
+ finish();
+}
diff --git a/devtools/client/canvasdebugger/test/browser_canvas-frontend-stop-02.js b/devtools/client/canvasdebugger/test/browser_canvas-frontend-stop-02.js
new file mode 100644
index 000000000..b062fbc5e
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/browser_canvas-frontend-stop-02.js
@@ -0,0 +1,35 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that a recording that does not have a rAF cycle fails after timeout.
+ */
+
+function* ifTestingSupported() {
+ let { target, panel } = yield initCanvasDebuggerFrontend(NO_CANVAS_URL);
+ let { window, EVENTS, $, SnapshotsListView } = panel.panelWin;
+
+ yield reload(target);
+
+ let recordingStarted = once(window, EVENTS.SNAPSHOT_RECORDING_STARTED);
+ SnapshotsListView._onRecordButtonClick();
+
+ yield recordingStarted;
+
+ is($("#empty-notice").hidden, true, "Empty notice not shown");
+ is($("#waiting-notice").hidden, false, "Waiting notice shown");
+
+ let recordingFinished = once(window, EVENTS.SNAPSHOT_RECORDING_FINISHED);
+ let recordingCancelled = once(window, EVENTS.SNAPSHOT_RECORDING_CANCELLED);
+
+ yield promise.all([recordingFinished, recordingCancelled]);
+
+ ok(true, "Recording stopped and was considered failed.");
+
+ is(SnapshotsListView.itemCount, 0, "No snapshots in the list.");
+ is($("#empty-notice").hidden, false, "Empty notice shown");
+ is($("#waiting-notice").hidden, true, "Waiting notice not shown");
+
+ yield teardown(panel);
+ finish();
+}
diff --git a/devtools/client/canvasdebugger/test/browser_canvas-frontend-stop-03.js b/devtools/client/canvasdebugger/test/browser_canvas-frontend-stop-03.js
new file mode 100644
index 000000000..70948311d
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/browser_canvas-frontend-stop-03.js
@@ -0,0 +1,36 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that a recording that has a rAF cycle, but no draw calls, fails
+ * after timeout.
+ */
+
+function* ifTestingSupported() {
+ let { target, panel } = yield initCanvasDebuggerFrontend(RAF_NO_CANVAS_URL);
+ let { window, EVENTS, $, SnapshotsListView } = panel.panelWin;
+
+ yield reload(target);
+
+ let recordingStarted = once(window, EVENTS.SNAPSHOT_RECORDING_STARTED);
+ SnapshotsListView._onRecordButtonClick();
+
+ yield recordingStarted;
+
+ is($("#empty-notice").hidden, true, "Empty notice not shown");
+ is($("#waiting-notice").hidden, false, "Waiting notice shown");
+
+ let recordingFinished = once(window, EVENTS.SNAPSHOT_RECORDING_FINISHED);
+ let recordingCancelled = once(window, EVENTS.SNAPSHOT_RECORDING_CANCELLED);
+
+ yield promise.all([recordingFinished, recordingCancelled]);
+
+ ok(true, "Recording stopped and was considered failed.");
+
+ is(SnapshotsListView.itemCount, 0, "No snapshots in the list.");
+ is($("#empty-notice").hidden, false, "Empty notice shown");
+ is($("#waiting-notice").hidden, true, "Waiting notice not shown");
+
+ yield teardown(panel);
+ finish();
+}
diff --git a/devtools/client/canvasdebugger/test/browser_profiling-canvas.js b/devtools/client/canvasdebugger/test/browser_profiling-canvas.js
new file mode 100644
index 000000000..ede8a4dbf
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/browser_profiling-canvas.js
@@ -0,0 +1,45 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if functions inside a single animation frame are recorded and stored
+ * for a canvas context profiling.
+ */
+
+function* ifTestingSupported() {
+ let currentTime = window.performance.now();
+ let { target, front } = yield initCanvasDebuggerBackend(SIMPLE_CANVAS_URL);
+
+ let navigated = once(target, "navigate");
+
+ yield front.setup({ reload: true });
+ ok(true, "The front was setup up successfully.");
+
+ yield navigated;
+ ok(true, "Target automatically navigated when the front was set up.");
+
+ let snapshotActor = yield front.recordAnimationFrame();
+ ok(snapshotActor,
+ "A snapshot actor was sent after recording.");
+
+ let animationOverview = yield snapshotActor.getOverview();
+ ok(animationOverview,
+ "An animation overview could be retrieved after recording.");
+
+ let functionCalls = animationOverview.calls;
+ ok(functionCalls,
+ "An array of function call actors was sent after recording.");
+ is(functionCalls.length, 8,
+ "The number of function call actors is correct.");
+
+ info("Check the timestamps of function calls");
+
+ for (let i = 0; i < functionCalls.length - 1; i += 2) {
+ ok(functionCalls[i].timestamp > 0, "The timestamp of the called function is larger than 0.");
+ ok(functionCalls[i].timestamp < currentTime, "The timestamp has been minus the frame start time.");
+ ok(functionCalls[i + 1].timestamp > functionCalls[i].timestamp, "The timestamp of the called function is correct.");
+ }
+
+ yield removeTab(target.tab);
+ finish();
+}
diff --git a/devtools/client/canvasdebugger/test/browser_profiling-webgl.js b/devtools/client/canvasdebugger/test/browser_profiling-webgl.js
new file mode 100644
index 000000000..83009317f
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/browser_profiling-webgl.js
@@ -0,0 +1,91 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if functions inside a single animation frame are recorded and stored
+ * for a canvas context profiling.
+ */
+
+function* ifTestingSupported() {
+ let currentTime = window.performance.now();
+ info("Start to estimate WebGL drawArrays function.");
+ var { target, front } = yield initCanvasDebuggerBackend(WEBGL_DRAW_ARRAYS);
+
+ let navigated = once(target, "navigate");
+
+ yield front.setup({ reload: true });
+ ok(true, "The front was setup up successfully.");
+
+ yield navigated;
+ ok(true, "Target automatically navigated when the front was set up.");
+
+ let snapshotActor = yield front.recordAnimationFrame();
+ ok(snapshotActor,
+ "A snapshot actor was sent after recording.");
+
+ let animationOverview = yield snapshotActor.getOverview();
+ ok(animationOverview,
+ "An animation overview could be retrieved after recording.");
+
+ let functionCalls = animationOverview.calls;
+ ok(functionCalls,
+ "An array of function call actors was sent after recording.");
+
+ testFunctionCallTimestamp(functionCalls, currentTime);
+
+ info("Check triangle and vertex counts in drawArrays()");
+ is(animationOverview.primitive.tris, 5, "The count of triangles is correct.");
+ is(animationOverview.primitive.vertices, 26, "The count of vertices is correct.");
+ is(animationOverview.primitive.points, 4, "The count of points is correct.");
+ is(animationOverview.primitive.lines, 8, "The count of lines is correct.");
+
+ yield removeTab(target.tab);
+
+ info("Start to estimate WebGL drawElements function.");
+ var { target, front } = yield initCanvasDebuggerBackend(WEBGL_DRAW_ELEMENTS);
+
+ navigated = once(target, "navigate");
+
+ yield front.setup({ reload: true });
+ ok(true, "The front was setup up successfully.");
+
+ yield navigated;
+ ok(true, "Target automatically navigated when the front was set up.");
+
+ snapshotActor = yield front.recordAnimationFrame();
+ ok(snapshotActor,
+ "A snapshot actor was sent after recording.");
+
+ animationOverview = yield snapshotActor.getOverview();
+ ok(animationOverview,
+ "An animation overview could be retrieved after recording.");
+
+ functionCalls = animationOverview.calls;
+ ok(functionCalls,
+ "An array of function call actors was sent after recording.");
+
+ testFunctionCallTimestamp(functionCalls, currentTime);
+
+ info("Check triangle and vertex counts in drawElements()");
+ is(animationOverview.primitive.tris, 5, "The count of triangles is correct.");
+ is(animationOverview.primitive.vertices, 26, "The count of vertices is correct.");
+ is(animationOverview.primitive.points, 4, "The count of points is correct.");
+ is(animationOverview.primitive.lines, 8, "The count of lines is correct.");
+
+ yield removeTab(target.tab);
+ finish();
+}
+
+function testFunctionCallTimestamp(functionCalls, currentTime) {
+
+ info("Check the timestamps of function calls");
+
+ for ( let i = 0; i < functionCalls.length-1; i += 2 ) {
+ ok( functionCalls[i].timestamp > 0, "The timestamp of the called function is larger than 0." );
+ ok( functionCalls[i].timestamp < currentTime, "The timestamp has been minus the frame start time." );
+ ok( functionCalls[i+1].timestamp > functionCalls[i].timestamp, "The timestamp of the called function is correct." );
+ }
+
+ yield removeTab(target.tab);
+ finish();
+}
diff --git a/devtools/client/canvasdebugger/test/doc_no-canvas.html b/devtools/client/canvasdebugger/test/doc_no-canvas.html
new file mode 100644
index 000000000..a5934e3e7
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/doc_no-canvas.html
@@ -0,0 +1,14 @@
+<!-- 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>
+ </body>
+
+</html>
diff --git a/devtools/client/canvasdebugger/test/doc_raf-begin.html b/devtools/client/canvasdebugger/test/doc_raf-begin.html
new file mode 100644
index 000000000..8727f8306
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/doc_raf-begin.html
@@ -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>
diff --git a/devtools/client/canvasdebugger/test/doc_raf-no-canvas.html b/devtools/client/canvasdebugger/test/doc_raf-no-canvas.html
new file mode 100644
index 000000000..fa937623c
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/doc_raf-no-canvas.html
@@ -0,0 +1,18 @@
+<!-- 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>
+ <script>
+ function render () { window.requestAnimationFrame(render); }
+ window.requestAnimationFrame(render);
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/canvasdebugger/test/doc_settimeout.html b/devtools/client/canvasdebugger/test/doc_settimeout.html
new file mode 100644
index 000000000..57cfbdab0
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/doc_settimeout.html
@@ -0,0 +1,37 @@
+<!-- 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() {
+ 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]);
+
+ window.setTimeout(drawScene, 50);
+ }
+
+ drawScene();
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/canvasdebugger/test/doc_simple-canvas-bitmasks.html b/devtools/client/canvasdebugger/test/doc_simple-canvas-bitmasks.html
new file mode 100644
index 000000000..bd5f67a6a
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/doc_simple-canvas-bitmasks.html
@@ -0,0 +1,34 @@
+<!-- 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() {
+ ctx.clearRect(0, 0, 4, 4);
+ drawRect("rgb(192, 192, 192)", [0, 0, 1, 1]);
+ window.requestAnimationFrame(drawScene);
+ }
+
+ drawScene();
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/canvasdebugger/test/doc_simple-canvas-deep-stack.html b/devtools/client/canvasdebugger/test/doc_simple-canvas-deep-stack.html
new file mode 100644
index 000000000..f5ecc45d6
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/doc_simple-canvas-deep-stack.html
@@ -0,0 +1,46 @@
+<!-- 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) {
+ function A() {
+ function B() {
+ function C() {
+ ctx.fillStyle = fill;
+ ctx.fillRect(size[0], size[1], size[2], size[3]);
+ }
+ C();
+ }
+ B();
+ }
+ A();
+ }
+
+ function 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]);
+
+ window.requestAnimationFrame(drawScene);
+ }
+
+ drawScene();
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/canvasdebugger/test/doc_simple-canvas-transparent.html b/devtools/client/canvasdebugger/test/doc_simple-canvas-transparent.html
new file mode 100644
index 000000000..f8daf1e24
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/doc_simple-canvas-transparent.html
@@ -0,0 +1,37 @@
+<!-- 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() {
+ ctx.clearRect(0, 0, 128, 128);
+ drawRect("rgba(255, 255, 255, 0)", [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]);
+
+ window.requestAnimationFrame(drawScene);
+ }
+
+ drawScene();
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/canvasdebugger/test/doc_simple-canvas.html b/devtools/client/canvasdebugger/test/doc_simple-canvas.html
new file mode 100644
index 000000000..4fe6b587a
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/doc_simple-canvas.html
@@ -0,0 +1,37 @@
+<!-- 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() {
+ 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]);
+
+ window.requestAnimationFrame(drawScene);
+ }
+
+ drawScene();
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/canvasdebugger/test/doc_webgl-bindings.html b/devtools/client/canvasdebugger/test/doc_webgl-bindings.html
new file mode 100644
index 000000000..eb1405359
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/doc_webgl-bindings.html
@@ -0,0 +1,61 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>WebGL editor test page</title>
+ </head>
+
+ <body>
+ <canvas id="canvas" width="1024" height="1024"></canvas>
+
+ <script type="text/javascript;version=1.8">
+ "use strict";
+
+ let canvas, gl;
+ let customFramebuffer;
+ let customRenderbuffer;
+ let customTexture;
+
+ window.onload = function() {
+ canvas = document.querySelector("canvas");
+ gl = canvas.getContext("webgl", { preserveDrawingBuffer: true });
+ gl.clearColor(1.0, 0.0, 0.0, 1.0);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+
+ customFramebuffer = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, customFramebuffer);
+
+ customRenderbuffer = gl.createRenderbuffer();
+ gl.bindRenderbuffer(gl.RENDERBUFFER, customRenderbuffer);
+ gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, 1024, 1024);
+
+ customTexture = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, customTexture);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1024, 1024, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
+
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, customTexture, 0);
+ gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, customRenderbuffer);
+
+ gl.viewport(128, 256, 384, 512);
+ gl.clearColor(0.0, 1.0, 0.0, 1.0);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+
+ drawScene();
+ }
+
+ function drawScene() {
+ gl.clearColor(0.0, 0.0, 1.0, 1.0);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ window.requestAnimationFrame(drawScene);
+ }
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/canvasdebugger/test/doc_webgl-drawArrays.html b/devtools/client/canvasdebugger/test/doc_webgl-drawArrays.html
new file mode 100644
index 000000000..7a6aea907
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/doc_webgl-drawArrays.html
@@ -0,0 +1,187 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>WebGL editor test page</title>
+ </head>
+
+ <body>
+ <canvas id="canvas" width="128" height="128"></canvas>
+ <script id="shader-fs" type="x-shader/x-fragment">
+ precision mediump float;
+ uniform vec4 mtrColor;
+
+ void main(void) {
+ gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0) * mtrColor;
+ }
+ </script>
+ <script id="shader-vs" type="x-shader/x-vertex">
+ attribute vec3 aVertexPosition;
+
+ void main(void) {
+ gl_PointSize = 5.0;
+ gl_Position = vec4(aVertexPosition, 1.0);
+ }
+ </script>
+ <script type="text/javascript;version=1.8">
+ "use strict";
+
+ let canvas, gl, shaderProgram;
+ let triangleVertexPositionBuffer, squareVertexPositionBuffer;
+
+ window.onload = function() {
+ canvas = document.querySelector("canvas");
+ gl = canvas.getContext("webgl", { preserveDrawingBuffer: true });
+ gl.viewportWidth = canvas.width;
+ gl.viewportHeight = canvas.height;
+
+ initShaders();
+ initBuffers();
+
+ gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
+ gl.clearColor(0.0, 0.0, 0.0, 1.0);
+ gl.disable(gl.DEPTH_TEST);
+ drawScene();
+ }
+
+ function getShader(gl, id) {
+ var shaderScript = document.getElementById(id);
+ if (!shaderScript) {
+ return null;
+ }
+
+ var str = "";
+ var k = shaderScript.firstChild;
+ while (k) {
+ if (k.nodeType == 3) {
+ str += k.textContent;
+ }
+ k = k.nextSibling;
+ }
+
+ var shader;
+ if (shaderScript.type == "x-shader/x-fragment") {
+ shader = gl.createShader(gl.FRAGMENT_SHADER);
+ } else if (shaderScript.type == "x-shader/x-vertex") {
+ shader = gl.createShader(gl.VERTEX_SHADER);
+ } else {
+ return null;
+ }
+
+ gl.shaderSource(shader, str);
+ gl.compileShader(shader);
+
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
+ alert(gl.getShaderInfoLog(shader));
+ return null;
+ }
+
+ return shader;
+ }
+
+ function initShaders() {
+ var fragmentShader = getShader(gl, "shader-fs");
+ var vertexShader = getShader(gl, "shader-vs");
+
+ shaderProgram = gl.createProgram();
+ gl.attachShader(shaderProgram, vertexShader);
+ gl.attachShader(shaderProgram, fragmentShader);
+ gl.linkProgram(shaderProgram);
+
+ if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
+ alert("Could not initialise shaders");
+ }
+
+ gl.useProgram(shaderProgram);
+
+ shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
+ shaderProgram.pMaterialColor = gl.getUniformLocation(shaderProgram, "mtrColor");
+ gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);
+ }
+
+ function initBuffers() {
+ // Create triangle vertex/index buffer
+ triangleVertexPositionBuffer = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer);
+ var vertices = [
+ 0.0, 0.5, 0.0,
+ -0.5, -0.5, 0.0,
+ 0.5, -0.5, 0.0
+ ];
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
+ triangleVertexPositionBuffer.itemSize = 3;
+ triangleVertexPositionBuffer.numItems = 3;
+
+ // Create square vertex/index buffer
+ squareVertexPositionBuffer = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
+ vertices = [
+ 0.8, 0.8, 0.0,
+ -0.8, 0.8, 0.0,
+ 0.8, -0.8, 0.0,
+ -0.8, -0.8, 0.0
+ ];
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
+ squareVertexPositionBuffer.itemSize = 3;
+ squareVertexPositionBuffer.numItems = 4;
+ }
+
+ function drawScene() {
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
+
+ // DrawArrays
+ // --------------
+ // draw square - triangle strip
+ gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
+ gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, squareVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
+ gl.uniform4f(shaderProgram.pMaterialColor, 1, 1, 1, 1);
+ gl.drawArrays(gl.TRIANGLE_STRIP, 0, squareVertexPositionBuffer.numItems);
+
+ // draw square - triangle fan
+ gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
+ gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, squareVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
+ gl.uniform4f(shaderProgram.pMaterialColor, 0, 1, 0, 1);
+ gl.drawArrays(gl.TRIANGLE_FAN, 0, squareVertexPositionBuffer.numItems);
+
+ // draw triangle
+ gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer);
+ gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, triangleVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
+ gl.uniform4f(shaderProgram.pMaterialColor, 1, 0, 0, 1);
+ gl.drawArrays(gl.TRIANGLES, 0, triangleVertexPositionBuffer.numItems);
+
+ // draw points
+ gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
+ gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, squareVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
+ gl.uniform4f(shaderProgram.pMaterialColor, 1, 0, 1, 1);
+ gl.drawArrays(gl.POINTS, 0, squareVertexPositionBuffer.numItems);
+
+ // draw lines
+ gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
+ gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, squareVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
+ gl.uniform4f(shaderProgram.pMaterialColor, 0, 0, 1, 1);
+ gl.lineWidth(8.0);
+ gl.drawArrays(gl.LINES, 0, squareVertexPositionBuffer.numItems);
+
+ // draw line strip
+ gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
+ gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, squareVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
+ gl.uniform4f(shaderProgram.pMaterialColor, 0.9, 0.6, 0, 1);
+ gl.lineWidth(3.0);
+ gl.drawArrays(gl.LINE_STRIP, 0, squareVertexPositionBuffer.numItems);
+
+ // draw line loop
+ gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer);
+ gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, triangleVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
+ gl.uniform4f(shaderProgram.pMaterialColor, 0, 1, 1, 1);
+ gl.lineWidth(3.0);
+ gl.drawArrays(gl.LINE_LOOP, 0, triangleVertexPositionBuffer.numItems);
+
+ window.requestAnimationFrame(drawScene);
+ }
+ </script>
+ </body>
+
+</html> \ No newline at end of file
diff --git a/devtools/client/canvasdebugger/test/doc_webgl-drawElements.html b/devtools/client/canvasdebugger/test/doc_webgl-drawElements.html
new file mode 100644
index 000000000..a8ba4a3e8
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/doc_webgl-drawElements.html
@@ -0,0 +1,225 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>WebGL editor test page</title>
+ </head>
+
+ <body>
+ <canvas id="canvas" width="128" height="128"></canvas>
+ <script id="shader-fs" type="x-shader/x-fragment">
+ precision mediump float;
+ uniform vec4 mtrColor;
+
+ void main(void) {
+ gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0) * mtrColor;
+ }
+ </script>
+ <script id="shader-vs" type="x-shader/x-vertex">
+ attribute vec3 aVertexPosition;
+
+ void main(void) {
+ gl_PointSize = 5.0;
+ gl_Position = vec4(aVertexPosition, 1.0);
+ }
+ </script>
+ <script type="text/javascript;version=1.8">
+ "use strict";
+
+ let canvas, gl, shaderProgram;
+ let triangleVertexPositionBuffer, squareVertexPositionBuffer;
+ let triangleIndexBuffer;
+ let squareIndexBuffer, squareStripIndexBuffer, squareFanIndexBuffer
+
+ window.onload = function() {
+ canvas = document.querySelector("canvas");
+ gl = canvas.getContext("webgl", { preserveDrawingBuffer: true });
+ gl.viewportWidth = canvas.width;
+ gl.viewportHeight = canvas.height;
+
+ initShaders();
+ initBuffers();
+
+ gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
+ gl.clearColor(0.0, 0.0, 0.0, 1.0);
+ gl.disable(gl.DEPTH_TEST);
+ drawScene();
+ }
+
+ function getShader(gl, id) {
+ var shaderScript = document.getElementById(id);
+ if (!shaderScript) {
+ return null;
+ }
+
+ var str = "";
+ var k = shaderScript.firstChild;
+ while (k) {
+ if (k.nodeType == 3) {
+ str += k.textContent;
+ }
+ k = k.nextSibling;
+ }
+
+ var shader;
+ if (shaderScript.type == "x-shader/x-fragment") {
+ shader = gl.createShader(gl.FRAGMENT_SHADER);
+ } else if (shaderScript.type == "x-shader/x-vertex") {
+ shader = gl.createShader(gl.VERTEX_SHADER);
+ } else {
+ return null;
+ }
+
+ gl.shaderSource(shader, str);
+ gl.compileShader(shader);
+
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
+ alert(gl.getShaderInfoLog(shader));
+ return null;
+ }
+
+ return shader;
+ }
+
+ function initShaders() {
+ var fragmentShader = getShader(gl, "shader-fs");
+ var vertexShader = getShader(gl, "shader-vs");
+
+ shaderProgram = gl.createProgram();
+ gl.attachShader(shaderProgram, vertexShader);
+ gl.attachShader(shaderProgram, fragmentShader);
+ gl.linkProgram(shaderProgram);
+
+ if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
+ alert("Could not initialise shaders");
+ }
+
+ gl.useProgram(shaderProgram);
+
+ shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
+ shaderProgram.pMaterialColor = gl.getUniformLocation(shaderProgram, "mtrColor");
+ gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);
+ }
+
+ function initBuffers() {
+ // Create triangle vertex/index buffer
+ triangleVertexPositionBuffer = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer);
+ var vertices = [
+ 0.0, 0.5, 0.0,
+ -0.5, -0.5, 0.0,
+ 0.5, -0.5, 0.0
+ ];
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
+ triangleVertexPositionBuffer.itemSize = 3;
+ triangleVertexPositionBuffer.numItems = 3;
+
+ triangleIndexBuffer = gl.createBuffer();
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, triangleIndexBuffer);
+ var indices = [
+ 0, 1, 2
+ ];
+ gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);
+ triangleIndexBuffer.itemSize = 1;
+ triangleIndexBuffer.numItems = 3;
+
+ // Create square vertex/index buffer
+ squareVertexPositionBuffer = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
+ vertices = [
+ 0.8, 0.8, 0.0,
+ -0.8, 0.8, 0.0,
+ 0.8, -0.8, 0.0,
+ -0.8, -0.8, 0.0
+ ];
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
+ squareVertexPositionBuffer.itemSize = 3;
+ squareVertexPositionBuffer.numItems = 4;
+
+ squareIndexBuffer = gl.createBuffer();
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, squareIndexBuffer);
+ indices = [
+ 0, 1, 2,
+ 1, 3, 2
+ ];
+ gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);
+ squareIndexBuffer.itemSize = 1;
+ squareIndexBuffer.numItems = 6;
+
+ squareStripIndexBuffer = gl.createBuffer();
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, squareStripIndexBuffer);
+ indices = [
+ 0, 1, 2, 3
+ ];
+ gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);
+ squareStripIndexBuffer.itemSize = 1;
+ squareStripIndexBuffer.numItems = 4;
+
+ }
+
+ function drawScene() {
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
+
+ // DrawElements
+ // --------------
+ // draw triangle
+ gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
+ gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, squareVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
+ gl.uniform4f(shaderProgram.pMaterialColor, 1, 1, 1, 1);
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, squareIndexBuffer);
+ gl.drawElements(gl.TRIANGLES, squareIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0);
+
+ // draw square - triangle strip
+ gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
+ gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, squareVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
+ gl.uniform4f(shaderProgram.pMaterialColor, 0, 1, 0, 1);
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, squareStripIndexBuffer);
+ gl.drawElements(gl.TRIANGLE_FAN, squareStripIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0);
+
+ // draw square - triangle fan
+ gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer);
+ gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, triangleVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
+ gl.uniform4f(shaderProgram.pMaterialColor, 1, 0, 0, 1);
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, triangleIndexBuffer);
+ gl.drawElements(gl.TRIANGLE_FAN, triangleIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0);
+
+ // draw points
+ gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
+ gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, squareVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
+ gl.uniform4f(shaderProgram.pMaterialColor, 1, 0, 1, 1);
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, squareStripIndexBuffer);
+ gl.drawElements(gl.POINTS, squareStripIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0);
+
+ // draw lines
+ gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
+ gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, squareVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
+ gl.uniform4f(shaderProgram.pMaterialColor, 0, 0, 1, 1);
+ gl.lineWidth(8.0);
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, squareStripIndexBuffer);
+ gl.drawElements(gl.LINES, squareStripIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0);
+
+ // draw line strip
+ gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
+ gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, squareVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
+ gl.uniform4f(shaderProgram.pMaterialColor, 0.9, 0.6, 0, 1);
+ gl.lineWidth(3.0);
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, squareStripIndexBuffer);
+ gl.drawElements(gl.LINE_STRIP, squareStripIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0);
+
+ // draw line loop
+ gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer);
+ gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, triangleVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
+ gl.uniform4f(shaderProgram.pMaterialColor, 0, 1, 1, 1);
+ gl.lineWidth(3.0);
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, triangleIndexBuffer);
+ gl.drawElements(gl.LINE_LOOP, triangleIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0);
+
+ window.requestAnimationFrame(drawScene);
+ }
+ </script>
+ </body>
+
+</html> \ No newline at end of file
diff --git a/devtools/client/canvasdebugger/test/doc_webgl-enum.html b/devtools/client/canvasdebugger/test/doc_webgl-enum.html
new file mode 100644
index 000000000..f7f4d6d1e
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/doc_webgl-enum.html
@@ -0,0 +1,34 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>WebGL editor test page</title>
+ </head>
+
+ <body>
+ <canvas id="canvas" width="128" height="128"></canvas>
+
+ <script type="text/javascript;version=1.8">
+ "use strict";
+
+ let canvas, gl;
+
+ window.onload = function() {
+ canvas = document.querySelector("canvas");
+ gl = canvas.getContext("webgl", { preserveDrawingBuffer: true });
+ gl.clearColor(0.0, 0.0, 0.0, 1.0);
+ drawScene();
+ }
+
+ function drawScene() {
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
+ gl.bindTexture(gl.TEXTURE_2D, null);
+ window.requestAnimationFrame(drawScene);
+ }
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/canvasdebugger/test/head.js b/devtools/client/canvasdebugger/test/head.js
new file mode 100644
index 000000000..a718551ce
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/head.js
@@ -0,0 +1,305 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+var { generateUUID } = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
+var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+
+var Services = require("Services");
+var promise = require("promise");
+var { gDevTools } = require("devtools/client/framework/devtools");
+var { DebuggerClient } = require("devtools/shared/client/main");
+var { DebuggerServer } = require("devtools/server/main");
+var { CallWatcherFront } = require("devtools/shared/fronts/call-watcher");
+var { CanvasFront } = require("devtools/shared/fronts/canvas");
+var { setTimeout } = require("sdk/timers");
+var DevToolsUtils = require("devtools/shared/DevToolsUtils");
+var flags = require("devtools/shared/flags");
+var { TargetFactory } = require("devtools/client/framework/target");
+var { Toolbox } = require("devtools/client/framework/toolbox");
+var { isWebGLSupported } = require("devtools/client/shared/webgl-utils");
+var mm = null;
+
+const FRAME_SCRIPT_UTILS_URL = "chrome://devtools/content/shared/frame-script-utils.js";
+const EXAMPLE_URL = "http://example.com/browser/devtools/client/canvasdebugger/test/";
+const SET_TIMEOUT_URL = EXAMPLE_URL + "doc_settimeout.html";
+const NO_CANVAS_URL = EXAMPLE_URL + "doc_no-canvas.html";
+const RAF_NO_CANVAS_URL = EXAMPLE_URL + "doc_raf-no-canvas.html";
+const SIMPLE_CANVAS_URL = EXAMPLE_URL + "doc_simple-canvas.html";
+const SIMPLE_BITMASKS_URL = EXAMPLE_URL + "doc_simple-canvas-bitmasks.html";
+const SIMPLE_CANVAS_TRANSPARENT_URL = EXAMPLE_URL + "doc_simple-canvas-transparent.html";
+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 WEBGL_DRAW_ARRAYS = EXAMPLE_URL + "doc_webgl-drawArrays.html";
+const WEBGL_DRAW_ELEMENTS = EXAMPLE_URL + "doc_webgl-drawElements.html";
+const RAF_BEGIN_URL = EXAMPLE_URL + "doc_raf-begin.html";
+
+// Disable logging for all the tests. Both the debugger server and frontend will
+// be affected by this pref.
+var gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log");
+Services.prefs.setBoolPref("devtools.debugger.log", false);
+
+// All tests are asynchronous.
+waitForExplicitFinish();
+
+var gToolEnabled = Services.prefs.getBoolPref("devtools.canvasdebugger.enabled");
+
+flags.testing = true;
+
+registerCleanupFunction(() => {
+ info("finish() was called, cleaning up...");
+ flags.testing = false;
+ Services.prefs.setBoolPref("devtools.debugger.log", gEnableLogging);
+ Services.prefs.setBoolPref("devtools.canvasdebugger.enabled", gToolEnabled);
+
+ // Some of yhese tests use a lot of memory due to GL contexts, so force a GC
+ // to help fragmentation.
+ info("Forcing GC after canvas debugger test.");
+ Cu.forceGC();
+});
+
+/**
+ * Call manually in tests that use frame script utils after initializing
+ * the shader editor. Call after init but before navigating to different pages.
+ */
+function loadFrameScripts() {
+ mm = gBrowser.selectedBrowser.messageManager;
+ mm.loadFrameScript(FRAME_SCRIPT_UTILS_URL, false);
+}
+
+function addTab(aUrl, aWindow) {
+ info("Adding tab: " + aUrl);
+
+ let deferred = promise.defer();
+ let targetWindow = aWindow || window;
+ let targetBrowser = targetWindow.gBrowser;
+
+ targetWindow.focus();
+ let tab = targetBrowser.selectedTab = targetBrowser.addTab(aUrl);
+ let linkedBrowser = tab.linkedBrowser;
+
+ BrowserTestUtils.browserLoaded(linkedBrowser)
+ .then(function () {
+ info("Tab added and finished loading: " + aUrl);
+ deferred.resolve(tab);
+ });
+
+ return deferred.promise;
+}
+
+function removeTab(aTab, aWindow) {
+ info("Removing tab.");
+
+ let deferred = promise.defer();
+ let targetWindow = aWindow || window;
+ let targetBrowser = targetWindow.gBrowser;
+ let tabContainer = targetBrowser.tabContainer;
+
+ tabContainer.addEventListener("TabClose", function onClose(aEvent) {
+ tabContainer.removeEventListener("TabClose", onClose, false);
+ info("Tab removed and finished closing.");
+ deferred.resolve();
+ }, false);
+
+ targetBrowser.removeTab(aTab);
+ return deferred.promise;
+}
+
+function handleError(aError) {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ finish();
+}
+
+var gRequiresWebGL = false;
+
+function ifTestingSupported() {
+ ok(false, "You need to define a 'ifTestingSupported' function.");
+ finish();
+}
+
+function ifTestingUnsupported() {
+ todo(false, "Skipping test because some required functionality isn't supported.");
+ finish();
+}
+
+function test() {
+ let generator = isTestingSupported() ? ifTestingSupported : ifTestingUnsupported;
+ Task.spawn(generator).then(null, handleError);
+}
+
+function createCanvas() {
+ return document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
+}
+
+function isTestingSupported() {
+ if (!gRequiresWebGL) {
+ info("This test does not require WebGL support.");
+ return true;
+ }
+
+ let supported = isWebGLSupported(document);
+
+ info("This test requires WebGL support.");
+ info("Apparently, WebGL is" + (supported ? "" : " not") + " supported.");
+ return supported;
+}
+
+function once(aTarget, aEventName, aUseCapture = false) {
+ info("Waiting for event: '" + aEventName + "' on " + aTarget + ".");
+
+ let deferred = promise.defer();
+
+ for (let [add, remove] of [
+ ["on", "off"], // Use event emitter before DOM events for consistency
+ ["addEventListener", "removeEventListener"],
+ ["addListener", "removeListener"]
+ ]) {
+ if ((add in aTarget) && (remove in aTarget)) {
+ aTarget[add](aEventName, function onEvent(...aArgs) {
+ info("Got event: '" + aEventName + "' on " + aTarget + ".");
+ aTarget[remove](aEventName, onEvent, aUseCapture);
+ deferred.resolve(...aArgs);
+ }, aUseCapture);
+ break;
+ }
+ }
+
+ return deferred.promise;
+}
+
+function waitForTick() {
+ let deferred = promise.defer();
+ executeSoon(deferred.resolve);
+ return deferred.promise;
+}
+
+function navigateInHistory(aTarget, aDirection, aWaitForTargetEvent = "navigate") {
+ executeSoon(() => content.history[aDirection]());
+ return once(aTarget, aWaitForTargetEvent);
+}
+
+function navigate(aTarget, aUrl, aWaitForTargetEvent = "navigate") {
+ executeSoon(() => aTarget.activeTab.navigateTo(aUrl));
+ return once(aTarget, aWaitForTargetEvent);
+}
+
+function reload(aTarget, aWaitForTargetEvent = "navigate") {
+ executeSoon(() => aTarget.activeTab.reload());
+ return once(aTarget, aWaitForTargetEvent);
+}
+
+function initServer() {
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+}
+
+function initCallWatcherBackend(aUrl) {
+ info("Initializing a call watcher front.");
+ initServer();
+
+ return Task.spawn(function* () {
+ let tab = yield addTab(aUrl);
+ let target = TargetFactory.forTab(tab);
+
+ yield target.makeRemote();
+
+ let front = new CallWatcherFront(target.client, target.form);
+ return { target, front };
+ });
+}
+
+function initCanvasDebuggerBackend(aUrl) {
+ info("Initializing a canvas debugger front.");
+ initServer();
+
+ return Task.spawn(function* () {
+ let tab = yield addTab(aUrl);
+ let target = TargetFactory.forTab(tab);
+
+ yield target.makeRemote();
+
+ let front = new CanvasFront(target.client, target.form);
+ return { target, front };
+ });
+}
+
+function initCanvasDebuggerFrontend(aUrl) {
+ info("Initializing a canvas debugger pane.");
+
+ return Task.spawn(function* () {
+ let tab = yield addTab(aUrl);
+ let target = TargetFactory.forTab(tab);
+
+ yield target.makeRemote();
+
+ Services.prefs.setBoolPref("devtools.canvasdebugger.enabled", true);
+ let toolbox = yield gDevTools.showToolbox(target, "canvasdebugger");
+ let panel = toolbox.getCurrentPanel();
+ return { target, panel };
+ });
+}
+
+function teardown({target}) {
+ info("Destroying the specified canvas debugger.");
+
+ let {tab} = target;
+ return gDevTools.closeToolbox(target).then(() => {
+ removeTab(tab);
+ });
+}
+
+/**
+ * Takes a string `script` and evaluates it directly in the content
+ * in potentially a different process.
+ */
+function evalInDebuggee(script) {
+ let deferred = promise.defer();
+
+ if (!mm) {
+ throw new Error("`loadFrameScripts()` must be called when using MessageManager.");
+ }
+
+ let id = generateUUID().toString();
+ mm.sendAsyncMessage("devtools:test:eval", { script: script, id: id });
+ mm.addMessageListener("devtools:test:eval:response", handler);
+
+ function handler({ data }) {
+ if (id !== data.id) {
+ return;
+ }
+
+ mm.removeMessageListener("devtools:test:eval:response", handler);
+ deferred.resolve(data.value);
+ }
+
+ return deferred.promise;
+}
+
+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;
+}