diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index a4731bdaef5..d67b633ef9b 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -1205,9 +1205,6 @@ pref("devtools.styleeditor.autocompletion-enabled", true); // Enable the Shader Editor. pref("devtools.shadereditor.enabled", false); -// Enable the Canvas Debugger. -pref("devtools.canvasdebugger.enabled", false); - // Enable tools for Chrome development. pref("devtools.chrome.enabled", false); diff --git a/browser/devtools/canvasdebugger/canvasdebugger.js b/browser/devtools/canvasdebugger/canvasdebugger.js deleted file mode 100644 index 9211919348a..00000000000 --- a/browser/devtools/canvasdebugger/canvasdebugger.js +++ /dev/null @@ -1,1270 +0,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/. */ -"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/SideMenuWidget.jsm"); -Cu.import("resource:///modules/devtools/ViewHelpers.jsm"); - -const require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require; -const promise = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise; -const EventEmitter = require("devtools/toolkit/event-emitter"); -const { CallWatcherFront } = require("devtools/server/actors/call-watcher"); -const { CanvasFront } = require("devtools/server/actors/canvas"); - -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.defineLazyModuleGetter(this, "DevToolsUtils", - "resource://gre/modules/devtools/DevToolsUtils.jsm"); - -// 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. - SNAPSHOT_RECORDING_STARTED: "CanvasDebugger:SnapshotRecordingStarted", - SNAPSHOT_RECORDING_FINISHED: "CanvasDebugger:SnapshotRecordingFinished", - - // 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 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. - */ -let 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. - */ -let EventsHandler = { - /** - * Listen for events emitted by the current tab target. - */ - initialize: function() { - 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; - } - // Make sure the backend is prepared to handle 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; - $("#import-notice").hidden = true; - - $("#debugging-pane-contents").hidden = true; - $("#screenshot-container").hidden = true; - $("#snapshot-filmstrip").hidden = true; - - window.emit(EVENTS.UI_RESET); - } -}; - -/** - * Functions handling the recorded animation frame snapshots UI. - */ -let 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.emptyText = L10N.getStr("noSnapshotsText"); - this.widget.addEventListener("select", this._onSelect, false); - }, - - /** - * Destruction function, called when the tool is closed. - */ - destroy: function() { - 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_HEIGHT; - thumbnail.height = CanvasFront.THUMBNAIL_HEIGHT; - - 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 - } - }); - }, - - /** - * 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 }) { - if (!snapshotItem) { - return; - } - let { calls, thumbnails, screenshot } = snapshotItem.attachment; - - $("#reload-notice").hidden = true; - $("#empty-notice").hidden = true; - $("#import-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; - $("#import-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; - $("#import-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() { - Task.spawn(function*() { - $("#record-snapshot").setAttribute("checked", "true"); - $("#record-snapshot").setAttribute("disabled", "true"); - - // 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. - let snapshotItem = this.addSnapshot(); - - // If this is the first item, immediately show the "Loading…" notice. - if (this.itemCount == 1) { - $("#empty-notice").hidden = true; - $("#import-notice").hidden = false; - } - - yield DevToolsUtils.waitForTime(SNAPSHOT_START_RECORDING_DELAY); - window.emit(EVENTS.SNAPSHOT_RECORDING_STARTED); - - let snapshotActor = yield gFront.recordAnimationFrame(); - let snapshotOverview = yield snapshotActor.getOverview(); - this.customizeSnapshot(snapshotItem, snapshotActor, snapshotOverview); - - $("#record-snapshot").removeAttribute("checked"); - $("#record-snapshot").removeAttribute("disabled"); - - window.emit(EVENTS.SNAPSHOT_RECORDING_FINISHED); - }.bind(this)); - }, - - /** - * 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(fp.file); - 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); - - // Create array buffers from the parsed pixel arrays. - for (let thumbnail of data.thumbnails) { - let thumbnailPixelsArray = thumbnail.pixels.split(","); - thumbnail.pixels = new Uint32Array(thumbnailPixelsArray); - } - let screenshotPixelsArray = data.screenshot.pixels.split(","); - data.screenshot.pixels = new Uint32Array(screenshotPixelsArray); - - 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, argsPreview, callerPreview } = call; - return call.getDetails().then(({ stack }) => { - data.calls[i] = { - type: type, - name: name, - file: file, - line: line, - stack: stack, - 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: index, - width: width, - height: height, - flipped: flipped, - pixels: Array.join(pixels, ",") - }); - }); - - // Prepare the screenshot for serialization. - let { index, width, height, flipped, pixels } = screenshot; - data.screenshot = { - index: index, - width: width, - height: height, - flipped: flipped, - pixels: Array.join(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.setAttribute("saving", ""); - 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.removeAttribute("saving"); - save.removeAttribute("disabled"); - save.setAttribute("value", L10N.getStr("snapshotsList.saveLabel")); - }); - }); - }}); - } -}); - -/** - * Functions handling details about a single recorded animation frame snapshot - * (the calls list, rendering preview, thumbnails filmstrip etc.). - */ -let 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); - - // Not all function calls have a caller that was stringified (e.g. - // context calls have a "gl" or "ctx" caller preview). - 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, flipped, pixels } = screenshot; - - let screenshotNode = $("#screenshot-image"); - screenshotNode.setAttribute("flipped", flipped); - drawBackground("screenshot-rendering", width, height, pixels); - - let dimensionsNode = $("#screenshot-dimensions"); - dimensionsNode.setAttribute("value", ~~width + " x " + ~~height); - - 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_HEIGHT, width); - thumbnailNode.height = Math.max(CanvasFront.THUMBNAIL_HEIGHT, 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; - }); - }); - }, - - /** - * 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.contains(lowerCaseSearchToken) || - file.contains(lowerCaseSearchToken) || - line.contains(lowerCaseSearchToken) || - args.contains(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; - 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.MozPaddingStart = (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 }) { - 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; - viewSourceInDebugger(file, line); - }, - - /** - * The click listener for the "step out" button in this container's toolbar. - */ - _onStepOut: function() { - this.selectedIndex = this.itemCount - 1; - } -}); - -/** - * Localization convenience methods. - */ -let L10N = new ViewHelpers.L10N(STRINGS_URI); - -/** - * Convenient way of emitting events from the panel window. - */ -EventEmitter.decorate(this); - -/** - * DOM query helpers. - */ -function $(selector, target = document) target.querySelector(selector); -function $all(selector, target = document) target.querySelectorAll(selector); - -/** - * Helper for getting an nsIURL instance out of a string. - */ -function nsIURL(url, store = nsIURL.store) { - if (store.has(url)) { - return store.get(url); - } - let uri = Services.io.newURI(url, null, null).QueryInterface(Ci.nsIURL); - store.set(url, uri); - return uri; -} - -// The cache used in the `nsIURL` function. -nsIURL.store = new Map(); - -/** - * Gets the fileName part of a string which happens to be an URL. - */ -function getFileName(url) { - try { - let { fileName } = 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 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 arrayBuffer = new Uint8Array(pixels.buffer); - let imageData = getImageDataStorage(ctx, width, height); - imageData.data.set(arrayBuffer); - - 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 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; -} - -/** - * Opens/selects the debugger in this toolbox and jumps to the specified - * file name and line number. - */ -function viewSourceInDebugger(url, line) { - let showSource = ({ DebuggerView }) => { - if (DebuggerView.Sources.containsValue(url)) { - DebuggerView.setEditorLocation(url, line, { noDebug: true }).then(() => { - window.emit(EVENTS.SOURCE_SHOWN_IN_JS_DEBUGGER); - }, () => { - window.emit(EVENTS.SOURCE_NOT_FOUND_IN_JS_DEBUGGER); - }); - } - } - - // If the Debugger was already open, switch to it and try to show the - // source immediately. Otherwise, initialize it and wait for the sources - // to be added first. - let debuggerAlreadyOpen = gToolbox.getPanel("jsdebugger"); - gToolbox.selectTool("jsdebugger").then(({ panelWin: dbg }) => { - if (debuggerAlreadyOpen) { - showSource(dbg); - } else { - dbg.once(dbg.EVENTS.SOURCES_ADDED, () => showSource(dbg)); - } - }); -} diff --git a/browser/devtools/canvasdebugger/canvasdebugger.xul b/browser/devtools/canvasdebugger/canvasdebugger.xul deleted file mode 100644 index 7ff241435fd..00000000000 --- a/browser/devtools/canvasdebugger/canvasdebugger.xul +++ /dev/null @@ -1,131 +0,0 @@ - - - - - - - - - %canvasDebuggerDTD; -]> - - - - - - diff --git a/browser/devtools/canvasdebugger/test/doc_simple-canvas-transparent.html b/browser/devtools/canvasdebugger/test/doc_simple-canvas-transparent.html deleted file mode 100644 index f8daf1e24ff..00000000000 --- a/browser/devtools/canvasdebugger/test/doc_simple-canvas-transparent.html +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - Canvas inspector test page - - - - - - - - - diff --git a/browser/devtools/canvasdebugger/test/doc_simple-canvas.html b/browser/devtools/canvasdebugger/test/doc_simple-canvas.html deleted file mode 100644 index 4fe6b587a71..00000000000 --- a/browser/devtools/canvasdebugger/test/doc_simple-canvas.html +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - Canvas inspector test page - - - - - - - - - diff --git a/browser/devtools/canvasdebugger/test/head.js b/browser/devtools/canvasdebugger/test/head.js deleted file mode 100644 index 889d09198ba..00000000000 --- a/browser/devtools/canvasdebugger/test/head.js +++ /dev/null @@ -1,234 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ */ -"use strict"; - -const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; - -let { Services } = Cu.import("resource://gre/modules/Services.jsm", {}); - -// Disable logging for all the tests. Both the debugger server and frontend will -// be affected by this pref. -let gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log"); -Services.prefs.setBoolPref("devtools.debugger.log", false); - -let { Task } = Cu.import("resource://gre/modules/Task.jsm", {}); -let { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {}); -let { gDevTools } = Cu.import("resource:///modules/devtools/gDevTools.jsm", {}); -let { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}); -let { DebuggerServer } = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {}); -let { DebuggerClient } = Cu.import("resource://gre/modules/devtools/dbg-client.jsm", {}); - -let { CallWatcherFront } = devtools.require("devtools/server/actors/call-watcher"); -let { CanvasFront } = devtools.require("devtools/server/actors/canvas"); -let TiltGL = devtools.require("devtools/tilt/tilt-gl"); -let TargetFactory = devtools.TargetFactory; -let Toolbox = devtools.Toolbox; - -const EXAMPLE_URL = "http://example.com/browser/browser/devtools/canvasdebugger/test/"; -const SIMPLE_CANVAS_URL = EXAMPLE_URL + "doc_simple-canvas.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"; - -// All tests are asynchronous. -waitForExplicitFinish(); - -let gToolEnabled = Services.prefs.getBoolPref("devtools.canvasdebugger.enabled"); - -registerCleanupFunction(() => { - info("finish() was called, cleaning up..."); - 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(); -}); - -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; - - linkedBrowser.addEventListener("load", function onLoad() { - linkedBrowser.removeEventListener("load", onLoad, true); - info("Tab added and finished loading: " + aUrl); - deferred.resolve(tab); - }, true); - - 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(); -} - -let 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 = - !TiltGL.isWebGLForceEnabled() && - TiltGL.isWebGLSupported() && - TiltGL.create3DContext(createCanvas()); - - 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) { - 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(() => true); - 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); - let debuggee = target.window.wrappedJSObject; - - yield target.makeRemote(); - - let front = new CallWatcherFront(target.client, target.form); - return [target, debuggee, front]; - }); -} - -function initCanavsDebuggerBackend(aUrl) { - info("Initializing a canvas debugger front."); - initServer(); - - return Task.spawn(function*() { - let tab = yield addTab(aUrl); - let target = TargetFactory.forTab(tab); - let debuggee = target.window.wrappedJSObject; - - yield target.makeRemote(); - - let front = new CanvasFront(target.client, target.form); - return [target, debuggee, front]; - }); -} - -function initCanavsDebuggerFrontend(aUrl) { - info("Initializing a canvas debugger pane."); - - return Task.spawn(function*() { - let tab = yield addTab(aUrl); - let target = TargetFactory.forTab(tab); - let debuggee = target.window.wrappedJSObject; - - yield target.makeRemote(); - - Services.prefs.setBoolPref("devtools.canvasdebugger.enabled", true); - let toolbox = yield gDevTools.showToolbox(target, "canvasdebugger"); - let panel = toolbox.getCurrentPanel(); - return [target, debuggee, panel]; - }); -} - -function teardown(aPanel) { - info("Destroying the specified canvas debugger."); - - return promise.all([ - once(aPanel, "destroyed"), - removeTab(aPanel.target.tab) - ]); -} diff --git a/browser/devtools/canvasdebugger/test/moz.build b/browser/devtools/canvasdebugger/test/moz.build deleted file mode 100644 index a21913edfc3..00000000000 --- a/browser/devtools/canvasdebugger/test/moz.build +++ /dev/null @@ -1,6 +0,0 @@ -# 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/. - -BROWSER_CHROME_MANIFESTS += ['browser.ini'] diff --git a/browser/devtools/debugger/debugger-view.js b/browser/devtools/debugger/debugger-view.js index 9ce42d4c91a..14af8f0a0df 100644 --- a/browser/devtools/debugger/debugger-view.js +++ b/browser/devtools/debugger/debugger-view.js @@ -390,7 +390,8 @@ let DebuggerView = { this._setEditorText(L10N.getStr("loadingText")); this._editorSource = { url: aSource.url, promise: deferred.promise }; - DebuggerController.SourceScripts.getText(aSource).then(([, aText, aContentType]) => { + DebuggerController.SourceScripts.getText(aSource) + .then(([, aText, aContentType]) => { // Avoid setting an unexpected source. This may happen when switching // very fast between sources that haven't been fetched yet. if (this._editorSource.url != aSource.url) { @@ -468,7 +469,8 @@ let DebuggerView = { // Make sure the requested source client is shown in the editor, then // update the source editor's caret position and debug location. - return this._setEditorSource(sourceForm, aFlags).then(([,, aContentType]) => { + return this._setEditorSource(sourceForm, aFlags) + .then(([,, aContentType]) => { // Record the contentType learned from fetching sourceForm.contentType = aContentType; // Line numbers in the source editor should start from 1. If invalid diff --git a/browser/devtools/debugger/debugger.xul b/browser/devtools/debugger/debugger.xul index 59bb3354016..fc5bb167807 100644 --- a/browser/devtools/debugger/debugger.xul +++ b/browser/devtools/debugger/debugger.xul @@ -434,7 +434,7 @@ - + diff --git a/browser/devtools/debugger/panel.js b/browser/devtools/debugger/panel.js index 3b01d963300..511f66c78cf 100644 --- a/browser/devtools/debugger/panel.js +++ b/browser/devtools/debugger/panel.js @@ -8,6 +8,7 @@ const { Cc, Ci, Cu, Cr } = require("chrome"); const promise = require("sdk/core/promise"); const EventEmitter = require("devtools/toolkit/event-emitter"); + const { DevToolsUtils } = Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm", {}); function DebuggerPanel(iframeWindow, toolbox) { @@ -59,7 +60,7 @@ DebuggerPanel.prototype = { return this; }) .then(null, function onError(aReason) { - DevToolsUtils.reportException("DebuggerPanel.prototype.open", aReason); + DevToolsUtils.reportException("DebuggerPane.prototype.open", aReason); }); }, diff --git a/browser/devtools/debugger/test/head.js b/browser/devtools/debugger/test/head.js index 90f982bcd7c..1258ff9dd75 100644 --- a/browser/devtools/debugger/test/head.js +++ b/browser/devtools/debugger/test/head.js @@ -245,7 +245,8 @@ function waitForSourceShown(aPanel, aUrl) { } function waitForEditorLocationSet(aPanel) { - return waitForDebuggerEvents(aPanel, aPanel.panelWin.EVENTS.EDITOR_LOCATION_SET); + return waitForDebuggerEvents(aPanel, + aPanel.panelWin.EVENTS.EDITOR_LOCATION_SET); } function ensureSourceIs(aPanel, aUrl, aWaitFlag = false) { diff --git a/browser/devtools/jar.mn b/browser/devtools/jar.mn index c2d2d36d12a..75a5ba506c8 100644 --- a/browser/devtools/jar.mn +++ b/browser/devtools/jar.mn @@ -60,8 +60,6 @@ browser.jar: content/browser/devtools/debugger-panes.js (debugger/debugger-panes.js) content/browser/devtools/shadereditor.xul (shadereditor/shadereditor.xul) content/browser/devtools/shadereditor.js (shadereditor/shadereditor.js) - content/browser/devtools/canvasdebugger.xul (canvasdebugger/canvasdebugger.xul) - content/browser/devtools/canvasdebugger.js (canvasdebugger/canvasdebugger.js) content/browser/devtools/profiler.xul (profiler/profiler.xul) content/browser/devtools/cleopatra.html (profiler/cleopatra/cleopatra.html) content/browser/devtools/profiler/cleopatra/css/ui.css (profiler/cleopatra/css/ui.css) diff --git a/browser/devtools/main.js b/browser/devtools/main.js index c68e260d954..9f37ef853ea 100644 --- a/browser/devtools/main.js +++ b/browser/devtools/main.js @@ -28,7 +28,6 @@ loader.lazyGetter(this, "WebConsolePanel", () => require("devtools/webconsole/pa loader.lazyGetter(this, "DebuggerPanel", () => require("devtools/debugger/panel").DebuggerPanel); loader.lazyGetter(this, "StyleEditorPanel", () => require("devtools/styleeditor/styleeditor-panel").StyleEditorPanel); loader.lazyGetter(this, "ShaderEditorPanel", () => require("devtools/shadereditor/panel").ShaderEditorPanel); -loader.lazyGetter(this, "CanvasDebuggerPanel", () => require("devtools/canvasdebugger/panel").CanvasDebuggerPanel); loader.lazyGetter(this, "ProfilerPanel", () => require("devtools/profiler/panel")); loader.lazyGetter(this, "NetMonitorPanel", () => require("devtools/netmonitor/panel").NetMonitorPanel); loader.lazyGetter(this, "ScratchpadPanel", () => require("devtools/scratchpad/scratchpad-panel").ScratchpadPanel); @@ -39,7 +38,6 @@ const inspectorProps = "chrome://browser/locale/devtools/inspector.properties"; const debuggerProps = "chrome://browser/locale/devtools/debugger.properties"; const styleEditorProps = "chrome://browser/locale/devtools/styleeditor.properties"; const shaderEditorProps = "chrome://browser/locale/devtools/shadereditor.properties"; -const canvasDebuggerProps = "chrome://browser/locale/devtools/canvasdebugger.properties"; const webConsoleProps = "chrome://browser/locale/devtools/webconsole.properties"; const profilerProps = "chrome://browser/locale/devtools/profiler.properties"; const netMonitorProps = "chrome://browser/locale/devtools/netmonitor.properties"; @@ -49,7 +47,6 @@ loader.lazyGetter(this, "webConsoleStrings", () => Services.strings.createBundle loader.lazyGetter(this, "debuggerStrings", () => Services.strings.createBundle(debuggerProps)); loader.lazyGetter(this, "styleEditorStrings", () => Services.strings.createBundle(styleEditorProps)); loader.lazyGetter(this, "shaderEditorStrings", () => Services.strings.createBundle(shaderEditorProps)); -loader.lazyGetter(this, "canvasDebuggerStrings", () => Services.strings.createBundle(canvasDebuggerProps)); loader.lazyGetter(this, "inspectorStrings", () => Services.strings.createBundle(inspectorProps)); loader.lazyGetter(this, "profilerStrings",() => Services.strings.createBundle(profilerProps)); loader.lazyGetter(this, "netMonitorStrings", () => Services.strings.createBundle(netMonitorProps)); @@ -203,31 +200,11 @@ Tools.shaderEditor = { } }; -Tools.canvasDebugger = { - id: "canvasdebugger", - ordinal: 6, - visibilityswitch: "devtools.canvasdebugger.enabled", - icon: "chrome://browser/skin/devtools/tool-styleeditor.svg", - invertIconForLightTheme: true, - url: "chrome://browser/content/devtools/canvasdebugger.xul", - label: l10n("ToolboxCanvasDebugger.label", canvasDebuggerStrings), - tooltip: l10n("ToolboxCanvasDebugger.tooltip", canvasDebuggerStrings), - - isTargetSupported: function(target) { - return true; - }, - - build: function(iframeWindow, toolbox) { - let panel = new CanvasDebuggerPanel(iframeWindow, toolbox); - return panel.open(); - } -}; - Tools.jsprofiler = { id: "jsprofiler", accesskey: l10n("profiler.accesskey", profilerStrings), key: l10n("profiler2.commandkey", profilerStrings), - ordinal: 7, + ordinal: 6, modifiers: "shift", visibilityswitch: "devtools.profiler.enabled", icon: "chrome://browser/skin/devtools/tool-profiler.svg", @@ -251,7 +228,7 @@ Tools.netMonitor = { id: "netmonitor", accesskey: l10n("netmonitor.accesskey", netMonitorStrings), key: l10n("netmonitor.commandkey", netMonitorStrings), - ordinal: 8, + ordinal: 7, modifiers: osString == "Darwin" ? "accel,alt" : "accel,shift", visibilityswitch: "devtools.netmonitor.enabled", icon: "chrome://browser/skin/devtools/tool-network.svg", @@ -274,7 +251,7 @@ Tools.netMonitor = { Tools.scratchpad = { id: "scratchpad", - ordinal: 9, + ordinal: 8, visibilityswitch: "devtools.scratchpad.enabled", icon: "chrome://browser/skin/devtools/tool-scratchpad.svg", invertIconForLightTheme: true, @@ -300,7 +277,6 @@ let defaultTools = [ Tools.jsdebugger, Tools.styleEditor, Tools.shaderEditor, - Tools.canvasDebugger, Tools.jsprofiler, Tools.netMonitor, Tools.scratchpad diff --git a/browser/devtools/moz.build b/browser/devtools/moz.build index ce16ea38a84..3f19e867c90 100644 --- a/browser/devtools/moz.build +++ b/browser/devtools/moz.build @@ -6,7 +6,6 @@ DIRS += [ 'app-manager', - 'canvasdebugger', 'commandline', 'debugger', 'fontinspector', diff --git a/browser/devtools/netmonitor/netmonitor-controller.js b/browser/devtools/netmonitor/netmonitor-controller.js index 64c6f772a98..e9ab5b82727 100644 --- a/browser/devtools/netmonitor/netmonitor-controller.js +++ b/browser/devtools/netmonitor/netmonitor-controller.js @@ -117,9 +117,6 @@ const {Tooltip} = require("devtools/shared/widgets/Tooltip"); XPCOMUtils.defineLazyModuleGetter(this, "Chart", "resource:///modules/devtools/Chart.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "Curl", - "resource:///modules/devtools/Curl.jsm"); - XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm"); @@ -129,17 +126,23 @@ XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", XPCOMUtils.defineLazyModuleGetter(this, "DevToolsUtils", "resource://gre/modules/devtools/DevToolsUtils.jsm"); -XPCOMUtils.defineLazyServiceGetter(this, "clipboardHelper", - "@mozilla.org/widget/clipboardhelper;1", "nsIClipboardHelper"); +XPCOMUtils.defineLazyModuleGetter(this, "devtools", + "resource://gre/modules/devtools/Loader.jsm"); Object.defineProperty(this, "NetworkHelper", { get: function() { - return require("devtools/toolkit/webconsole/network-helper"); + return devtools.require("devtools/toolkit/webconsole/network-helper"); }, configurable: true, enumerable: true }); +XPCOMUtils.defineLazyServiceGetter(this, "clipboardHelper", + "@mozilla.org/widget/clipboardhelper;1", "nsIClipboardHelper"); + +XPCOMUtils.defineLazyModuleGetter(this, "Curl", + "resource:///modules/devtools/Curl.jsm"); + /** * Object defining the network monitor controller components. */ diff --git a/browser/devtools/netmonitor/panel.js b/browser/devtools/netmonitor/panel.js index 83734533236..7f1a5c750f6 100644 --- a/browser/devtools/netmonitor/panel.js +++ b/browser/devtools/netmonitor/panel.js @@ -8,7 +8,6 @@ const { Cc, Ci, Cu, Cr } = require("chrome"); const { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {}); const EventEmitter = require("devtools/toolkit/event-emitter"); -const { DevToolsUtils } = Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm", {}); function NetMonitorPanel(iframeWindow, toolbox) { this.panelWin = iframeWindow; @@ -50,7 +49,8 @@ NetMonitorPanel.prototype = { return this; }) .then(null, function onError(aReason) { - DevToolsUtils.reportException("NetMonitorPanel.prototype.open", aReason); + Cu.reportError("NetMonitorPanel open failed. " + + aReason.error + ": " + aReason.message); }); }, diff --git a/browser/devtools/shadereditor/moz.build b/browser/devtools/shadereditor/moz.build index 64fa91a0c7e..1978c0d9f9b 100644 --- a/browser/devtools/shadereditor/moz.build +++ b/browser/devtools/shadereditor/moz.build @@ -10,3 +10,4 @@ JS_MODULES_PATH = 'modules/devtools/shadereditor' EXTRA_JS_MODULES += [ 'panel.js' ] + diff --git a/browser/devtools/shadereditor/panel.js b/browser/devtools/shadereditor/panel.js index 4bd25ad7d27..1238c9b6341 100644 --- a/browser/devtools/shadereditor/panel.js +++ b/browser/devtools/shadereditor/panel.js @@ -9,7 +9,6 @@ const { Cc, Ci, Cu, Cr } = require("chrome"); const promise = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise; const EventEmitter = require("devtools/toolkit/event-emitter"); const { WebGLFront } = require("devtools/server/actors/webgl"); -const { DevToolsUtils } = Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm", {}); function ShaderEditorPanel(iframeWindow, toolbox) { this.panelWin = iframeWindow; @@ -22,12 +21,6 @@ function ShaderEditorPanel(iframeWindow, toolbox) { exports.ShaderEditorPanel = ShaderEditorPanel; ShaderEditorPanel.prototype = { - /** - * Open is effectively an asynchronous constructor. - * - * @return object - * A promise that is resolved when the Shader Editor completes opening. - */ open: function() { let targetPromise; @@ -51,7 +44,8 @@ ShaderEditorPanel.prototype = { return this; }) .then(null, function onError(aReason) { - DevToolsUtils.reportException("ShaderEditorPanel.prototype.open", aReason); + Cu.reportError("ShaderEditorPanel open failed. " + + aReason.error + ": " + aReason.message); }); }, diff --git a/browser/devtools/shadereditor/shadereditor.js b/browser/devtools/shadereditor/shadereditor.js index 11330095652..6820e074574 100644 --- a/browser/devtools/shadereditor/shadereditor.js +++ b/browser/devtools/shadereditor/shadereditor.js @@ -8,6 +8,7 @@ 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://gre/modules/Task.jsm"); +Cu.import("resource://gre/modules/devtools/Loader.jsm"); Cu.import("resource:///modules/devtools/SideMenuWidget.jsm"); Cu.import("resource:///modules/devtools/ViewHelpers.jsm"); diff --git a/browser/devtools/shared/widgets/ViewHelpers.jsm b/browser/devtools/shared/widgets/ViewHelpers.jsm index 9a6b00217a5..fd7e10accd5 100644 --- a/browser/devtools/shared/widgets/ViewHelpers.jsm +++ b/browser/devtools/shared/widgets/ViewHelpers.jsm @@ -20,8 +20,7 @@ Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm"); this.EXPORTED_SYMBOLS = [ "Heritage", "ViewHelpers", "WidgetMethods", - "setNamedTimeout", "clearNamedTimeout", - "setConditionalTimeout", "clearConditionalTimeout", + "setNamedTimeout", "clearNamedTimeout" ]; /** @@ -58,7 +57,7 @@ this.Heritage = { * @param function aCallback * Invoked when no more events are fired after the specified time. */ -this.setNamedTimeout = function setNamedTimeout(aId, aWait, aCallback) { +this.setNamedTimeout = function(aId, aWait, aCallback) { clearNamedTimeout(aId); namedTimeoutsStore.set(aId, setTimeout(() => @@ -72,7 +71,7 @@ this.setNamedTimeout = function setNamedTimeout(aId, aWait, aCallback) { * @param string aId * A string identifier for the named timeout. */ -this.clearNamedTimeout = function clearNamedTimeout(aId) { +this.clearNamedTimeout = function(aId) { if (!namedTimeoutsStore) { return; } @@ -80,41 +79,6 @@ this.clearNamedTimeout = function clearNamedTimeout(aId) { namedTimeoutsStore.delete(aId); }; -/** - * Same as `setNamedTimeout`, but invokes the callback only if the provided - * predicate function returns true. Otherwise, the timeout is re-triggered. - * - * @param string aId - * A string identifier for the conditional timeout. - * @param number aWait - * The amount of milliseconds to wait after no more events are fired. - * @param function aPredicate - * The predicate function used to determine whether the timeout restarts. - * @param function aCallback - * Invoked when no more events are fired after the specified time, and - * the provided predicate function returns true. - */ -this.setConditionalTimeout = function setConditionalTimeout(aId, aWait, aPredicate, aCallback) { - setNamedTimeout(aId, aWait, function maybeCallback() { - if (aPredicate()) { - aCallback(); - return; - } - setConditionalTimeout(aId, aWait, aPredicate, aCallback); - }); -}; - -/** - * Clears a conditional timeout. - * @see setConditionalTimeout - * - * @param string aId - * A string identifier for the conditional timeout. - */ -this.clearConditionalTimeout = function clearConditionalTimeout(aId) { - clearNamedTimeout(aId); -}; - XPCOMUtils.defineLazyGetter(this, "namedTimeoutsStore", () => new Map()); /** diff --git a/browser/locales/en-US/chrome/browser/devtools/canvasdebugger.dtd b/browser/locales/en-US/chrome/browser/devtools/canvasdebugger.dtd deleted file mode 100644 index 5ffd6175d62..00000000000 --- a/browser/locales/en-US/chrome/browser/devtools/canvasdebugger.dtd +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/browser/locales/en-US/chrome/browser/devtools/canvasdebugger.properties b/browser/locales/en-US/chrome/browser/devtools/canvasdebugger.properties deleted file mode 100644 index acc217f72b6..00000000000 --- a/browser/locales/en-US/chrome/browser/devtools/canvasdebugger.properties +++ /dev/null @@ -1,74 +0,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/. - -# LOCALIZATION NOTE These strings are used inside the Canvas Debugger -# which is available from the Web Developer sub-menu -> 'Canvas'. -# The correct localization of this file might be to keep it in -# English, or another language commonly spoken among web developers. -# You want to make that choice consistent across the developer tools. -# A good criteria is the language in which you'd find the best -# documentation on web development on the web. - -# LOCALIZATION NOTE (ToolboxCanvasDebugger.label): -# This string is displayed in the title of the tab when the Shader Editor is -# displayed inside the developer tools window and in the Developer Tools Menu. -ToolboxCanvasDebugger.label=Canvas - -# LOCALIZATION NOTE (ToolboxCanvasDebugger.tooltip): -# This string is displayed in the tooltip of the tab when the Shader Editor is -# displayed inside the developer tools window. -ToolboxCanvasDebugger.tooltip=Tools to inspect and debug contexts - -# LOCALIZATION NOTE (noSnapshotsText): The text to display in the snapshots menu -# when there are no recorded snapshots yet. -noSnapshotsText=There are no snapshots yet. - -# LOCALIZATION NOTE (snapshotsList.itemLabel): -# This string is displayed in the snapshots list of the Canvas Debugger, -# identifying a set of function calls of a recorded animation frame. -snapshotsList.itemLabel=Snapshot #%S - -# LOCALIZATION NOTE (snapshotsList.loadingLabel): -# This string is displayed in the snapshots list of the Canvas Debugger, -# for an item that has not finished loading. -snapshotsList.loadingLabel=Loading… - -# LOCALIZATION NOTE (snapshotsList.saveLabel): -# This string is displayed in the snapshots list of the Canvas Debugger, -# for saving an item to disk. -snapshotsList.saveLabel=Save - -# LOCALIZATION NOTE (snapshotsList.savingLabel): -# This string is displayed in the snapshots list of the Canvas Debugger, -# while saving an item to disk. -snapshotsList.savingLabel=Saving… - -# LOCALIZATION NOTE (snapshotsList.loadedLabel): -# This string is displayed in the snapshots list of the Canvas Debugger, -# for an item which was loaded from disk -snapshotsList.loadedLabel=Loaded from disk - -# LOCALIZATION NOTE (snapshotsList.saveDialogTitle): -# This string is displayed as a title for saving a snapshot to disk. -snapshotsList.saveDialogTitle=Save animation frame snapshot… - -# LOCALIZATION NOTE (snapshotsList.saveDialogJSONFilter): -# This string is displayed as a filter for saving a snapshot to disk. -snapshotsList.saveDialogJSONFilter=JSON Files - -# LOCALIZATION NOTE (snapshotsList.saveDialogAllFilter): -# This string is displayed as a filter for saving a snapshot to disk. -snapshotsList.saveDialogAllFilter=All Files - -# LOCALIZATION NOTE (snapshotsList.drawCallsLabel): -# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals -# This string is displayed in the snapshots list of the Canvas Debugger, -# as a generic description about how many draw calls were made. -snapshotsList.drawCallsLabel=#1 draw;#1 draws - -# LOCALIZATION NOTE (snapshotsList.functionCallsLabel): -# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals -# This string is displayed in the snapshots list of the Canvas Debugger, -# as a generic description about how many function calls were made in total. -snapshotsList.functionCallsLabel=#1 call;#1 calls diff --git a/browser/locales/jar.mn b/browser/locales/jar.mn index 6318564c9ff..71a194d3460 100644 --- a/browser/locales/jar.mn +++ b/browser/locales/jar.mn @@ -32,8 +32,6 @@ locale/browser/devtools/netmonitor.properties (%chrome/browser/devtools/netmonitor.properties) locale/browser/devtools/shadereditor.dtd (%chrome/browser/devtools/shadereditor.dtd) locale/browser/devtools/shadereditor.properties (%chrome/browser/devtools/shadereditor.properties) - locale/browser/devtools/canvasdebugger.dtd (%chrome/browser/devtools/canvasdebugger.dtd) - locale/browser/devtools/canvasdebugger.properties (%chrome/browser/devtools/canvasdebugger.properties) locale/browser/devtools/gcli.properties (%chrome/browser/devtools/gcli.properties) locale/browser/devtools/gclicommands.properties (%chrome/browser/devtools/gclicommands.properties) locale/browser/devtools/webconsole.properties (%chrome/browser/devtools/webconsole.properties) diff --git a/browser/themes/linux/devtools/canvasdebugger.css b/browser/themes/linux/devtools/canvasdebugger.css deleted file mode 100644 index 504c3fb9bf1..00000000000 --- a/browser/themes/linux/devtools/canvasdebugger.css +++ /dev/null @@ -1,5 +0,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/. */ - -%include ../../shared/devtools/canvasdebugger.inc.css diff --git a/browser/themes/linux/jar.mn b/browser/themes/linux/jar.mn index fc5d2c7fc6b..100012e93ab 100644 --- a/browser/themes/linux/jar.mn +++ b/browser/themes/linux/jar.mn @@ -209,7 +209,6 @@ browser.jar: * skin/classic/browser/devtools/splitview.css (../shared/devtools/splitview.css) skin/classic/browser/devtools/styleeditor.css (../shared/devtools/styleeditor.css) * skin/classic/browser/devtools/shadereditor.css (devtools/shadereditor.css) -* skin/classic/browser/devtools/canvasdebugger.css (devtools/canvasdebugger.css) * skin/classic/browser/devtools/debugger.css (devtools/debugger.css) * skin/classic/browser/devtools/profiler.css (devtools/profiler.css) * skin/classic/browser/devtools/netmonitor.css (devtools/netmonitor.css) diff --git a/browser/themes/osx/devtools/canvasdebugger.css b/browser/themes/osx/devtools/canvasdebugger.css deleted file mode 100644 index 0f393d1b093..00000000000 --- a/browser/themes/osx/devtools/canvasdebugger.css +++ /dev/null @@ -1,6 +0,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/. */ - -%include ../shared.inc -%include ../../shared/devtools/canvasdebugger.inc.css diff --git a/browser/themes/osx/jar.mn b/browser/themes/osx/jar.mn index 888a81df496..6f6dd1a49d0 100644 --- a/browser/themes/osx/jar.mn +++ b/browser/themes/osx/jar.mn @@ -330,7 +330,6 @@ browser.jar: * skin/classic/browser/devtools/splitview.css (../shared/devtools/splitview.css) skin/classic/browser/devtools/styleeditor.css (../shared/devtools/styleeditor.css) * skin/classic/browser/devtools/shadereditor.css (devtools/shadereditor.css) -* skin/classic/browser/devtools/canvasdebugger.css (devtools/canvasdebugger.css) * skin/classic/browser/devtools/debugger.css (devtools/debugger.css) * skin/classic/browser/devtools/profiler.css (devtools/profiler.css) * skin/classic/browser/devtools/netmonitor.css (devtools/netmonitor.css) diff --git a/browser/themes/shared/devtools/canvasdebugger.inc.css b/browser/themes/shared/devtools/canvasdebugger.inc.css deleted file mode 100644 index b2c55d8d4d5..00000000000 --- a/browser/themes/shared/devtools/canvasdebugger.inc.css +++ /dev/null @@ -1,501 +0,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/. */ - -%filter substitution -%define darkCheckerboardBackground #000 -%define lightCheckerboardBackground #fff -%define checkerboardCell rgba(128,128,128,0.2) -%define checkerboardPattern linear-gradient(45deg, @checkerboardCell@ 25%, transparent 25%, transparent 75%, @checkerboardCell@ 75%, @checkerboardCell@), linear-gradient(45deg, @checkerboardCell@ 25%, transparent 25%, transparent 75%, @checkerboardCell@ 75%, @checkerboardCell@) -%define gutterWidth 3em -%define gutterPaddingStart 22px - -/* Reload and waiting notices */ - -.notice-container { - margin-top: -50vh; - font-size: 120%; -} - -.theme-dark .notice-container { - background: url(background-noise-toolbar.png), #343c45; /* Toolbars */ - color: #f5f7fa; /* Light foreground text */ -} - -.theme-light .notice-container { - background: url(background-noise-toolbar.png), #f0f1f2; /* Toolbars */ - color: #585959; /* Grey foreground text */ -} - -#reload-notice > button { - min-height: 2em; -} - -#empty-notice > button { - min-width: 30px; - min-height: 28px; - margin: 0; - list-style-image: url(profiler-stopwatch.png); - -moz-image-region: rect(0px,16px,16px,0px); -} - -#empty-notice > button .button-text { - display: none; -} - -.theme-dark #import-notice { - font-size: 250%; - color: rgba(255,255,255,0.2); -} - -.theme-light #import-notice { - font-size: 250%; - color: rgba(0,0,0,0.2); -} - -/* Snapshots pane */ - -#snapshots-pane > tabs { - -moz-border-end: 1px solid; -} - -#snapshots-pane .devtools-toolbar { - -moz-border-end: 1px solid; -} - -.theme-dark #snapshots-pane > tabs, -.theme-dark #snapshots-pane .devtools-toolbar { - -moz-border-end-color: black; /* Match the splitter color. */ -} - -.theme-light #snapshots-pane > tabs, -.theme-light #snapshots-pane .devtools-toolbar { - -moz-border-end-color: #aaa; /* Match the splitter color. */ -} - -#record-snapshot { - list-style-image: url("chrome://browser/skin/devtools/profiler-stopwatch.png"); - -moz-image-region: rect(0px,16px,16px,0px); -} - -#record-snapshot[checked] { - -moz-image-region: rect(0px,32px,16px,16px); -} - -/* Snapshots items */ - -.snapshot-item-thumbnail { - image-rendering: -moz-crisp-edges; - background-image: @checkerboardPattern@; - background-size: 12px 12px, 12px 12px; - background-position: 0px 0px, 6px 6px; - background-repeat: repeat, repeat; -} - -.snapshot-item-thumbnail[flipped=true] { - transform: scaleY(-1); -} - -.theme-dark .snapshot-item-thumbnail { - background-color: @darkCheckerboardBackground@; -} - -.theme-light .snapshot-item-thumbnail { - background-color: @lightCheckerboardBackground@; -} - -.snapshot-item-details { - -moz-padding-start: 6px; -} - -.snapshot-item-calls { - padding-top: 4px; - font-size: 80%; -} - -.snapshot-item-save { - padding-bottom: 2px; - font-size: 90%; -} - -.theme-dark .snapshot-item-calls, -.theme-dark .snapshot-item-save { - color: #b6babf; /* Foreground (Text) - Grey */ -} - -.theme-light .snapshot-item-calls, -.theme-light .snapshot-item-save { - color: #585959; /* Foreground (Text) - Grey */ -} - -.snapshot-item-save { - text-decoration: underline; - cursor: pointer; -} - -.snapshot-item-save[disabled=true] { - text-decoration: none; - pointer-events: none; -} - -.snapshot-item-footer[saving]::before { - display: inline-block; - content: ""; - background: url("chrome://global/skin/icons/loading_16.png") center no-repeat; - width: 16px; - height: 16px; - margin-top: -2px; - -moz-margin-end: 4px; -} - -#snapshots-list .selected label { - /* Text inside a selected item should not be custom colored. */ - color: inherit !important; -} - -/* Debugging pane controls */ - -#resume { - list-style-image: url(debugger-play.png); - -moz-image-region: rect(0px,32px,16px,16px); -} - -#step-over { - list-style-image: url(debugger-step-over.png); -} - -#step-in { - list-style-image: url(debugger-step-in.png); -} - -#step-out { - list-style-image: url(debugger-step-out.png); -} - -#debugging-controls > toolbarbutton { - transition: opacity 0.15s ease-in-out; -} - -#debugging-controls > toolbarbutton[disabled=true] { - opacity: 0.5; -} - -#calls-slider { - -moz-padding-end: 24px; -} - -#calls-slider .scale-slider { - margin: 0; -} - -#debugging-toolbar-sizer-button { - /* This button's only purpose in life is to make the - container .devtools-toolbar have the right height. */ - visibility: hidden; - min-width: 1px; -} - -/* Calls list pane */ - -#calls-list .side-menu-widget-container { - background: transparent; -} - -#calls-list .side-menu-widget-item { - padding: 0; -} - -/* Calls list items */ - -.theme-dark #calls-list .side-menu-widget-item { - border-color: #111; - border-bottom-color: transparent; -} - -.theme-light #calls-list .side-menu-widget-item { - border-color: #eee; - border-bottom-color: transparent; -} - -.theme-dark .call-item-view:hover { - background-color: rgba(255,255,255,.025); -} - -.theme-light .call-item-view:hover { - background-color: rgba(0,0,0,.025); -} - -.theme-dark .call-item-view[draw-call] { - background-color: rgba(112,191,83,0.15); -} - -.theme-light .call-item-view[draw-call] { - background-color: rgba(44,187,15,0.1); -} - -.theme-dark .call-item-view[interesting-call] { - background-color: rgba(223,128,255,0.15); -} - -.theme-light .call-item-view[interesting-call] { - background-color: rgba(184,46,229,0.1); -} - -.call-item-gutter { - width: calc(@gutterWidth@ + @gutterPaddingStart@); - -moz-padding-start: @gutterPaddingStart@; - -moz-padding-end: 4px; - padding-top: 2px; - padding-bottom: 2px; - -moz-border-end: 1px solid; - -moz-margin-end: 6px; -} - -.selected .call-item-gutter { - background-image: url("editor-debug-location.png"); - background-repeat: no-repeat; - background-position: 6px center; - background-size: 12px; -} - -.theme-dark .call-item-gutter { - background-color: #181d20; - color: #5f7387; - border-color: #000; -} - -.theme-light .call-item-gutter { - background-color: #f7f7f7; - color: #667380; - border-color: #aaa; -} - -.call-item-index { - text-align: end; -} - -.theme-dark .call-item-context { - color: #eb5368; /* Highlight Orange */ -} - -.theme-light .call-item-context { - color: #f13c00; /* Highlight Orange */ -} - -.theme-dark .call-item-name { - color: #46afe3; /* Highlight Blue */ -} - -.theme-light .call-item-name { - color: #0088cc; /* Highlight Blue */ -} - -.call-item-location { - -moz-padding-start: 2px; - -moz-padding-end: 6px; - text-align: end; - cursor: pointer; -} - -.theme-dark .call-item-location:hover { - color: #0088cc; /* Highlight Blue */ -} - -.theme-light .call-item-location:hover { - color: #46afe3; /* Highlight Blue */ -} - -.call-item-view:hover .call-item-location, -.call-item-view[expanded] .call-item-location { - text-decoration: underline; -} - -.theme-dark .call-item-location { - border-color: #111; - color: #5e88b0; /* Highlight Blue-Grey */ -} - -.theme-light .call-item-location { - border-color: #eee; - color: #5f88b0; /* Highlight Blue-Grey */ -} - -.call-item-stack { - -moz-padding-start: calc(@gutterWidth@ + @gutterPaddingStart@); - padding-bottom: 10px; -} - -.theme-dark .call-item-stack { - background: rgba(0,0,0,0.9); -} - -.theme-light .call-item-stack { - background: rgba(255,255,255,0.9); -} - -.call-item-stack-fn { - padding-top: 2px; - padding-bottom: 2px; -} - -.call-item-stack-fn-location { - -moz-padding-start: 2px; - -moz-padding-end: 6px; - text-align: end; - cursor: pointer; - text-decoration: underline; -} - -.theme-dark .call-item-stack-fn-name { - color: #a9bacb; /* Content (Text) - Light */ -} - -.theme-light .call-item-stack-fn-name { - color: #667380; /* Content (Text) - Dark Grey */ -} - -.theme-dark .call-item-stack-fn-location { - color: #5e88b0; /* Highlight Blue-Grey */ -} - -.theme-light .call-item-stack-fn-location { - color: #5e88b0; /* Highlight Blue-Grey */ -} - -.theme-dark .call-item-stack-fn-location:hover { - color: #0088cc; /* Highlight Blue */ -} - -.theme-light .call-item-stack-fn-location:hover { - color: #46afe3; /* Highlight Blue */ -} - -#calls-list .selected .call-item-contents > label:not(.call-item-gutter) { - /* Text inside a selected item should not be custom colored. */ - color: inherit !important; -} - -/* Rendering preview */ - -#screenshot-container { - background-image: @checkerboardPattern@; - background-size: 30px 30px, 30px 30px; - background-position: 0px 0px, 15px 15px; - background-repeat: repeat, repeat; -} - -.theme-dark #screenshot-container { - background-color: @darkCheckerboardBackground@; -} - -.theme-light #screenshot-container { - background-color: @lightCheckerboardBackground@; -} - -@media (min-width: 701px) { - #screenshot-container { - width: 30vw; - max-width: 50vw; - min-width: 100px; - } -} - -@media (max-width: 700px) { - #screenshot-container { - height: 40vh; - max-height: 70vh; - min-height: 100px; - } -} - -#screenshot-image { - background-image: -moz-element(#screenshot-rendering); - background-size: contain; - background-position: center, center; - background-repeat: no-repeat; -} - -#screenshot-image[flipped=true] { - transform: scaleY(-1); -} - -#screenshot-dimensions { - padding-top: 4px; - padding-bottom: 4px; - text-align: center; -} - -.theme-dark #screenshot-dimensions { - background-color: rgba(0,0,0,0.4); -} - -.theme-light #screenshot-dimensions { - background-color: rgba(255,255,255,0.8); -} - -/* Snapshot filmstrip */ - -#snapshot-filmstrip { - overflow: hidden; -} - -.theme-dark #snapshot-filmstrip { - border-top: 1px solid #000; - background-image: url(background-noise-toolbar.png); - color: #f5f7fa; /* Light foreground text */ -} - -.theme-light #snapshot-filmstrip { - border-top: 1px solid #aaa; - background-image: url(background-noise-toolbar.png); - color: #585959; /* Grey foreground text */ -} - -.filmstrip-thumbnail { - image-rendering: -moz-crisp-edges; - background-image: @checkerboardPattern@; - background-size: 12px 12px, 12px 12px; - background-position: 0px -1px, 6px 5px; - background-repeat: repeat, repeat; - background-origin: content-box; - cursor: pointer; - padding-top: 1px; - padding-bottom: 1px; - transition: opacity 0.1s ease-in-out; -} - -.filmstrip-thumbnail[flipped=true] { - transform: scaleY(-1); -} - -.theme-dark .filmstrip-thumbnail { - background-color: @darkCheckerboardBackground@; -} - -.theme-light .filmstrip-thumbnail { - background-color: @lightCheckerboardBackground@; -} - -.theme-dark .filmstrip-thumbnail { - -moz-border-end: 1px solid #000; -} - -.theme-light .filmstrip-thumbnail { - -moz-border-end: 1px solid #aaa; -} - -.theme-dark #snapshot-filmstrip > .filmstrip-thumbnail:hover, -.theme-dark #snapshot-filmstrip:not(:hover) > .filmstrip-thumbnail[highlighted] { - border: 1px solid #46afe3; /* Highlight Blue */ - margin: 0 0 0 -1px; - padding: 0; - opacity: 0.66; -} - -.theme-light #snapshot-filmstrip > .filmstrip-thumbnail:hover, -.theme-light #snapshot-filmstrip:not(:hover) > .filmstrip-thumbnail[highlighted] { - border: 1px solid #0088cc; /* Highlight Blue */ - margin: 0 0 0 -1px; - padding: 0; - opacity: 0.66; -} diff --git a/browser/themes/shared/devtools/toolbars.inc.css b/browser/themes/shared/devtools/toolbars.inc.css index be2e56b1918..a91a48d94a7 100644 --- a/browser/themes/shared/devtools/toolbars.inc.css +++ b/browser/themes/shared/devtools/toolbars.inc.css @@ -774,7 +774,6 @@ .theme-light .scrollbutton-up > .toolbarbutton-icon, .theme-light .scrollbutton-down > .toolbarbutton-icon, .theme-light #black-boxed-message-button .button-icon, -.theme-light #canvas-debugging-empty-notice-button .button-icon, .theme-light #requests-menu-perf-notice-button .button-icon, .theme-light #requests-menu-network-summary-button .button-icon { filter: url(filters.svg#invert); diff --git a/browser/themes/shared/devtools/widgets.inc.css b/browser/themes/shared/devtools/widgets.inc.css index 59a0b6f14c9..09b1dc39a45 100644 --- a/browser/themes/shared/devtools/widgets.inc.css +++ b/browser/themes/shared/devtools/widgets.inc.css @@ -564,12 +564,10 @@ } .theme-dark .side-menu-widget-empty-text { - background: url(background-noise-toolbar.png), #343c45; /* Toolbars */ color: #b6babf; /* Foreground (Text) - Grey */ } .theme-light .side-menu-widget-empty-text { - background: #f7f7f7; /* Toolbars */ color: #585959; /* Grey foreground text */ } diff --git a/browser/themes/windows/devtools/canvasdebugger.css b/browser/themes/windows/devtools/canvasdebugger.css deleted file mode 100644 index 504c3fb9bf1..00000000000 --- a/browser/themes/windows/devtools/canvasdebugger.css +++ /dev/null @@ -1,5 +0,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/. */ - -%include ../../shared/devtools/canvasdebugger.inc.css diff --git a/browser/themes/windows/jar.mn b/browser/themes/windows/jar.mn index 35e2c1a4ae7..cdeec87bf99 100644 --- a/browser/themes/windows/jar.mn +++ b/browser/themes/windows/jar.mn @@ -242,7 +242,6 @@ browser.jar: * skin/classic/browser/devtools/splitview.css (../shared/devtools/splitview.css) skin/classic/browser/devtools/styleeditor.css (../shared/devtools/styleeditor.css) * skin/classic/browser/devtools/shadereditor.css (devtools/shadereditor.css) -* skin/classic/browser/devtools/canvasdebugger.css (devtools/canvasdebugger.css) * skin/classic/browser/devtools/debugger.css (devtools/debugger.css) * skin/classic/browser/devtools/profiler.css (devtools/profiler.css) * skin/classic/browser/devtools/netmonitor.css (devtools/netmonitor.css) @@ -590,7 +589,6 @@ browser.jar: * skin/classic/aero/browser/devtools/splitview.css (../shared/devtools/splitview.css) skin/classic/aero/browser/devtools/styleeditor.css (../shared/devtools/styleeditor.css) * skin/classic/aero/browser/devtools/shadereditor.css (devtools/shadereditor.css) -* skin/classic/aero/browser/devtools/canvasdebugger.css (devtools/canvasdebugger.css) * skin/classic/aero/browser/devtools/debugger.css (devtools/debugger.css) * skin/classic/aero/browser/devtools/profiler.css (devtools/profiler.css) * skin/classic/aero/browser/devtools/netmonitor.css (devtools/netmonitor.css) diff --git a/toolkit/devtools/DevToolsUtils.js b/toolkit/devtools/DevToolsUtils.js index f662a8126ae..9fe2f347c6a 100644 --- a/toolkit/devtools/DevToolsUtils.js +++ b/toolkit/devtools/DevToolsUtils.js @@ -8,7 +8,6 @@ const { Ci, Cu } = require("chrome"); let { Services } = Cu.import("resource://gre/modules/Services.jsm", {}); -let { setTimeout, clearTimeout } = Cu.import("resource://gre/modules/Timer.jsm", {}); /** * Turn the error |aError| into a string, without fail. @@ -28,8 +27,6 @@ exports.safeErrorString = function safeErrorString(aError) { } } catch (ee) { } - // Append additional line and column number information to the output, - // since it might not be part of the stringified error. if (typeof aError.lineNumber == "number" && typeof aError.columnNumber == "number") { errorString += "Line: " + aError.lineNumber + ", column: " + aError.columnNumber; } @@ -116,41 +113,12 @@ exports.zip = function zip(a, b) { return pairs; }; -/** - * Waits for the next tick in the event loop to execute a callback. - */ -exports.executeSoon = function executeSoon(aFn) { +const executeSoon = aFn => { Services.tm.mainThread.dispatch({ run: exports.makeInfallible(aFn) }, Ci.nsIThread.DISPATCH_NORMAL); }; -/** - * Waits for the next tick in the event loop. - * - * @return Promise - * A promise that is resolved after the next tick in the event loop. - */ -exports.waitForTick = function waitForTick() { - let deferred = promise.defer(); - exports.executeSoon(deferred.resolve); - return deferred.promise; -}; - -/** - * Waits for the specified amount of time to pass. - * - * @param number aDelay - * The amount of time to wait, in milliseconds. - * @return Promise - * A promise that is resolved after the specified amount of time passes. - */ -exports.waitForTime = function waitForTime(aDelay) { - let deferred = promise.defer(); - setTimeout(deferred.resolve, aDelay); - return deferred.promise; -}; - /** * Like Array.prototype.forEach, but doesn't cause jankiness when iterating over * very large arrays by yielding to the browser and continuing execution on the @@ -159,19 +127,16 @@ exports.waitForTime = function waitForTime(aDelay) { * @param Array aArray * The array being iterated over. * @param Function aFn - * The function called on each item in the array. If a promise is - * returned by this function, iterating over the array will be paused - * until the respective promise is resolved. + * The function called on each item in the array. * @returns Promise * A promise that is resolved once the whole array has been iterated - * over, and all promises returned by the aFn callback are resolved. + * over. */ exports.yieldingEach = function yieldingEach(aArray, aFn) { const deferred = promise.defer(); let i = 0; let len = aArray.length; - let outstanding = [deferred.promise]; (function loop() { const start = Date.now(); @@ -182,12 +147,12 @@ exports.yieldingEach = function yieldingEach(aArray, aFn) { // aren't including time spent in non-JS here, but this is Good // Enough(tm). if (Date.now() - start > 16) { - exports.executeSoon(loop); + executeSoon(loop); return; } try { - outstanding.push(aFn(aArray[i], i++)); + aFn(aArray[i++]); } catch (e) { deferred.reject(e); return; @@ -197,9 +162,10 @@ exports.yieldingEach = function yieldingEach(aArray, aFn) { deferred.resolve(); }()); - return promise.all(outstanding); + return deferred.promise; } + /** * Like XPCOMUtils.defineLazyGetter, but with a |this| sensitive getter that * allows the lazy getter to be defined on a prototype and work correctly with @@ -300,3 +266,4 @@ exports.isSafeJSObject = function isSafeJSObject(aObj) { return Cu.isXrayWrapper(aObj); }; + diff --git a/toolkit/devtools/Loader.jsm b/toolkit/devtools/Loader.jsm index e39c8b1fc73..2b0970a94a3 100644 --- a/toolkit/devtools/Loader.jsm +++ b/toolkit/devtools/Loader.jsm @@ -71,7 +71,6 @@ BuiltinProvider.prototype = { "devtools/client": "resource://gre/modules/devtools/client", "devtools/pretty-fast": "resource://gre/modules/devtools/pretty-fast.js", "devtools/async-utils": "resource://gre/modules/devtools/async-utils", - "devtools/content-observer": "resource://gre/modules/devtools/content-observer", "gcli": "resource://gre/modules/devtools/gcli", "acorn": "resource://gre/modules/devtools/acorn", "acorn/util/walk": "resource://gre/modules/devtools/acorn/walk.js", diff --git a/toolkit/devtools/content-observer.js b/toolkit/devtools/content-observer.js deleted file mode 100644 index 5c859a28753..00000000000 --- a/toolkit/devtools/content-observer.js +++ /dev/null @@ -1,72 +0,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/. */ -"use strict"; - -const {Cc, Ci, Cu, Cr} = require("chrome"); -const {Services} = Cu.import("resource://gre/modules/Services.jsm", {}); - -const events = require("sdk/event/core"); -const promise = require("sdk/core/promise"); - -/** - * Handles adding an observer for the creation of content document globals, - * event sent immediately after a web content document window has been set up, - * but before any script code has been executed. - */ -function ContentObserver(tabActor) { - this._contentWindow = tabActor.window; - this._onContentGlobalCreated = this._onContentGlobalCreated.bind(this); - this._onInnerWindowDestroyed = this._onInnerWindowDestroyed.bind(this); - this.startListening(); -} - -module.exports.ContentObserver = ContentObserver; - -ContentObserver.prototype = { - /** - * Starts listening for the required observer messages. - */ - startListening: function() { - Services.obs.addObserver( - this._onContentGlobalCreated, "content-document-global-created", false); - Services.obs.addObserver( - this._onInnerWindowDestroyed, "inner-window-destroyed", false); - }, - - /** - * Stops listening for the required observer messages. - */ - stopListening: function() { - Services.obs.removeObserver( - this._onContentGlobalCreated, "content-document-global-created", false); - Services.obs.removeObserver( - this._onInnerWindowDestroyed, "inner-window-destroyed", false); - }, - - /** - * Fired immediately after a web content document window has been set up. - */ - _onContentGlobalCreated: function(subject, topic, data) { - if (subject == this._contentWindow) { - events.emit(this, "global-created", subject); - } - }, - - /** - * Fired when an inner window is removed from the backward/forward cache. - */ - _onInnerWindowDestroyed: function(subject, topic, data) { - let id = subject.QueryInterface(Ci.nsISupportsPRUint64).data; - events.emit(this, "global-destroyed", id); - } -}; - -// Utility functions. - -ContentObserver.GetInnerWindowID = function(window) { - return window - .QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindowUtils) - .currentInnerWindowID; -}; diff --git a/toolkit/devtools/server/actors/call-watcher.js b/toolkit/devtools/server/actors/call-watcher.js deleted file mode 100644 index 3c66b1aa6a4..00000000000 --- a/toolkit/devtools/server/actors/call-watcher.js +++ /dev/null @@ -1,559 +0,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/. */ -"use strict"; - -const {Cc, Ci, Cu, Cr} = require("chrome"); -const events = require("sdk/event/core"); -const promise = require("sdk/core/promise"); -const protocol = require("devtools/server/protocol"); -const {ContentObserver} = require("devtools/content-observer"); - -const {on, once, off, emit} = events; -const {method, Arg, Option, RetVal} = protocol; - -exports.register = function(handle) { - handle.addTabActor(CallWatcherActor, "callWatcherActor"); -}; - -exports.unregister = function(handle) { - handle.removeTabActor(CallWatcherActor); -}; - -/** - * Type describing a single function call in a stack trace. - */ -protocol.types.addDictType("call-stack-item", { - name: "string", - file: "string", - line: "number" -}); - -/** - * Type describing an overview of a function call. - */ -protocol.types.addDictType("call-details", { - type: "number", - name: "string", - stack: "array:call-stack-item" -}); - -/** - * This actor contains information about a function call, like the function - * type, name, stack, arguments, returned value etc. - */ -let FunctionCallActor = protocol.ActorClass({ - typeName: "function-call", - - /** - * Creates the function call actor. - * - * @param DebuggerServerConnection conn - * The server connection. - * @param DOMWindow window - * The content window. - * @param string global - * The name of the global object owning this function, like - * "CanvasRenderingContext2D" or "WebGLRenderingContext". - * @param object caller - * The object owning the function when it was called. - * For example, in `foo.bar()`, the caller is `foo`. - * @param number type - * Either METHOD_FUNCTION, METHOD_GETTER or METHOD_SETTER. - * @param string name - * The called function's name. - * @param array stack - * The called function's stack, as a list of { name, file, line } objects. - * @param array args - * The called function's arguments. - * @param any result - * The value returned by the function call. - */ - initialize: function(conn, [window, global, caller, type, name, stack, args, result]) { - protocol.Actor.prototype.initialize.call(this, conn); - - this.details = { - window: window, - caller: caller, - type: type, - name: name, - stack: stack, - args: args, - return: result - }; - - this.meta = { - global: -1, - previews: { caller: "", args: "" } - }; - - if (global == "WebGLRenderingContext") { - this.meta.global = CallWatcherFront.CANVAS_WEBGL_CONTEXT; - } else if (global == "CanvasRenderingContext2D") { - this.meta.global = CallWatcherFront.CANVAS_2D_CONTEXT; - } else if (global == "window") { - this.meta.global = CallWatcherFront.UNKNOWN_SCOPE; - } else { - this.meta.global = CallWatcherFront.GLOBAL_SCOPE; - } - - this.meta.previews.caller = this._generateCallerPreview(); - this.meta.previews.args = this._generateArgsPreview(); - }, - - /** - * Customize the marshalling of this actor to provide some generic information - * directly on the Front instance. - */ - form: function() { - return { - actor: this.actorID, - type: this.details.type, - name: this.details.name, - file: this.details.stack[0].file, - line: this.details.stack[0].line, - callerPreview: this.meta.previews.caller, - argsPreview: this.meta.previews.args - }; - }, - - /** - * Gets more information about this function call, which is not necessarily - * available on the Front instance. - */ - getDetails: method(function() { - let { type, name, stack } = this.details; - - // Since not all calls on the stack have corresponding owner files (e.g. - // callbacks of a requestAnimationFrame etc.), there's no benefit in - // returning them, as the user can't jump to the Debugger from them. - for (let i = stack.length - 1;;) { - if (stack[i].file) { - break; - } - stack.pop(); - i--; - } - - // XXX: Use grips for objects and serialize them properly, in order - // to add the function's caller, arguments and return value. Bug 978957. - return { - type: type, - name: name, - stack: stack - }; - }, { - response: { info: RetVal("call-details") } - }), - - /** - * Serializes the caller's name so that it can be easily be transferred - * as a string, but still be useful when displayed in a potential UI. - * - * @return string - * The caller's name as a string. - */ - _generateCallerPreview: function() { - let global = this.meta.global; - if (global == CallWatcherFront.CANVAS_WEBGL_CONTEXT) { - return "gl"; - } - if (global == CallWatcherFront.CANVAS_2D_CONTEXT) { - return "ctx"; - } - return ""; - }, - - /** - * Serializes the arguments so that they can be easily be transferred - * as a string, but still be useful when displayed in a potential UI. - * - * @return string - * The arguments as a string. - */ - _generateArgsPreview: function() { - let { caller, args } = this.details; - let { global } = this.meta; - - // XXX: All of this sucks. Make this smarter, so that the frontend - // can inspect each argument, be it object or primitive. Bug 978960. - let serializeArgs = () => args.map(arg => { - if (typeof arg == "undefined") { - return "undefined"; - } - if (typeof arg == "function") { - return "Function"; - } - if (typeof arg == "object") { - return "Object"; - } - if (global == CallWatcherFront.CANVAS_WEBGL_CONTEXT) { - // XXX: This doesn't handle combined bitmasks. Bug 978964. - return getEnumsLookupTable("webgl", caller)[arg] || arg; - } - if (global == CallWatcherFront.CANVAS_2D_CONTEXT) { - return getEnumsLookupTable("2d", caller)[arg] || arg; - } - return arg; - }); - - return serializeArgs().join(", "); - } -}); - -/** - * The corresponding Front object for the FunctionCallActor. - */ -let FunctionCallFront = protocol.FrontClass(FunctionCallActor, { - initialize: function(client, form) { - protocol.Front.prototype.initialize.call(this, client, form); - }, - - /** - * Adds some generic information directly to this instance, - * to avoid extra roundtrips. - */ - form: function(form) { - this.actorID = form.actor; - this.type = form.type; - this.name = form.name; - this.file = form.file; - this.line = form.line; - this.callerPreview = form.callerPreview; - this.argsPreview = form.argsPreview; - } -}); - -/** - * This actor observes function calls on certain objects or globals. - */ -let CallWatcherActor = exports.CallWatcherActor = protocol.ActorClass({ - typeName: "call-watcher", - initialize: function(conn, tabActor) { - protocol.Actor.prototype.initialize.call(this, conn); - this.tabActor = tabActor; - this._onGlobalCreated = this._onGlobalCreated.bind(this); - this._onGlobalDestroyed = this._onGlobalDestroyed.bind(this); - this._onContentFunctionCall = this._onContentFunctionCall.bind(this); - }, - destroy: function(conn) { - protocol.Actor.prototype.destroy.call(this, conn); - this.finalize(); - }, - - /** - * Starts waiting for the current tab actor's document global to be - * created, in order to instrument the specified objects and become - * aware of everything the content does with them. - */ - setup: method(function({ tracedGlobals, tracedFunctions, startRecording, performReload }) { - if (this._initialized) { - return; - } - this._initialized = true; - - this._functionCalls = []; - this._tracedGlobals = tracedGlobals || []; - this._tracedFunctions = tracedFunctions || []; - this._contentObserver = new ContentObserver(this.tabActor); - - on(this._contentObserver, "global-created", this._onGlobalCreated); - on(this._contentObserver, "global-destroyed", this._onGlobalDestroyed); - - if (startRecording) { - this.resumeRecording(); - } - if (performReload) { - this.tabActor.window.location.reload(); - } - }, { - request: { - tracedGlobals: Option(0, "nullable:array:string"), - tracedFunctions: Option(0, "nullable:array:string"), - startRecording: Option(0, "boolean"), - performReload: Option(0, "boolean") - }, - oneway: true - }), - - /** - * Stops listening for document global changes and puts this actor - * to hibernation. This method is called automatically just before the - * actor is destroyed. - */ - finalize: method(function() { - if (!this._initialized) { - return; - } - this._initialized = false; - - this._contentObserver.stopListening(); - off(this._contentObserver, "global-created", this._onGlobalCreated); - off(this._contentObserver, "global-destroyed", this._onGlobalDestroyed); - - this._tracedGlobals = null; - this._tracedFunctions = null; - this._contentObserver = null; - }, { - oneway: true - }), - - /** - * Returns whether the instrumented function calls are currently recorded. - */ - isRecording: method(function() { - return this._recording; - }, { - response: RetVal("boolean") - }), - - /** - * Starts recording function calls. - */ - resumeRecording: method(function() { - this._recording = true; - }), - - /** - * Stops recording function calls. - */ - pauseRecording: method(function() { - this._recording = false; - return this._functionCalls; - }, { - response: { calls: RetVal("array:function-call") } - }), - - /** - * Erases all the recorded function calls. - * Calling `resumeRecording` or `pauseRecording` does not erase history. - */ - eraseRecording: method(function() { - this._functionCalls = []; - }), - - /** - * Lightweight listener invoked whenever an instrumented function is called - * while recording. We're doing this to avoid the event emitter overhead, - * since this is expected to be a very hot function. - */ - onCall: function() {}, - - /** - * Invoked whenever the current tab actor's document global is created. - */ - _onGlobalCreated: function(window) { - let self = this; - - this._tracedWindowId = ContentObserver.GetInnerWindowID(window); - let unwrappedWindow = XPCNativeWrapper.unwrap(window); - let callback = this._onContentFunctionCall; - - for (let global of this._tracedGlobals) { - let prototype = unwrappedWindow[global].prototype; - let properties = Object.keys(prototype); - properties.forEach(name => overrideSymbol(global, prototype, name, callback)); - } - - for (let name of this._tracedFunctions) { - overrideSymbol("window", unwrappedWindow, name, callback); - } - - /** - * Instruments a method, getter or setter on the specified target object to - * invoke a callback whenever it is called. - */ - function overrideSymbol(global, target, name, callback) { - let propertyDescriptor = Object.getOwnPropertyDescriptor(target, name); - - if (propertyDescriptor.get || propertyDescriptor.set) { - overrideAccessor(global, target, name, propertyDescriptor, callback); - return; - } - if (propertyDescriptor.writable && typeof propertyDescriptor.value == "function") { - overrideFunction(global, target, name, propertyDescriptor, callback); - return; - } - } - - /** - * Instruments a function on the specified target object. - */ - function overrideFunction(global, target, name, descriptor, callback) { - let originalFunc = target[name]; - - Object.defineProperty(target, name, { - value: function(...args) { - let result = originalFunc.apply(this, args); - - if (self._recording) { - let stack = getStack(name); - let type = CallWatcherFront.METHOD_FUNCTION; - callback(unwrappedWindow, global, this, type, name, stack, args, result); - } - return result; - }, - configurable: descriptor.configurable, - enumerable: descriptor.enumerable, - writable: true - }); - } - - /** - * Instruments a getter or setter on the specified target object. - */ - function overrideAccessor(global, target, name, descriptor, callback) { - let originalGetter = target.__lookupGetter__(name); - let originalSetter = target.__lookupSetter__(name); - - Object.defineProperty(target, name, { - get: function(...args) { - if (!originalGetter) return undefined; - let result = originalGetter.apply(this, args); - - if (self._recording) { - let stack = getStack(name); - let type = CallWatcherFront.GETTER_FUNCTION; - callback(unwrappedWindow, global, this, type, name, stack, args, result); - } - return result; - }, - set: function(...args) { - if (!originalSetter) return; - originalSetter.apply(this, args); - - if (self._recording) { - let stack = getStack(name); - let type = CallWatcherFront.SETTER_FUNCTION; - callback(unwrappedWindow, global, this, type, name, stack, args, undefined); - } - }, - configurable: descriptor.configurable, - enumerable: descriptor.enumerable - }); - } - - /** - * Stores the relevant information about calls on the stack when - * a function is called. - */ - function getStack(caller) { - try { - // Using Components.stack wouldn't be a better idea, since it's - // much slower because it attempts to retrieve the C++ stack as well. - throw new Error(); - } catch (e) { - var stack = e.stack; - } - - // Of course, using a simple regex like /(.*?)@(.*):(\d*):\d*/ would be - // much prettier, but this is a very hot function, so let's sqeeze - // every drop of performance out of it. - let calls = []; - let callIndex = 0; - let currNewLinePivot = stack.indexOf("\n") + 1; - let nextNewLinePivot = stack.indexOf("\n", currNewLinePivot); - - while (nextNewLinePivot > 0) { - let nameDelimiterIndex = stack.indexOf("@", currNewLinePivot); - let columnDelimiterIndex = stack.lastIndexOf(":", nextNewLinePivot - 1); - let lineDelimiterIndex = stack.lastIndexOf(":", columnDelimiterIndex - 1); - - if (!calls[callIndex]) { - calls[callIndex] = { name: "", file: "", line: 0 }; - } - if (!calls[callIndex + 1]) { - calls[callIndex + 1] = { name: "", file: "", line: 0 }; - } - - if (callIndex > 0) { - let file = stack.substring(nameDelimiterIndex + 1, lineDelimiterIndex); - let line = stack.substring(lineDelimiterIndex + 1, columnDelimiterIndex); - let name = stack.substring(currNewLinePivot, nameDelimiterIndex); - calls[callIndex].name = name; - calls[callIndex - 1].file = file; - calls[callIndex - 1].line = line; - } else { - // Since the topmost stack frame is actually our overwritten function, - // it will not have the expected name. - calls[0].name = caller; - } - - currNewLinePivot = nextNewLinePivot + 1; - nextNewLinePivot = stack.indexOf("\n", currNewLinePivot); - callIndex++; - } - - return calls; - } - }, - - /** - * Invoked whenever the current tab actor's inner window is destroyed. - */ - _onGlobalDestroyed: function(id) { - if (this._tracedWindowId == id) { - this.pauseRecording(); - this.eraseRecording(); - } - }, - - /** - * Invoked whenever an instrumented function is called. - */ - _onContentFunctionCall: function(...details) { - let functionCall = new FunctionCallActor(this.conn, details); - this._functionCalls.push(functionCall); - this.onCall(functionCall); - } -}); - -/** - * The corresponding Front object for the CallWatcherActor. - */ -let CallWatcherFront = exports.CallWatcherFront = protocol.FrontClass(CallWatcherActor, { - initialize: function(client, { callWatcherActor }) { - protocol.Front.prototype.initialize.call(this, client, { actor: callWatcherActor }); - client.addActorPool(this); - this.manage(this); - } -}); - -/** - * Constants. - */ -CallWatcherFront.METHOD_FUNCTION = 0; -CallWatcherFront.GETTER_FUNCTION = 1; -CallWatcherFront.SETTER_FUNCTION = 2; - -CallWatcherFront.GLOBAL_SCOPE = 0; -CallWatcherFront.UNKNOWN_SCOPE = 1; -CallWatcherFront.CANVAS_WEBGL_CONTEXT = 2; -CallWatcherFront.CANVAS_2D_CONTEXT = 3; - -/** - * A lookup table for cross-referencing flags or properties with their name - * assuming they look LIKE_THIS most of the time. - * - * For example, when gl.clear(gl.COLOR_BUFFER_BIT) is called, the actual passed - * argument's value is 16384, which we want identified as "COLOR_BUFFER_BIT". - */ -var gEnumRegex = /^[A-Z_]+$/; -var gEnumsLookupTable = {}; - -function getEnumsLookupTable(type, object) { - let cachedEnum = gEnumsLookupTable[type]; - if (cachedEnum) { - return cachedEnum; - } - - let table = gEnumsLookupTable[type] = {}; - - for (let key in object) { - if (key.match(gEnumRegex)) { - table[object[key]] = key; - } - } - - return table; -} diff --git a/toolkit/devtools/server/actors/canvas.js b/toolkit/devtools/server/actors/canvas.js deleted file mode 100644 index 3ee14defee6..00000000000 --- a/toolkit/devtools/server/actors/canvas.js +++ /dev/null @@ -1,759 +0,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/. */ -"use strict"; - -const {Cc, Ci, Cu, Cr} = require("chrome"); -const events = require("sdk/event/core"); -const promise = require("sdk/core/promise"); -const protocol = require("devtools/server/protocol"); -const {CallWatcherActor, CallWatcherFront} = require("devtools/server/actors/call-watcher"); -const DevToolsUtils = require("devtools/toolkit/DevToolsUtils.js"); - -const {on, once, off, emit} = events; -const {method, custom, Arg, Option, RetVal} = protocol; - -const CANVAS_CONTEXTS = [ - "CanvasRenderingContext2D", - "WebGLRenderingContext" -]; - -const ANIMATION_GENERATORS = [ - "requestAnimationFrame", - "mozRequestAnimationFrame" -]; - -const DRAW_CALLS = [ - // 2D canvas - "fill", - "stroke", - "clearRect", - "fillRect", - "strokeRect", - "fillText", - "strokeText", - "drawImage", - - // WebGL - "clear", - "drawArrays", - "drawElements", - "finish", - "flush" -]; - -const INTERESTING_CALLS = [ - // 2D canvas - "save", - "restore", - - // WebGL - "useProgram" -]; - -exports.register = function(handle) { - handle.addTabActor(CanvasActor, "canvasActor"); -}; - -exports.unregister = function(handle) { - handle.removeTabActor(CanvasActor); -}; - -/** - * Type representing an Uint32Array buffer, serialized fast(er). - * - * XXX: It would be nice if on local connections (only), we could just *give* - * the buffer directly to the front, instead of going through all this - * serialization redundancy. - */ -protocol.types.addType("uint32-array", { - write: (v) => "[" + Array.join(v, ",") + "]", - read: (v) => new Uint32Array(JSON.parse(v)) -}); - -/** - * Type describing a thumbnail or screenshot in a recorded animation frame. - */ -protocol.types.addDictType("snapshot-image", { - index: "number", - width: "number", - height: "number", - flipped: "boolean", - pixels: "uint32-array" -}); - -/** - * Type describing an overview of a recorded animation frame. - */ -protocol.types.addDictType("snapshot-overview", { - calls: "array:function-call", - thumbnails: "array:snapshot-image", - screenshot: "snapshot-image" -}); - -/** - * This actor represents a recorded animation frame snapshot, along with - * all the corresponding canvas' context methods invoked in that frame, - * thumbnails for each draw call and a screenshot of the end result. - */ -let FrameSnapshotActor = protocol.ActorClass({ - typeName: "frame-snapshot", - - /** - * Creates the frame snapshot call actor. - * - * @param DebuggerServerConnection conn - * The server connection. - * @param HTMLCanvasElement canvas - * A reference to the content canvas. - * @param array calls - * An array of "function-call" actor instances. - * @param object screenshot - * A single "snapshot-image" type instance. - */ - initialize: function(conn, { canvas, calls, screenshot }) { - protocol.Actor.prototype.initialize.call(this, conn); - this._contentCanvas = canvas; - this._functionCalls = calls; - this._lastDrawCallScreenshot = screenshot; - }, - - /** - * Gets as much data about this snapshot without computing anything costly. - */ - getOverview: method(function() { - return { - calls: this._functionCalls, - thumbnails: this._functionCalls.map(e => e._thumbnail).filter(e => !!e), - screenshot: this._lastDrawCallScreenshot - }; - }, { - response: { overview: RetVal("snapshot-overview") } - }), - - /** - * Gets a screenshot of the canvas's contents after the specified - * function was called. - */ - generateScreenshotFor: method(function(functionCall) { - let caller = functionCall.details.caller; - let global = functionCall.meta.global; - - let canvas = this._contentCanvas; - let calls = this._functionCalls; - let index = calls.indexOf(functionCall); - - // To get a screenshot, replay all the steps necessary to render the frame, - // by invoking the context calls up to and including the specified one. - // This will be done in a custom framebuffer in case of a WebGL context. - let { replayContext, lastDrawCallIndex } = ContextUtils.replayAnimationFrame({ - contextType: global, - canvas: canvas, - calls: calls, - first: 0, - last: index - }); - - // To keep things fast, generate an image that's relatively small. - let dimensions = Math.min(CanvasFront.SCREENSHOT_HEIGHT_MAX, canvas.height); - let screenshot; - - // Depending on the canvas' context, generating a screenshot is done - // in different ways. In case of the WebGL context, we also need to reset - // the framebuffer binding to the default value. - if (global == CallWatcherFront.CANVAS_WEBGL_CONTEXT) { - screenshot = ContextUtils.getPixelsForWebGL(replayContext); - replayContext.bindFramebuffer(replayContext.FRAMEBUFFER, null); - screenshot.flipped = true; - } - // In case of 2D contexts, no additional special treatment is necessary. - else if (global == CallWatcherFront.CANVAS_2D_CONTEXT) { - screenshot = ContextUtils.getPixelsFor2D(replayContext); - screenshot.flipped = false; - } - - screenshot.index = lastDrawCallIndex; - return screenshot; - }, { - request: { call: Arg(0, "function-call") }, - response: { screenshot: RetVal("snapshot-image") } - }) -}); - -/** - * The corresponding Front object for the FrameSnapshotActor. - */ -let FrameSnapshotFront = protocol.FrontClass(FrameSnapshotActor, { - initialize: function(client, form) { - protocol.Front.prototype.initialize.call(this, client, form); - this._lastDrawCallScreenshot = null; - this._cachedScreenshots = new WeakMap(); - }, - - /** - * This implementation caches the last draw call screenshot to optimize - * frontend requests to `generateScreenshotFor`. - */ - getOverview: custom(function() { - return this._getOverview().then(data => { - this._lastDrawCallScreenshot = data.screenshot; - return data; - }); - }, { - impl: "_getOverview" - }), - - /** - * This implementation saves a roundtrip to the backend if the screenshot - * was already generated and retrieved once. - */ - generateScreenshotFor: custom(function(functionCall) { - if (CanvasFront.ANIMATION_GENERATORS.has(functionCall.name)) { - return promise.resolve(this._lastDrawCallScreenshot); - } - let cachedScreenshot = this._cachedScreenshots.get(functionCall); - if (cachedScreenshot) { - return cachedScreenshot; - } - let screenshot = this._generateScreenshotFor(functionCall); - this._cachedScreenshots.set(functionCall, screenshot); - return screenshot; - }, { - impl: "_generateScreenshotFor" - }) -}); - -/** - * This Canvas Actor handles simple instrumentation of all the methods - * of a 2D or WebGL context, to provide information regarding all the calls - * made when drawing frame inside an animation loop. - */ -let CanvasActor = exports.CanvasActor = protocol.ActorClass({ - typeName: "canvas", - initialize: function(conn, tabActor) { - protocol.Actor.prototype.initialize.call(this, conn); - this.tabActor = tabActor; - this._onContentFunctionCall = this._onContentFunctionCall.bind(this); - }, - destroy: function(conn) { - protocol.Actor.prototype.destroy.call(this, conn); - this.finalize(); - }, - - /** - * Starts listening for function calls. - */ - setup: method(function({ reload }) { - if (this._initialized) { - return; - } - this._initialized = true; - - this._callWatcher = new CallWatcherActor(this.conn, this.tabActor); - this._callWatcher.onCall = this._onContentFunctionCall; - this._callWatcher.setup({ - tracedGlobals: CANVAS_CONTEXTS, - tracedFunctions: ANIMATION_GENERATORS, - performReload: reload - }); - }, { - request: { reload: Option(0, "boolean") }, - oneway: true - }), - - /** - * Stops listening for function calls. - */ - finalize: method(function() { - if (!this._initialized) { - return; - } - this._initialized = false; - - this._callWatcher.finalize(); - this._callWatcher = null; - }, { - oneway: true - }), - - /** - * Returns whether this actor has been set up. - */ - isInitialized: method(function() { - return !!this._initialized; - }, { - response: { initialized: RetVal("boolean") } - }), - - /** - * Records a snapshot of all the calls made during the next animation frame. - * The animation should be implemented via the de-facto requestAnimationFrame - * utility, not inside a `setInterval` or recursive `setTimeout`. - * - * XXX: Currently only supporting requestAnimationFrame. When this isn't used, - * it'd be a good idea to display a huge red flashing banner telling people to - * STOP USING `setInterval` OR `setTimeout` FOR ANIMATION. Bug 978948. - */ - recordAnimationFrame: method(function() { - if (this._callWatcher.isRecording()) { - return this._currentAnimationFrameSnapshot.promise; - } - - this._callWatcher.eraseRecording(); - this._callWatcher.resumeRecording(); - - let deferred = this._currentAnimationFrameSnapshot = promise.defer(); - return deferred.promise; - }, { - response: { snapshot: RetVal("frame-snapshot") } - }), - - /** - * Invoked whenever an instrumented function is called, be it on a - * 2d or WebGL context, or an animation generator like requestAnimationFrame. - */ - _onContentFunctionCall: function(functionCall) { - let { window, name, args } = functionCall.details; - - // The function call arguments are required to replay animation frames, - // in order to generate screenshots. However, simply storing references to - // every kind of object is a bad idea, since their properties may change. - // Consider transformation matrices for example, which are typically - // Float32Arrays whose values can easily change across context calls. - // They need to be cloned. - inplaceShallowCloneArrays(args, window); - - if (CanvasFront.ANIMATION_GENERATORS.has(name)) { - this._handleAnimationFrame(functionCall); - return; - } - if (CanvasFront.DRAW_CALLS.has(name) && this._animationStarted) { - this._handleDrawCall(functionCall); - return; - } - }, - - /** - * Handle animations generated using requestAnimationFrame. - */ - _handleAnimationFrame: function(functionCall) { - if (!this._animationStarted) { - this._handleAnimationFrameBegin(); - } else { - this._handleAnimationFrameEnd(functionCall); - } - }, - - /** - * Called whenever an animation frame rendering begins. - */ - _handleAnimationFrameBegin: function() { - this._callWatcher.eraseRecording(); - this._animationStarted = true; - }, - - /** - * Called whenever an animation frame rendering ends. - */ - _handleAnimationFrameEnd: function() { - // Get a hold of all the function calls made during this animation frame. - // Since only one snapshot can be recorded at a time, erase all the - // previously recorded calls. - let functionCalls = this._callWatcher.pauseRecording(); - this._callWatcher.eraseRecording(); - - // Since the animation frame finished, get a hold of the (already retrieved) - // canvas pixels to conveniently create a screenshot of the final rendering. - let index = this._lastDrawCallIndex; - let width = this._lastContentCanvasWidth; - let height = this._lastContentCanvasHeight; - let flipped = this._lastThumbnailFlipped; - let pixels = ContextUtils.getPixelStorage()["32bit"]; - let lastDrawCallScreenshot = { - index: index, - width: width, - height: height, - flipped: flipped, - pixels: pixels.subarray(0, width * height) - }; - - // Wrap the function calls and screenshot in a FrameSnapshotActor instance, - // which will resolve the promise returned by `recordAnimationFrame`. - let frameSnapshot = new FrameSnapshotActor(this.conn, { - canvas: this._lastDrawCallCanvas, - calls: functionCalls, - screenshot: lastDrawCallScreenshot - }); - - this._currentAnimationFrameSnapshot.resolve(frameSnapshot); - this._currentAnimationFrameSnapshot = null; - this._animationStarted = false; - }, - - /** - * Invoked whenever a draw call is detected in the animation frame which is - * currently being recorded. - */ - _handleDrawCall: function(functionCall) { - let functionCalls = this._callWatcher.pauseRecording(); - let caller = functionCall.details.caller; - let global = functionCall.meta.global; - - let contentCanvas = this._lastDrawCallCanvas = caller.canvas; - let index = this._lastDrawCallIndex = functionCalls.indexOf(functionCall); - let w = this._lastContentCanvasWidth = contentCanvas.width; - let h = this._lastContentCanvasHeight = contentCanvas.height; - - // To keep things fast, generate images of small and fixed dimensions. - let dimensions = CanvasFront.THUMBNAIL_HEIGHT; - let thumbnail; - - // Create a thumbnail on every draw call on the canvas context, to augment - // the respective function call actor with this additional data. - if (global == CallWatcherFront.CANVAS_WEBGL_CONTEXT) { - // Check if drawing to a custom framebuffer (when rendering to texture). - // Don't create a thumbnail in this particular case. - let framebufferBinding = caller.getParameter(caller.FRAMEBUFFER_BINDING); - if (framebufferBinding == null) { - thumbnail = ContextUtils.getPixelsForWebGL(caller, 0, 0, w, h, dimensions); - thumbnail.flipped = this._lastThumbnailFlipped = true; - thumbnail.index = index; - } - } else if (global == CallWatcherFront.CANVAS_2D_CONTEXT) { - thumbnail = ContextUtils.getPixelsFor2D(caller, 0, 0, w, h, dimensions); - thumbnail.flipped = this._lastThumbnailFlipped = false; - thumbnail.index = index; - } - - functionCall._thumbnail = thumbnail; - this._callWatcher.resumeRecording(); - } -}); - -/** - * A collection of methods for manipulating canvas contexts. - */ -let ContextUtils = { - /** - * WebGL contexts are sensitive to how they're queried. Use this function - * to make sure the right context is always retrieved, if available. - * - * @param HTMLCanvasElement canvas - * The canvas element for which to get a WebGL context. - * @param WebGLRenderingContext gl - * The queried WebGL context, or null if unavailable. - */ - getWebGLContext: function(canvas) { - return canvas.getContext("webgl") || - canvas.getContext("experimental-webgl"); - }, - - /** - * Gets a hold of the rendered pixels in the most efficient way possible for - * a canvas with a WebGL context. - * - * @param WebGLRenderingContext gl - * The WebGL context to get a screenshot from. - * @param number srcX [optional] - * The first left pixel that is read from the framebuffer. - * @param number srcY [optional] - * The first top pixel that is read from the framebuffer. - * @param number srcWidth [optional] - * The number of pixels to read on the X axis. - * @param number srcHeight [optional] - * The number of pixels to read on the Y axis. - * @param number dstHeight [optional] - * The desired generated screenshot height. - * @return object - * An objet containing the screenshot's width, height and pixel data. - */ - getPixelsForWebGL: function(gl, - srcX = 0, srcY = 0, - srcWidth = gl.canvas.width, - srcHeight = gl.canvas.height, - dstHeight = srcHeight) - { - let contentPixels = ContextUtils.getPixelStorage(srcWidth, srcHeight); - let { "8bit": charView, "32bit": intView } = contentPixels; - gl.readPixels(srcX, srcY, srcWidth, srcHeight, gl.RGBA, gl.UNSIGNED_BYTE, charView); - return this.resizePixels(intView, srcWidth, srcHeight, dstHeight); - }, - - /** - * Gets a hold of the rendered pixels in the most efficient way possible for - * a canvas with a 2D context. - * - * @param CanvasRenderingContext2D ctx - * The 2D context to get a screenshot from. - * @param number srcX [optional] - * The first left pixel that is read from the canvas. - * @param number srcY [optional] - * The first top pixel that is read from the canvas. - * @param number srcWidth [optional] - * The number of pixels to read on the X axis. - * @param number srcHeight [optional] - * The number of pixels to read on the Y axis. - * @param number dstHeight [optional] - * The desired generated screenshot height. - * @return object - * An objet containing the screenshot's width, height and pixel data. - */ - getPixelsFor2D: function(ctx, - srcX = 0, srcY = 0, - srcWidth = ctx.canvas.width, - srcHeight = ctx.canvas.height, - dstHeight = srcHeight) - { - let { data } = ctx.getImageData(srcX, srcY, srcWidth, srcHeight); - let { "32bit": intView } = ContextUtils.usePixelStorage(data.buffer); - return this.resizePixels(intView, srcWidth, srcHeight, dstHeight); - }, - - /** - * Resizes the provided pixels to fit inside a rectangle with the specified - * height and the same aspect ratio as the source. - * - * @param Uint32Array srcPixels - * The source pixel data, assuming 32bit/pixel and 4 color components. - * @param number srcWidth - * The source pixel data width. - * @param number srcHeight - * The source pixel data height. - * @param number dstHeight [optional] - * The desired resized pixel data height. - * @return object - * An objet containing the resized pixels width, height and data. - */ - resizePixels: function(srcPixels, srcWidth, srcHeight, dstHeight) { - let screenshotRatio = dstHeight / srcHeight; - let dstWidth = Math.floor(srcWidth * screenshotRatio); - - // Use a plain array instead of a Uint32Array to make serializing faster. - let dstPixels = new Array(dstWidth * dstHeight); - - // If the resized image ends up being completely transparent, returning - // an empty array will skip some redundant serialization cycles. - let isTransparent = true; - - for (let dstX = 0; dstX < dstWidth; dstX++) { - for (let dstY = 0; dstY < dstHeight; dstY++) { - let srcX = Math.floor(dstX / screenshotRatio); - let srcY = Math.floor(dstY / screenshotRatio); - let cPos = srcX + srcWidth * srcY; - let dPos = dstX + dstWidth * dstY; - let color = dstPixels[dPos] = srcPixels[cPos]; - if (color) { - isTransparent = false; - } - } - } - - return { - width: dstWidth, - height: dstHeight, - pixels: isTransparent ? [] : dstPixels - }; - }, - - /** - * Invokes a series of canvas context calls, to "replay" an animation frame - * and generate a screenshot. - * - * In case of a WebGL context, an offscreen framebuffer is created for - * the respective canvas, and the rendering will be performed into it. - * This is necessary because some state (like shaders, textures etc.) can't - * be shared between two different WebGL contexts. - * Hopefully, once SharedResources are a thing this won't be necessary: - * http://www.khronos.org/webgl/wiki/SharedResouces - * - * In case of a 2D context, a new canvas is created, since there's no - * intrinsic state that can't be easily duplicated. - * - * @param number contexType - * The type of context to use. See the CallWatcherFront scope types. - * @param HTMLCanvasElement canvas - * The canvas element which is the source of all context calls. - * @param array calls - * An array of function call actors. - * @param number first - * The first function call to start from. - * @param number last - * The last (inclusive) function call to end at. - * @return object - * The context on which the specified calls were invoked and the - * last registered draw call's index. - */ - replayAnimationFrame: function({ contextType, canvas, calls, first, last }) { - let w = canvas.width; - let h = canvas.height; - - let replayCanvas; - let replayContext; - let customFramebuffer; - let lastDrawCallIndex = -1; - - // In case of WebGL contexts, rendering will be done offscreen, in a - // custom framebuffer, but on the provided canvas context. - if (contextType == CallWatcherFront.CANVAS_WEBGL_CONTEXT) { - replayCanvas = canvas; - replayContext = this.getWebGLContext(replayCanvas); - customFramebuffer = this.createBoundFramebuffer(replayContext, w, h); - } - // In case of 2D contexts, draw everything on a separate canvas context. - else if (contextType == CallWatcherFront.CANVAS_2D_CONTEXT) { - let contentDocument = canvas.ownerDocument; - replayCanvas = contentDocument.createElement("canvas"); - replayCanvas.width = w; - replayCanvas.height = h; - replayContext = replayCanvas.getContext("2d"); - replayContext.clearRect(0, 0, w, h); - } - - // Replay all the context calls up to and including the specified one. - for (let i = first; i <= last; i++) { - let { type, name, args } = calls[i].details; - - // Prevent WebGL context calls that try to reset the framebuffer binding - // to the default value, since we want to perform the rendering offscreen. - if (name == "bindFramebuffer" && args[1] == null) { - replayContext.bindFramebuffer(replayContext.FRAMEBUFFER, customFramebuffer); - } else { - if (type == CallWatcherFront.METHOD_FUNCTION) { - replayContext[name].apply(replayContext, args); - } else if (type == CallWatcherFront.SETTER_FUNCTION) { - replayContext[name] = args; - } else { - // Ignore getter calls. - } - if (CanvasFront.DRAW_CALLS.has(name)) { - lastDrawCallIndex = i; - } - } - } - - return { - replayContext: replayContext, - lastDrawCallIndex: lastDrawCallIndex - }; - }, - - /** - * Gets an object containing a buffer large enough to hold width * height - * pixels, assuming 32bit/pixel and 4 color components. - * - * This method avoids allocating memory and tries to reuse a common buffer - * as much as possible. - * - * @param number w - * The desired pixel array storage width. - * @param number h - * The desired pixel array storage height. - * @return object - * The requested pixel array buffer. - */ - getPixelStorage: function(w = 0, h = 0) { - let storage = this._currentPixelStorage; - if (storage && storage["32bit"].length >= w * h) { - return storage; - } - return this.usePixelStorage(new ArrayBuffer(w * h * 4)); - }, - - /** - * Creates and saves the array buffer views used by `getPixelStorage`. - * - * @param ArrayBuffer buffer - * The raw buffer used as storage for various array buffer views. - */ - usePixelStorage: function(buffer) { - let array8bit = new Uint8Array(buffer); - let array32bit = new Uint32Array(buffer); - return this._currentPixelStorage = { - "8bit": array8bit, - "32bit": array32bit - }; - }, - - /** - * Creates a framebuffer of the specified dimensions for a WebGL context, - * assuming a RGBA color buffer, a depth buffer and no stencil buffer. - * - * @param WebGLRenderingContext gl - * The WebGL context to create and bind a framebuffer for. - * @param number width - * The desired width of the renderbuffers. - * @param number height - * The desired height of the renderbuffers. - * @return WebGLFramebuffer - * The generated framebuffer object. - */ - createBoundFramebuffer: function(gl, width, height) { - let framebuffer = gl.createFramebuffer(); - gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer); - - // Use a texture as the color rendebuffer attachment, since consumenrs of - // this function will most likely want to read the rendered pixels back. - let colorBuffer = gl.createTexture(); - gl.bindTexture(gl.TEXTURE_2D, colorBuffer); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); - gl.generateMipmap(gl.TEXTURE_2D); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - - let depthBuffer = gl.createRenderbuffer(); - gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, width, height); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, colorBuffer, 0); - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthBuffer); - - gl.bindTexture(gl.TEXTURE_2D, null); - gl.bindRenderbuffer(gl.RENDERBUFFER, null); - - return framebuffer; - } -}; - -/** - * The corresponding Front object for the CanvasActor. - */ -let CanvasFront = exports.CanvasFront = protocol.FrontClass(CanvasActor, { - initialize: function(client, { canvasActor }) { - protocol.Front.prototype.initialize.call(this, client, { actor: canvasActor }); - client.addActorPool(this); - this.manage(this); - } -}); - -/** - * Constants. - */ -CanvasFront.CANVAS_CONTEXTS = new Set(CANVAS_CONTEXTS); -CanvasFront.ANIMATION_GENERATORS = new Set(ANIMATION_GENERATORS); -CanvasFront.DRAW_CALLS = new Set(DRAW_CALLS); -CanvasFront.INTERESTING_CALLS = new Set(INTERESTING_CALLS); -CanvasFront.THUMBNAIL_HEIGHT = 50; // px -CanvasFront.SCREENSHOT_HEIGHT_MAX = 256; // px -CanvasFront.INVALID_SNAPSHOT_IMAGE = { - index: -1, - width: 0, - height: 0, - pixels: [] -}; - -/** - * Goes through all the arguments and creates a one-level shallow copy - * of all arrays and array buffers. - */ -function inplaceShallowCloneArrays(functionArguments, contentWindow) { - let { Object, Array, ArrayBuffer } = contentWindow; - - functionArguments.forEach((arg, index, store) => { - if (arg instanceof Array) { - store[index] = arg.slice(); - } - if (arg instanceof Object && arg.buffer instanceof ArrayBuffer) { - store[index] = new arg.constructor(arg); - } - }); -} diff --git a/toolkit/devtools/server/actors/webgl.js b/toolkit/devtools/server/actors/webgl.js index e1707ecc4b5..9df1ce75e82 100644 --- a/toolkit/devtools/server/actors/webgl.js +++ b/toolkit/devtools/server/actors/webgl.js @@ -4,9 +4,9 @@ "use strict"; const {Cc, Ci, Cu, Cr} = require("chrome"); +const Services = require("Services"); const events = require("sdk/event/core"); const protocol = require("devtools/server/protocol"); -const { ContentObserver } = require("devtools/content-observer"); const { on, once, off, emit } = events; const { method, Arg, Option, RetVal } = protocol; @@ -293,7 +293,7 @@ let WebGLActor = exports.WebGLActor = protocol.ActorClass({ * This is useful for dealing with bfcache, when no new programs are linked. */ getPrograms: method(function() { - let id = ContentObserver.GetInnerWindowID(this.tabActor.window); + let id = getInnerWindowID(this.tabActor.window); return this._programActorsCache.filter(e => e.ownerWindow == id); }, { response: { programs: RetVal("array:gl-program") } @@ -346,6 +346,58 @@ let WebGLFront = exports.WebGLFront = protocol.FrontClass(WebGLActor, { } }); +/** + * Handles adding an observer for the creation of content document globals, + * event sent immediately after a web content document window has been set up, + * but before any script code has been executed. This will allow us to + * instrument the HTMLCanvasElement with the appropriate inspection methods. + */ +function ContentObserver(tabActor) { + this._contentWindow = tabActor.window; + this._onContentGlobalCreated = this._onContentGlobalCreated.bind(this); + this._onInnerWindowDestroyed = this._onInnerWindowDestroyed.bind(this); + this.startListening(); +} + +ContentObserver.prototype = { + /** + * Starts listening for the required observer messages. + */ + startListening: function() { + Services.obs.addObserver( + this._onContentGlobalCreated, "content-document-global-created", false); + Services.obs.addObserver( + this._onInnerWindowDestroyed, "inner-window-destroyed", false); + }, + + /** + * Stops listening for the required observer messages. + */ + stopListening: function() { + Services.obs.removeObserver( + this._onContentGlobalCreated, "content-document-global-created", false); + Services.obs.removeObserver( + this._onInnerWindowDestroyed, "inner-window-destroyed", false); + }, + + /** + * Fired immediately after a web content document window has been set up. + */ + _onContentGlobalCreated: function(subject, topic, data) { + if (subject == this._contentWindow) { + emit(this, "global-created", subject); + } + }, + + /** + * Fired when an inner window is removed from the backward/forward cache. + */ + _onInnerWindowDestroyed: function(subject, topic, data) { + let id = subject.QueryInterface(Ci.nsISupportsPRUint64).data; + emit(this, "global-destroyed", id); + } +}; + /** * Instruments a HTMLCanvasElement with the appropriate inspection methods. */ @@ -361,7 +413,7 @@ let WebGLInstrumenter = { handle: function(window, observer) { let self = this; - let id = ContentObserver.GetInnerWindowID(window); + let id = getInnerWindowID(window); let canvasElem = XPCNativeWrapper.unwrap(window.HTMLCanvasElement); let canvasPrototype = canvasElem.prototype; let originalGetContext = canvasPrototype.getContext; @@ -1302,6 +1354,13 @@ WebGLProxy.prototype = { // Utility functions. +function getInnerWindowID(window) { + return window + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils) + .currentInnerWindowID; +} + function removeFromMap(map, predicate) { for (let [key, value] of map) { if (predicate(value)) { diff --git a/toolkit/devtools/server/main.js b/toolkit/devtools/server/main.js index ab385abf96d..de306a0ed15 100644 --- a/toolkit/devtools/server/main.js +++ b/toolkit/devtools/server/main.js @@ -392,8 +392,6 @@ var DebuggerServer = { this.addActors("resource://gre/modules/devtools/server/actors/script.js"); this.addActors("resource://gre/modules/devtools/server/actors/webconsole.js"); this.registerModule("devtools/server/actors/inspector"); - this.registerModule("devtools/server/actors/call-watcher"); - this.registerModule("devtools/server/actors/canvas"); this.registerModule("devtools/server/actors/webgl"); this.registerModule("devtools/server/actors/stylesheets"); this.registerModule("devtools/server/actors/styleeditor"); @@ -402,9 +400,8 @@ var DebuggerServer = { this.registerModule("devtools/server/actors/tracer"); this.registerModule("devtools/server/actors/memory"); this.registerModule("devtools/server/actors/eventlooplag"); - if ("nsIProfiler" in Ci) { + if ("nsIProfiler" in Ci) this.addActors("resource://gre/modules/devtools/server/actors/profiler.js"); - } }, /**