import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo

This commit is contained in:
Roy Tam 2018-01-19 03:59:58 +08:00
commit dcd9973243
150858 changed files with 23884658 additions and 0 deletions

View file

@ -0,0 +1,526 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
/* import-globals-from canvasdebugger.js */
/* globals window, document */
"use strict";
/**
* Functions handling details about a single recorded animation frame snapshot
* (the calls list, rendering preview, thumbnails filmstrip etc.).
*/
var CallsListView = Heritage.extend(WidgetMethods, {
/**
* Initialization function, called when the tool is started.
*/
initialize: function () {
this.widget = new SideMenuWidget($("#calls-list"));
this._slider = $("#calls-slider");
this._searchbox = $("#calls-searchbox");
this._filmstrip = $("#snapshot-filmstrip");
this._onSelect = this._onSelect.bind(this);
this._onSlideMouseDown = this._onSlideMouseDown.bind(this);
this._onSlideMouseUp = this._onSlideMouseUp.bind(this);
this._onSlide = this._onSlide.bind(this);
this._onSearch = this._onSearch.bind(this);
this._onScroll = this._onScroll.bind(this);
this._onExpand = this._onExpand.bind(this);
this._onStackFileClick = this._onStackFileClick.bind(this);
this._onThumbnailClick = this._onThumbnailClick.bind(this);
this.widget.addEventListener("select", this._onSelect, false);
this._slider.addEventListener("mousedown", this._onSlideMouseDown, false);
this._slider.addEventListener("mouseup", this._onSlideMouseUp, false);
this._slider.addEventListener("change", this._onSlide, false);
this._searchbox.addEventListener("input", this._onSearch, false);
this._filmstrip.addEventListener("wheel", this._onScroll, false);
},
/**
* Destruction function, called when the tool is closed.
*/
destroy: function () {
this.widget.removeEventListener("select", this._onSelect, false);
this._slider.removeEventListener("mousedown", this._onSlideMouseDown, false);
this._slider.removeEventListener("mouseup", this._onSlideMouseUp, false);
this._slider.removeEventListener("change", this._onSlide, false);
this._searchbox.removeEventListener("input", this._onSearch, false);
this._filmstrip.removeEventListener("wheel", this._onScroll, false);
},
/**
* Populates this container with a list of function calls.
*
* @param array functionCalls
* A list of function call actors received from the backend.
*/
showCalls: function (functionCalls) {
this.empty();
for (let i = 0, len = functionCalls.length; i < len; i++) {
let call = functionCalls[i];
let view = document.createElement("vbox");
view.className = "call-item-view devtools-monospace";
view.setAttribute("flex", "1");
let contents = document.createElement("hbox");
contents.className = "call-item-contents";
contents.setAttribute("align", "center");
contents.addEventListener("dblclick", this._onExpand);
view.appendChild(contents);
let index = document.createElement("label");
index.className = "plain call-item-index";
index.setAttribute("flex", "1");
index.setAttribute("value", i + 1);
let gutter = document.createElement("hbox");
gutter.className = "call-item-gutter";
gutter.appendChild(index);
contents.appendChild(gutter);
if (call.callerPreview) {
let context = document.createElement("label");
context.className = "plain call-item-context";
context.setAttribute("value", call.callerPreview);
contents.appendChild(context);
let separator = document.createElement("label");
separator.className = "plain call-item-separator";
separator.setAttribute("value", ".");
contents.appendChild(separator);
}
let name = document.createElement("label");
name.className = "plain call-item-name";
name.setAttribute("value", call.name);
contents.appendChild(name);
let argsPreview = document.createElement("label");
argsPreview.className = "plain call-item-args";
argsPreview.setAttribute("crop", "end");
argsPreview.setAttribute("flex", "100");
// Getters and setters are displayed differently from regular methods.
if (call.type == CallWatcherFront.METHOD_FUNCTION) {
argsPreview.setAttribute("value", "(" + call.argsPreview + ")");
} else {
argsPreview.setAttribute("value", " = " + call.argsPreview);
}
contents.appendChild(argsPreview);
let location = document.createElement("label");
location.className = "plain call-item-location";
location.setAttribute("value", getFileName(call.file) + ":" + call.line);
location.setAttribute("crop", "start");
location.setAttribute("flex", "1");
location.addEventListener("mousedown", this._onExpand);
contents.appendChild(location);
// Append a function call item to this container.
this.push([view], {
staged: true,
attachment: {
actor: call
}
});
// Highlight certain calls that are probably more interesting than
// everything else, making it easier to quickly glance over them.
if (CanvasFront.DRAW_CALLS.has(call.name)) {
view.setAttribute("draw-call", "");
}
if (CanvasFront.INTERESTING_CALLS.has(call.name)) {
view.setAttribute("interesting-call", "");
}
}
// Flushes all the prepared function call items into this container.
this.commit();
window.emit(EVENTS.CALL_LIST_POPULATED);
// Resetting the function selection slider's value (shown in this
// container's toolbar) would trigger a selection event, which should be
// ignored in this case.
this._ignoreSliderChanges = true;
this._slider.value = 0;
this._slider.max = functionCalls.length - 1;
this._ignoreSliderChanges = false;
},
/**
* Displays an image in the rendering preview of this container, generated
* for the specified draw call in the recorded animation frame snapshot.
*
* @param array screenshot
* A single "snapshot-image" instance received from the backend.
*/
showScreenshot: function (screenshot) {
let { index, width, height, scaling, flipped, pixels } = screenshot;
let screenshotNode = $("#screenshot-image");
screenshotNode.setAttribute("flipped", flipped);
drawBackground("screenshot-rendering", width, height, pixels);
let dimensionsNode = $("#screenshot-dimensions");
let actualWidth = (width / scaling) | 0;
let actualHeight = (height / scaling) | 0;
dimensionsNode.setAttribute("value",
SHARED_L10N.getFormatStr("dimensions", actualWidth, actualHeight));
window.emit(EVENTS.CALL_SCREENSHOT_DISPLAYED);
},
/**
* Populates this container's footer with a list of thumbnails, one generated
* for each draw call in the recorded animation frame snapshot.
*
* @param array thumbnails
* An array of "snapshot-image" instances received from the backend.
*/
showThumbnails: function (thumbnails) {
while (this._filmstrip.hasChildNodes()) {
this._filmstrip.firstChild.remove();
}
for (let thumbnail of thumbnails) {
this.appendThumbnail(thumbnail);
}
window.emit(EVENTS.THUMBNAILS_DISPLAYED);
},
/**
* Displays an image in the thumbnails list of this container, generated
* for the specified draw call in the recorded animation frame snapshot.
*
* @param array thumbnail
* A single "snapshot-image" instance received from the backend.
*/
appendThumbnail: function (thumbnail) {
let { index, width, height, flipped, pixels } = thumbnail;
let thumbnailNode = document.createElementNS(HTML_NS, "canvas");
thumbnailNode.setAttribute("flipped", flipped);
thumbnailNode.width = Math.max(CanvasFront.THUMBNAIL_SIZE, width);
thumbnailNode.height = Math.max(CanvasFront.THUMBNAIL_SIZE, height);
drawImage(thumbnailNode, width, height, pixels, { centered: true });
thumbnailNode.className = "filmstrip-thumbnail";
thumbnailNode.onmousedown = e => this._onThumbnailClick(e, index);
thumbnailNode.setAttribute("index", index);
this._filmstrip.appendChild(thumbnailNode);
},
/**
* Sets the currently highlighted thumbnail in this container.
* A screenshot will always correlate to a thumbnail in the filmstrip,
* both being identified by the same 'index' of the context function call.
*
* @param number index
* The context function call's index.
*/
set highlightedThumbnail(index) {
let currHighlightedThumbnail = $(".filmstrip-thumbnail[index='" + index + "']");
if (currHighlightedThumbnail == null) {
return;
}
let prevIndex = this._highlightedThumbnailIndex;
let prevHighlightedThumbnail = $(".filmstrip-thumbnail[index='" + prevIndex + "']");
if (prevHighlightedThumbnail) {
prevHighlightedThumbnail.removeAttribute("highlighted");
}
currHighlightedThumbnail.setAttribute("highlighted", "");
currHighlightedThumbnail.scrollIntoView();
this._highlightedThumbnailIndex = index;
},
/**
* Gets the currently highlighted thumbnail in this container.
* @return number
*/
get highlightedThumbnail() {
return this._highlightedThumbnailIndex;
},
/**
* The select listener for this container.
*/
_onSelect: function ({ detail: callItem }) {
if (!callItem) {
return;
}
// Some of the stepping buttons don't make sense specifically while the
// last function call is selected.
if (this.selectedIndex == this.itemCount - 1) {
$("#resume").setAttribute("disabled", "true");
$("#step-over").setAttribute("disabled", "true");
$("#step-out").setAttribute("disabled", "true");
} else {
$("#resume").removeAttribute("disabled");
$("#step-over").removeAttribute("disabled");
$("#step-out").removeAttribute("disabled");
}
// Correlate the currently selected item with the function selection
// slider's value. Avoid triggering a redundant selection event.
this._ignoreSliderChanges = true;
this._slider.value = this.selectedIndex;
this._ignoreSliderChanges = false;
// Can't generate screenshots for function call actors loaded from disk.
// XXX: Bug 984844.
if (callItem.attachment.actor.isLoadedFromDisk) {
return;
}
// To keep continuous selection buttery smooth (for example, while pressing
// the DOWN key or moving the slider), only display the screenshot after
// any kind of user input stops.
setConditionalTimeout("screenshot-display", SCREENSHOT_DISPLAY_DELAY, () => {
return !this._isSliding;
}, () => {
let frameSnapshot = SnapshotsListView.selectedItem.attachment.actor;
let functionCall = callItem.attachment.actor;
frameSnapshot.generateScreenshotFor(functionCall).then(screenshot => {
this.showScreenshot(screenshot);
this.highlightedThumbnail = screenshot.index;
}).catch(e => console.error(e));
});
},
/**
* The mousedown listener for the call selection slider.
*/
_onSlideMouseDown: function () {
this._isSliding = true;
},
/**
* The mouseup listener for the call selection slider.
*/
_onSlideMouseUp: function () {
this._isSliding = false;
},
/**
* The change listener for the call selection slider.
*/
_onSlide: function () {
// Avoid performing any operations when programatically changing the value.
if (this._ignoreSliderChanges) {
return;
}
let selectedFunctionCallIndex = this.selectedIndex = this._slider.value;
// While sliding, immediately show the most relevant thumbnail for a
// function call, for a nice diff-like animation effect between draws.
let thumbnails = SnapshotsListView.selectedItem.attachment.thumbnails;
let thumbnail = getThumbnailForCall(thumbnails, selectedFunctionCallIndex);
// Avoid drawing and highlighting if the selected function call has the
// same thumbnail as the last one.
if (thumbnail.index == this.highlightedThumbnail) {
return;
}
// If a thumbnail wasn't found (e.g. the backend avoids creating thumbnails
// when rendering offscreen), simply defer to the first available one.
if (thumbnail.index == -1) {
thumbnail = thumbnails[0];
}
let { index, width, height, flipped, pixels } = thumbnail;
this.highlightedThumbnail = index;
let screenshotNode = $("#screenshot-image");
screenshotNode.setAttribute("flipped", flipped);
drawBackground("screenshot-rendering", width, height, pixels);
},
/**
* The input listener for the calls searchbox.
*/
_onSearch: function (e) {
let lowerCaseSearchToken = this._searchbox.value.toLowerCase();
this.filterContents(e => {
let call = e.attachment.actor;
let name = call.name.toLowerCase();
let file = call.file.toLowerCase();
let line = call.line.toString().toLowerCase();
let args = call.argsPreview.toLowerCase();
return name.includes(lowerCaseSearchToken) ||
file.includes(lowerCaseSearchToken) ||
line.includes(lowerCaseSearchToken) ||
args.includes(lowerCaseSearchToken);
});
},
/**
* The wheel listener for the filmstrip that contains all the thumbnails.
*/
_onScroll: function (e) {
this._filmstrip.scrollLeft += e.deltaX;
},
/**
* The click/dblclick listener for an item or location url in this container.
* When expanding an item, it's corresponding call stack will be displayed.
*/
_onExpand: function (e) {
let callItem = this.getItemForElement(e.target);
let view = $(".call-item-view", callItem.target);
// If the call stack nodes were already created, simply re-show them
// or jump to the corresponding file and line in the Debugger if a
// location link was clicked.
if (view.hasAttribute("call-stack-populated")) {
let isExpanded = view.getAttribute("call-stack-expanded") == "true";
// If clicking on the location, jump to the Debugger.
if (e.target.classList.contains("call-item-location")) {
let { file, line } = callItem.attachment.actor;
this._viewSourceInDebugger(file, line);
return;
}
// Otherwise hide the call stack.
else {
view.setAttribute("call-stack-expanded", !isExpanded);
$(".call-item-stack", view).hidden = isExpanded;
return;
}
}
let list = document.createElement("vbox");
list.className = "call-item-stack";
view.setAttribute("call-stack-populated", "");
view.setAttribute("call-stack-expanded", "true");
view.appendChild(list);
/**
* Creates a function call nodes in this container for a stack.
*/
let display = stack => {
for (let i = 1; i < stack.length; i++) {
let call = stack[i];
let contents = document.createElement("hbox");
contents.className = "call-item-stack-fn";
contents.style.paddingInlineStart = (i * STACK_FUNC_INDENTATION) + "px";
let name = document.createElement("label");
name.className = "plain call-item-stack-fn-name";
name.setAttribute("value", "↳ " + call.name + "()");
contents.appendChild(name);
let spacer = document.createElement("spacer");
spacer.setAttribute("flex", "100");
contents.appendChild(spacer);
let location = document.createElement("label");
location.className = "plain call-item-stack-fn-location";
location.setAttribute("value", getFileName(call.file) + ":" + call.line);
location.setAttribute("crop", "start");
location.setAttribute("flex", "1");
location.addEventListener("mousedown", e => this._onStackFileClick(e, call));
contents.appendChild(location);
list.appendChild(contents);
}
window.emit(EVENTS.CALL_STACK_DISPLAYED);
};
// If this animation snapshot is loaded from disk, there are no corresponding
// backend actors available and the data is immediately available.
let functionCall = callItem.attachment.actor;
if (functionCall.isLoadedFromDisk) {
display(functionCall.stack);
}
// ..otherwise we need to request the function call stack from the backend.
else {
callItem.attachment.actor.getDetails().then(fn => display(fn.stack));
}
},
/**
* The click listener for a location link in the call stack.
*
* @param string file
* The url of the source owning the function.
* @param number line
* The line of the respective function.
*/
_onStackFileClick: function (e, { file, line }) {
this._viewSourceInDebugger(file, line);
},
/**
* The click listener for a thumbnail in the filmstrip.
*
* @param number index
* The function index in the recorded animation frame snapshot.
*/
_onThumbnailClick: function (e, index) {
this.selectedIndex = index;
},
/**
* The click listener for the "resume" button in this container's toolbar.
*/
_onResume: function () {
// Jump to the next draw call in the recorded animation frame snapshot.
let drawCall = getNextDrawCall(this.items, this.selectedItem);
if (drawCall) {
this.selectedItem = drawCall;
return;
}
// If there are no more draw calls, just jump to the last context call.
this._onStepOut();
},
/**
* The click listener for the "step over" button in this container's toolbar.
*/
_onStepOver: function () {
this.selectedIndex++;
},
/**
* The click listener for the "step in" button in this container's toolbar.
*/
_onStepIn: function () {
if (this.selectedIndex == -1) {
this._onResume();
return;
}
let callItem = this.selectedItem;
let { file, line } = callItem.attachment.actor;
this._viewSourceInDebugger(file, line);
},
/**
* The click listener for the "step out" button in this container's toolbar.
*/
_onStepOut: function () {
this.selectedIndex = this.itemCount - 1;
},
/**
* Opens the specified file and line in the debugger. Falls back to Firefox's View Source.
*/
_viewSourceInDebugger: function (file, line) {
gToolbox.viewSourceInDebugger(file, line).then(success => {
if (success) {
window.emit(EVENTS.SOURCE_SHOWN_IN_JS_DEBUGGER);
} else {
window.emit(EVENTS.SOURCE_NOT_FOUND_IN_JS_DEBUGGER);
}
});
}
});

View file

@ -0,0 +1,341 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
const { SideMenuWidget } = require("resource://devtools/client/shared/widgets/SideMenuWidget.jsm");
const promise = require("promise");
const Services = require("Services");
const EventEmitter = require("devtools/shared/event-emitter");
const { CallWatcherFront } = require("devtools/shared/fronts/call-watcher");
const { CanvasFront } = require("devtools/shared/fronts/canvas");
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
const flags = require("devtools/shared/flags");
const { LocalizationHelper } = require("devtools/shared/l10n");
const { PluralForm } = require("devtools/shared/plural-form");
const { Heritage, WidgetMethods, setNamedTimeout, clearNamedTimeout,
setConditionalTimeout } = require("devtools/client/shared/widgets/view-helpers");
const CANVAS_ACTOR_RECORDING_ATTEMPT = flags.testing ? 500 : 5000;
const { Task } = require("devtools/shared/task");
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
"resource://gre/modules/FileUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
"resource://gre/modules/NetUtil.jsm");
XPCOMUtils.defineLazyGetter(this, "NetworkHelper", function () {
return require("devtools/shared/webconsole/network-helper");
});
// The panel's window global is an EventEmitter firing the following events:
const EVENTS = {
// When the UI is reset from tab navigation.
UI_RESET: "CanvasDebugger:UIReset",
// When all the animation frame snapshots are removed by the user.
SNAPSHOTS_LIST_CLEARED: "CanvasDebugger:SnapshotsListCleared",
// When an animation frame snapshot starts/finishes being recorded, and
// whether it was completed succesfully or cancelled.
SNAPSHOT_RECORDING_STARTED: "CanvasDebugger:SnapshotRecordingStarted",
SNAPSHOT_RECORDING_FINISHED: "CanvasDebugger:SnapshotRecordingFinished",
SNAPSHOT_RECORDING_COMPLETED: "CanvasDebugger:SnapshotRecordingCompleted",
SNAPSHOT_RECORDING_CANCELLED: "CanvasDebugger:SnapshotRecordingCancelled",
// When an animation frame snapshot was selected and all its data displayed.
SNAPSHOT_RECORDING_SELECTED: "CanvasDebugger:SnapshotRecordingSelected",
// After all the function calls associated with an animation frame snapshot
// are displayed in the UI.
CALL_LIST_POPULATED: "CanvasDebugger:CallListPopulated",
// After the stack associated with a call in an animation frame snapshot
// is displayed in the UI.
CALL_STACK_DISPLAYED: "CanvasDebugger:CallStackDisplayed",
// After a screenshot associated with a call in an animation frame snapshot
// is displayed in the UI.
CALL_SCREENSHOT_DISPLAYED: "CanvasDebugger:ScreenshotDisplayed",
// After all the thumbnails associated with an animation frame snapshot
// are displayed in the UI.
THUMBNAILS_DISPLAYED: "CanvasDebugger:ThumbnailsDisplayed",
// When a source is shown in the JavaScript Debugger at a specific location.
SOURCE_SHOWN_IN_JS_DEBUGGER: "CanvasDebugger:SourceShownInJsDebugger",
SOURCE_NOT_FOUND_IN_JS_DEBUGGER: "CanvasDebugger:SourceNotFoundInJsDebugger"
};
XPCOMUtils.defineConstant(this, "EVENTS", EVENTS);
const HTML_NS = "http://www.w3.org/1999/xhtml";
const STRINGS_URI = "devtools/client/locales/canvasdebugger.properties";
const SHARED_STRINGS_URI = "devtools/client/locales/shared.properties";
const SNAPSHOT_START_RECORDING_DELAY = 10; // ms
const SNAPSHOT_DATA_EXPORT_MAX_BLOCK = 1000; // ms
const SNAPSHOT_DATA_DISPLAY_DELAY = 10; // ms
const SCREENSHOT_DISPLAY_DELAY = 100; // ms
const STACK_FUNC_INDENTATION = 14; // px
// This identifier string is simply used to tentatively ascertain whether or not
// a JSON loaded from disk is actually something generated by this tool or not.
// It isn't, of course, a definitive verification, but a Good Enough™
// approximation before continuing the import. Don't localize this.
const CALLS_LIST_SERIALIZER_IDENTIFIER = "Recorded Animation Frame Snapshot";
const CALLS_LIST_SERIALIZER_VERSION = 1;
const CALLS_LIST_SLOW_SAVE_DELAY = 100; // ms
/**
* The current target and the Canvas front, set by this tool's host.
*/
var gToolbox, gTarget, gFront;
/**
* Initializes the canvas debugger controller and views.
*/
function startupCanvasDebugger() {
return promise.all([
EventsHandler.initialize(),
SnapshotsListView.initialize(),
CallsListView.initialize()
]);
}
/**
* Destroys the canvas debugger controller and views.
*/
function shutdownCanvasDebugger() {
return promise.all([
EventsHandler.destroy(),
SnapshotsListView.destroy(),
CallsListView.destroy()
]);
}
/**
* Functions handling target-related lifetime events.
*/
var EventsHandler = {
/**
* Listen for events emitted by the current tab target.
*/
initialize: function () {
// Make sure the backend is prepared to handle <canvas> contexts.
// Since actors are created lazily on the first request to them, we need to send an
// early request to ensure the CallWatcherActor is running and watching for new window
// globals.
gFront.setup({ reload: false });
this._onTabNavigated = this._onTabNavigated.bind(this);
gTarget.on("will-navigate", this._onTabNavigated);
gTarget.on("navigate", this._onTabNavigated);
},
/**
* Remove events emitted by the current tab target.
*/
destroy: function () {
gTarget.off("will-navigate", this._onTabNavigated);
gTarget.off("navigate", this._onTabNavigated);
},
/**
* Called for each location change in the debugged tab.
*/
_onTabNavigated: function (event) {
if (event != "will-navigate") {
return;
}
// Reset UI.
SnapshotsListView.empty();
CallsListView.empty();
$("#record-snapshot").removeAttribute("checked");
$("#record-snapshot").removeAttribute("disabled");
$("#record-snapshot").hidden = false;
$("#reload-notice").hidden = true;
$("#empty-notice").hidden = false;
$("#waiting-notice").hidden = true;
$("#debugging-pane-contents").hidden = true;
$("#screenshot-container").hidden = true;
$("#snapshot-filmstrip").hidden = true;
window.emit(EVENTS.UI_RESET);
}
};
/**
* Localization convenience methods.
*/
var L10N = new LocalizationHelper(STRINGS_URI);
var SHARED_L10N = new LocalizationHelper(SHARED_STRINGS_URI);
/**
* Convenient way of emitting events from the panel window.
*/
EventEmitter.decorate(this);
/**
* DOM query helpers.
*/
var $ = (selector, target = document) => target.querySelector(selector);
var $all = (selector, target = document) => target.querySelectorAll(selector);
/**
* Gets the fileName part of a string which happens to be an URL.
*/
function getFileName(url) {
try {
let { fileName } = NetworkHelper.nsIURL(url);
return fileName || "/";
} catch (e) {
// This doesn't look like a url, or nsIURL can't handle it.
return "";
}
}
/**
* Gets an image data object containing a buffer large enough to hold
* width * height pixels.
*
* This method avoids allocating memory and tries to reuse a common buffer
* as much as possible.
*
* @param number w
* The desired image data storage width.
* @param number h
* The desired image data storage height.
* @return ImageData
* The requested image data buffer.
*/
function getImageDataStorage(ctx, w, h) {
let storage = getImageDataStorage.cache;
if (storage && storage.width == w && storage.height == h) {
return storage;
}
return getImageDataStorage.cache = ctx.createImageData(w, h);
}
// The cache used in the `getImageDataStorage` function.
getImageDataStorage.cache = null;
/**
* Draws image data into a canvas.
*
* This method makes absolutely no assumptions about the canvas element
* dimensions, or pre-existing rendering. It's a dumb proxy that copies pixels.
*
* @param HTMLCanvasElement canvas
* The canvas element to put the image data into.
* @param number width
* The image data width.
* @param number height
* The image data height.
* @param array pixels
* An array buffer view of the image data.
* @param object options
* Additional options supported by this operation:
* - centered: specifies whether the image data should be centered
* when copied in the canvas; this is useful when the
* supplied pixels don't completely cover the canvas.
*/
function drawImage(canvas, width, height, pixels, options = {}) {
let ctx = canvas.getContext("2d");
// FrameSnapshot actors return "snapshot-image" type instances with just an
// empty pixel array if the source image is completely transparent.
if (pixels.length <= 1) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
return;
}
let imageData = getImageDataStorage(ctx, width, height);
imageData.data.set(pixels);
if (options.centered) {
let left = (canvas.width - width) / 2;
let top = (canvas.height - height) / 2;
ctx.putImageData(imageData, left, top);
} else {
ctx.putImageData(imageData, 0, 0);
}
}
/**
* Draws image data into a canvas, and sets that as the rendering source for
* an element with the specified id as the -moz-element background image.
*
* @param string id
* The id of the -moz-element background image.
* @param number width
* The image data width.
* @param number height
* The image data height.
* @param array pixels
* An array buffer view of the image data.
*/
function drawBackground(id, width, height, pixels) {
let canvas = document.createElementNS(HTML_NS, "canvas");
canvas.width = width;
canvas.height = height;
drawImage(canvas, width, height, pixels);
document.mozSetImageElement(id, canvas);
// Used in tests. Not emitting an event because this shouldn't be "interesting".
if (window._onMozSetImageElement) {
window._onMozSetImageElement(pixels);
}
}
/**
* Iterates forward to find the next draw call in a snapshot.
*/
function getNextDrawCall(calls, call) {
for (let i = calls.indexOf(call) + 1, len = calls.length; i < len; i++) {
let nextCall = calls[i];
let name = nextCall.attachment.actor.name;
if (CanvasFront.DRAW_CALLS.has(name)) {
return nextCall;
}
}
return null;
}
/**
* Iterates backwards to find the most recent screenshot for a function call
* in a snapshot loaded from disk.
*/
function getScreenshotFromCallLoadedFromDisk(calls, call) {
for (let i = calls.indexOf(call); i >= 0; i--) {
let prevCall = calls[i];
let screenshot = prevCall.screenshot;
if (screenshot) {
return screenshot;
}
}
return CanvasFront.INVALID_SNAPSHOT_IMAGE;
}
/**
* Iterates backwards to find the most recent thumbnail for a function call.
*/
function getThumbnailForCall(thumbnails, index) {
for (let i = thumbnails.length - 1; i >= 0; i--) {
let thumbnail = thumbnails[i];
if (thumbnail.index <= index) {
return thumbnail;
}
}
return CanvasFront.INVALID_SNAPSHOT_IMAGE;
}

View file

@ -0,0 +1,135 @@
<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://devtools/content/shared/widgets/widgets.css" type="text/css"?>
<?xml-stylesheet href="chrome://devtools/skin/widgets.css" type="text/css"?>
<?xml-stylesheet href="chrome://devtools/skin/canvasdebugger.css" type="text/css"?>
<!DOCTYPE window [
<!ENTITY % canvasDebuggerDTD SYSTEM "chrome://devtools/locale/canvasdebugger.dtd">
%canvasDebuggerDTD;
]>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script src="chrome://devtools/content/shared/theme-switching.js"/>
<script type="application/javascript" src="canvasdebugger.js"/>
<script type="application/javascript" src="callslist.js"/>
<script type="application/javascript" src="snapshotslist.js"/>
<hbox class="theme-body" flex="1">
<vbox id="snapshots-pane">
<toolbar id="snapshots-toolbar"
class="devtools-toolbar">
<hbox id="snapshots-controls">
<toolbarbutton id="clear-snapshots"
class="devtools-toolbarbutton devtools-clear-icon"
oncommand="SnapshotsListView._onClearButtonClick()"
tooltiptext="&canvasDebuggerUI.clearSnapshots;"/>
<toolbarbutton id="record-snapshot"
class="devtools-toolbarbutton"
oncommand="SnapshotsListView._onRecordButtonClick()"
tooltiptext="&canvasDebuggerUI.recordSnapshot.tooltip;"
hidden="true"/>
<toolbarbutton id="import-snapshot"
class="devtools-toolbarbutton"
oncommand="SnapshotsListView._onImportButtonClick()"
tooltiptext="&canvasDebuggerUI.importSnapshot;"/>
</hbox>
</toolbar>
<vbox id="snapshots-list" flex="1"/>
</vbox>
<vbox id="debugging-pane" class="devtools-main-content" flex="1">
<hbox id="reload-notice"
class="notice-container"
align="center"
pack="center"
flex="1">
<button id="reload-notice-button"
class="devtools-toolbarbutton"
standalone="true"
label="&canvasDebuggerUI.reloadNotice1;"
oncommand="gFront.setup({ reload: true })"/>
<label id="reload-notice-label"
class="plain"
value="&canvasDebuggerUI.reloadNotice2;"/>
</hbox>
<hbox id="empty-notice"
class="notice-container"
align="center"
pack="center"
flex="1"
hidden="true">
<label value="&canvasDebuggerUI.emptyNotice1;"/>
<button id="canvas-debugging-empty-notice-button"
class="devtools-toolbarbutton"
standalone="true"
oncommand="SnapshotsListView._onRecordButtonClick()"/>
<label value="&canvasDebuggerUI.emptyNotice2;"/>
</hbox>
<hbox id="waiting-notice"
class="notice-container devtools-throbber"
align="center"
pack="center"
flex="1"
hidden="true">
<label id="requests-menu-waiting-notice-label"
class="plain"
value="&canvasDebuggerUI.waitingNotice;"/>
</hbox>
<box id="debugging-pane-contents"
class="devtools-responsive-container"
flex="1"
hidden="true">
<vbox id="calls-list-container" flex="1">
<toolbar id="debugging-toolbar"
class="devtools-toolbar">
<hbox id="debugging-controls"
class="devtools-toolbarbutton-group">
<toolbarbutton id="resume"
class="devtools-toolbarbutton"
oncommand="CallsListView._onResume()"/>
<toolbarbutton id="step-over"
class="devtools-toolbarbutton"
oncommand="CallsListView._onStepOver()"/>
<toolbarbutton id="step-in"
class="devtools-toolbarbutton"
oncommand="CallsListView._onStepIn()"/>
<toolbarbutton id="step-out"
class="devtools-toolbarbutton"
oncommand="CallsListView._onStepOut()"/>
</hbox>
<toolbarbutton id="debugging-toolbar-sizer-button"
class="devtools-toolbarbutton"
label=""/>
<scale id="calls-slider"
movetoclick="true"
flex="100"/>
<textbox id="calls-searchbox"
class="devtools-filterinput"
placeholder="&canvasDebuggerUI.searchboxPlaceholder;"
type="search"
flex="1"/>
</toolbar>
<vbox id="calls-list" flex="1"/>
</vbox>
<splitter class="devtools-side-splitter"/>
<vbox id="screenshot-container"
hidden="true">
<vbox id="screenshot-image" flex="1"/>
<label id="screenshot-dimensions" class="plain"/>
</vbox>
</box>
<hbox id="snapshot-filmstrip"
hidden="true"/>
</vbox>
</hbox>
</window>

View file

@ -0,0 +1,10 @@
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
DevToolsModules(
'panel.js'
)
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']

View file

@ -0,0 +1,76 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { Cc, Ci, Cu, Cr } = require("chrome");
const promise = require("promise");
const EventEmitter = require("devtools/shared/event-emitter");
const { CanvasFront } = require("devtools/shared/fronts/canvas");
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
function CanvasDebuggerPanel(iframeWindow, toolbox) {
this.panelWin = iframeWindow;
this._toolbox = toolbox;
this._destroyer = null;
EventEmitter.decorate(this);
}
exports.CanvasDebuggerPanel = CanvasDebuggerPanel;
CanvasDebuggerPanel.prototype = {
/**
* Open is effectively an asynchronous constructor.
*
* @return object
* A promise that is resolved when the Canvas Debugger completes opening.
*/
open: function () {
let targetPromise;
// Local debugging needs to make the target remote.
if (!this.target.isRemote) {
targetPromise = this.target.makeRemote();
} else {
targetPromise = promise.resolve(this.target);
}
return targetPromise
.then(() => {
this.panelWin.gToolbox = this._toolbox;
this.panelWin.gTarget = this.target;
this.panelWin.gFront = new CanvasFront(this.target.client, this.target.form);
return this.panelWin.startupCanvasDebugger();
})
.then(() => {
this.isReady = true;
this.emit("ready");
return this;
})
.then(null, function onError(aReason) {
DevToolsUtils.reportException("CanvasDebuggerPanel.prototype.open", aReason);
});
},
// DevToolPanel API
get target() {
return this._toolbox.target;
},
destroy: function () {
// Make sure this panel is not already destroyed.
if (this._destroyer) {
return this._destroyer;
}
return this._destroyer = this.panelWin.shutdownCanvasDebugger().then(() => {
// Destroy front to ensure packet handler is removed from client
this.panelWin.gFront.destroy();
this.emit("destroyed");
});
}
};

View file

@ -0,0 +1,495 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
/* import-globals-from canvasdebugger.js */
/* globals window, document */
"use strict";
/**
* Functions handling the recorded animation frame snapshots UI.
*/
var SnapshotsListView = Heritage.extend(WidgetMethods, {
/**
* Initialization function, called when the tool is started.
*/
initialize: function () {
this.widget = new SideMenuWidget($("#snapshots-list"), {
showArrows: true
});
this._onSelect = this._onSelect.bind(this);
this._onClearButtonClick = this._onClearButtonClick.bind(this);
this._onRecordButtonClick = this._onRecordButtonClick.bind(this);
this._onImportButtonClick = this._onImportButtonClick.bind(this);
this._onSaveButtonClick = this._onSaveButtonClick.bind(this);
this._onRecordSuccess = this._onRecordSuccess.bind(this);
this._onRecordFailure = this._onRecordFailure.bind(this);
this._stopRecordingAnimation = this._stopRecordingAnimation.bind(this);
window.on(EVENTS.SNAPSHOT_RECORDING_FINISHED, this._enableRecordButton);
this.emptyText = L10N.getStr("noSnapshotsText");
this.widget.addEventListener("select", this._onSelect, false);
},
/**
* Destruction function, called when the tool is closed.
*/
destroy: function () {
clearNamedTimeout("canvas-actor-recording");
window.off(EVENTS.SNAPSHOT_RECORDING_FINISHED, this._enableRecordButton);
this.widget.removeEventListener("select", this._onSelect, false);
},
/**
* Adds a snapshot entry to this container.
*
* @return object
* The newly inserted item.
*/
addSnapshot: function () {
let contents = document.createElement("hbox");
contents.className = "snapshot-item";
let thumbnail = document.createElementNS(HTML_NS, "canvas");
thumbnail.className = "snapshot-item-thumbnail";
thumbnail.width = CanvasFront.THUMBNAIL_SIZE;
thumbnail.height = CanvasFront.THUMBNAIL_SIZE;
let title = document.createElement("label");
title.className = "plain snapshot-item-title";
title.setAttribute("value",
L10N.getFormatStr("snapshotsList.itemLabel", this.itemCount + 1));
let calls = document.createElement("label");
calls.className = "plain snapshot-item-calls";
calls.setAttribute("value",
L10N.getStr("snapshotsList.loadingLabel"));
let save = document.createElement("label");
save.className = "plain snapshot-item-save";
save.addEventListener("click", this._onSaveButtonClick, false);
let spacer = document.createElement("spacer");
spacer.setAttribute("flex", "1");
let footer = document.createElement("hbox");
footer.className = "snapshot-item-footer";
footer.appendChild(save);
let details = document.createElement("vbox");
details.className = "snapshot-item-details";
details.appendChild(title);
details.appendChild(calls);
details.appendChild(spacer);
details.appendChild(footer);
contents.appendChild(thumbnail);
contents.appendChild(details);
// Append a recorded snapshot item to this container.
return this.push([contents], {
attachment: {
// The snapshot and function call actors, along with the thumbnails
// will be available as soon as recording finishes.
actor: null,
calls: null,
thumbnails: null,
screenshot: null
}
});
},
/**
* Removes the last snapshot added, in the event no requestAnimationFrame loop was found.
*/
removeLastSnapshot: function () {
this.removeAt(this.itemCount - 1);
// If this is the only item, revert back to the empty notice
if (this.itemCount === 0) {
$("#empty-notice").hidden = false;
$("#waiting-notice").hidden = true;
}
},
/**
* Customizes a shapshot in this container.
*
* @param Item snapshotItem
* An item inserted via `SnapshotsListView.addSnapshot`.
* @param object snapshotActor
* The frame snapshot actor received from the backend.
* @param object snapshotOverview
* Additional data about the snapshot received from the backend.
*/
customizeSnapshot: function (snapshotItem, snapshotActor, snapshotOverview) {
// Make sure the function call actors are stored on the item,
// to be used when populating the CallsListView.
snapshotItem.attachment.actor = snapshotActor;
let functionCalls = snapshotItem.attachment.calls = snapshotOverview.calls;
let thumbnails = snapshotItem.attachment.thumbnails = snapshotOverview.thumbnails;
let screenshot = snapshotItem.attachment.screenshot = snapshotOverview.screenshot;
let lastThumbnail = thumbnails[thumbnails.length - 1];
let { width, height, flipped, pixels } = lastThumbnail;
let thumbnailNode = $(".snapshot-item-thumbnail", snapshotItem.target);
thumbnailNode.setAttribute("flipped", flipped);
drawImage(thumbnailNode, width, height, pixels, { centered: true });
let callsNode = $(".snapshot-item-calls", snapshotItem.target);
let drawCalls = functionCalls.filter(e => CanvasFront.DRAW_CALLS.has(e.name));
let drawCallsStr = PluralForm.get(drawCalls.length,
L10N.getStr("snapshotsList.drawCallsLabel"));
let funcCallsStr = PluralForm.get(functionCalls.length,
L10N.getStr("snapshotsList.functionCallsLabel"));
callsNode.setAttribute("value",
drawCallsStr.replace("#1", drawCalls.length) + ", " +
funcCallsStr.replace("#1", functionCalls.length));
let saveNode = $(".snapshot-item-save", snapshotItem.target);
saveNode.setAttribute("disabled", !!snapshotItem.isLoadedFromDisk);
saveNode.setAttribute("value", snapshotItem.isLoadedFromDisk
? L10N.getStr("snapshotsList.loadedLabel")
: L10N.getStr("snapshotsList.saveLabel"));
// Make sure there's always a selected item available.
if (!this.selectedItem) {
this.selectedIndex = 0;
}
},
/**
* The select listener for this container.
*/
_onSelect: function ({ detail: snapshotItem }) {
// Check to ensure the attachment has an actor, like
// an in-progress recording.
if (!snapshotItem || !snapshotItem.attachment.actor) {
return;
}
let { calls, thumbnails, screenshot } = snapshotItem.attachment;
$("#reload-notice").hidden = true;
$("#empty-notice").hidden = true;
$("#waiting-notice").hidden = false;
$("#debugging-pane-contents").hidden = true;
$("#screenshot-container").hidden = true;
$("#snapshot-filmstrip").hidden = true;
Task.spawn(function* () {
// Wait for a few milliseconds between presenting the function calls,
// screenshot and thumbnails, to allow each component being
// sequentially drawn. This gives the illusion of snappiness.
yield DevToolsUtils.waitForTime(SNAPSHOT_DATA_DISPLAY_DELAY);
CallsListView.showCalls(calls);
$("#debugging-pane-contents").hidden = false;
$("#waiting-notice").hidden = true;
yield DevToolsUtils.waitForTime(SNAPSHOT_DATA_DISPLAY_DELAY);
CallsListView.showThumbnails(thumbnails);
$("#snapshot-filmstrip").hidden = false;
yield DevToolsUtils.waitForTime(SNAPSHOT_DATA_DISPLAY_DELAY);
CallsListView.showScreenshot(screenshot);
$("#screenshot-container").hidden = false;
window.emit(EVENTS.SNAPSHOT_RECORDING_SELECTED);
});
},
/**
* The click listener for the "clear" button in this container.
*/
_onClearButtonClick: function () {
Task.spawn(function* () {
SnapshotsListView.empty();
CallsListView.empty();
$("#reload-notice").hidden = true;
$("#empty-notice").hidden = true;
$("#waiting-notice").hidden = true;
if (yield gFront.isInitialized()) {
$("#empty-notice").hidden = false;
} else {
$("#reload-notice").hidden = false;
}
$("#debugging-pane-contents").hidden = true;
$("#screenshot-container").hidden = true;
$("#snapshot-filmstrip").hidden = true;
window.emit(EVENTS.SNAPSHOTS_LIST_CLEARED);
});
},
/**
* The click listener for the "record" button in this container.
*/
_onRecordButtonClick: function () {
this._disableRecordButton();
if (this._recording) {
this._stopRecordingAnimation();
return;
}
// Insert a "dummy" snapshot item in the view, to hint that recording
// has now started. However, wait for a few milliseconds before actually
// starting the recording, since that might block rendering and prevent
// the dummy snapshot item from being drawn.
this.addSnapshot();
// If this is the first item, immediately show the "Loading…" notice.
if (this.itemCount == 1) {
$("#empty-notice").hidden = true;
$("#waiting-notice").hidden = false;
}
this._recordAnimation();
},
/**
* Makes the record button able to be clicked again.
*/
_enableRecordButton: function () {
$("#record-snapshot").removeAttribute("disabled");
},
/**
* Makes the record button unable to be clicked.
*/
_disableRecordButton: function () {
$("#record-snapshot").setAttribute("disabled", true);
},
/**
* Begins recording an animation.
*/
_recordAnimation: Task.async(function* () {
if (this._recording) {
return;
}
this._recording = true;
$("#record-snapshot").setAttribute("checked", "true");
setNamedTimeout("canvas-actor-recording", CANVAS_ACTOR_RECORDING_ATTEMPT, this._stopRecordingAnimation);
yield DevToolsUtils.waitForTime(SNAPSHOT_START_RECORDING_DELAY);
window.emit(EVENTS.SNAPSHOT_RECORDING_STARTED);
gFront.recordAnimationFrame().then(snapshot => {
if (snapshot) {
this._onRecordSuccess(snapshot);
} else {
this._onRecordFailure();
}
});
// Wait another delay before reenabling the button to stop the recording
// if a recording is not found.
yield DevToolsUtils.waitForTime(SNAPSHOT_START_RECORDING_DELAY);
this._enableRecordButton();
}),
/**
* Stops recording animation. Called when a click on the stopwatch occurs during a recording,
* or if a recording times out.
*/
_stopRecordingAnimation: Task.async(function* () {
clearNamedTimeout("canvas-actor-recording");
let actorCanStop = yield gTarget.actorHasMethod("canvas", "stopRecordingAnimationFrame");
if (actorCanStop) {
yield gFront.stopRecordingAnimationFrame();
}
// If actor does not have the method to stop recording (Fx39+),
// manually call the record failure method. This will call a connection failure
// on disconnect as a result of `gFront.recordAnimationFrame()` never resolving,
// but this is better than it hanging when there is no requestAnimationFrame anyway.
else {
this._onRecordFailure();
}
this._recording = false;
$("#record-snapshot").removeAttribute("checked");
this._enableRecordButton();
}),
/**
* Resolves from the front's recordAnimationFrame to setup the interface with the screenshots.
*/
_onRecordSuccess: Task.async(function* (snapshotActor) {
// Clear bail-out case if frame found in CANVAS_ACTOR_RECORDING_ATTEMPT milliseconds
clearNamedTimeout("canvas-actor-recording");
let snapshotItem = this.getItemAtIndex(this.itemCount - 1);
let snapshotOverview = yield snapshotActor.getOverview();
this.customizeSnapshot(snapshotItem, snapshotActor, snapshotOverview);
this._recording = false;
$("#record-snapshot").removeAttribute("checked");
window.emit(EVENTS.SNAPSHOT_RECORDING_COMPLETED);
window.emit(EVENTS.SNAPSHOT_RECORDING_FINISHED);
}),
/**
* Called as a reject from the front's recordAnimationFrame.
*/
_onRecordFailure: function () {
clearNamedTimeout("canvas-actor-recording");
showNotification(gToolbox, "canvas-debugger-timeout", L10N.getStr("recordingTimeoutFailure"));
window.emit(EVENTS.SNAPSHOT_RECORDING_CANCELLED);
window.emit(EVENTS.SNAPSHOT_RECORDING_FINISHED);
this.removeLastSnapshot();
},
/**
* The click listener for the "import" button in this container.
*/
_onImportButtonClick: function () {
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
fp.init(window, L10N.getStr("snapshotsList.saveDialogTitle"), Ci.nsIFilePicker.modeOpen);
fp.appendFilter(L10N.getStr("snapshotsList.saveDialogJSONFilter"), "*.json");
fp.appendFilter(L10N.getStr("snapshotsList.saveDialogAllFilter"), "*.*");
if (fp.show() != Ci.nsIFilePicker.returnOK) {
return;
}
let channel = NetUtil.newChannel({
uri: NetUtil.newURI(fp.file), loadUsingSystemPrincipal: true});
channel.contentType = "text/plain";
NetUtil.asyncFetch(channel, (inputStream, status) => {
if (!Components.isSuccessCode(status)) {
console.error("Could not import recorded animation frame snapshot file.");
return;
}
try {
let string = NetUtil.readInputStreamToString(inputStream, inputStream.available());
var data = JSON.parse(string);
} catch (e) {
console.error("Could not read animation frame snapshot file.");
return;
}
if (data.fileType != CALLS_LIST_SERIALIZER_IDENTIFIER) {
console.error("Unrecognized animation frame snapshot file.");
return;
}
// Add a `isLoadedFromDisk` flag on everything to avoid sending invalid
// requests to the backend, since we're not dealing with actors anymore.
let snapshotItem = this.addSnapshot();
snapshotItem.isLoadedFromDisk = true;
data.calls.forEach(e => e.isLoadedFromDisk = true);
this.customizeSnapshot(snapshotItem, data.calls, data);
});
},
/**
* The click listener for the "save" button of each item in this container.
*/
_onSaveButtonClick: function (e) {
let snapshotItem = this.getItemForElement(e.target);
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
fp.init(window, L10N.getStr("snapshotsList.saveDialogTitle"), Ci.nsIFilePicker.modeSave);
fp.appendFilter(L10N.getStr("snapshotsList.saveDialogJSONFilter"), "*.json");
fp.appendFilter(L10N.getStr("snapshotsList.saveDialogAllFilter"), "*.*");
fp.defaultString = "snapshot.json";
// Start serializing all the function call actors for the specified snapshot,
// while the nsIFilePicker dialog is being opened. Snappy.
let serialized = Task.spawn(function* () {
let data = {
fileType: CALLS_LIST_SERIALIZER_IDENTIFIER,
version: CALLS_LIST_SERIALIZER_VERSION,
calls: [],
thumbnails: [],
screenshot: null
};
let functionCalls = snapshotItem.attachment.calls;
let thumbnails = snapshotItem.attachment.thumbnails;
let screenshot = snapshotItem.attachment.screenshot;
// Prepare all the function calls for serialization.
yield DevToolsUtils.yieldingEach(functionCalls, (call, i) => {
let { type, name, file, line, timestamp, argsPreview, callerPreview } = call;
return call.getDetails().then(({ stack }) => {
data.calls[i] = {
type: type,
name: name,
file: file,
line: line,
stack: stack,
timestamp: timestamp,
argsPreview: argsPreview,
callerPreview: callerPreview
};
});
});
// Prepare all the thumbnails for serialization.
yield DevToolsUtils.yieldingEach(thumbnails, (thumbnail, i) => {
let { index, width, height, flipped, pixels } = thumbnail;
data.thumbnails.push({ index, width, height, flipped, pixels });
});
// Prepare the screenshot for serialization.
let { index, width, height, flipped, pixels } = screenshot;
data.screenshot = { index, width, height, flipped, pixels };
let string = JSON.stringify(data);
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
createInstance(Ci.nsIScriptableUnicodeConverter);
converter.charset = "UTF-8";
return converter.convertToInputStream(string);
});
// Open the nsIFilePicker and wait for the function call actors to finish
// being serialized, in order to save the generated JSON data to disk.
fp.open({ done: result => {
if (result == Ci.nsIFilePicker.returnCancel) {
return;
}
let footer = $(".snapshot-item-footer", snapshotItem.target);
let save = $(".snapshot-item-save", snapshotItem.target);
// Show a throbber and a "Saving…" label if serializing isn't immediate.
setNamedTimeout("call-list-save", CALLS_LIST_SLOW_SAVE_DELAY, () => {
footer.classList.add("devtools-throbber");
save.setAttribute("disabled", "true");
save.setAttribute("value", L10N.getStr("snapshotsList.savingLabel"));
});
serialized.then(inputStream => {
let outputStream = FileUtils.openSafeFileOutputStream(fp.file);
NetUtil.asyncCopy(inputStream, outputStream, status => {
if (!Components.isSuccessCode(status)) {
console.error("Could not save recorded animation frame snapshot file.");
}
clearNamedTimeout("call-list-save");
footer.classList.remove("devtools-throbber");
save.removeAttribute("disabled");
save.setAttribute("value", L10N.getStr("snapshotsList.saveLabel"));
});
});
}});
}
});
function showNotification(toolbox, name, message) {
let notificationBox = toolbox.getNotificationBox();
let notification = notificationBox.getNotificationWithValue(name);
if (!notification) {
notificationBox.appendNotification(message, name, "", notificationBox.PRIORITY_WARNING_HIGH);
}
}

View file

@ -0,0 +1,6 @@
"use strict";
module.exports = {
// Extend from the shared list of defined globals for mochitests.
"extends": "../../../.eslintrc.mochitests.js"
};

View file

@ -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

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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;
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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;
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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();
}

View file

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

View file

@ -0,0 +1,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();
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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;
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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>

View file

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

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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;
}