In a following patch, all DevTools moz.build files will use DevToolsModules to install JS modules at a path that corresponds directly to their source tree location. Here we rewrite all require and import calls to match the new location that these files are installed to.
344 lines
11 KiB
JavaScript
344 lines
11 KiB
JavaScript
/* 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 { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
|
|
|
|
Cu.import("resource://gre/modules/Services.jsm");
|
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
Cu.import("resource:///modules/devtools/client/shared/widgets/SideMenuWidget.jsm");
|
|
Cu.import("resource:///modules/devtools/client/shared/widgets/ViewHelpers.jsm");
|
|
Cu.import("resource://gre/modules/devtools/shared/Console.jsm");
|
|
|
|
const { require } = Cu.import("resource://gre/modules/devtools/shared/Loader.jsm", {});
|
|
const promise = require("promise");
|
|
const EventEmitter = require("devtools/shared/event-emitter");
|
|
const { CallWatcherFront } = require("devtools/server/actors/call-watcher");
|
|
const { CanvasFront } = require("devtools/server/actors/canvas");
|
|
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
|
|
|
|
const Telemetry = require("devtools/client/shared/telemetry");
|
|
const telemetry = new Telemetry();
|
|
|
|
const CANVAS_ACTOR_RECORDING_ATTEMPT = DevToolsUtils.testing ? 500 : 5000;
|
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "Task",
|
|
"resource://gre/modules/Task.jsm");
|
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
|
|
"resource://gre/modules/PluralForm.jsm");
|
|
|
|
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"
|
|
};
|
|
|
|
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
|
const STRINGS_URI = "chrome://browser/locale/devtools/canvasdebugger.properties";
|
|
const SHARED_STRINGS_URI = "chrome://browser/locale/devtools/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() {
|
|
telemetry.toolOpened("canvasdebugger");
|
|
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() {
|
|
telemetry.toolClosed("canvasdebugger");
|
|
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;
|
|
}
|
|
// Make sure the backend is prepared to handle <canvas> contexts.
|
|
gFront.setup({ reload: false });
|
|
|
|
// 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 ViewHelpers.L10N(STRINGS_URI);
|
|
var SHARED_L10N = new ViewHelpers.L10N(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;
|
|
}
|