diff options
Diffstat (limited to 'devtools/server')
179 files changed, 2282 insertions, 867 deletions
diff --git a/devtools/server/actors/accessibility/accessible.js b/devtools/server/actors/accessibility/accessible.js index 1866d0a91b..451fa7f99a 100644 --- a/devtools/server/actors/accessibility/accessible.js +++ b/devtools/server/actors/accessibility/accessible.js @@ -67,11 +67,9 @@ loader.lazyGetter( () => ChromeUtils.importESModule( "resource://gre/modules/ContentDOMReference.sys.mjs", - { - // ContentDOMReference needs to be retrieved from the shared global - // since it is a shared singleton. - loadInDevToolsLoader: false, - } + // ContentDOMReference needs to be retrieved from the shared global + // since it is a shared singleton. + { global: "shared" } ).ContentDOMReference ); diff --git a/devtools/server/actors/addon/addons.js b/devtools/server/actors/addon/addons.js index 95a3738d61..9d37df93e4 100644 --- a/devtools/server/actors/addon/addons.js +++ b/devtools/server/actors/addon/addons.js @@ -11,10 +11,11 @@ const { const { AddonManager } = ChromeUtils.importESModule( "resource://gre/modules/AddonManager.sys.mjs", - { loadInDevToolsLoader: false } + { global: "shared" } ); const { FileUtils } = ChromeUtils.importESModule( - "resource://gre/modules/FileUtils.sys.mjs" + "resource://gre/modules/FileUtils.sys.mjs", + { global: "contextual" } ); // This actor is used by DevTools as well as external tools such as webext-run @@ -43,12 +44,13 @@ class AddonsActor extends Actor { // so for now, there is no chance of calling this on Android. if (openDevTools) { // This module is typically loaded in the loader spawn by DevToolsStartup, - // in a distinct compartment thanks to useDistinctSystemPrincipalLoader and loadInDevToolsLoader flag. + // in a distinct compartment thanks to useDistinctSystemPrincipalLoader + // and global flag. // But here we want to reuse the shared module loader. // We do not want to load devtools.js in the server's distinct module loader. const loader = ChromeUtils.importESModule( "resource://devtools/shared/loader/Loader.sys.mjs", - { loadInDevToolsLoader: false } + { global: "shared" } ); const { gDevTools, diff --git a/devtools/server/actors/addon/webextension-inspected-window.js b/devtools/server/actors/addon/webextension-inspected-window.js index e69a206c9d..041045eec0 100644 --- a/devtools/server/actors/addon/webextension-inspected-window.js +++ b/devtools/server/actors/addon/webextension-inspected-window.js @@ -218,7 +218,7 @@ class CustomizedReload { return this.waitForReloadCompleted; } - observe(subject, topic, data) { + observe(subject, topic) { if (topic !== "initial-document-element-inserted") { return; } @@ -319,7 +319,7 @@ class WebExtensionInspectedWindowActor extends Actor { this.targetActor = targetActor; } - destroy(conn) { + destroy() { super.destroy(); if (this.customizedReload) { diff --git a/devtools/server/actors/animation-type-longhand.js b/devtools/server/actors/animation-type-longhand.js index febf8457ad..6da49c7b4b 100644 --- a/devtools/server/actors/animation-type-longhand.js +++ b/devtools/server/actors/animation-type-longhand.js @@ -258,6 +258,7 @@ exports.ANIMATION_TYPE_FOR_LONGHANDS = [ "scroll-timeline-axis", "scroll-timeline-name", "size", + "transition-behavior", "transition-delay", "transition-duration", "transition-property", diff --git a/devtools/server/actors/animation.js b/devtools/server/actors/animation.js index d8de85fd73..89a2f5e84d 100644 --- a/devtools/server/actors/animation.js +++ b/devtools/server/actors/animation.js @@ -171,7 +171,7 @@ class AnimationPlayerActor extends Actor { */ release() {} - form(detail) { + form() { const data = this.getCurrentState(); data.actor = this.actorID; diff --git a/devtools/server/actors/breakpoint.js b/devtools/server/actors/breakpoint.js index d1a469658c..bfa563bf55 100644 --- a/devtools/server/actors/breakpoint.js +++ b/devtools/server/actors/breakpoint.js @@ -90,7 +90,7 @@ class BreakpointActor { /** * Called on changes to this breakpoint's script offsets or options. */ - _newOffsetsOrOptions(script, offsets, oldOptions) { + _newOffsetsOrOptions(script, offsets) { // Clear any existing handler first in case this is called multiple times // after options change. for (const offset of offsets) { diff --git a/devtools/server/actors/descriptors/tab.js b/devtools/server/actors/descriptors/tab.js index ea20d3fb36..f5980126cb 100644 --- a/devtools/server/actors/descriptors/tab.js +++ b/devtools/server/actors/descriptors/tab.js @@ -21,12 +21,17 @@ const { connectToFrame, } = require("resource://devtools/server/connectors/frame-connector.js"); const lazy = {}; -ChromeUtils.defineESModuleGetters(lazy, { - PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs", -}); +ChromeUtils.defineESModuleGetters( + lazy, + { + PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs", + }, + { global: "contextual" } +); const { AppConstants } = ChromeUtils.importESModule( - "resource://gre/modules/AppConstants.sys.mjs" + "resource://gre/modules/AppConstants.sys.mjs", + { global: "contextual" } ); const { createBrowserElementSessionContext, diff --git a/devtools/server/actors/descriptors/webextension.js b/devtools/server/actors/descriptors/webextension.js index 56e4abfc41..b202036aef 100644 --- a/devtools/server/actors/descriptors/webextension.js +++ b/devtools/server/actors/descriptors/webextension.js @@ -28,13 +28,13 @@ const lazy = {}; loader.lazyGetter(lazy, "AddonManager", () => { return ChromeUtils.importESModule( "resource://gre/modules/AddonManager.sys.mjs", - { loadInDevToolsLoader: false } + { global: "shared" } ).AddonManager; }); loader.lazyGetter(lazy, "ExtensionParent", () => { return ChromeUtils.importESModule( "resource://gre/modules/ExtensionParent.sys.mjs", - { loadInDevToolsLoader: false } + { global: "shared" } ).ExtensionParent; }); loader.lazyRequireGetter( @@ -202,7 +202,7 @@ class WebExtensionDescriptorActor extends Actor { * * bypassCache has no impact for addon reloads. */ - reloadDescriptor({ bypassCache }) { + reloadDescriptor() { return this.reload(); } diff --git a/devtools/server/actors/descriptors/worker.js b/devtools/server/actors/descriptors/worker.js index 89ca918e05..4c8689fac8 100644 --- a/devtools/server/actors/descriptors/worker.js +++ b/devtools/server/actors/descriptors/worker.js @@ -23,7 +23,8 @@ const { DevToolsServer, } = require("resource://devtools/server/devtools-server.js"); const { XPCOMUtils } = ChromeUtils.importESModule( - "resource://gre/modules/XPCOMUtils.sys.mjs" + "resource://gre/modules/XPCOMUtils.sys.mjs", + { global: "contextual" } ); const { createWorkerSessionContext, diff --git a/devtools/server/actors/device.js b/devtools/server/actors/device.js index 2aa05959e5..69ec02a3ec 100644 --- a/devtools/server/actors/device.js +++ b/devtools/server/actors/device.js @@ -12,7 +12,8 @@ const { } = require("resource://devtools/server/devtools-server.js"); const { getSystemInfo } = require("resource://devtools/shared/system.js"); const { AppConstants } = ChromeUtils.importESModule( - "resource://gre/modules/AppConstants.sys.mjs" + "resource://gre/modules/AppConstants.sys.mjs", + { global: "contextual" } ); exports.DeviceActor = class DeviceActor extends Actor { diff --git a/devtools/server/actors/heap-snapshot-file.js b/devtools/server/actors/heap-snapshot-file.js index f3fd9242b2..942f72a98b 100644 --- a/devtools/server/actors/heap-snapshot-file.js +++ b/devtools/server/actors/heap-snapshot-file.js @@ -27,7 +27,7 @@ loader.lazyRequireGetter( * system. */ exports.HeapSnapshotFileActor = class HeapSnapshotFileActor extends Actor { - constructor(conn, parent) { + constructor(conn) { super(conn, heapSnapshotFileSpec); if ( diff --git a/devtools/server/actors/highlighters/css/highlighters.css b/devtools/server/actors/highlighters/css/highlighters.css index 33c8a04aae..04584e4bf1 100644 --- a/devtools/server/actors/highlighters/css/highlighters.css +++ b/devtools/server/actors/highlighters/css/highlighters.css @@ -1010,12 +1010,12 @@ button.paused-dbg-resume-button { } .accessible-infobar-audit .accessible-audit.WARNING::before { - background-image: url(chrome://devtools/skin/images/alert-small.svg); + background-image: url(resource://devtools-shared-images/alert-small.svg); fill: var(--yellow-60); } .accessible-infobar-audit .accessible-audit.BEST_PRACTICES::before { - background-image: url(chrome://devtools/skin/images/info-small.svg); + background-image: url(resource://devtools-shared-images/info-small.svg); } .accessible-infobar-name { diff --git a/devtools/server/actors/highlighters/shapes.js b/devtools/server/actors/highlighters/shapes.js index a77e8c31be..6cc9194f57 100644 --- a/devtools/server/actors/highlighters/shapes.js +++ b/devtools/server/actors/highlighters/shapes.js @@ -519,7 +519,7 @@ class ShapesHighlighter extends AutoRefreshHighlighter { } // eslint-disable-next-line complexity - handleEvent(event, id) { + handleEvent(event) { // No event handling if the highlighter is hidden if (this.areShapesHidden()) { return; @@ -1222,7 +1222,7 @@ class ShapesHighlighter extends AutoRefreshHighlighter { coordinates.splice(point, 1); let polygonDef = this.fillRule ? `${this.fillRule}, ` : ""; polygonDef += coordinates - .map((coords, i) => { + .map(coords => { return `${coords[0]} ${coords[1]}`; }) .join(", "); @@ -2735,11 +2735,8 @@ class ShapesHighlighter extends AutoRefreshHighlighter { /** * Update the SVG polygon to fit the CSS polygon. - * @param {Number} width the width of the element quads - * @param {Number} height the height of the element quads - * @param {Number} zoom the zoom level of the window */ - _updatePolygonShape(width, height, zoom) { + _updatePolygonShape() { // Draw and show the polygon. const points = this.coordinates.map(point => point.join(",")).join(" "); @@ -2758,11 +2755,8 @@ class ShapesHighlighter extends AutoRefreshHighlighter { /** * Update the SVG ellipse to fit the CSS circle or ellipse. - * @param {Number} width the width of the element quads - * @param {Number} height the height of the element quads - * @param {Number} zoom the zoom level of the window */ - _updateEllipseShape(width, height, zoom) { + _updateEllipseShape() { const { rx, ry, cx, cy } = this.coordinates; const ellipseEl = this.getElement("ellipse"); ellipseEl.setAttribute("rx", rx); @@ -2788,11 +2782,8 @@ class ShapesHighlighter extends AutoRefreshHighlighter { /** * Update the SVG rect to fit the CSS inset. - * @param {Number} width the width of the element quads - * @param {Number} height the height of the element quads - * @param {Number} zoom the zoom level of the window */ - _updateInsetShape(width, height, zoom) { + _updateInsetShape() { const { top, left, right, bottom } = this.coordinates; const rectEl = this.getElement("rect"); rectEl.setAttribute("x", left); diff --git a/devtools/server/actors/highlighters/tabbing-order.js b/devtools/server/actors/highlighters/tabbing-order.js index ab96d30fe6..ccc70779bb 100644 --- a/devtools/server/actors/highlighters/tabbing-order.js +++ b/devtools/server/actors/highlighters/tabbing-order.js @@ -11,11 +11,9 @@ loader.lazyGetter( () => ChromeUtils.importESModule( "resource://gre/modules/ContentDOMReference.sys.mjs", - { - // ContentDOMReference needs to be retrieved from the shared global - // since it is a shared singleton. - loadInDevToolsLoader: false, - } + // ContentDOMReference needs to be retrieved from the shared global + // since it is a shared singleton. + { global: "shared" } ).ContentDOMReference ); loader.lazyRequireGetter( diff --git a/devtools/server/actors/inspector/constants.js b/devtools/server/actors/inspector/constants.js index c253c67b02..b55cd58389 100644 --- a/devtools/server/actors/inspector/constants.js +++ b/devtools/server/actors/inspector/constants.js @@ -10,7 +10,7 @@ * * const someListener = () => {}; * someListener[EXCLUDED_LISTENER] = true; - * eventListenerService.addSystemEventListener(node, "event", someListener); + * node.addEventListener("event", someListener); */ const EXCLUDED_LISTENER = Symbol("event-collector-excluded-listener"); diff --git a/devtools/server/actors/inspector/event-collector.js b/devtools/server/actors/inspector/event-collector.js index 4ee8dc388f..7ae7ac45d7 100644 --- a/devtools/server/actors/inspector/event-collector.js +++ b/devtools/server/actors/inspector/event-collector.js @@ -241,7 +241,7 @@ class MainEventCollector { * @return {Array} * An array of event handlers. */ - getListeners(node, { checkOnly }) { + getListeners(_node, { checkOnly: _checkOnly }) { throw new Error("You have to implement the method getListeners()!"); } diff --git a/devtools/server/actors/inspector/inspector.js b/devtools/server/actors/inspector/inspector.js index cdfa892889..95f7f76622 100644 --- a/devtools/server/actors/inspector/inspector.js +++ b/devtools/server/actors/inspector/inspector.js @@ -56,7 +56,8 @@ const { } = require("resource://devtools/shared/specs/inspector.js"); const { setTimeout } = ChromeUtils.importESModule( - "resource://gre/modules/Timer.sys.mjs" + "resource://gre/modules/Timer.sys.mjs", + { global: "contextual" } ); const { LongStringActor, @@ -181,7 +182,7 @@ class InspectorActor extends Actor { return this._pageStylePromise; } - this._pageStylePromise = this.getWalker().then(walker => { + this._pageStylePromise = this.getWalker().then(() => { const pageStyle = new PageStyleActor(this); this.manage(pageStyle); return pageStyle; @@ -263,7 +264,7 @@ class InspectorActor extends Actor { return url; } - const baseURI = Services.io.newURI(document.location.href); + const baseURI = Services.io.newURI(document.baseURI); return Services.io.newURI(url, null, baseURI).spec; } diff --git a/devtools/server/actors/inspector/walker.js b/devtools/server/actors/inspector/walker.js index f8da1385e9..50df1720b7 100644 --- a/devtools/server/actors/inspector/walker.js +++ b/devtools/server/actors/inspector/walker.js @@ -111,11 +111,9 @@ if (!isWorker) { () => ChromeUtils.importESModule( "resource://gre/modules/ContentDOMReference.sys.mjs", - { - // ContentDOMReference needs to be retrieved from the shared global - // since it is a shared singleton. - loadInDevToolsLoader: false, - } + // ContentDOMReference needs to be retrieved from the shared global + // since it is a shared singleton. + { global: "shared" } ).ContentDOMReference ); } @@ -341,7 +339,11 @@ class WalkerActor extends Actor { return { actor: this.actorID, root: this.rootNode.form(), - traits: {}, + traits: { + // @backward-compat { version 125 } Indicate to the client that it can use getIdrefNode. + // This trait can be removed once 125 hits release. + hasGetIdrefNode: true, + }, }; } @@ -524,7 +526,7 @@ class WalkerActor extends Actor { * When a custom element is defined, send a customElementDefined mutation for all the * NodeActors using this tag name. */ - onCustomElementDefined({ name, actors }) { + onCustomElementDefined({ actors }) { actors.forEach(actor => this.queueMutation({ target: actor.actorID, @@ -534,7 +536,7 @@ class WalkerActor extends Actor { ); } - _onReflows(reflows) { + _onReflows() { // Going through the nodes the walker knows about, see which ones have had their // containerType, display, scrollable or overflow state changed and send events if any. const containerTypeChanges = []; @@ -1070,6 +1072,32 @@ class WalkerActor extends Actor { } /** + * Return the node in the baseNode rootNode matching the passed id referenced in a + * idref/idreflist attribute, as those are scoped within a shadow root. + * + * @param NodeActor baseNode + * @param string id + */ + getIdrefNode(baseNode, id) { + if (isNodeDead(baseNode)) { + return {}; + } + + // Get the document or the shadow root for baseNode + const rootNode = baseNode.rawNode.getRootNode({ composed: false }); + if (!rootNode) { + return {}; + } + + const node = rootNode.getElementById(id); + if (!node) { + return {}; + } + + return this.attachElement(node); + } + + /** * Get a list of nodes that match the given selector in all known frames of * the current content page. * @param {String} selector. diff --git a/devtools/server/actors/manifest.js b/devtools/server/actors/manifest.js index 5436d4a53a..183ce1edea 100644 --- a/devtools/server/actors/manifest.js +++ b/devtools/server/actors/manifest.js @@ -11,9 +11,13 @@ const { const lazy = {}; -ChromeUtils.defineESModuleGetters(lazy, { - ManifestObtainer: "resource://gre/modules/ManifestObtainer.sys.mjs", -}); +ChromeUtils.defineESModuleGetters( + lazy, + { + ManifestObtainer: "resource://gre/modules/ManifestObtainer.sys.mjs", + }, + { global: "contextual" } +); /** * An actor for a Web Manifest diff --git a/devtools/server/actors/network-monitor/channel-event-sink.js b/devtools/server/actors/network-monitor/channel-event-sink.js index 8ff00302f9..3db6bf148e 100644 --- a/devtools/server/actors/network-monitor/channel-event-sink.js +++ b/devtools/server/actors/network-monitor/channel-event-sink.js @@ -5,11 +5,12 @@ "use strict"; const { ComponentUtils } = ChromeUtils.importESModule( - "resource://gre/modules/ComponentUtils.sys.mjs" + "resource://gre/modules/ComponentUtils.sys.mjs", + { global: "contextual" } ); /** - * This is a nsIChannelEventSink implementation that monitors channel redirects and + * THIS is a nsIChannelEventSink implementation that monitors channel redirects and * informs the registered "collectors" about the old and new channels. */ const SINK_CLASS_DESCRIPTION = "NetworkMonitor Channel Event Sink"; diff --git a/devtools/server/actors/network-monitor/network-event-actor.js b/devtools/server/actors/network-monitor/network-event-actor.js index e59738dd38..38607279d4 100644 --- a/devtools/server/actors/network-monitor/network-event-actor.js +++ b/devtools/server/actors/network-monitor/network-event-actor.js @@ -18,10 +18,14 @@ const { const lazy = {}; -ChromeUtils.defineESModuleGetters(lazy, { - NetworkUtils: - "resource://devtools/shared/network-observer/NetworkUtils.sys.mjs", -}); +ChromeUtils.defineESModuleGetters( + lazy, + { + NetworkUtils: + "resource://devtools/shared/network-observer/NetworkUtils.sys.mjs", + }, + { global: "contextual" } +); const CONTENT_TYPE_REGEXP = /^content-type/i; @@ -559,13 +563,8 @@ class NetworkEventActor extends Actor { * @param object content * The response content. * @param object - * - boolean discardedResponseBody - * Tells if the response content was recorded or not. */ - addResponseContent( - content, - { discardResponseBody, blockedReason, blockingExtension } - ) { + addResponseContent(content, { blockedReason, blockingExtension }) { // Ignore calls when this actor is already destroyed if (this.isDestroyed()) { return; diff --git a/devtools/server/actors/object.js b/devtools/server/actors/object.js index 9b52c4ebe7..1d16eb2b75 100644 --- a/devtools/server/actors/object.js +++ b/devtools/server/actors/object.js @@ -63,11 +63,9 @@ if (!isWorker) { () => ChromeUtils.importESModule( "resource://gre/modules/ContentDOMReference.sys.mjs", - { - // ContentDOMReference needs to be retrieved from the shared global - // since it is a shared singleton. - loadInDevToolsLoader: false, - } + // ContentDOMReference needs to be retrieved from the shared global + // since it is a shared singleton. + { global: "shared" } ).ContentDOMReference ); } @@ -171,7 +169,7 @@ class ObjectActor extends Actor { } // Only process custom formatters if the feature is enabled. - if (this.thread?._parent?.customFormatters) { + if (this.thread?.targetActor?.customFormatters) { const result = customFormatterHeader(this); if (result) { const { formatter, ...header } = result; diff --git a/devtools/server/actors/object/previewers.js b/devtools/server/actors/object/previewers.js index 451858a826..55217a72ee 100644 --- a/devtools/server/actors/object/previewers.js +++ b/devtools/server/actors/object/previewers.js @@ -612,6 +612,30 @@ const previewers = { return true; }, ], + + CustomStateSet: [ + function(objectActor, grip) { + const size = DevToolsUtils.getProperty(objectActor.obj, "size"); + if (typeof size != "number") { + return false; + } + + grip.preview = { + kind: "ArrayLike", + length: size, + }; + + const items = (grip.preview.items = []); + for (const item of PropertyIterators.enumCustomStateSetEntries(objectActor)) { + items.push(item); + if (items.length == OBJECT_PREVIEW_MAX_ITEMS) { + break; + } + } + + return true; + }, + ], }; /** diff --git a/devtools/server/actors/object/property-iterator.js b/devtools/server/actors/object/property-iterator.js index 7bd2c0a704..e7a4d2c041 100644 --- a/devtools/server/actors/object/property-iterator.js +++ b/devtools/server/actors/object/property-iterator.js @@ -75,6 +75,8 @@ class PropertyIteratorActor extends Actor { this.iterator = enumMidiInputMapEntries(objectActor); } else if (cls == "MIDIOutputMap") { this.iterator = enumMidiOutputMapEntries(objectActor); + } else if (cls == "CustomStateSet") { + this.iterator = enumCustomStateSetEntries(objectActor); } else { throw new Error( "Unsupported class to enumerate entries from: " + cls @@ -658,6 +660,35 @@ function enumWeakSetEntries(objectActor) { }; } +function enumCustomStateSetEntries(objectActor) { + let raw = objectActor.obj.unsafeDereference(); + // We need to waive `raw` as we can't get the iterator from the Xray for SetLike (See Bug 1173651). + // We also need to waive Xrays on the result of the call to `values` as we don't have + // Xrays to Iterator objects (see Bug 1023984) + const values = Array.from( + waiveXrays(CustomStateSet.prototype.values.call(waiveXrays(raw))) + ); + + return { + [Symbol.iterator]: function*() { + for (const item of values) { + yield gripFromEntry(objectActor, item); + } + }, + size: values.length, + propertyName(index) { + return index; + }, + propertyDescription(index) { + const val = values[index]; + return { + enumerable: true, + value: gripFromEntry(objectActor, val), + }; + }, + }; +} + /** * Returns true if the parameter can be stored as a 32-bit unsigned integer. * If so, it will be suitable for use as the length of an array object. @@ -672,6 +703,7 @@ function isUint32(num) { module.exports = { PropertyIteratorActor, + enumCustomStateSetEntries, enumMapEntries, enumMidiInputMapEntries, enumMidiOutputMapEntries, diff --git a/devtools/server/actors/objects-manager.js b/devtools/server/actors/objects-manager.js index 529b33b246..9338f7c8f6 100644 --- a/devtools/server/actors/objects-manager.js +++ b/devtools/server/actors/objects-manager.js @@ -14,7 +14,7 @@ const { * inspected by DevTools. Typically from the Console or Debugger. */ class ObjectsManagerActor extends Actor { - constructor(conn, targetActor) { + constructor(conn) { super(conn, objectsManagerSpec); } diff --git a/devtools/server/actors/page-style.js b/devtools/server/actors/page-style.js index cfaa35ed46..1783b58a8f 100644 --- a/devtools/server/actors/page-style.js +++ b/devtools/server/actors/page-style.js @@ -727,9 +727,13 @@ class PageStyleActor extends Actor { case "::-moz-range-progress": case "::-moz-range-thumb": case "::-moz-range-track": + case "::slider-fill": + case "::slider-thumb": + case "::slider-track": return node.nodeName == "INPUT" && node.type == "range"; default: - throw Error("Unhandled pseudo-element " + pseudo); + console.error("Unhandled pseudo-element " + pseudo); + return false; } } diff --git a/devtools/server/actors/preference.js b/devtools/server/actors/preference.js index 3435fe9eb1..17a3116d9e 100644 --- a/devtools/server/actors/preference.js +++ b/devtools/server/actors/preference.js @@ -57,7 +57,7 @@ class PreferenceActor extends Actor { getAllPrefs() { const prefs = {}; - Services.prefs.getChildList("").forEach(function (name, index) { + Services.prefs.getChildList("").forEach(function (name) { // append all key/value pairs into a huge json object. try { let value; diff --git a/devtools/server/actors/reflow.js b/devtools/server/actors/reflow.js index 3eda67cc8c..6fd3962d75 100644 --- a/devtools/server/actors/reflow.js +++ b/devtools/server/actors/reflow.js @@ -162,11 +162,11 @@ class Observable { } } - _startListeners(windows) { + _startListeners() { // To be implemented by sub-classes. } - _stopListeners(windows) { + _stopListeners() { // To be implemented by sub-classes. } diff --git a/devtools/server/actors/resources/extensions-backgroundscript-status.js b/devtools/server/actors/resources/extensions-backgroundscript-status.js index 08f51a23f5..f6c7b3067d 100644 --- a/devtools/server/actors/resources/extensions-backgroundscript-status.js +++ b/devtools/server/actors/resources/extensions-backgroundscript-status.js @@ -35,7 +35,7 @@ class ExtensionsBackgroundScriptStatusWatcher { Services.obs.addObserver(this, "extension:background-script-status"); } - observe(subject, topic, data) { + observe(subject, topic) { switch (topic) { case "extension:background-script-status": { const { addonId, isRunning } = subject.wrappedJSObject; diff --git a/devtools/server/actors/resources/network-events-content.js b/devtools/server/actors/resources/network-events-content.js index 5135583fab..2b4b09dfa7 100644 --- a/devtools/server/actors/resources/network-events-content.js +++ b/devtools/server/actors/resources/network-events-content.js @@ -13,10 +13,14 @@ loader.lazyRequireGetter( const lazy = {}; -ChromeUtils.defineESModuleGetters(lazy, { - NetworkUtils: - "resource://devtools/shared/network-observer/NetworkUtils.sys.mjs", -}); +ChromeUtils.defineESModuleGetters( + lazy, + { + NetworkUtils: + "resource://devtools/shared/network-observer/NetworkUtils.sys.mjs", + }, + { global: "contextual" } +); /** * Handles network events from the content process @@ -64,7 +68,7 @@ class NetworkEventContentWatcher { this.networkEvents.clear(); } - httpFailedOpeningRequest(subject, topic) { + httpFailedOpeningRequest(subject) { const channel = subject.QueryInterface(Ci.nsIHttpChannel); // Ignore preload requests to avoid duplicity request entries in diff --git a/devtools/server/actors/resources/network-events-stacktraces.js b/devtools/server/actors/resources/network-events-stacktraces.js index a458278680..da082069f5 100644 --- a/devtools/server/actors/resources/network-events-stacktraces.js +++ b/devtools/server/actors/resources/network-events-stacktraces.js @@ -17,10 +17,14 @@ loader.lazyRequireGetter( const lazy = {}; -ChromeUtils.defineESModuleGetters(lazy, { - NetworkUtils: - "resource://devtools/shared/network-observer/NetworkUtils.sys.mjs", -}); +ChromeUtils.defineESModuleGetters( + lazy, + { + NetworkUtils: + "resource://devtools/shared/network-observer/NetworkUtils.sys.mjs", + }, + { global: "contextual" } +); class NetworkEventStackTracesWatcher { /** @@ -53,11 +57,8 @@ class NetworkEventStackTracesWatcher { /** * Stop watching for network event's strack traces related to a given Target Actor. - * - * @param TargetActor targetActor - * The target actor from which we should stop observing the strack traces */ - destroy(targetActor) { + destroy() { this.clear(); Services.obs.removeObserver(this, "http-on-opening-request"); Services.obs.removeObserver(this, "document-on-opening-request"); @@ -65,7 +66,7 @@ class NetworkEventStackTracesWatcher { ChannelEventSinkFactory.getService().unregisterCollector(this); } - onChannelRedirect(oldChannel, newChannel, flags) { + onChannelRedirect(oldChannel, newChannel) { // We can be called with any nsIChannel, but are interested only in HTTP channels try { oldChannel.QueryInterface(Ci.nsIHttpChannel); diff --git a/devtools/server/actors/resources/network-events.js b/devtools/server/actors/resources/network-events.js index c1440f2c8d..909c16e052 100644 --- a/devtools/server/actors/resources/network-events.js +++ b/devtools/server/actors/resources/network-events.js @@ -6,26 +6,29 @@ const { Pool } = require("resource://devtools/shared/protocol/Pool.js"); const { isWindowGlobalPartOfContext } = ChromeUtils.importESModule( - "resource://devtools/server/actors/watcher/browsing-context-helpers.sys.mjs" + "resource://devtools/server/actors/watcher/browsing-context-helpers.sys.mjs", + { global: "contextual" } ); const { WatcherRegistry } = ChromeUtils.importESModule( "resource://devtools/server/actors/watcher/WatcherRegistry.sys.mjs", - { - // WatcherRegistry needs to be a true singleton and loads ActorManagerParent - // which also has to be a true singleton. - loadInDevToolsLoader: false, - } + // WatcherRegistry needs to be a true singleton and loads ActorManagerParent + // which also has to be a true singleton. + { global: "shared" } ); const Targets = require("resource://devtools/server/actors/targets/index.js"); const lazy = {}; -ChromeUtils.defineESModuleGetters(lazy, { - NetworkObserver: - "resource://devtools/shared/network-observer/NetworkObserver.sys.mjs", - NetworkUtils: - "resource://devtools/shared/network-observer/NetworkUtils.sys.mjs", -}); +ChromeUtils.defineESModuleGetters( + lazy, + { + NetworkObserver: + "resource://devtools/shared/network-observer/NetworkObserver.sys.mjs", + NetworkUtils: + "resource://devtools/shared/network-observer/NetworkUtils.sys.mjs", + }, + { global: "contextual" } +); loader.lazyRequireGetter( this, diff --git a/devtools/server/actors/resources/parent-process-document-event.js b/devtools/server/actors/resources/parent-process-document-event.js index e156a32fe5..27f929190c 100644 --- a/devtools/server/actors/resources/parent-process-document-event.js +++ b/devtools/server/actors/resources/parent-process-document-event.js @@ -98,7 +98,7 @@ class ParentProcessDocumentEventWatcher { return Promise.resolve(); } - onStateChange(progress, request, flag, status) { + onStateChange(progress, request, flag) { const isStart = flag & Ci.nsIWebProgressListener.STATE_START; const isDocument = flag & Ci.nsIWebProgressListener.STATE_IS_DOCUMENT; if (isDocument && isStart) { diff --git a/devtools/server/actors/resources/server-sent-events.js b/devtools/server/actors/resources/server-sent-events.js index 5b16f8bb9f..894bbc76bc 100644 --- a/devtools/server/actors/resources/server-sent-events.js +++ b/devtools/server/actors/resources/server-sent-events.js @@ -102,7 +102,7 @@ class ServerSentEventWatcher { } // nsIEventSourceEventService specific functions - eventSourceConnectionOpened(httpChannelId) {} + eventSourceConnectionOpened() {} eventSourceConnectionClosed(httpChannelId) { const resource = ServerSentEventWatcher.createResource( diff --git a/devtools/server/actors/resources/sources.js b/devtools/server/actors/resources/sources.js index 6b3ab1d5e1..c4f0601106 100644 --- a/devtools/server/actors/resources/sources.js +++ b/devtools/server/actors/resources/sources.js @@ -69,13 +69,13 @@ class SourceWatcher { // Before fetching all sources, process existing ones. // The ThreadActor is already up and running before this code runs // and have sources already registered and for which newSource event already fired. - onAvailable( - threadActor.sourcesManager.iter().map(s => { - const resource = s.form(); - resource.resourceType = SOURCE; - return resource; - }) - ); + const sources = []; + for (const sourceActor of threadActor.sourcesManager.iter()) { + const resource = sourceActor.form(); + resource.resourceType = SOURCE; + sources.push(resource); + } + onAvailable(sources); // Requesting all sources should end up emitting newSource on threadActor.sourcesManager threadActor.addAllSources(); diff --git a/devtools/server/actors/resources/storage/extension-storage.js b/devtools/server/actors/resources/storage/extension-storage.js index d14d3320c7..be98c917b3 100644 --- a/devtools/server/actors/resources/storage/extension-storage.js +++ b/devtools/server/actors/resources/storage/extension-storage.js @@ -13,25 +13,25 @@ const { const { LongStringActor, } = require("resource://devtools/server/actors/string.js"); -// Use loadInDevToolsLoader: false for these extension modules, because these +// Use global: "shared" for these extension modules, because these // are singletons with shared state, and we must not create a new instance if a // dedicated loader was used to load this module. loader.lazyGetter(this, "ExtensionParent", () => { return ChromeUtils.importESModule( "resource://gre/modules/ExtensionParent.sys.mjs", - { loadInDevToolsLoader: false } + { global: "shared" } ).ExtensionParent; }); loader.lazyGetter(this, "ExtensionProcessScript", () => { return ChromeUtils.importESModule( "resource://gre/modules/ExtensionProcessScript.sys.mjs", - { loadInDevToolsLoader: false } + { global: "shared" } ).ExtensionProcessScript; }); loader.lazyGetter(this, "ExtensionStorageIDB", () => { return ChromeUtils.importESModule( "resource://gre/modules/ExtensionStorageIDB.sys.mjs", - { loadInDevToolsLoader: false } + { global: "shared" } ).ExtensionStorageIDB; }); @@ -310,7 +310,7 @@ class ExtensionStorageActor extends BaseStorageActor { }); } - async editItem({ host, field, items, oldValue }) { + async editItem({ host, items }) { const db = this.dbConnectionForHost.get(host); if (!db) { return; diff --git a/devtools/server/actors/resources/storage/indexed-db.js b/devtools/server/actors/resources/storage/indexed-db.js index 8ded705c4f..05c523ac57 100644 --- a/devtools/server/actors/resources/storage/indexed-db.js +++ b/devtools/server/actors/resources/storage/indexed-db.js @@ -30,9 +30,13 @@ loader.lazyGetter(this, "indexedDBForStorage", () => { } }); const lazy = {}; -ChromeUtils.defineESModuleGetters(lazy, { - Sqlite: "resource://gre/modules/Sqlite.sys.mjs", -}); +ChromeUtils.defineESModuleGetters( + lazy, + { + Sqlite: "resource://gre/modules/Sqlite.sys.mjs", + }, + { global: "contextual" } +); /** * An async method equivalent to setTimeout but using Promises @@ -882,7 +886,7 @@ class IndexedDBStorageActor extends BaseStorageActor { const { name } = this.splitNameAndStorage(dbName); const request = this.openWithPrincipal(principal, name, storage); - return new Promise((resolve, reject) => { + return new Promise(resolve => { let { objectStore, id, index, offset, size } = requestOptions; const data = []; let db; diff --git a/devtools/server/actors/resources/thread-states.js b/devtools/server/actors/resources/thread-states.js index 9ac79088d2..11aef81734 100644 --- a/devtools/server/actors/resources/thread-states.js +++ b/devtools/server/actors/resources/thread-states.js @@ -7,6 +7,7 @@ const { TYPES: { THREAD_STATE }, } = require("resource://devtools/server/actors/resources/index.js"); +const Targets = require("resource://devtools/server/actors/targets/index.js"); const { PAUSE_REASONS, @@ -48,6 +49,18 @@ class BreakpointWatcher { * This will be called for each resource. */ async watch(targetActor, { onAvailable }) { + // When debugging the whole browser (via the Browser Toolbox), we instantiate both content process and window global (FRAME) targets. + // But the debugger will only use the content process target's thread actor. + // Thread actor, Sources and Breakpoints have to be only managed for the content process target, + // and we should explicitly ignore the window global target. + if ( + targetActor.sessionContext.type == "all" && + targetActor.targetType === Targets.TYPES.FRAME && + targetActor.typeName != "parentProcessTarget" + ) { + return; + } + const { threadActor } = targetActor; this.threadActor = threadActor; this.onAvailable = onAvailable; @@ -87,6 +100,9 @@ class BreakpointWatcher { * Stop watching for breakpoints */ destroy() { + if (!this.threadActor) { + return; + } this.threadActor.off("paused", this.onPaused); this.threadActor.off("resumed", this.onResumed); } @@ -116,7 +132,7 @@ class BreakpointWatcher { ]); } - onResumed(packet) { + onResumed() { // NOTE: resumed events are suppressed while interrupted // to prevent unintentional behavior. if (this.isInterrupted) { diff --git a/devtools/server/actors/resources/utils/content-process-storage.js b/devtools/server/actors/resources/utils/content-process-storage.js index 7e126ce3f7..80d7e319a8 100644 --- a/devtools/server/actors/resources/utils/content-process-storage.js +++ b/devtools/server/actors/resources/utils/content-process-storage.js @@ -7,10 +7,14 @@ const EventEmitter = require("resource://devtools/shared/event-emitter.js"); const lazy = {}; -ChromeUtils.defineESModuleGetters(lazy, { - getAddonIdForWindowGlobal: - "resource://devtools/server/actors/watcher/browsing-context-helpers.sys.mjs", -}); +ChromeUtils.defineESModuleGetters( + lazy, + { + getAddonIdForWindowGlobal: + "resource://devtools/server/actors/watcher/browsing-context-helpers.sys.mjs", + }, + { global: "contextual" } +); // ms of delay to throttle updates const BATCH_DELAY = 200; diff --git a/devtools/server/actors/resources/utils/nsi-console-listener-watcher.js b/devtools/server/actors/resources/utils/nsi-console-listener-watcher.js index 8d1ed43612..d4bde43818 100644 --- a/devtools/server/actors/resources/utils/nsi-console-listener-watcher.js +++ b/devtools/server/actors/resources/utils/nsi-console-listener-watcher.js @@ -79,7 +79,7 @@ class nsIConsoleListenerWatcher { * @param {TargetActor} targetActor * @return {Boolean} */ - shouldHandleTarget(targetActor) { + shouldHandleTarget() { return true; } @@ -91,7 +91,7 @@ class nsIConsoleListenerWatcher { * @param {nsIScriptError|nsIConsoleMessage} message * @return {Boolean} */ - shouldHandleMessage(targetActor, message) { + shouldHandleMessage() { throw new Error( "'shouldHandleMessage' should be implemented in the class that extends nsIConsoleListenerWatcher" ); @@ -101,12 +101,12 @@ class nsIConsoleListenerWatcher { * Prepare the resource to be sent to the client. This should be implemented on the * child class. * - * @param targetActor - * @param nsIScriptError|nsIConsoleMessage message + * @param _targetActor + * @param nsIScriptError|nsIConsoleMessage _message * @return object * The object you can send to the remote client. */ - buildResource(targetActor, message) { + buildResource(_targetActor, _message) { throw new Error( "'buildResource' should be implemented in the class that extends nsIConsoleListenerWatcher" ); diff --git a/devtools/server/actors/resources/utils/parent-process-storage.js b/devtools/server/actors/resources/utils/parent-process-storage.js index 423d13b6b5..760e6e4d38 100644 --- a/devtools/server/actors/resources/utils/parent-process-storage.js +++ b/devtools/server/actors/resources/utils/parent-process-storage.js @@ -6,7 +6,8 @@ const EventEmitter = require("resource://devtools/shared/event-emitter.js"); const { isWindowGlobalPartOfContext } = ChromeUtils.importESModule( - "resource://devtools/server/actors/watcher/browsing-context-helpers.sys.mjs" + "resource://devtools/server/actors/watcher/browsing-context-helpers.sys.mjs", + { global: "contextual" } ); // ms of delay to throttle updates diff --git a/devtools/server/actors/resources/websockets.js b/devtools/server/actors/resources/websockets.js index 5845357a9c..58507f9fbb 100644 --- a/devtools/server/actors/resources/websockets.js +++ b/devtools/server/actors/resources/websockets.js @@ -97,7 +97,7 @@ class WebSocketWatcher { } // methods for the nsIWebSocketEventService - webSocketCreated(webSocketSerialID, uri, protocols) {} + webSocketCreated() {} webSocketOpened( webSocketSerialID, @@ -117,7 +117,7 @@ class WebSocketWatcher { this.onAvailable([resource]); } - webSocketMessageAvailable(webSocketSerialID, data, messageType) {} + webSocketMessageAvailable() {} webSocketClosed(webSocketSerialID, wasClean, code, reason) { const httpChannelId = this.connections.get(webSocketSerialID); diff --git a/devtools/server/actors/root.js b/devtools/server/actors/root.js index df16c70b2f..90836d524e 100644 --- a/devtools/server/actors/root.js +++ b/devtools/server/actors/root.js @@ -135,8 +135,6 @@ class RootActor extends Actor { "dom.worker.console.dispatch_events_to_main_thread" ) : true, - // @backward-compat { version 123 } A new Objects Manager front has a new "releaseActors" method. - supportsReleaseActors: true, }; } diff --git a/devtools/server/actors/source.js b/devtools/server/actors/source.js index ff08bcb4c2..e246e11b8c 100644 --- a/devtools/server/actors/source.js +++ b/devtools/server/actors/source.js @@ -128,7 +128,7 @@ class SourceActor extends Actor { // because we can't easily fetch the full html content of the srcdoc attribute. this.__isInlineSource = source.introductionType === "inlineScript" && - !resolveSourceURL(source.displayURL, this.threadActor._parent) && + !resolveSourceURL(source.displayURL, this.threadActor.targetActor) && !this.url.startsWith("about:srcdoc"); } return this.__isInlineSource; @@ -148,7 +148,7 @@ class SourceActor extends Actor { } get url() { if (this._url === undefined) { - this._url = getSourceURL(this._source, this.threadActor._parent); + this._url = getSourceURL(this._source, this.threadActor.targetActor); } return this._url; } @@ -205,7 +205,7 @@ class SourceActor extends Actor { isBlackBoxed: this.sourcesManager.isBlackBoxed(this.url), sourceMapBaseURL: getSourcemapBaseURL( this.url, - this.threadActor._parent.window + this.threadActor.targetActor.window ), sourceMapURL: source.sourceMapURL, introductionType, diff --git a/devtools/server/actors/target-configuration.js b/devtools/server/actors/target-configuration.js index 35340ee668..b6db235143 100644 --- a/devtools/server/actors/target-configuration.js +++ b/devtools/server/actors/target-configuration.js @@ -13,7 +13,8 @@ const { SessionDataHelpers, } = require("resource://devtools/server/actors/watcher/SessionDataHelpers.jsm"); const { isBrowsingContextPartOfContext } = ChromeUtils.importESModule( - "resource://devtools/server/actors/watcher/browsing-context-helpers.sys.mjs" + "resource://devtools/server/actors/watcher/browsing-context-helpers.sys.mjs", + { global: "contextual" } ); const { SUPPORTED_DATA } = SessionDataHelpers; const { TARGET_CONFIGURATION } = SUPPORTED_DATA; diff --git a/devtools/server/actors/targets/content-process.js b/devtools/server/actors/targets/content-process.js index 56b1934ef1..cb6c34cea6 100644 --- a/devtools/server/actors/targets/content-process.js +++ b/devtools/server/actors/targets/content-process.js @@ -31,9 +31,7 @@ const { } = require("resource://devtools/server/actors/targets/base-target-actor.js"); const { TargetActorRegistry } = ChromeUtils.importESModule( "resource://devtools/server/actors/targets/target-actor-registry.sys.mjs", - { - loadInDevToolsLoader: false, - } + { global: "shared" } ); loader.lazyRequireGetter( @@ -67,7 +65,7 @@ class ContentProcessTargetActor extends BaseTargetActor { this.makeDebugger = makeDebugger.bind(null, { findDebuggees: dbg => dbg.findAllGlobals().map(g => g.unsafeDereference()), - shouldAddNewGlobalAsDebuggee: global => true, + shouldAddNewGlobalAsDebuggee: () => true, }); const sandboxPrototype = { @@ -149,7 +147,7 @@ class ContentProcessTargetActor extends BaseTargetActor { } if (!this.threadActor) { - this.threadActor = new ThreadActor(this, null); + this.threadActor = new ThreadActor(this); this.manage(this.threadActor); } if (!this.memoryActor) { @@ -218,7 +216,7 @@ class ContentProcessTargetActor extends BaseTargetActor { this.ensureWorkerList().workerPauser.setPauseServiceWorkers(request.origin); } - destroy() { + destroy({ isModeSwitching } = {}) { // Avoid reentrancy. We will destroy the Transport when emitting "destroyed", // which will force destroying all actors. if (this.destroying) { @@ -230,7 +228,7 @@ class ContentProcessTargetActor extends BaseTargetActor { // otherwise you might have leaks reported when running browser_browser_toolbox_netmonitor.js in debug builds Resources.unwatchAllResources(this); - this.emit("destroyed"); + this.emit("destroyed", { isModeSwitching }); super.destroy(); diff --git a/devtools/server/actors/targets/session-data-processors/blackboxing.js b/devtools/server/actors/targets/session-data-processors/blackboxing.js index 70f4397a72..92bfa74569 100644 --- a/devtools/server/actors/targets/session-data-processors/blackboxing.js +++ b/devtools/server/actors/targets/session-data-processors/blackboxing.js @@ -20,7 +20,7 @@ module.exports = { } }, - removeSessionDataEntry(targetActor, entries, isDocumentCreation) { + removeSessionDataEntry(targetActor, entries) { for (const { url, range } of entries) { targetActor.sourcesManager.unblackBox(url, range); } diff --git a/devtools/server/actors/targets/session-data-processors/breakpoints.js b/devtools/server/actors/targets/session-data-processors/breakpoints.js index ff7cb7ec0a..67c270654d 100644 --- a/devtools/server/actors/targets/session-data-processors/breakpoints.js +++ b/devtools/server/actors/targets/session-data-processors/breakpoints.js @@ -7,6 +7,7 @@ const { STATES: THREAD_STATES, } = require("resource://devtools/server/actors/thread.js"); +const Targets = require("resource://devtools/server/actors/targets/index.js"); module.exports = { async addOrSetSessionDataEntry( @@ -15,6 +16,17 @@ module.exports = { isDocumentCreation, updateType ) { + // When debugging the whole browser (via the Browser Toolbox), we instantiate both content process and window global (FRAME) targets. + // But the debugger will only use the content process target's thread actor. + // Thread actor, Sources and Breakpoints have to be only managed for the content process target, + // and we should explicitly ignore the window global target. + if ( + targetActor.sessionContext.type == "all" && + targetActor.targetType === Targets.TYPES.FRAME && + targetActor.typeName != "parentProcessTarget" + ) { + return; + } const { threadActor } = targetActor; if (updateType == "set") { threadActor.removeAllBreakpoints(); @@ -37,7 +49,7 @@ module.exports = { } }, - removeSessionDataEntry(targetActor, entries, isDocumentCreation) { + removeSessionDataEntry(targetActor, entries) { for (const { location } of entries) { targetActor.threadActor.removeBreakpoint(location); } diff --git a/devtools/server/actors/targets/session-data-processors/event-breakpoints.js b/devtools/server/actors/targets/session-data-processors/event-breakpoints.js index c0a2fb7ffe..4eb9e4f3a8 100644 --- a/devtools/server/actors/targets/session-data-processors/event-breakpoints.js +++ b/devtools/server/actors/targets/session-data-processors/event-breakpoints.js @@ -30,7 +30,7 @@ module.exports = { } }, - removeSessionDataEntry(targetActor, entries, isDocumentCreation) { + removeSessionDataEntry(targetActor, entries) { targetActor.threadActor.removeEventBreakpoints(entries); }, }; diff --git a/devtools/server/actors/targets/session-data-processors/resources.js b/devtools/server/actors/targets/session-data-processors/resources.js index 8f33ba8e0f..1e08397256 100644 --- a/devtools/server/actors/targets/session-data-processors/resources.js +++ b/devtools/server/actors/targets/session-data-processors/resources.js @@ -19,7 +19,7 @@ module.exports = { await Resources.watchResources(targetActor, entries); }, - removeSessionDataEntry(targetActor, entries, isDocumentCreation) { + removeSessionDataEntry(targetActor, entries) { Resources.unwatchResources(targetActor, entries); }, }; diff --git a/devtools/server/actors/targets/session-data-processors/target-configuration.js b/devtools/server/actors/targets/session-data-processors/target-configuration.js index f68e82d69f..8f10692178 100644 --- a/devtools/server/actors/targets/session-data-processors/target-configuration.js +++ b/devtools/server/actors/targets/session-data-processors/target-configuration.js @@ -5,12 +5,7 @@ "use strict"; module.exports = { - async addOrSetSessionDataEntry( - targetActor, - entries, - isDocumentCreation, - updateType - ) { + async addOrSetSessionDataEntry(targetActor, entries, isDocumentCreation) { // Only WindowGlobalTargetActor implements updateTargetConfiguration, // skip targetActor data entry update for other targets. if (typeof targetActor.updateTargetConfiguration == "function") { @@ -26,7 +21,7 @@ module.exports = { } }, - removeSessionDataEntry(targetActor, entries, isDocumentCreation) { + removeSessionDataEntry() { // configuration data entries are always added/updated, never removed. }, }; diff --git a/devtools/server/actors/targets/session-data-processors/thread-configuration.js b/devtools/server/actors/targets/session-data-processors/thread-configuration.js index 716d2a9b21..ad5c0fe024 100644 --- a/devtools/server/actors/targets/session-data-processors/thread-configuration.js +++ b/devtools/server/actors/targets/session-data-processors/thread-configuration.js @@ -7,14 +7,21 @@ const { STATES: THREAD_STATES, } = require("resource://devtools/server/actors/thread.js"); +const Targets = require("resource://devtools/server/actors/targets/index.js"); module.exports = { - async addOrSetSessionDataEntry( - targetActor, - entries, - isDocumentCreation, - updateType - ) { + async addOrSetSessionDataEntry(targetActor, entries) { + // When debugging the whole browser (via the Browser Toolbox), we instantiate both content process and window global (FRAME) targets. + // But the debugger will only use the content process target's thread actor. + // Thread actor, Sources and Breakpoints have to be only managed for the content process target, + // and we should explicitly ignore the window global target. + if ( + targetActor.sessionContext.type == "all" && + targetActor.targetType === Targets.TYPES.FRAME && + targetActor.typeName != "parentProcessTarget" + ) { + return; + } const threadOptions = {}; for (const { key, value } of entries) { @@ -35,7 +42,7 @@ module.exports = { } }, - removeSessionDataEntry(targetActor, entries, isDocumentCreation) { + removeSessionDataEntry() { // configuration data entries are always added/updated, never removed. }, }; diff --git a/devtools/server/actors/targets/session-data-processors/xhr-breakpoints.js b/devtools/server/actors/targets/session-data-processors/xhr-breakpoints.js index 7a0fd815aa..3bbcf54aaf 100644 --- a/devtools/server/actors/targets/session-data-processors/xhr-breakpoints.js +++ b/devtools/server/actors/targets/session-data-processors/xhr-breakpoints.js @@ -36,7 +36,7 @@ module.exports = { ); }, - removeSessionDataEntry(targetActor, entries, isDocumentCreation) { + removeSessionDataEntry(targetActor, entries) { for (const { path, method } of entries) { targetActor.threadActor.removeXHRBreakpoint(path, method); } diff --git a/devtools/server/actors/targets/target-actor-registry.sys.mjs b/devtools/server/actors/targets/target-actor-registry.sys.mjs index 4cb6d13868..25c1ac1234 100644 --- a/devtools/server/actors/targets/target-actor-registry.sys.mjs +++ b/devtools/server/actors/targets/target-actor-registry.sys.mjs @@ -24,7 +24,7 @@ export var TargetActorRegistry = { xpcShellTargetActor = targetActor; }, - unregisterXpcShellTargetActor(targetActor) { + unregisterXpcShellTargetActor() { xpcShellTargetActor = null; }, diff --git a/devtools/server/actors/targets/window-global.js b/devtools/server/actors/targets/window-global.js index 5d2bb10164..6719f0518d 100644 --- a/devtools/server/actors/targets/window-global.js +++ b/devtools/server/actors/targets/window-global.js @@ -33,12 +33,11 @@ var makeDebugger = require("resource://devtools/server/actors/utils/make-debugge const Targets = require("resource://devtools/server/actors/targets/index.js"); const { TargetActorRegistry } = ChromeUtils.importESModule( "resource://devtools/server/actors/targets/target-actor-registry.sys.mjs", - { - loadInDevToolsLoader: false, - } + { global: "shared" } ); const { PrivateBrowsingUtils } = ChromeUtils.importESModule( - "resource://gre/modules/PrivateBrowsingUtils.sys.mjs" + "resource://gre/modules/PrivateBrowsingUtils.sys.mjs", + { global: "contextual" } ); const EXTENSION_CONTENT_SYS_MJS = @@ -82,7 +81,7 @@ loader.lazyGetter(lazy, "ExtensionContent", () => { // main loader. Note that the user of lazy.ExtensionContent elsewhere in // this file (at webextensionsContentScriptGlobals) looks up the module // via Cu.isESModuleLoaded, which also uses the main loader as desired. - loadInDevToolsLoader: false, + global: "shared", }).ExtensionContent; }); @@ -888,7 +887,7 @@ class WindowGlobalTargetActor extends BaseTargetActor { return {}; } - listFrames(request) { + listFrames() { const windows = this._docShellsToWindows(this.docShells); return { frames: windows }; } @@ -912,7 +911,7 @@ class WindowGlobalTargetActor extends BaseTargetActor { ); } - listWorkers(request) { + listWorkers() { return this.ensureWorkerDescriptorActorList() .getList() .then(actors => { @@ -960,7 +959,7 @@ class WindowGlobalTargetActor extends BaseTargetActor { this.emit("workerListChanged"); } - _onConsoleApiProfilerEvent(subject, topic, data) { + _onConsoleApiProfilerEvent() { // TODO: We will receive console-api-profiler events for any browser running // in the same process as this target. We should filter irrelevant events, // but console-api-profiler currently doesn't emit any information to identify @@ -977,7 +976,7 @@ class WindowGlobalTargetActor extends BaseTargetActor { }); } - observe(subject, topic, data) { + observe(subject, topic) { // Ignore any event that comes before/after the actor is attached. // That typically happens during Firefox shutdown. if (this.isDestroyed()) { @@ -1165,7 +1164,7 @@ class WindowGlobalTargetActor extends BaseTargetActor { * This sets up the content window for being debugged */ _createThreadActor() { - this.threadActor = new ThreadActor(this, this.window); + this.threadActor = new ThreadActor(this); this.manage(this.threadActor); } @@ -1187,7 +1186,7 @@ class WindowGlobalTargetActor extends BaseTargetActor { // Protocol Request Handlers - detach(request) { + detach() { // Destroy the actor in the next event loop in order // to ensure responding to the `detach` request. DevToolsUtils.executeSoon(() => { @@ -1824,7 +1823,7 @@ class DebuggerProgressListener { this._knownWindowIDs.delete(getWindowID(window)); }, "DebuggerProgressListener.prototype.onWindowHidden"); - observe = DevToolsUtils.makeInfallible(function (subject, topic) { + observe = DevToolsUtils.makeInfallible(function (subject) { if (this._targetActor.isDestroyed()) { return; } @@ -1859,8 +1858,7 @@ class DebuggerProgressListener { onStateChange = DevToolsUtils.makeInfallible(function ( progress, request, - flag, - status + flag ) { if (this._targetActor.isDestroyed()) { return; diff --git a/devtools/server/actors/targets/worker.js b/devtools/server/actors/targets/worker.js index cf5f7b83c9..20b60cfa24 100644 --- a/devtools/server/actors/targets/worker.js +++ b/devtools/server/actors/targets/worker.js @@ -72,7 +72,7 @@ class WorkerTargetActor extends BaseTargetActor { }); // needed by the console actor - this.threadActor = new ThreadActor(this, this.workerGlobal); + this.threadActor = new ThreadActor(this); // needed by the thread actor to communicate with the console when evaluating logpoints. this._consoleActor = new WebConsoleActor(this.conn, this); diff --git a/devtools/server/actors/thread-configuration.js b/devtools/server/actors/thread-configuration.js index 8cd28f5887..f0c697bb51 100644 --- a/devtools/server/actors/thread-configuration.js +++ b/devtools/server/actors/thread-configuration.js @@ -17,29 +17,33 @@ const { } = SessionDataHelpers; // List of options supported by this thread configuration actor. +/* eslint sort-keys: "error" */ const SUPPORTED_OPTIONS = { - // Controls pausing on debugger statement. - // (This is enabled by default if omitted) - shouldPauseOnDebuggerStatement: true, - // Enable pausing on exceptions. - pauseOnExceptions: true, // Disable pausing on caught exceptions. ignoreCaughtExceptions: true, - // Include previously saved stack frames when paused. - shouldIncludeSavedFrames: true, - // Include async stack frames when paused. - shouldIncludeAsyncLiveFrames: true, - // Stop pausing on breakpoints. - skipBreakpoints: true, // Log the event break points. logEventBreakpoints: true, // Enable debugging asm & wasm. // See https://searchfox.org/mozilla-central/source/js/src/doc/Debugger/Debugger.md#16-26 observeAsmJS: true, observeWasm: true, + // Enable pausing on exceptions. + pauseOnExceptions: true, + // Boolean to know if we should display the overlay when pausing + pauseOverlay: true, // Should pause all the workers untill thread has attached. pauseWorkersUntilAttach: true, + // Include async stack frames when paused. + shouldIncludeAsyncLiveFrames: true, + // Include previously saved stack frames when paused. + shouldIncludeSavedFrames: true, + // Controls pausing on debugger statement. + // (This is enabled by default if omitted) + shouldPauseOnDebuggerStatement: true, + // Stop pausing on breakpoints. + skipBreakpoints: true, }; +/* eslint-disable sort-keys */ /** * This actor manages the configuration options which apply to thread actor for all the targets. diff --git a/devtools/server/actors/thread.js b/devtools/server/actors/thread.js index d33b3e5eb2..07dcc27a6a 100644 --- a/devtools/server/actors/thread.js +++ b/devtools/server/actors/thread.js @@ -32,6 +32,7 @@ const { const { logEvent, } = require("resource://devtools/server/actors/utils/logEvent.js"); +const Targets = require("devtools/server/actors/targets/index"); loader.lazyRequireGetter( this, @@ -169,24 +170,23 @@ class ThreadActor extends Actor { * * ThreadActors manage execution/inspection of debuggees. * - * @param parent TargetActor - * This |ThreadActor|'s parent actor. i.e. one of the many Target actors. - * @param aGlobal object [optional] - * An optional (for content debugging only) reference to the content - * window. + * @param {TargetActor} targetActor + * This `ThreadActor`'s parent actor. i.e. one of the many Target actors. */ - constructor(parent, global) { - super(parent.conn, threadSpec); + constructor(targetActor) { + super(targetActor.conn, threadSpec); + + // This attribute is used by various other actors to find the target actor + this.targetActor = targetActor; this._state = STATES.DETACHED; - this._parent = parent; - this.global = global; this._options = { skipBreakpoints: false, }; this._gripDepth = 0; - this._parentClosed = false; + this._targetActorClosed = false; this._observingNetwork = false; + this._shouldShowPauseOverlay = true; this._frameActors = []; this._xhrBreakpoints = []; @@ -233,9 +233,9 @@ class ThreadActor extends Actor { this._onWillNavigate = this._onWillNavigate.bind(this); this._onNavigate = this._onNavigate.bind(this); - this._parent.on("window-ready", this._onWindowReady); - this._parent.on("will-navigate", this._onWillNavigate); - this._parent.on("navigate", this._onNavigate); + this.targetActor.on("window-ready", this._onWindowReady); + this.targetActor.on("will-navigate", this._onWillNavigate); + this.targetActor.on("navigate", this._onNavigate); this._firstStatementBreakpoint = null; this._debuggerNotificationObserver = new DebuggerNotificationObserver(); @@ -246,7 +246,7 @@ class ThreadActor extends Actor { get dbg() { if (!this._dbg) { - this._dbg = this._parent.dbg; + this._dbg = this.targetActor.dbg; // Keep the debugger disabled until a client attaches. if (this._state === STATES.DETACHED) { this._dbg.disable(); @@ -289,11 +289,11 @@ class ThreadActor extends Actor { } get sourcesManager() { - return this._parent.sourcesManager; + return this.targetActor.sourcesManager; } get breakpoints() { - return this._parent.breakpoints; + return this.targetActor.breakpoints; } get youngestFrame() { @@ -360,9 +360,9 @@ class ThreadActor extends Actor { } catch (e) {} } - this._parent.off("window-ready", this._onWindowReady); - this._parent.off("will-navigate", this._onWillNavigate); - this._parent.off("navigate", this._onNavigate); + this.targetActor.off("window-ready", this._onWindowReady); + this.targetActor.off("will-navigate", this._onWillNavigate); + this.targetActor.off("navigate", this._onNavigate); this.sourcesManager.off("newSource", this.onNewSourceEvent); this.clearDebuggees(); @@ -418,11 +418,11 @@ class ThreadActor extends Actor { this.alreadyAttached = true; this.dbg.enable(); - // Notify the parent that we've finished attaching. If this is a worker + // Notify the target actor that we've finished attaching. If this is a worker // thread which was paused until attaching, this will allow content to // begin executing. - if (this._parent.onThreadAttached) { - this._parent.onThreadAttached(); + if (this.targetActor.onThreadAttached) { + this.targetActor.onThreadAttached(); } if (Services.obs) { // Set a wrappedJSObject property so |this| can be sent via the observer service @@ -443,7 +443,7 @@ class ThreadActor extends Actor { } const env = new HighlighterEnvironment(); - env.initFromTargetActor(this._parent); + env.initFromTargetActor(this.targetActor); const highlighter = new PausedDebuggerOverlay(env, { resume: () => this.resume(null), stepOver: () => this.resume({ type: "next" }), @@ -453,7 +453,13 @@ class ThreadActor extends Actor { } _canShowOverlay() { - const { window } = this._parent; + // Only attempt to show on overlay on WindowGlobal targets, which displays a document. + // Workers and content processes can't display any overlay. + if (this.targetActor.targetType != Targets.TYPES.FRAME) { + return false; + } + + const { window } = this.targetActor; // The CanvasFrameAnonymousContentHelper class we're using to create the paused overlay // need to have access to a documentElement. @@ -473,21 +479,22 @@ class ThreadActor extends Actor { async showOverlay() { if ( - this.isPaused() && - this._canShowOverlay() && - this._parent.on && - this.pauseOverlay + !this._shouldShowPauseOverlay || + !this.isPaused() || + !this._canShowOverlay() ) { - const reason = this._priorPause.why.type; - await this.pauseOverlay.isReady; + return; + } - // we might not be paused anymore. - if (!this.isPaused()) { - return; - } + const reason = this._priorPause.why.type; + await this.pauseOverlay.isReady; - this.pauseOverlay.show(reason); + // we might not be paused anymore. + if (!this.isPaused()) { + return; } + + this.pauseOverlay.show(reason); } hideOverlay() { @@ -590,7 +597,7 @@ class ThreadActor extends Actor { } getAvailableEventBreakpoints() { - return getAvailableEventBreakpoints(this._parent.window); + return getAvailableEventBreakpoints(this.targetActor.window); } getActiveEventBreakpoints() { return Array.from(this._activeEventBreakpoints); @@ -805,12 +812,15 @@ class ThreadActor extends Actor { if ("observeWasm" in options) { this.dbg.allowUnobservedWasm = !options.observeWasm; } + if ("pauseOverlay" in options) { + this._shouldShowPauseOverlay = !!options.pauseOverlay; + } if ( "pauseWorkersUntilAttach" in options && - this._parent.pauseWorkersUntilAttach + this.targetActor.pauseWorkersUntilAttach ) { - this._parent.pauseWorkersUntilAttach(options.pauseWorkersUntilAttach); + this.targetActor.pauseWorkersUntilAttach(options.pauseWorkersUntilAttach); } if (options.breakpoints) { @@ -977,10 +987,10 @@ class ThreadActor extends Actor { // If the parent actor has been closed, terminate the debuggee script // instead of continuing. Executing JS after the content window is gone is // a bad idea. - return this._parentClosed ? null : undefined; + return this._targetActorClosed ? null : undefined; } - _makeOnEnterFrame({ pauseAndRespond }) { + _makeOnEnterFrame() { return frame => { if (this._requestedFrameRestart) { return null; @@ -1089,7 +1099,7 @@ class ThreadActor extends Actor { return line !== newLocation.line || column !== newLocation.column; } - _makeOnStep({ pauseAndRespond, startFrame, steppingType, completion }) { + _makeOnStep({ pauseAndRespond, startFrame, completion }) { const thread = this; return function () { if (thread._validFrameStepOffset(this, startFrame, this.offset)) { @@ -1336,7 +1346,7 @@ class ThreadActor extends Actor { * when we do not want to notify the front end of a resume, for example when * we are shutting down. */ - doResume({ resumeLimit } = {}) { + doResume() { this._state = STATES.RUNNING; // Drop the actors in the pause actor pool. @@ -1520,7 +1530,7 @@ class ThreadActor extends Actor { } } - sources(request) { + sources() { this.addAllSources(); // No need to flush the new source packets here, as we are sending the @@ -1528,7 +1538,11 @@ class ThreadActor extends Actor { // overhead of an RDP packet for every source right now. Let the default // timeout flush the buffered packets. - return this.sourcesManager.iter().map(s => s.form()); + const forms = []; + for (const source of this.sourcesManager.iter()) { + forms.push(source.form()); + } + return forms; } /** @@ -1809,7 +1823,7 @@ class ThreadActor extends Actor { this.threadLifetimePool.objectActors.set(actor.obj, actor); } - _onWindowReady({ isTopLevel, isBFCache, window }) { + _onWindowReady({ isTopLevel, isBFCache }) { // Note that this code relates to the disabling of Debugger API from will-navigate listener. // And should only be triggered when the target actor doesn't follow WindowGlobal lifecycle. // i.e. when the Thread Actor manages more than one top level WindowGlobal. @@ -1817,9 +1831,6 @@ class ThreadActor extends Actor { this.sourcesManager.reset(); this.clearDebuggees(); this.dbg.enable(); - // Update the global no matter if the debugger is on or off, - // otherwise the global will be wrong when enabled later. - this.global = window; } // Refresh the debuggee list when a new window object appears (top window or @@ -2119,7 +2130,7 @@ class ThreadActor extends Actor { // when debugging a tab (i.e. browser-element). As we still want to debug them // from the browser toolbox. if ( - this._parent.sessionContext.type == "browser-element" && + this.targetActor.sessionContext.type == "browser-element" && source.url.endsWith("ExtensionContent.sys.mjs") ) { return false; @@ -2208,7 +2219,7 @@ class ThreadActor extends Actor { // HTML files can contain any number of inline sources. We have to find // all the inline sources and their start line without running any of the // scripts on the page. The approach used here is approximate. - if (!this._parent.window) { + if (!this.targetActor.window) { return; } diff --git a/devtools/server/actors/tracer.js b/devtools/server/actors/tracer.js index 028d084584..bf759cee5f 100644 --- a/devtools/server/actors/tracer.js +++ b/devtools/server/actors/tracer.js @@ -129,29 +129,40 @@ class TracerActor extends Actor { this.tracingListener = { onTracingFrame: this.onTracingFrame.bind(this), + onTracingFrameStep: this.onTracingFrameStep.bind(this), onTracingFrameExit: this.onTracingFrameExit.bind(this), onTracingInfiniteLoop: this.onTracingInfiniteLoop.bind(this), onTracingToggled: this.onTracingToggled.bind(this), onTracingPending: this.onTracingPending.bind(this), + onTracingDOMMutation: this.onTracingDOMMutation.bind(this), }; addTracingListener(this.tracingListener); this.traceValues = !!options.traceValues; - startTracing({ - global: this.targetActor.window || this.targetActor.workerGlobal, - prefix: options.prefix || "", - // Enable receiving the `currentDOMEvent` being passed to `onTracingFrame` - traceDOMEvents: true, - // Enable tracing function arguments as well as returned values - traceValues: !!options.traceValues, - // Enable tracing only on next user interaction - traceOnNextInteraction: !!options.traceOnNextInteraction, - // Notify about frame exit / function call returning - traceFunctionReturn: !!options.traceFunctionReturn, - // Ignore frames beyond the given depth - maxDepth: options.maxDepth, - // Stop the tracing after a number of top level frames - maxRecords: options.maxRecords, - }); + try { + startTracing({ + global: this.targetActor.window || this.targetActor.workerGlobal, + prefix: options.prefix || "", + // Enable receiving the `currentDOMEvent` being passed to `onTracingFrame` + traceDOMEvents: true, + // Enable tracing DOM Mutations + traceDOMMutations: options.traceDOMMutations, + // Enable tracing function arguments as well as returned values + traceValues: !!options.traceValues, + // Enable tracing only on next user interaction + traceOnNextInteraction: !!options.traceOnNextInteraction, + // Notify about frame exit / function call returning + traceFunctionReturn: !!options.traceFunctionReturn, + // Ignore frames beyond the given depth + maxDepth: options.maxDepth, + // Stop the tracing after a number of top level frames + maxRecords: options.maxRecords, + }); + } catch (e) { + // If startTracing throws, it probably rejected one of its options and we should + // unregister the tracing listener. + this.stopTracing(); + throw e; + } } stopTracing() { @@ -188,12 +199,10 @@ class TracerActor extends Actor { * * @param {Boolean} enabled * True if the tracer starts tracing, false it it stops. - * @param {String} reason - * Optional string to justify why the tracer stopped. * @return {Boolean} * Return true, if the JavaScriptTracer should log a message to stdout. */ - onTracingToggled(enabled, reason) { + onTracingToggled(enabled) { // stopTracing will clear `logMethod`, so compute this before calling it. const shouldLogToStdout = this.logMethod == LOG_METHODS.STDOUT; @@ -266,11 +275,111 @@ class TracerActor extends Actor { } /** + * Called by JavaScriptTracer class when a new mutation happened on any DOM Element. + * + * @param {Object} options + * @param {Number} options.depth + * Represents the depth of the frame in the call stack. + * @param {String} options.prefix + * A string to be displayed as a prefix of any logged frame. + * @param {nsIStackFrame} options.caller + * The JS Callsite which caused this mutation. + * @param {String} options.type + * Type of DOM Mutation: + * - "add": Node being added, + * - "attributes": Node whose attributes changed, + * - "remove": Node being removed, + * @param {DOMNode} options.element + * The DOM Node related to the current mutation. + */ + onTracingDOMMutation({ depth, prefix, type, caller, element }) { + // Delegate to JavaScriptTracer to log to stdout + if (this.logMethod == LOG_METHODS.STDOUT) { + return true; + } + + if (this.logMethod == LOG_METHODS.CONSOLE) { + const dbgObj = makeDebuggeeValue(this.targetActor, element); + this.throttledTraces.push({ + resourceType: JSTRACER_TRACE, + prefix, + timeStamp: ChromeUtils.dateNow(), + + filename: caller?.filename, + lineNumber: caller?.lineNumber, + columnNumber: caller?.columnNumber, + sourceId: caller.sourceId, + + depth, + mutationType: type, + mutationElement: createValueGripForTarget(this.targetActor, dbgObj), + }); + this.throttleEmitTraces(); + return false; + } + return false; + } + + /** + * Called by JavaScriptTracer class on each step of a function call. + * + * @param {Object} options + * @param {Debugger.Frame} options.frame + * A descriptor object for the JavaScript frame. + * @param {Number} options.depth + * Represents the depth of the frame in the call stack. + * @param {String} options.prefix + * A string to be displayed as a prefix of any logged frame. + * @return {Boolean} + * Return true, if the JavaScriptTracer should log the step to stdout. + */ + onTracingFrameStep({ frame, depth, prefix }) { + const { script } = frame; + const { lineNumber, columnNumber } = script.getOffsetMetadata(frame.offset); + const url = script.source.url; + + // NOTE: Debugger.Script.prototype.getOffsetMetadata returns + // columnNumber in 1-based. + // Convert to 0-based, while keeping the wasm's column (1) as is. + // (bug 1863878) + const columnBase = script.format === "wasm" ? 0 : 1; + + // Ignore blackboxed sources + if ( + this.sourcesManager.isBlackBoxed( + url, + lineNumber, + columnNumber - columnBase + ) + ) { + return false; + } + + if (this.logMethod == LOG_METHODS.STDOUT) { + // By returning true, we let JavaScriptTracer class log the message to stdout. + return true; + } + + if (this.logMethod == LOG_METHODS.CONSOLE) { + this.throttledTraces.push({ + resourceType: JSTRACER_TRACE, + prefix, + timeStamp: ChromeUtils.dateNow(), + + depth, + filename: url, + lineNumber, + columnNumber: columnNumber - columnBase, + sourceId: script.source.id, + }); + this.throttleEmitTraces(); + } + + return false; + } + /** * Called by JavaScriptTracer class when a new JavaScript frame is executed. * - * @param {Number} frameId - * Unique identifier for the current frame. - * This should match a frame notified via onTracingFrameExit. * @param {Debugger.Frame} frame * A descriptor object for the JavaScript frame. * @param {Number} depth @@ -287,7 +396,6 @@ class TracerActor extends Actor { * Return true, if the JavaScriptTracer should log the frame to stdout. */ onTracingFrame({ - frameId, frame, depth, formatedDisplayName, diff --git a/devtools/server/actors/utils/custom-formatters.js b/devtools/server/actors/utils/custom-formatters.js index e4ae20dad7..f6d8cab797 100644 --- a/devtools/server/actors/utils/custom-formatters.js +++ b/devtools/server/actors/utils/custom-formatters.js @@ -71,7 +71,7 @@ function customFormatterHeader(objectActor) { return null; } - const targetActor = objectActor.thread._parent; + const { targetActor } = objectActor.thread; const { customFormatterConfigDbgObj: configDbgObj, @@ -253,7 +253,7 @@ async function customFormatterBody(objectActor, formatter) { const customFormatterIndex = global.devtoolsFormatters.indexOf(formatter); - const targetActor = objectActor.thread._parent; + const { targetActor } = objectActor.thread; try { const { customFormatterConfigDbgObj, customFormatterObjectTagDepth } = objectActor.hooks; diff --git a/devtools/server/actors/utils/inactive-property-helper.js b/devtools/server/actors/utils/inactive-property-helper.js index 759c2e6215..3f6e748167 100644 --- a/devtools/server/actors/utils/inactive-property-helper.js +++ b/devtools/server/actors/utils/inactive-property-helper.js @@ -21,6 +21,11 @@ const TEXT_WRAP_BALANCE_LIMIT = Services.prefs.getIntPref( 10 ); +const ALIGN_CONTENT_BLOCKS = Services.prefs.getBoolPref( + "layout.css.align-content.blocks.enabled", + false +); + const VISITED_MDN_LINK = "https://developer.mozilla.org/docs/Web/CSS/:visited"; const VISITED_INVALID_PROPERTIES = allCssPropertiesExcept([ "all", @@ -227,12 +232,29 @@ class InactivePropertyHelper { // See https://bugzilla.mozilla.org/show_bug.cgi?id=1598730 { invalidProperties: ["align-content"], - when: () => - !this.style["align-content"].includes("baseline") && - !this.gridContainer && - !this.flexContainer, - fixId: "inactive-css-not-grid-or-flex-container-fix", - msgId: "inactive-css-not-grid-or-flex-container", + when: () => { + if (this.style["align-content"].includes("baseline")) { + return false; + } + const supportedDisplay = [ + "flex", + "inline-flex", + "grid", + "inline-grid", + // Uncomment table-cell when Bug 1883357 is fixed. + // "table-cell" + ]; + if (ALIGN_CONTENT_BLOCKS) { + supportedDisplay.push("block", "inline-block"); + } + return !this.checkComputedStyle("display", supportedDisplay); + }, + fixId: ALIGN_CONTENT_BLOCKS + ? "inactive-css-not-grid-or-flex-or-block-container-fix" + : "inactive-css-not-grid-or-flex-container-fix", + msgId: ALIGN_CONTENT_BLOCKS + ? "inactive-css-property-because-of-display" + : "inactive-css-not-grid-or-flex-container", }, // column-gap and shorthands used on non-grid or non-flex or non-multi-col container. { @@ -1223,11 +1245,8 @@ class InactivePropertyHelper { /** * Check if a node is a grid item. - * - * @param {DOMNode} node - * The node to check. */ - isGridItem(node) { + isGridItem() { return !!this.getParentGridElement(this.node); } diff --git a/devtools/server/actors/utils/logEvent.js b/devtools/server/actors/utils/logEvent.js index 88b166619e..5f40085fde 100644 --- a/devtools/server/actors/utils/logEvent.js +++ b/devtools/server/actors/utils/logEvent.js @@ -34,7 +34,7 @@ function logEvent({ threadActor, frame, level, expression, bindings }) { // TODO remove this branch when (#1592584) lands (#1609540) if (isWorker) { - threadActor._parent._consoleActor.evaluateJS({ + threadActor.targetActor._consoleActor.evaluateJS({ text: `console.log(...${expression})`, bindings: { displayName, ...bindings }, url: sourceActor.url, @@ -76,7 +76,7 @@ function logEvent({ threadActor, frame, level, expression, bindings }) { value = value.unsafeDereference(); } - const targetActor = threadActor._parent; + const targetActor = threadActor.targetActor; const message = { filename: sourceActor.url, lineNumber: line, diff --git a/devtools/server/actors/utils/sources-manager.js b/devtools/server/actors/utils/sources-manager.js index b80da69bfa..fda37a3184 100644 --- a/devtools/server/actors/utils/sources-manager.js +++ b/devtools/server/actors/utils/sources-manager.js @@ -341,8 +341,13 @@ class SourcesManager extends EventEmitter { return this.blackBoxedSources.set(url, ranges); } + /** + * List all currently registered source actors. + * + * @return Iterator<SourceActor> + */ iter() { - return [...this._sourceActors.values()]; + return this._sourceActors.values(); } /** @@ -429,15 +434,15 @@ class SourcesManager extends EventEmitter { // Without this check, the cache may return stale data that doesn't match // the document shown in the browser. let loadFromCache = canUseCache; - if (canUseCache && this._thread._parent.browsingContext) { + if (canUseCache && this._thread.targetActor.browsingContext) { loadFromCache = !( - this._thread._parent.browsingContext.defaultLoadFlags === + this._thread.targetActor.browsingContext.defaultLoadFlags === Ci.nsIRequest.LOAD_BYPASS_CACHE ); } // Fetch the sources with the same principal as the original document - const win = this._thread._parent.window; + const win = this._thread.targetActor.window; let principal, cacheKey; // On xpcshell, we don't have a window but a Sandbox if (!isWorker && win instanceof Ci.nsIDOMWindow) { diff --git a/devtools/server/actors/utils/stylesheets-manager.js b/devtools/server/actors/utils/stylesheets-manager.js index 838e5be602..a9c0705e8d 100644 --- a/devtools/server/actors/utils/stylesheets-manager.js +++ b/devtools/server/actors/utils/stylesheets-manager.js @@ -640,10 +640,14 @@ class StyleSheetsManager extends EventEmitter { return win; }; - const styleSheetRules = - InspectorUtils.getAllStyleSheetCSSStyleRules(styleSheet); - const ruleCount = styleSheetRules.length; - // We need to go through nested rules to extract all the rules we're interested in + // This returns the following type of at-rules: + // - CSSMediaRule + // - CSSContainerRule + // - CSSSupportsRule + // - CSSLayerBlockRule + // New types can be added from InpsectorUtils.cpp `CollectAtRules` + const { atRules: styleSheetRules, ruleCount } = + InspectorUtils.getStyleSheetRuleCountAndAtRules(styleSheet); const atRules = []; for (const rule of styleSheetRules) { const className = ChromeUtils.getClassName(rule); @@ -703,7 +707,10 @@ class StyleSheetsManager extends EventEmitter { }); } } - return { ruleCount, atRules }; + return { + ruleCount, + atRules, + }; } /** diff --git a/devtools/server/actors/watcher.js b/devtools/server/actors/watcher.js index 97d2be01e4..935d33faa8 100644 --- a/devtools/server/actors/watcher.js +++ b/devtools/server/actors/watcher.js @@ -9,21 +9,18 @@ const { watcherSpec } = require("resource://devtools/shared/specs/watcher.js"); const Resources = require("resource://devtools/server/actors/resources/index.js"); const { TargetActorRegistry } = ChromeUtils.importESModule( "resource://devtools/server/actors/targets/target-actor-registry.sys.mjs", - { - loadInDevToolsLoader: false, - } + { global: "shared" } ); const { WatcherRegistry } = ChromeUtils.importESModule( "resource://devtools/server/actors/watcher/WatcherRegistry.sys.mjs", - { - // WatcherRegistry needs to be a true singleton and loads ActorManagerParent - // which also has to be a true singleton. - loadInDevToolsLoader: false, - } + // WatcherRegistry needs to be a true singleton and loads ActorManagerParent + // which also has to be a true singleton. + { global: "shared" } ); const Targets = require("resource://devtools/server/actors/targets/index.js"); const { getAllBrowsingContextsForContext } = ChromeUtils.importESModule( - "resource://devtools/server/actors/watcher/browsing-context-helpers.sys.mjs" + "resource://devtools/server/actors/watcher/browsing-context-helpers.sys.mjs", + { global: "contextual" } ); const { SESSION_TYPES, @@ -257,11 +254,11 @@ exports.WatcherActor = class WatcherActor extends Actor { const targetHelperModule = TARGET_HELPERS[targetType]; targetHelperModule.destroyTargets(this, options); - // Unregister the JS Window Actor if there is no more DevTools code observing any target/resource, + // Unregister the JS Actors if there is no more DevTools code observing any target/resource, // unless we're switching mode (having both condition at the same time should only // happen in tests). if (!options.isModeSwitching) { - WatcherRegistry.maybeUnregisteringJSWindowActor(); + WatcherRegistry.maybeUnregisterJSActors(); } } @@ -637,7 +634,7 @@ exports.WatcherActor = class WatcherActor extends Actor { } // Unregister the JS Window Actor if there is no more DevTools code observing any target/resource - WatcherRegistry.maybeUnregisteringJSWindowActor(); + WatcherRegistry.maybeUnregisterJSActors(); } clearResources(resourceTypes) { diff --git a/devtools/server/actors/watcher/WatcherRegistry.sys.mjs b/devtools/server/actors/watcher/WatcherRegistry.sys.mjs index 1068a253c9..ac8bc7f0c8 100644 --- a/devtools/server/actors/watcher/WatcherRegistry.sys.mjs +++ b/devtools/server/actors/watcher/WatcherRegistry.sys.mjs @@ -180,6 +180,9 @@ export const WatcherRegistry = { // Register the JS Window Actor the first time we start watching for something (e.g. resource, target, …). registerJSWindowActor(); + if (sessionData?.targets?.includes("process")) { + registerJSProcessActor(); + } persistMapToSharedData(); }, @@ -245,7 +248,17 @@ export const WatcherRegistry = { unregisterWatcher(watcher) { sessionDataByWatcherActor.delete(watcher.actorID); watcherActors.delete(watcher.actorID); - this.maybeUnregisteringJSWindowActor(); + this.maybeUnregisterJSActors(); + }, + + /** + * Unregister the JS Actors if there is no more DevTools code observing any target/resource. + */ + maybeUnregisterJSActors() { + if (sessionDataByWatcherActor.size == 0) { + unregisterJSWindowActor(); + unregisterJSProcessActor(); + } }, /** @@ -319,15 +332,6 @@ export const WatcherRegistry = { resourceTypes ); }, - - /** - * Unregister the JS Window Actor if there is no more DevTools code observing any target/resource. - */ - maybeUnregisteringJSWindowActor() { - if (sessionDataByWatcherActor.size == 0) { - unregisterJSWindowActor(); - } - }, }; // Boolean flag to know if the DevToolsFrame JS Window Actor is currently registered @@ -395,3 +399,63 @@ function unregisterJSWindowActor() { ChromeUtils.unregisterWindowActor(JSWindowActorName); } } + +// Boolean flag to know if the DevToolsProcess JS Process Actor is currently registered +let isJSProcessActorRegistered = false; + +const JSProcessActorConfig = { + parent: { + esModuleURI: + "resource://devtools/server/connectors/js-process-actor/DevToolsProcessParent.sys.mjs", + }, + child: { + esModuleURI: + "resource://devtools/server/connectors/js-process-actor/DevToolsProcessChild.sys.mjs", + // There is no good observer service notification we can listen to to instantiate the JSProcess Actor + // reliably as soon as the process start. + // So manually spawn our JSProcessActor from a process script emitting a custom observer service notification... + observers: ["init-devtools-content-process-actor"], + }, + // The parent process is handled very differently from content processes + // This uses the ParentProcessTarget which inherits from BrowsingContextTarget + // and is, for now, manually created by the descriptor as the top level target. + includeParent: false, + + // This JS Process Actor is used to bootstrap DevTools code debugging the privileged code + // in content processes. The privileged code runs in the "shared JSM global" (See mozJSModuleLoader). + // DevTools modules should be loaded in a distinct global in order to be able to debug this privileged code. + // There is a strong requirement in spidermonkey for the debuggee and debugger to be using distinct compartments. + // This flag will force both parent and child modules to be loaded via a dedicated loader (See mozJSModuleLoader::GetOrCreateDevToolsLoader) + // + // Note that as a side effect, it makes these modules and all their dependencies to be invisible to the debugger. + loadInDevToolsLoader: true, +}; + +const PROCESS_SCRIPT_URL = + "resource://devtools/server/actors/watcher/target-helpers/content-process-jsprocessactor-startup.js"; + +function registerJSProcessActor() { + if (isJSProcessActorRegistered) { + return; + } + isJSProcessActorRegistered = true; + ChromeUtils.registerProcessActor("DevToolsProcess", JSProcessActorConfig); + + // There is no good observer service notification we can listen to to instantiate the JSProcess Actor + // as soon as the process start. + // So manually spawn our JSProcessActor from a process script emitting a custom observer service notification... + Services.ppmm.loadProcessScript(PROCESS_SCRIPT_URL, true); +} + +function unregisterJSProcessActor() { + if (!isJSProcessActorRegistered) { + return; + } + isJSProcessActorRegistered = false; + try { + ChromeUtils.unregisterProcessActor("DevToolsProcess"); + } catch (e) { + // If any pending query was still ongoing, this would throw + } + Services.ppmm.removeDelayedProcessScript(PROCESS_SCRIPT_URL); +} diff --git a/devtools/server/actors/watcher/target-helpers/content-process-jsprocessactor-startup.js b/devtools/server/actors/watcher/target-helpers/content-process-jsprocessactor-startup.js new file mode 100644 index 0000000000..1765bcc66c --- /dev/null +++ b/devtools/server/actors/watcher/target-helpers/content-process-jsprocessactor-startup.js @@ -0,0 +1,26 @@ +/* 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 { setTimeout } = ChromeUtils.importESModule( + "resource://gre/modules/Timer.sys.mjs" +); + +/* + We can't spawn the JSProcessActor right away and have to spin the event loop. + Otherwise it isn't registered yet and isn't listening to observer service. + Could it be the reason why JSProcessActor aren't spawn via process actor option's child.observers notifications ?? +*/ +setTimeout(function () { + /* + This notification is registered in DevToolsServiceWorker JS process actor's options's `observers` attribute + and will force the JS Process actor to be instantiated in all processes. + */ + Services.obs.notifyObservers(null, "init-devtools-content-process-actor"); + /* + Instead of using observer service, we could also manually call some method of the actor: + ChromeUtils.domProcessChild.getActor("DevToolsProcess").observe(null, "foo"); + */ +}, 0); diff --git a/devtools/server/actors/watcher/target-helpers/frame-helper.js b/devtools/server/actors/watcher/target-helpers/frame-helper.js index 0e6f4f80d3..18d4d8f92e 100644 --- a/devtools/server/actors/watcher/target-helpers/frame-helper.js +++ b/devtools/server/actors/watcher/target-helpers/frame-helper.js @@ -6,14 +6,13 @@ const { WatcherRegistry } = ChromeUtils.importESModule( "resource://devtools/server/actors/watcher/WatcherRegistry.sys.mjs", - { - // WatcherRegistry needs to be a true singleton and loads ActorManagerParent - // which also has to be a true singleton. - loadInDevToolsLoader: false, - } + // WatcherRegistry needs to be a true singleton and loads ActorManagerParent + // which also has to be a true singleton. + { global: "shared" } ); const { WindowGlobalLogger } = ChromeUtils.importESModule( - "resource://devtools/server/connectors/js-window-actor/WindowGlobalLogger.sys.mjs" + "resource://devtools/server/connectors/js-window-actor/WindowGlobalLogger.sys.mjs", + { global: "contextual" } ); const Targets = require("resource://devtools/server/actors/targets/index.js"); diff --git a/devtools/server/actors/watcher/target-helpers/moz.build b/devtools/server/actors/watcher/target-helpers/moz.build index b7c8983590..3b00f0ef47 100644 --- a/devtools/server/actors/watcher/target-helpers/moz.build +++ b/devtools/server/actors/watcher/target-helpers/moz.build @@ -5,6 +5,7 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. DevToolsModules( + "content-process-jsprocessactor-startup.js", "frame-helper.js", "process-helper.js", "service-worker-helper.js", diff --git a/devtools/server/actors/watcher/target-helpers/process-helper.js b/devtools/server/actors/watcher/target-helpers/process-helper.js index 8895d7ed66..e36f0a204c 100644 --- a/devtools/server/actors/watcher/target-helpers/process-helper.js +++ b/devtools/server/actors/watcher/target-helpers/process-helper.js @@ -4,205 +4,36 @@ "use strict"; -const { WatcherRegistry } = ChromeUtils.importESModule( - "resource://devtools/server/actors/watcher/WatcherRegistry.sys.mjs", - { - // WatcherRegistry needs to be a true singleton and loads ActorManagerParent - // which also has to be a true singleton. - loadInDevToolsLoader: false, - } -); - -loader.lazyRequireGetter( - this, - "ChildDebuggerTransport", - "resource://devtools/shared/transport/child-transport.js", - true -); - -const CONTENT_PROCESS_SCRIPT = - "resource://devtools/server/startup/content-process-script.js"; - /** - * Map a MessageManager key to an Array of ContentProcessTargetActor "description" objects. - * A single MessageManager might be linked to several ContentProcessTargetActors if there are several - * Watcher actors instantiated on the DevToolsServer, via a single connection (in theory), but rather - * via distinct connections (ex: a content toolbox and the browser toolbox). - * Note that if we spawn two DevToolsServer, this module will be instantiated twice. + * Return the list of all DOM Processes except the one for the parent process * - * Each ContentProcessTargetActor "description" object is structured as follows - * - {Object} actor: form of the content process target actor - * - {String} prefix: forwarding prefix used to redirect all packet to the right content process's transport - * - {ChildDebuggerTransport} childTransport: Transport forwarding all packets to the target's content process - * - {WatcherActor} watcher: The Watcher actor for which we instantiated this content process target actor + * @return Array<nsIDOMProcessParent> */ -const actors = new WeakMap(); - -// Save the list of all watcher actors that are watching for processes -const watchers = new Set(); - -function onContentProcessActorCreated(msg) { - const { watcherActorID, prefix, actor } = msg.data; - const watcher = WatcherRegistry.getWatcher(watcherActorID); - if (!watcher) { - throw new Error( - `Receiving a content process actor without a watcher actor ${watcherActorID}` - ); - } - // Ignore watchers of other connections. - // We may have two browser toolbox connected to the same process. - // This will spawn two distinct Watcher actor and two distinct process target helper module. - // Avoid processing the event many times, otherwise we will notify about the same target - // multiple times. - if (!watchers.has(watcher)) { - return; - } - const messageManager = msg.target; - const connection = watcher.conn; - - // Pipe Debugger message from/to parent/child via the message manager - const childTransport = new ChildDebuggerTransport(messageManager, prefix); - childTransport.hooks = { - onPacket: connection.send.bind(connection), - }; - childTransport.ready(); - - connection.setForwarding(prefix, childTransport); - - const list = actors.get(messageManager) || []; - list.push({ - prefix, - childTransport, - actor, - watcher, - }); - actors.set(messageManager, list); - - watcher.notifyTargetAvailable(actor); -} - -function onContentProcessActorDestroyed(msg) { - const { watcherActorID } = msg.data; - const watcher = WatcherRegistry.getWatcher(watcherActorID); - if (!watcher) { - throw new Error( - `Receiving a content process actor destruction without a watcher actor ${watcherActorID}` - ); - } - // Ignore watchers of other connections. - // We may have two browser toolbox connected to the same process. - // This will spawn two distinct Watcher actor and two distinct process target helper module. - // Avoid processing the event many times, otherwise we will notify about the same target - // multiple times. - if (!watchers.has(watcher)) { - return; - } - const messageManager = msg.target; - unregisterWatcherForMessageManager(watcher, messageManager); -} - -function onMessageManagerClose(messageManager, topic, data) { - const list = actors.get(messageManager); - if (!list || !list.length) { - return; - } - for (const { prefix, childTransport, actor, watcher } of list) { - watcher.notifyTargetDestroyed(actor); - - // If we have a child transport, the actor has already - // been created. We need to stop using this message manager. - childTransport.close(); - watcher.conn.cancelForwarding(prefix); - } - actors.delete(messageManager); -} - -/** - * Unregister everything created for a given watcher against a precise message manager: - * - clear up things from `actors` WeakMap, - * - notify all related target actors as being destroyed, - * - close all DevTools Transports being created for each Message Manager. - * - * @param {WatcherActor} watcher - * @param {MessageManager} - * @param {object} options - * @param {boolean} options.isModeSwitching - * true when this is called as the result of a change to the devtools.browsertoolbox.scope pref - */ -function unregisterWatcherForMessageManager(watcher, messageManager, options) { - const targetActorDescriptions = actors.get(messageManager); - if (!targetActorDescriptions || !targetActorDescriptions.length) { - return; - } - - // Destroy all transports related to this watcher and tells the client to purge all related actors - const matchingTargetActorDescriptions = targetActorDescriptions.filter( - item => item.watcher === watcher +function getAllContentProcesses() { + return ChromeUtils.getAllDOMProcesses().filter( + process => process.childID !== 0 ); - for (const { - prefix, - childTransport, - actor, - } of matchingTargetActorDescriptions) { - watcher.notifyTargetDestroyed(actor, options); - - childTransport.close(); - watcher.conn.cancelForwarding(prefix); - } - - // Then update global `actors` WeakMap by stripping all data about this watcher - const remainingTargetActorDescriptions = targetActorDescriptions.filter( - item => item.watcher !== watcher - ); - if (!remainingTargetActorDescriptions.length) { - actors.delete(messageManager); - } else { - actors.set(messageManager, remainingTargetActorDescriptions); - } } /** - * Destroy everything related to a given watcher that has been created in this module: - * (See unregisterWatcherForMessageManager) + * Instantiate all Content Process targets in all the DOM Processes. * * @param {WatcherActor} watcher - * @param {object} options - * @param {boolean} options.isModeSwitching - * true when this is called as the result of a change to the devtools.browsertoolbox.scope pref */ -function closeWatcherTransports(watcher, options) { - for (let i = 0; i < Services.ppmm.childCount; i++) { - const messageManager = Services.ppmm.getChildAt(i); - unregisterWatcherForMessageManager(watcher, messageManager, options); - } -} - -function maybeRegisterMessageListeners(watcher) { - const sizeBefore = watchers.size; - watchers.add(watcher); - if (sizeBefore == 0 && watchers.size == 1) { - Services.ppmm.addMessageListener( - "debug:content-process-actor", - onContentProcessActorCreated - ); - Services.ppmm.addMessageListener( - "debug:content-process-actor-destroyed", - onContentProcessActorDestroyed +async function createTargets(watcher) { + const promises = []; + for (const domProcess of getAllContentProcesses()) { + const processActor = domProcess.getActor("DevToolsProcess"); + promises.push( + processActor.instantiateTarget({ + watcherActorID: watcher.actorID, + connectionPrefix: watcher.conn.prefix, + sessionContext: watcher.sessionContext, + sessionData: watcher.sessionData, + }) ); - Services.obs.addObserver(onMessageManagerClose, "message-manager-close"); - - // Load the content process server startup script only once, - // otherwise it will be evaluated twice, listen to events twice and create - // target actors twice. - // We may try to load it twice when opening one Browser Toolbox via about:debugging - // and another regular Browser Toolbox. Both will spawn a WatcherActor and watch for processes. - const isContentProcessScripLoaded = Services.ppmm - .getDelayedProcessScripts() - .some(([uri]) => uri === CONTENT_PROCESS_SCRIPT); - if (!isContentProcessScripLoaded) { - Services.ppmm.loadProcessScript(CONTENT_PROCESS_SCRIPT, true); - } } + await Promise.all(promises); } /** @@ -211,96 +42,16 @@ function maybeRegisterMessageListeners(watcher) { * @param {boolean} options.isModeSwitching * true when this is called as the result of a change to the devtools.browsertoolbox.scope pref */ -function maybeUnregisterMessageListeners(watcher, options = {}) { - const sizeBefore = watchers.size; - watchers.delete(watcher); - closeWatcherTransports(watcher, options); - - if (sizeBefore == 1 && watchers.size == 0) { - Services.ppmm.removeMessageListener( - "debug:content-process-actor", - onContentProcessActorCreated - ); - Services.ppmm.removeMessageListener( - "debug:content-process-actor-destroyed", - onContentProcessActorDestroyed - ); - Services.obs.removeObserver(onMessageManagerClose, "message-manager-close"); - - // We inconditionally remove the process script, while we should only remove it - // once the last DevToolsServer stop watching for processes. - // We might have many server, using distinct loaders, so that this module - // will be spawn many times and we should remove the script only once the last - // module unregister the last watcher of all. - Services.ppmm.removeDelayedProcessScript(CONTENT_PROCESS_SCRIPT); - - Services.ppmm.broadcastAsyncMessage("debug:destroy-process-script", { - options, +function destroyTargets(watcher, options) { + for (const domProcess of getAllContentProcesses()) { + const processActor = domProcess.getActor("DevToolsProcess"); + processActor.destroyTarget({ + watcherActorID: watcher.actorID, + isModeSwitching: options.isModeSwitching, }); } } -async function createTargets(watcher) { - // XXX: Should this move to WatcherRegistry?? - maybeRegisterMessageListeners(watcher); - - // Bug 1648499: This could be simplified when migrating to JSProcessActor by using sendQuery. - // For now, hack into WatcherActor in order to know when we created one target - // actor for each existing content process. - // Also, we substract one as the parent process has a message manager and is counted - // in `childCount`, but we ignore it from the process script and it won't reply. - let contentProcessCount = Services.ppmm.childCount - 1; - if (contentProcessCount == 0) { - return; - } - const onTargetsCreated = new Promise(resolve => { - let receivedTargetCount = 0; - const listener = () => { - receivedTargetCount++; - mayBeResolve(); - }; - watcher.on("target-available-form", listener); - const onContentProcessClosed = () => { - // Update the content process count as one has been just destroyed - contentProcessCount--; - mayBeResolve(); - }; - Services.obs.addObserver(onContentProcessClosed, "message-manager-close"); - function mayBeResolve() { - if (receivedTargetCount >= contentProcessCount) { - watcher.off("target-available-form", listener); - Services.obs.removeObserver( - onContentProcessClosed, - "message-manager-close" - ); - resolve(); - } - } - }); - - Services.ppmm.broadcastAsyncMessage("debug:instantiate-already-available", { - watcherActorID: watcher.actorID, - connectionPrefix: watcher.conn.prefix, - sessionData: watcher.sessionData, - }); - - await onTargetsCreated; -} - -/** - * @param {WatcherActor} watcher - * @param {object} options - * @param {boolean} options.isModeSwitching - * true when this is called as the result of a change to the devtools.browsertoolbox.scope pref - */ -function destroyTargets(watcher, options) { - maybeUnregisterMessageListeners(watcher, options); - - Services.ppmm.broadcastAsyncMessage("debug:destroy-target", { - watcherActorID: watcher.actorID, - }); -} - /** * Go over all existing content processes in order to communicate about new data entries * @@ -321,51 +72,19 @@ async function addOrSetSessionDataEntry({ entries, updateType, }) { - let expectedCount = Services.ppmm.childCount - 1; - if (expectedCount == 0) { - return; - } - const onAllReplied = new Promise(resolve => { - let count = 0; - const listener = msg => { - if (msg.data.watcherActorID != watcher.actorID) { - return; - } - count++; - maybeResolve(); - }; - Services.ppmm.addMessageListener( - "debug:add-or-set-session-data-entry-done", - listener + const promises = []; + for (const domProcess of getAllContentProcesses()) { + const processActor = domProcess.getActor("DevToolsProcess"); + promises.push( + processActor.addOrSetSessionDataEntry({ + watcherActorID: watcher.actorID, + type, + entries, + updateType, + }) ); - const onContentProcessClosed = (messageManager, topic, data) => { - expectedCount--; - maybeResolve(); - }; - const maybeResolve = () => { - if (count == expectedCount) { - Services.ppmm.removeMessageListener( - "debug:add-or-set-session-data-entry-done", - listener - ); - Services.obs.removeObserver( - onContentProcessClosed, - "message-manager-close" - ); - resolve(); - } - }; - Services.obs.addObserver(onContentProcessClosed, "message-manager-close"); - }); - - Services.ppmm.broadcastAsyncMessage("debug:add-or-set-session-data-entry", { - watcherActorID: watcher.actorID, - type, - entries, - updateType, - }); - - await onAllReplied; + } + await Promise.all(promises); } /** @@ -373,12 +92,19 @@ async function addOrSetSessionDataEntry({ * * See addOrSetSessionDataEntry for argument documentation. */ -function removeSessionDataEntry({ watcher, type, entries }) { - Services.ppmm.broadcastAsyncMessage("debug:remove-session-data-entry", { - watcherActorID: watcher.actorID, - type, - entries, - }); +async function removeSessionDataEntry({ watcher, type, entries }) { + const promises = []; + for (const domProcess of getAllContentProcesses()) { + const processActor = domProcess.getActor("DevToolsProcess"); + promises.push( + processActor.removeSessionDataEntry({ + watcherActorID: watcher.actorID, + type, + entries, + }) + ); + } + await Promise.all(promises); } module.exports = { diff --git a/devtools/server/actors/webbrowser.js b/devtools/server/actors/webbrowser.js index c05a863839..89db57a642 100644 --- a/devtools/server/actors/webbrowser.js +++ b/devtools/server/actors/webbrowser.js @@ -52,7 +52,7 @@ const lazy = {}; loader.lazyGetter(lazy, "AddonManager", () => { return ChromeUtils.importESModule( "resource://gre/modules/AddonManager.sys.mjs", - { loadInDevToolsLoader: false } + { global: "shared" } ).AddonManager; }); @@ -710,35 +710,35 @@ Object.defineProperty(BrowserAddonList.prototype, "onListChanged", { /** * AddonManager listener must implement onDisabled. */ -BrowserAddonList.prototype.onDisabled = function (addon) { +BrowserAddonList.prototype.onDisabled = function () { this._onAddonManagerUpdated(); }; /** * AddonManager listener must implement onEnabled. */ -BrowserAddonList.prototype.onEnabled = function (addon) { +BrowserAddonList.prototype.onEnabled = function () { this._onAddonManagerUpdated(); }; /** * AddonManager listener must implement onInstalled. */ -BrowserAddonList.prototype.onInstalled = function (addon) { +BrowserAddonList.prototype.onInstalled = function () { this._onAddonManagerUpdated(); }; /** * AddonManager listener must implement onOperationCancelled. */ -BrowserAddonList.prototype.onOperationCancelled = function (addon) { +BrowserAddonList.prototype.onOperationCancelled = function () { this._onAddonManagerUpdated(); }; /** * AddonManager listener must implement onUninstalling. */ -BrowserAddonList.prototype.onUninstalling = function (addon) { +BrowserAddonList.prototype.onUninstalling = function () { this._onAddonManagerUpdated(); }; @@ -750,7 +750,7 @@ BrowserAddonList.prototype.onUninstalled = function (addon) { this._onAddonManagerUpdated(); }; -BrowserAddonList.prototype._onAddonManagerUpdated = function (addon) { +BrowserAddonList.prototype._onAddonManagerUpdated = function () { this._notifyListChanged(); this._adjustListener(); }; diff --git a/devtools/server/actors/webconsole.js b/devtools/server/actors/webconsole.js index 14401b3fbe..3de636be96 100644 --- a/devtools/server/actors/webconsole.js +++ b/devtools/server/actors/webconsole.js @@ -1704,7 +1704,7 @@ class WebConsoleActor extends Actor { * The "will-navigate" progress listener. This is used to clear the current * eval scope. */ - _onWillNavigate({ window, isTopLevel }) { + _onWillNavigate({ isTopLevel }) { if (isTopLevel) { this._evalGlobal = null; EventEmitter.off(this.parentActor, "will-navigate", this._onWillNavigate); diff --git a/devtools/server/actors/webconsole/commands/experimental-commands.ftl b/devtools/server/actors/webconsole/commands/experimental-commands.ftl index b11c29006b..66cb9d151e 100644 --- a/devtools/server/actors/webconsole/commands/experimental-commands.ftl +++ b/devtools/server/actors/webconsole/commands/experimental-commands.ftl @@ -9,13 +9,29 @@ webconsole-commands-usage-trace3 = :trace - Toggles the JavaScript tracer + Toggles the JavaScript tracer. + + The tracer will display all functions being called by your page. It supports the following arguments: - --logMethod to be set to ‘console’ for logging to the web console (the default), or ‘stdout’ for logging to the standard output, + --logMethod to be set to ‘console’ for logging to the web console (the default), or ‘stdout’ for logging to the standard output. + + --return Optional flag to be passed to also log when functions return. + --values Optional flag to be passed to log function call arguments as well as returned values (when returned frames are enabled). + --on-next-interaction Optional flag, when set, the tracer will only start on next mousedown or keydown event. + + --dom-mutations Optional flag, when set, the tracer will log all DOM Mutations. + When passing a value, you can restrict to a particular mutation type via a coma-separated list: + - ‘add’ will only track DOM Node being added, + - ‘attributes’ will only track DOM Node whose attributes changed, + - ‘remove’ will only track DOM Node being removed. + --max-depth Optional flag, will restrict logging trace to a given depth passed as argument. + --max-records Optional flag, will automatically stop the tracer after having logged the passed amount of top level frames. - --prefix Optional string which will be logged in front of all the trace logs, + + --prefix Optional string which will be logged in front of all the trace logs. + --help or --usage to show this message. diff --git a/devtools/server/actors/webconsole/commands/manager.js b/devtools/server/actors/webconsole/commands/manager.js index 538ee7eac1..025e197e3b 100644 --- a/devtools/server/actors/webconsole/commands/manager.js +++ b/devtools/server/actors/webconsole/commands/manager.js @@ -11,6 +11,13 @@ loader.lazyRequireGetter( true ); +loader.lazyRequireGetter( + this, + ["DOM_MUTATIONS"], + "resource://devtools/server/tracer/tracer.jsm", + true +); + loader.lazyGetter(this, "l10n", () => { return new Localization( [ @@ -88,7 +95,7 @@ const WebConsoleCommandsManager = { * } * }); */ - register({ name, isSideEffectFree, command, validArguments, usage }) { + register({ name, isSideEffectFree, command, validArguments }) { if ( typeof command != "function" && !(typeof command == "object" && typeof command.get == "function") @@ -684,7 +691,7 @@ WebConsoleCommandsManager.register({ WebConsoleCommandsManager.register({ name: "help", isSideEffectFree: false, - command(owner, args) { + command(owner) { owner.helperResult = { type: "help" }; }, }); @@ -873,13 +880,35 @@ WebConsoleCommandsManager.register({ const tracerActor = owner.consoleActor.parentActor.getTargetScopedActor("tracer"); const logMethod = args.logMethod || "console"; + let traceDOMMutations = null; + if ("dom-mutations" in args) { + // When no value is passed, track all types of mutations + if (args["dom-mutations"] === true) { + traceDOMMutations = ["add", "attributes", "remove"]; + } else if (typeof args["dom-mutations"] == "string") { + // Otherwise consider the value as coma seperated list and remove any white space. + traceDOMMutations = args["dom-mutations"].split(",").map(e => e.trim()); + const acceptedValues = Object.values(DOM_MUTATIONS); + if (!traceDOMMutations.every(e => acceptedValues.includes(e))) { + throw new Error( + `:trace --dom-mutations only accept a list of strings whose values can be: ${acceptedValues}` + ); + } + } else { + throw new Error( + ":trace --dom-mutations accept only no arguments, or a list mutation type strings (add,attributes,remove)" + ); + } + } // Note that toggleTracing does some sanity checks and will throw meaningful error // when the arguments are wrong. const enabled = tracerActor.toggleTracing({ logMethod, prefix: args.prefix || null, + traceFunctionReturn: !!args.returns, traceValues: !!args.values, traceOnNextInteraction: args["on-next-interaction"] || null, + traceDOMMutations, maxDepth: args["max-depth"] || null, maxRecords: args["max-records"] || null, }); @@ -895,7 +924,9 @@ WebConsoleCommandsManager.register({ "max-depth", "max-records", "on-next-interaction", + "dom-mutations", "prefix", + "returns", "values", ], }); diff --git a/devtools/server/actors/webconsole/eager-ecma-allowlist.js b/devtools/server/actors/webconsole/eager-ecma-allowlist.js index defe98ad8b..041a4c4194 100644 --- a/devtools/server/actors/webconsole/eager-ecma-allowlist.js +++ b/devtools/server/actors/webconsole/eager-ecma-allowlist.js @@ -46,7 +46,11 @@ const functionAllowList = [ Array.prototype.reduceRight, Array.prototype.slice, Array.prototype.some, + Array.prototype.toReversed, + Array.prototype.toSorted, + Array.prototype.toSpliced, Array.prototype.values, + Array.prototype.with, ArrayBuffer, ArrayBuffer.isView, ArrayBuffer.prototype.slice, @@ -94,7 +98,10 @@ const functionAllowList = [ TypedArray.prototype.slice, TypedArray.prototype.some, TypedArray.prototype.subarray, + TypedArray.prototype.toReversed, + TypedArray.prototype.toSorted, TypedArray.prototype.values, + TypedArray.prototype.with, ...allProperties(JSON), Map, Map.prototype.forEach, @@ -230,20 +237,4 @@ const getterAllowList = [ getter(TypedArray, Symbol.species), ]; -// TODO: Integrate in main list when changes array by copy ships by default -const changesArrayByCopy = [ - Array.prototype.toReversed, - Array.prototype.toSorted, - Array.prototype.toSpliced, - Array.prototype.with, - TypedArray.prototype.toReversed, - TypedArray.prototype.toSorted, - TypedArray.prototype.with, -]; -for (const fn of changesArrayByCopy) { - if (typeof fn == "function") { - functionAllowList.push(fn); - } -} - module.exports = { functions: functionAllowList, getters: getterAllowList }; diff --git a/devtools/server/actors/webconsole/eval-with-debugger.js b/devtools/server/actors/webconsole/eval-with-debugger.js index d422d6cd5e..34836c354f 100644 --- a/devtools/server/actors/webconsole/eval-with-debugger.js +++ b/devtools/server/actors/webconsole/eval-with-debugger.js @@ -8,9 +8,15 @@ const Debugger = require("Debugger"); const DevToolsUtils = require("resource://devtools/shared/DevToolsUtils.js"); const lazy = {}; -ChromeUtils.defineESModuleGetters(lazy, { - Reflect: "resource://gre/modules/reflect.sys.mjs", -}); +if (!isWorker) { + ChromeUtils.defineESModuleGetters( + lazy, + { + Reflect: "resource://gre/modules/reflect.sys.mjs", + }, + { global: "contextual" } + ); +} loader.lazyRequireGetter( this, ["isCommand"], @@ -600,8 +606,12 @@ function nativeIsEagerlyEvaluateable(fn) { return true; } + // This needs to use isSameNativeWithJitInfo instead of isSameNative, given + // DOM methods share single native function with different JSJitInto, + // and isSameNative cannot distinguish between side-effect-free methods + // and others. const natives = gSideEffectFreeNatives.get(fn.name); - return natives && natives.some(n => fn.isSameNative(n)); + return natives && natives.some(n => fn.isSameNativeWithJitInfo(n)); } function updateConsoleInputEvaluation(dbg, webConsole) { @@ -616,7 +626,7 @@ function updateConsoleInputEvaluation(dbg, webConsole) { } } -function getEvalInput(string, bindings) { +function getEvalInput(string) { const trimmedString = string.trim(); // Add easter egg for console.mihai(). if ( diff --git a/devtools/server/actors/webconsole/listeners/console-file-activity.js b/devtools/server/actors/webconsole/listeners/console-file-activity.js index 7e5ae0d1a8..ccc28c3b2a 100644 --- a/devtools/server/actors/webconsole/listeners/console-file-activity.js +++ b/devtools/server/actors/webconsole/listeners/console-file-activity.js @@ -82,7 +82,7 @@ ConsoleFileActivityListener.prototype = { * URI has been loaded, then the remote Web Console instance is notified. * @private */ - _checkFileActivity(progress, request, state, status) { + _checkFileActivity(progress, request, state) { if (!(state & Ci.nsIWebProgressListener.STATE_START)) { return; } diff --git a/devtools/server/actors/webconsole/listeners/document-events.js b/devtools/server/actors/webconsole/listeners/document-events.js index 1c1f926436..42296bf62e 100644 --- a/devtools/server/actors/webconsole/listeners/document-events.js +++ b/devtools/server/actors/webconsole/listeners/document-events.js @@ -90,13 +90,7 @@ DocumentEventsListener.prototype = { }); }, - onWillNavigate({ - window, - isTopLevel, - newURI, - navigationStart, - isFrameSwitching, - }) { + onWillNavigate({ isTopLevel, newURI, navigationStart, isFrameSwitching }) { // Ignore iframes if (!isTopLevel) { return; @@ -177,7 +171,7 @@ DocumentEventsListener.prototype = { }); }, - onStateChange(progress, request, flag, status) { + onStateChange(progress, request, flag) { progress.QueryInterface(Ci.nsIDocShell); // Ignore destroyed, or progress for same-process iframes if (progress.isBeingDestroyed() || progress != this.webProgress) { diff --git a/devtools/server/actors/worker/service-worker-registration-list.js b/devtools/server/actors/worker/service-worker-registration-list.js index 9821108faf..6093edc97e 100644 --- a/devtools/server/actors/worker/service-worker-registration-list.js +++ b/devtools/server/actors/worker/service-worker-registration-list.js @@ -5,7 +5,8 @@ "use strict"; const { XPCOMUtils } = ChromeUtils.importESModule( - "resource://gre/modules/XPCOMUtils.sys.mjs" + "resource://gre/modules/XPCOMUtils.sys.mjs", + { global: "contextual" } ); loader.lazyRequireGetter( this, @@ -102,11 +103,11 @@ class ServiceWorkerRegistrationActorList { this._mustNotify = false; } - onRegister(registration) { + onRegister() { this._notifyListChanged(); } - onUnregister(registration) { + onUnregister() { this._notifyListChanged(); } } diff --git a/devtools/server/actors/worker/service-worker-registration.js b/devtools/server/actors/worker/service-worker-registration.js index 1e5e80ae8b..1276711191 100644 --- a/devtools/server/actors/worker/service-worker-registration.js +++ b/devtools/server/actors/worker/service-worker-registration.js @@ -10,7 +10,8 @@ const { } = require("resource://devtools/shared/specs/worker/service-worker-registration.js"); const { XPCOMUtils } = ChromeUtils.importESModule( - "resource://gre/modules/XPCOMUtils.sys.mjs" + "resource://gre/modules/XPCOMUtils.sys.mjs", + { global: "contextual" } ); const { PushSubscriptionActor, @@ -210,7 +211,7 @@ class ServiceWorkerRegistrationActor extends Actor { if (pushSubscriptionActor) { return Promise.resolve(pushSubscriptionActor); } - return new Promise((resolve, reject) => { + return new Promise(resolve => { PushService.getSubscription( registration.scope, registration.principal, diff --git a/devtools/server/actors/worker/worker-descriptor-actor-list.js b/devtools/server/actors/worker/worker-descriptor-actor-list.js index 10bdb5d5d3..9826791511 100644 --- a/devtools/server/actors/worker/worker-descriptor-actor-list.js +++ b/devtools/server/actors/worker/worker-descriptor-actor-list.js @@ -5,7 +5,8 @@ "use strict"; const { XPCOMUtils } = ChromeUtils.importESModule( - "resource://gre/modules/XPCOMUtils.sys.mjs" + "resource://gre/modules/XPCOMUtils.sys.mjs", + { global: "contextual" } ); loader.lazyRequireGetter( this, diff --git a/devtools/server/connectors/content-process-connector.js b/devtools/server/connectors/content-process-connector.js index ea95a5d6ab..a2d359e4a5 100644 --- a/devtools/server/connectors/content-process-connector.js +++ b/devtools/server/connectors/content-process-connector.js @@ -109,13 +109,11 @@ function connectToContentProcess(connection, mm, onDestroy) { } } - const onMessageManagerClose = DevToolsUtils.makeInfallible( - (subject, topic, data) => { - if (subject == mm) { - onClose(); - } + const onMessageManagerClose = DevToolsUtils.makeInfallible(subject => { + if (subject == mm) { + onClose(); } - ); + }); Services.obs.addObserver(onMessageManagerClose, "message-manager-close"); EventEmitter.on(connection, "closed", onClose); diff --git a/devtools/server/connectors/frame-connector.js b/devtools/server/connectors/frame-connector.js index 789d405d90..06fbcfd1e4 100644 --- a/devtools/server/connectors/frame-connector.js +++ b/devtools/server/connectors/frame-connector.js @@ -149,7 +149,7 @@ function connectToFrame( trackMessageManager(); // Listen for app process exit - const onMessageManagerClose = function (subject, topic, data) { + const onMessageManagerClose = function (subject) { if (subject == mm) { destroy(); } diff --git a/devtools/server/connectors/js-process-actor/DevToolsProcessChild.sys.mjs b/devtools/server/connectors/js-process-actor/DevToolsProcessChild.sys.mjs new file mode 100644 index 0000000000..9e8ad64eea --- /dev/null +++ b/devtools/server/connectors/js-process-actor/DevToolsProcessChild.sys.mjs @@ -0,0 +1,362 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { EventEmitter } from "resource://gre/modules/EventEmitter.sys.mjs"; + +const lazy = {}; +ChromeUtils.defineESModuleGetters( + lazy, + { + releaseDistinctSystemPrincipalLoader: + "resource://devtools/shared/loader/DistinctSystemPrincipalLoader.sys.mjs", + useDistinctSystemPrincipalLoader: + "resource://devtools/shared/loader/DistinctSystemPrincipalLoader.sys.mjs", + }, + { global: "contextual" } +); + +// Name of the attribute into which we save data in `sharedData` object. +const SHARED_DATA_KEY_NAME = "DevTools:watchedPerWatcher"; + +// If true, log info about DOMProcess's being created. +const DEBUG = false; + +/** + * Print information about operation being done against each content process. + * + * @param {nsIDOMProcessChild} domProcessChild + * The process for which we should log a message. + * @param {String} message + * Message to log. + */ +function logDOMProcess(domProcessChild, message) { + if (!DEBUG) { + return; + } + dump(" [pid:" + domProcessChild + "] " + message + "\n"); +} + +export class DevToolsProcessChild extends JSProcessActorChild { + constructor() { + super(); + + // The map is indexed by the Watcher Actor ID. + // The values are objects containing the following properties: + // - connection: the DevToolsServerConnection itself + // - actor: the ContentProcessTargetActor instance + this._connections = new Map(); + + this._onConnectionChange = this._onConnectionChange.bind(this); + EventEmitter.decorate(this); + } + + instantiate() { + const { sharedData } = Services.cpmm; + const watchedDataByWatcherActor = sharedData.get(SHARED_DATA_KEY_NAME); + if (!watchedDataByWatcherActor) { + throw new Error( + "Request to instantiate the target(s) for the process, but `sharedData` is empty about watched targets" + ); + } + + // Create one Target actor for each prefix/client which listen to processes + for (const [watcherActorID, sessionData] of watchedDataByWatcherActor) { + const { connectionPrefix } = sessionData; + + if (sessionData.targets?.includes("process")) { + this._createTargetActor(watcherActorID, connectionPrefix, sessionData); + } + } + } + + /** + * Instantiate a new ProcessTarget for the given connection. + * + * @param String watcherActorID + * The ID of the WatcherActor who requested to observe and create these target actors. + * @param String parentConnectionPrefix + * The prefix of the DevToolsServerConnection of the Watcher Actor. + * This is used to compute a unique ID for the target actor. + * @param Object sessionData + * All data managed by the Watcher Actor and WatcherRegistry.sys.mjs, containing + * target types, resources types to be listened as well as breakpoints and any + * other data meant to be shared across processes and threads. + */ + _createTargetActor(watcherActorID, parentConnectionPrefix, sessionData) { + // This method will be concurrently called from `observe()` and `DevToolsProcessParent:instantiate-already-available` + // When the JSprocessActor initializes itself and when the watcher want to force instantiating existing targets. + // Simply ignore the second call as there is nothing to return, neither to wait for as this method is synchronous. + if (this._connections.has(watcherActorID)) { + return; + } + + // Compute a unique prefix, just for this DOM Process, + // which will be used to create a JSWindowActorTransport pair between content and parent processes. + // This is slightly hacky as we typicaly compute Prefix and Actor ID via `DevToolsServerConnection.allocID()`, + // but here, we can't have access to any DevTools connection as we are really early in the content process startup + // XXX: nsIDOMProcessChild's childID should be unique across processes, I think. So that should be safe? + // (this.manager == nsIDOMProcessChild interface) + // Ensure appending a final slash, otherwise the prefix may be the same between childID 1 and 10... + const forwardingPrefix = + parentConnectionPrefix + "contentProcess" + this.manager.childID + "/"; + + logDOMProcess( + this.manager, + "Instantiate ContentProcessTarget with prefix: " + forwardingPrefix + ); + + const { connection, targetActor } = this._createConnectionAndActor( + watcherActorID, + forwardingPrefix, + sessionData + ); + this._connections.set(watcherActorID, { + connection, + actor: targetActor, + }); + + // Immediately queue a message for the parent process, + // in order to ensure that the JSWindowActorTransport is instantiated + // before any packet is sent from the content process. + // As the order of messages is guaranteed to be delivered in the order they + // were queued, we don't have to wait for anything around this sendAsyncMessage call. + // In theory, the ContentProcessTargetActor may emit events in its constructor. + // If it does, such RDP packets may be lost. But in practice, no events + // are emitted during its construction. Instead the frontend will start + // the communication first. + this.sendAsyncMessage("DevToolsProcessChild:connectFromContent", { + watcherActorID, + forwardingPrefix, + actor: targetActor.form(), + }); + + // Pass initialization data to the target actor + for (const type in sessionData) { + // `sessionData` will also contain `browserId` as well as entries with empty arrays, + // which shouldn't be processed. + const entries = sessionData[type]; + if (!Array.isArray(entries) || !entries.length) { + continue; + } + targetActor.addOrSetSessionDataEntry( + type, + sessionData[type], + false, + "set" + ); + } + } + + _destroyTargetActor(watcherActorID, isModeSwitching) { + const connectionInfo = this._connections.get(watcherActorID); + // This connection has already been cleaned? + if (!connectionInfo) { + throw new Error( + `Trying to destroy a target actor that doesn't exists, or has already been destroyed. Watcher Actor ID:${watcherActorID}` + ); + } + connectionInfo.connection.close({ isModeSwitching }); + this._connections.delete(watcherActorID); + if (this._connections.size == 0) { + this.didDestroy({ isModeSwitching }); + } + } + + _createConnectionAndActor(watcherActorID, forwardingPrefix, sessionData) { + if (!this.loader) { + this.loader = lazy.useDistinctSystemPrincipalLoader(this); + } + const { DevToolsServer } = this.loader.require( + "devtools/server/devtools-server" + ); + + const { ContentProcessTargetActor } = this.loader.require( + "devtools/server/actors/targets/content-process" + ); + + DevToolsServer.init(); + + // For browser content toolbox, we do need a regular root actor and all tab + // actors, but don't need all the "browser actors" that are only useful when + // debugging the parent process via the browser toolbox. + DevToolsServer.registerActors({ target: true }); + DevToolsServer.on("connectionchange", this._onConnectionChange); + + const connection = DevToolsServer.connectToParentWindowActor( + this, + forwardingPrefix, + "DevToolsProcessChild:packet" + ); + + // Create the actual target actor. + const targetActor = new ContentProcessTargetActor(connection, { + sessionContext: sessionData.sessionContext, + }); + // There is no root actor in content processes and so + // the target actor can't be managed by it, but we do have to manage + // the actor to have it working and be registered in the DevToolsServerConnection. + // We make it manage itself and become a top level actor. + targetActor.manage(targetActor); + + const form = targetActor.form(); + targetActor.once("destroyed", options => { + // This will destroy the content process one + this._destroyTargetActor(watcherActorID, options.isModeSwitching); + // And this will destroy the parent process one + try { + this.sendAsyncMessage("DevToolsProcessChild:destroy", { + actors: [ + { + watcherActorID, + form, + }, + ], + options, + }); + } catch (e) { + // Ignore exception when the JSProcessActorChild has already been destroyed. + // We often try to emit this message while the process is being destroyed, + // but sendAsyncMessage doesn't have time to complete and throws. + if ( + !e.message.includes("JSProcessActorChild cannot send at the moment") + ) { + throw e; + } + } + }); + + return { connection, targetActor }; + } + + /** + * Destroy the server once its last connection closes. Note that multiple + * frame scripts may be running in parallel and reuse the same server. + */ + _onConnectionChange() { + if (this._destroyed) { + return; + } + this._destroyed = true; + + const { DevToolsServer } = this.loader.require( + "devtools/server/devtools-server" + ); + + // Only destroy the server if there is no more connections to it. It may be + // used to debug another tab running in the same process. + if (DevToolsServer.hasConnection() || DevToolsServer.keepAlive) { + return; + } + + DevToolsServer.off("connectionchange", this._onConnectionChange); + DevToolsServer.destroy(); + } + + /** + * Supported Queries + */ + + sendPacket(packet, prefix) { + this.sendAsyncMessage("DevToolsProcessChild:packet", { packet, prefix }); + } + + /** + * JsWindowActor API + */ + + async sendQuery(msg, args) { + try { + const res = await super.sendQuery(msg, args); + return res; + } catch (e) { + console.error("Failed to sendQuery in DevToolsProcessChild", msg); + console.error(e.toString()); + throw e; + } + } + + receiveMessage(message) { + switch (message.name) { + case "DevToolsProcessParent:instantiate-already-available": { + const { watcherActorID, connectionPrefix, sessionData } = message.data; + return this._createTargetActor( + watcherActorID, + connectionPrefix, + sessionData + ); + } + case "DevToolsProcessParent:destroy": { + const { watcherActorID, isModeSwitching } = message.data; + return this._destroyTargetActor(watcherActorID, isModeSwitching); + } + case "DevToolsProcessParent:addOrSetSessionDataEntry": { + const { watcherActorID, type, entries, updateType } = message.data; + return this._addOrSetSessionDataEntry( + watcherActorID, + type, + entries, + updateType + ); + } + case "DevToolsProcessParent:removeSessionDataEntry": { + const { watcherActorID, type, entries } = message.data; + return this._removeSessionDataEntry(watcherActorID, type, entries); + } + case "DevToolsProcessParent:packet": + return this.emit("packet-received", message); + default: + throw new Error( + "Unsupported message in DevToolsProcessParent: " + message.name + ); + } + } + + _getTargetActorForWatcherActorID(watcherActorID) { + const connectionInfo = this._connections.get(watcherActorID); + return connectionInfo?.actor; + } + + _addOrSetSessionDataEntry(watcherActorID, type, entries, updateType) { + const targetActor = this._getTargetActorForWatcherActorID(watcherActorID); + if (!targetActor) { + throw new Error( + `No target actor for this Watcher Actor ID:"${watcherActorID}"` + ); + } + return targetActor.addOrSetSessionDataEntry( + type, + entries, + false, + updateType + ); + } + + _removeSessionDataEntry(watcherActorID, type, entries) { + const targetActor = this._getTargetActorForWatcherActorID(watcherActorID); + // By the time we are calling this, the target may already have been destroyed. + if (!targetActor) { + return null; + } + return targetActor.removeSessionDataEntry(type, entries); + } + + observe(subject, topic) { + if (topic === "init-devtools-content-process-actor") { + // This is triggered by the process actor registration and some code in process-helper.js + // which defines a unique topic to be observed + this.instantiate(); + } + } + + didDestroy(options) { + for (const { connection } of this._connections.values()) { + connection.close(options); + } + this._connections.clear(); + if (this.loader) { + lazy.releaseDistinctSystemPrincipalLoader(this); + this.loader = null; + } + } +} diff --git a/devtools/server/connectors/js-process-actor/DevToolsProcessParent.sys.mjs b/devtools/server/connectors/js-process-actor/DevToolsProcessParent.sys.mjs new file mode 100644 index 0000000000..28e11def68 --- /dev/null +++ b/devtools/server/connectors/js-process-actor/DevToolsProcessParent.sys.mjs @@ -0,0 +1,256 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { loader } from "resource://devtools/shared/loader/Loader.sys.mjs"; +import { EventEmitter } from "resource://gre/modules/EventEmitter.sys.mjs"; + +const { WatcherRegistry } = ChromeUtils.importESModule( + "resource://devtools/server/actors/watcher/WatcherRegistry.sys.mjs", + // WatcherRegistry needs to be a true singleton and loads ActorManagerParent + // which also has to be a true singleton. + { global: "shared" } +); + +const lazy = {}; +loader.lazyRequireGetter( + lazy, + "JsWindowActorTransport", + "devtools/shared/transport/js-window-actor-transport", + true +); + +export class DevToolsProcessParent extends JSProcessActorParent { + constructor() { + super(); + + this._destroyed = false; + + // Map of DevToolsServerConnection's used to forward the messages from/to + // the client. The connections run in the parent process, as this code. We + // may have more than one when there is more than one client debugging the + // same frame. For example, a content toolbox and the browser toolbox. + // + // The map is indexed by the connection prefix. + // The values are objects containing the following properties: + // - actor: the frame target actor(as a form) + // - connection: the DevToolsServerConnection used to communicate with the + // frame target actor + // - prefix: the forwarding prefix used by the connection to know + // how to forward packets to the frame target + // - transport: the JsWindowActorTransport + // + // Reminder about prefixes: all DevToolsServerConnections have a `prefix` + // which can be considered as a kind of id. On top of this, parent process + // DevToolsServerConnections also have forwarding prefixes because they are + // responsible for forwarding messages to content process connections. + this._connections = new Map(); + + this._onConnectionClosed = this._onConnectionClosed.bind(this); + EventEmitter.decorate(this); + } + + /** + * Request the content process to create the ContentProcessTarget + */ + instantiateTarget({ + watcherActorID, + connectionPrefix, + sessionContext, + sessionData, + }) { + return this.sendQuery( + "DevToolsProcessParent:instantiate-already-available", + { + watcherActorID, + connectionPrefix, + sessionContext, + sessionData, + } + ); + } + + destroyTarget({ watcherActorID, isModeSwitching }) { + this.sendAsyncMessage("DevToolsProcessParent:destroy", { + watcherActorID, + isModeSwitching, + }); + } + + /** + * Communicate to the content process that some data have been added. + */ + addOrSetSessionDataEntry({ watcherActorID, type, entries, updateType }) { + return this.sendQuery("DevToolsProcessParent:addOrSetSessionDataEntry", { + watcherActorID, + type, + entries, + updateType, + }); + } + + /** + * Communicate to the content process that some data have been removed. + */ + removeSessionDataEntry({ watcherActorID, type, entries }) { + this.sendAsyncMessage("DevToolsProcessParent:removeSessionDataEntry", { + watcherActorID, + type, + entries, + }); + } + + connectFromContent({ watcherActorID, forwardingPrefix, actor }) { + const watcher = WatcherRegistry.getWatcher(watcherActorID); + + if (!watcher) { + throw new Error( + `Watcher Actor with ID '${watcherActorID}' can't be found.` + ); + } + const connection = watcher.conn; + + connection.on("closed", this._onConnectionClosed); + + // Create a js-window-actor based transport. + const transport = new lazy.JsWindowActorTransport( + this, + forwardingPrefix, + "DevToolsProcessParent:packet" + ); + transport.hooks = { + onPacket: connection.send.bind(connection), + onClosed() {}, + }; + transport.ready(); + + connection.setForwarding(forwardingPrefix, transport); + + this._connections.set(watcher.conn.prefix, { + watcher, + connection, + // This prefix is the prefix of the DevToolsServerConnection, running + // in the content process, for which we should forward packets to, based on its prefix. + // While `watcher.connection` is also a DevToolsServerConnection, but from this process, + // the parent process. It is the one receiving Client packets and the one, from which + // we should forward packets from. + forwardingPrefix, + transport, + actor, + }); + + watcher.notifyTargetAvailable(actor); + } + + _onConnectionClosed(status, prefix) { + if (this._connections.has(prefix)) { + const { connection } = this._connections.get(prefix); + this._cleanupConnection(connection); + } + } + + /** + * Close and unregister a given DevToolsServerConnection. + * + * @param {DevToolsServerConnection} connection + * @param {object} options + * @param {boolean} options.isModeSwitching + * true when this is called as the result of a change to the devtools.browsertoolbox.scope pref + */ + async _cleanupConnection(connection, options = {}) { + const connectionInfo = this._connections.get(connection.prefix); + if (!connectionInfo) { + return; + } + const { forwardingPrefix, transport } = connectionInfo; + + connection.off("closed", this._onConnectionClosed); + if (transport) { + // If we have a child transport, the actor has already + // been created. We need to stop using this transport. + transport.close(options); + } + + this._connections.delete(connection.prefix); + if (!this._connections.size) { + this._destroy(options); + } + + // When cancelling the forwarding, one RDP event is sent to the client to purge all requests + // and actors related to a given prefix. Do this *after* calling _destroy which will emit + // the target-destroyed RDP event. This helps the Watcher Front retrieve the related target front, + // otherwise it would be too eagerly destroyed by the purge event. + connection.cancelForwarding(forwardingPrefix); + } + + /** + * Destroy and cleanup everything for this DOM Process. + * + * @param {object} options + * @param {boolean} options.isModeSwitching + * true when this is called as the result of a change to the devtools.browsertoolbox.scope pref + */ + _destroy(options) { + if (this._destroyed) { + return; + } + this._destroyed = true; + + for (const { actor, connection, watcher } of this._connections.values()) { + watcher.notifyTargetDestroyed(actor, options); + this._cleanupConnection(connection, options); + } + } + + /** + * Supported Queries + */ + + sendPacket(packet, prefix) { + this.sendAsyncMessage("DevToolsProcessParent:packet", { packet, prefix }); + } + + /** + * JsWindowActor API + */ + + async sendQuery(msg, args) { + try { + const res = await super.sendQuery(msg, args); + return res; + } catch (e) { + console.error("Failed to sendQuery in DevToolsProcessParent", msg); + console.error(e.toString()); + throw e; + } + } + + receiveMessage(message) { + switch (message.name) { + case "DevToolsProcessChild:connectFromContent": + return this.connectFromContent(message.data); + case "DevToolsProcessChild:packet": + return this.emit("packet-received", message); + case "DevToolsProcessChild:destroy": + for (const { form, watcherActorID } of message.data.actors) { + const watcher = WatcherRegistry.getWatcher(watcherActorID); + // As we instruct to destroy all targets when the watcher is destroyed, + // we may easily receive the target destruction notification *after* + // the watcher has been removed from the registry. + if (watcher) { + watcher.notifyTargetDestroyed(form, message.data.options); + this._cleanupConnection(watcher.conn, message.data.options); + } + } + return null; + default: + throw new Error( + "Unsupported message in DevToolsProcessParent: " + message.name + ); + } + } + + didDestroy() { + this._destroy(); + } +} diff --git a/devtools/server/connectors/js-process-actor/moz.build b/devtools/server/connectors/js-process-actor/moz.build new file mode 100644 index 0000000000..e1a1f5dc9d --- /dev/null +++ b/devtools/server/connectors/js-process-actor/moz.build @@ -0,0 +1,10 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +DevToolsModules( + "DevToolsProcessChild.sys.mjs", + "DevToolsProcessParent.sys.mjs", +) diff --git a/devtools/server/connectors/js-window-actor/DevToolsFrameChild.sys.mjs b/devtools/server/connectors/js-window-actor/DevToolsFrameChild.sys.mjs index 519cd10325..acb5e97110 100644 --- a/devtools/server/connectors/js-window-actor/DevToolsFrameChild.sys.mjs +++ b/devtools/server/connectors/js-window-actor/DevToolsFrameChild.sys.mjs @@ -173,7 +173,7 @@ export class DevToolsFrameChild extends JSWindowActorChild { * The prefix of the DevToolsServerConnection of the Watcher Actor. * This is used to compute a unique ID for the target actor. * @param Object options.sessionData - * All data managed by the Watcher Actor and WatcherRegistry.jsm, containing + * All data managed by the Watcher Actor and WatcherRegistry.sys.mjs, containing * target types, resources types to be listened as well as breakpoints and any * other data meant to be shared across processes and threads. * @param Boolean options.isDocumentCreation @@ -364,6 +364,10 @@ export class DevToolsFrameChild extends JSWindowActorChild { ignoreSubFrames: isEveryFrameTargetEnabled, sessionContext: sessionData.sessionContext, }); + // There is no root actor in content processes and so + // the target actor can't be managed by it, but we do have to manage + // the actor to have it working and be registered in the DevToolsServerConnection. + // We make it manage itself and become a top level actor. targetActor.manage(targetActor); targetActor.createdFromJsWindowActor = true; @@ -554,10 +558,10 @@ export class DevToolsFrameChild extends JSWindowActorChild { sessionContext, }); // By the time we are calling this, the target may already have been destroyed. - if (targetActor) { - return targetActor.removeSessionDataEntry(type, entries); + if (!targetActor) { + return null; } - return null; + return targetActor.removeSessionDataEntry(type, entries); } handleEvent({ type, persisted, target }) { @@ -691,8 +695,8 @@ export class DevToolsFrameChild extends JSWindowActorChild { didDestroy(options) { logWindowGlobal(this.manager, "Destroy WindowGlobalTarget"); - for (const [, connectionInfo] of this._connections) { - connectionInfo.connection.close(options); + for (const { connection } of this._connections.values()) { + connection.close(options); } this._connections.clear(); diff --git a/devtools/server/connectors/js-window-actor/DevToolsFrameParent.sys.mjs b/devtools/server/connectors/js-window-actor/DevToolsFrameParent.sys.mjs index 3c5af2a724..31750d58e4 100644 --- a/devtools/server/connectors/js-window-actor/DevToolsFrameParent.sys.mjs +++ b/devtools/server/connectors/js-window-actor/DevToolsFrameParent.sys.mjs @@ -7,11 +7,9 @@ import { EventEmitter } from "resource://gre/modules/EventEmitter.sys.mjs"; const { WatcherRegistry } = ChromeUtils.importESModule( "resource://devtools/server/actors/watcher/WatcherRegistry.sys.mjs", - { - // WatcherRegistry needs to be a true singleton and loads ActorManagerParent - // which also has to be a true singleton. - loadInDevToolsLoader: false, - } + // WatcherRegistry needs to be a true singleton and loads ActorManagerParent + // which also has to be a true singleton. + { global: "shared" } ); const lazy = {}; diff --git a/devtools/server/connectors/js-window-actor/DevToolsWorkerParent.sys.mjs b/devtools/server/connectors/js-window-actor/DevToolsWorkerParent.sys.mjs index ebe3d10ad5..cb9bffc2ca 100644 --- a/devtools/server/connectors/js-window-actor/DevToolsWorkerParent.sys.mjs +++ b/devtools/server/connectors/js-window-actor/DevToolsWorkerParent.sys.mjs @@ -7,11 +7,9 @@ import { EventEmitter } from "resource://gre/modules/EventEmitter.sys.mjs"; const { WatcherRegistry } = ChromeUtils.importESModule( "resource://devtools/server/actors/watcher/WatcherRegistry.sys.mjs", - { - // WatcherRegistry needs to be a true singleton and loads ActorManagerParent - // which also has to be a true singleton. - loadInDevToolsLoader: false, - } + // WatcherRegistry needs to be a true singleton and loads ActorManagerParent + // which also has to be a true singleton. + { global: "shared" } ); const lazy = {}; @@ -175,11 +173,7 @@ export class DevToolsWorkerParent extends JSWindowActorParent { watcher.notifyTargetAvailable(workerTargetForm); } - workerTargetDestroyed({ - watcherActorID, - forwardingPrefix, - workerTargetForm, - }) { + workerTargetDestroyed({ watcherActorID, workerTargetForm }) { const watcher = WatcherRegistry.getWatcher(watcherActorID); if (!watcher) { diff --git a/devtools/server/connectors/moz.build b/devtools/server/connectors/moz.build index a8b6fa1fea..fd4baf81ff 100644 --- a/devtools/server/connectors/moz.build +++ b/devtools/server/connectors/moz.build @@ -5,6 +5,7 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. DIRS += [ + "js-process-actor", "js-window-actor", "process-actor", ] diff --git a/devtools/server/connectors/process-actor/DevToolsServiceWorkerParent.sys.mjs b/devtools/server/connectors/process-actor/DevToolsServiceWorkerParent.sys.mjs index 17fa89e7ac..2073f47e76 100644 --- a/devtools/server/connectors/process-actor/DevToolsServiceWorkerParent.sys.mjs +++ b/devtools/server/connectors/process-actor/DevToolsServiceWorkerParent.sys.mjs @@ -6,11 +6,9 @@ import { EventEmitter } from "resource://gre/modules/EventEmitter.sys.mjs"; const { WatcherRegistry } = ChromeUtils.importESModule( "resource://devtools/server/actors/watcher/WatcherRegistry.sys.mjs", - { - // WatcherRegistry needs to be a true singleton and loads ActorManagerParent - // which also has to be a true singleton. - loadInDevToolsLoader: false, - } + // WatcherRegistry needs to be a true singleton and loads ActorManagerParent + // which also has to be a true singleton. + { global: "shared" } ); const lazy = {}; @@ -185,11 +183,7 @@ export class DevToolsServiceWorkerParent extends JSProcessActorParent { watcher.notifyTargetAvailable(serviceWorkerTargetForm); } - serviceWorkerTargetDestroyed({ - watcherActorID, - forwardingPrefix, - serviceWorkerTargetForm, - }) { + serviceWorkerTargetDestroyed({ watcherActorID, serviceWorkerTargetForm }) { const watcher = WatcherRegistry.getWatcher(watcherActorID); if (!watcher) { diff --git a/devtools/server/devtools-server-connection.js b/devtools/server/devtools-server-connection.js index 53d977a8fe..4c5a1180b0 100644 --- a/devtools/server/devtools-server-connection.js +++ b/devtools/server/devtools-server-connection.js @@ -73,14 +73,6 @@ DevToolsServerConnection.prototype = { return this._prefix; }, - /** - * For a DevToolsServerConnection used in content processes, - * returns the prefix of the connection it originates from, from the parent process. - */ - get parentPrefix() { - this.prefix.replace(/child\d+\//, ""); - }, - _transport: null, get transport() { return this._transport; diff --git a/devtools/server/performance/memory.js b/devtools/server/performance/memory.js index c983a742ec..96f20ed0b6 100644 --- a/devtools/server/performance/memory.js +++ b/devtools/server/performance/memory.js @@ -15,9 +15,13 @@ loader.lazyRequireGetter( "resource://devtools/shared/event-emitter.js" ); const lazy = {}; -ChromeUtils.defineESModuleGetters(lazy, { - DeferredTask: "resource://gre/modules/DeferredTask.sys.mjs", -}); +ChromeUtils.defineESModuleGetters( + lazy, + { + DeferredTask: "resource://gre/modules/DeferredTask.sys.mjs", + }, + { global: "contextual" } +); loader.lazyRequireGetter( this, "StackFrameCache", diff --git a/devtools/server/socket/websocket-server.js b/devtools/server/socket/websocket-server.js index 4236ec2921..d49c6fbf1b 100644 --- a/devtools/server/socket/websocket-server.js +++ b/devtools/server/socket/websocket-server.js @@ -27,7 +27,7 @@ function readLine(input) { let line = ""; const wait = () => { input.asyncWait( - stream => { + () => { try { const amountToRead = HEADER_MAX_LEN - line.length; line += delimitedRead(input, "\n", amountToRead); @@ -72,7 +72,7 @@ function writeString(output, data) { } output.asyncWait( - stream => { + () => { try { const written = output.write(data, data.length); data = data.slice(written); diff --git a/devtools/server/startup/content-process-script.js b/devtools/server/startup/content-process-script.js index ffd461c4e2..3449eb465a 100644 --- a/devtools/server/startup/content-process-script.js +++ b/devtools/server/startup/content-process-script.js @@ -37,7 +37,7 @@ class ContentProcessStartup { this.maybeCreateExistingTargetActors(); } - observe(subject, topic, data) { + observe(subject, topic) { switch (topic) { case "xpcom-shutdown": { this.destroy(); @@ -143,7 +143,7 @@ class ContentProcessStartup { /** * Called when the content process just started. - * This will start creating ContentProcessTarget actors, but only if DevTools code (WatcherActor / WatcherRegistry.jsm) + * This will start creating ContentProcessTarget actors, but only if DevTools code (WatcherActor / WatcherRegistry.sys.mjs) * put some data in `sharedData` telling us to do so. */ maybeCreateExistingTargetActors() { diff --git a/devtools/server/tests/browser/browser_accessibility_keyboard_audit.js b/devtools/server/tests/browser/browser_accessibility_keyboard_audit.js index fee9814b6c..e59cac87a3 100644 --- a/devtools/server/tests/browser/browser_accessibility_keyboard_audit.js +++ b/devtools/server/tests/browser/browser_accessibility_keyboard_audit.js @@ -186,7 +186,11 @@ add_task(async function () { null, ], ["Interactive grid that is not focusable.", "#grid-1", null], - ["Focusable interactive grid.", "#grid-2", null], + [ + "Focusable interactive grid.", + "#grid-2", + { score: "WARNING", issue: "FOCUSABLE_NO_SEMANTICS" }, + ], [ "Non interactive ARIA table does not need to be focusable.", "#table-1", diff --git a/devtools/server/tests/browser/browser_connectToFrame.js b/devtools/server/tests/browser/browser_connectToFrame.js index 568eb1acc1..1047393470 100644 --- a/devtools/server/tests/browser/browser_connectToFrame.js +++ b/devtools/server/tests/browser/browser_connectToFrame.js @@ -39,7 +39,7 @@ add_task(async function () { const { Actor } = require("resource://devtools/shared/protocol/Actor.js"); class ConnectToFrameTestActor extends Actor { - constructor(conn, tab) { + constructor(conn) { super(conn, { typeName: "connectToFrameTest", methods: [] }); dump("instantiate test actor\n"); this.requestTypes = { diff --git a/devtools/server/tests/browser/browser_debugger_server.js b/devtools/server/tests/browser/browser_debugger_server.js index 8b36076b34..11a96a66c9 100644 --- a/devtools/server/tests/browser/browser_debugger_server.js +++ b/devtools/server/tests/browser/browser_debugger_server.js @@ -181,7 +181,7 @@ async function assertDevToolsOpened(tab, expected, message) { ); } -async function setContentServerKeepAlive(tab, keepAlive, message) { +async function setContentServerKeepAlive(tab, keepAlive) { await SpecialPowers.spawn( tab.linkedBrowser, [keepAlive], diff --git a/devtools/server/tests/browser/browser_storage_updates.js b/devtools/server/tests/browser/browser_storage_updates.js index 50926538a5..c1b56f6706 100644 --- a/devtools/server/tests/browser/browser_storage_updates.js +++ b/devtools/server/tests/browser/browser_storage_updates.js @@ -17,7 +17,7 @@ const sessionString = l10n.formatValueSync("storage-expires-session"); const TESTS = [ // index 0 { - async action(win) { + async action() { await addCookie("c1", "foobar1"); await addCookie("c2", "foobar2"); await localStorageSetItem("l1", "foobar1"); diff --git a/devtools/server/tests/chrome/inactive-property-helper/align-content.mjs b/devtools/server/tests/chrome/inactive-property-helper/align-content.mjs index a871081fad..49469baf22 100644 --- a/devtools/server/tests/chrome/inactive-property-helper/align-content.mjs +++ b/devtools/server/tests/chrome/inactive-property-helper/align-content.mjs @@ -6,10 +6,17 @@ export default [ { - info: "align-content is inactive on block elements (until bug 1105571 is fixed)", + info: "align-content is active on block elements when layout.css.align-content.blocks.enabled is true", property: "align-content", tagName: "div", rules: ["div { align-content: center; }"], + isActive: true, + }, + { + info: "align-content is inactive on inline elements", + property: "align-content", + tagName: "span", + rules: ["div { align-content: center; }"], isActive: false, }, { @@ -27,7 +34,7 @@ export default [ isActive: true, }, { - info: "align-content is inactive on flex items", + info: "align-content is active on flex items (as they have a computed display of block)", property: "align-content", createTestElement: rootNode => { const container = document.createElement("div"); @@ -38,10 +45,10 @@ export default [ }, rules: ["div { display: flex; }", "span { align-content: center; }"], ruleIndex: 1, - isActive: false, + isActive: true, }, { - info: "align-content is inactive on grid items", + info: "align-content is active on grid items (as they have a computed display of block)", property: "align-content", createTestElement: rootNode => { const container = document.createElement("div"); @@ -52,7 +59,7 @@ export default [ }, rules: ["div { display: grid; }", "span { align-content: center; }"], ruleIndex: 1, - isActive: false, + isActive: true, }, { info: "align-content:baseline is active on flex items", @@ -89,4 +96,11 @@ export default [ rules: ["div { display: table-cell; align-content: baseline; }"], isActive: true, }, + { + info: "align-content:end is inactive on table cells until Bug 1883357 is fixed", + property: "align-content", + tagName: "div", + rules: ["div { display: table-cell; align-content: end; }"], + isActive: false, + }, ]; diff --git a/devtools/server/tests/chrome/memory-helpers.js b/devtools/server/tests/chrome/memory-helpers.js index e4db689134..ac24568779 100644 --- a/devtools/server/tests/chrome/memory-helpers.js +++ b/devtools/server/tests/chrome/memory-helpers.js @@ -57,7 +57,7 @@ async function destroyServerAndFinish(target) { } function waitForTime(ms) { - return new Promise((resolve, reject) => { + return new Promise(resolve => { setTimeout(resolve, ms); }); } diff --git a/devtools/server/tests/chrome/suspendTimeouts_content.js b/devtools/server/tests/chrome/suspendTimeouts_content.js index cb41653cff..3114578877 100644 --- a/devtools/server/tests/chrome/suspendTimeouts_content.js +++ b/devtools/server/tests/chrome/suspendTimeouts_content.js @@ -62,7 +62,7 @@ function resume_timeouts() { // The buggy code calls this handler from the resumeTimeouts call, before the // main thread returns to the event loop. The correct code calls this only once // the JavaScript invocation that called resumeTimeouts has run to completion. -function handle_echo({ data }) { +function handle_echo() { ok( resumeTimeouts_has_returned, "worker message delivered from main event loop" diff --git a/devtools/server/tests/chrome/test_inspector-hide.html b/devtools/server/tests/chrome/test_inspector-hide.html index e699400ee0..ea6ed74560 100644 --- a/devtools/server/tests/chrome/test_inspector-hide.html +++ b/devtools/server/tests/chrome/test_inspector-hide.html @@ -40,7 +40,7 @@ addTest(function testRearrange() { const computed = gInspectee.defaultView.getComputedStyle(listNode); is(computed.visibility, "visible", "Node should be visible to start with"); return gWalker.hideNode(listFront); - }).then(response => { + }).then(() => { const computed = gInspectee.defaultView.getComputedStyle(listNode); is(computed.visibility, "hidden", "Node should be hidden"); return gWalker.unhideNode(listFront); diff --git a/devtools/server/tests/chrome/test_inspector-inactive-property-helper.html b/devtools/server/tests/chrome/test_inspector-inactive-property-helper.html index 86c783c035..c3d9d26aee 100644 --- a/devtools/server/tests/chrome/test_inspector-inactive-property-helper.html +++ b/devtools/server/tests/chrome/test_inspector-inactive-property-helper.html @@ -16,15 +16,18 @@ SimpleTest.waitForExplicitFinish(); const INACTIVE_CSS_PREF = "devtools.inspector.inactive.css.enabled"; const CUSTOM_HIGHLIGHT_API = "dom.customHighlightAPI.enabled"; const TEXT_WRAP_BALANCE = "layout.css.text-wrap-balance.enabled"; + const ALIGN_CONTENT_BLOCKS = "layout.css.align-content.blocks.enabled"; Services.prefs.setBoolPref(INACTIVE_CSS_PREF, true); Services.prefs.setBoolPref(CUSTOM_HIGHLIGHT_API, true); Services.prefs.setBoolPref(TEXT_WRAP_BALANCE, true); + Services.prefs.setBoolPref(ALIGN_CONTENT_BLOCKS, true); SimpleTest.registerCleanupFunction(() => { Services.prefs.clearUserPref(INACTIVE_CSS_PREF); Services.prefs.clearUserPref(CUSTOM_HIGHLIGHT_API); Services.prefs.clearUserPref(TEXT_WRAP_BALANCE); + Services.prefs.clearUserPref(ALIGN_CONTENT_BLOCKS); }); const FOLDER = "./inactive-property-helper"; diff --git a/devtools/server/tests/chrome/test_inspector-pseudoclass-lock.html b/devtools/server/tests/chrome/test_inspector-pseudoclass-lock.html index 949066255d..c40a94d469 100644 --- a/devtools/server/tests/chrome/test_inspector-pseudoclass-lock.html +++ b/devtools/server/tests/chrome/test_inspector-pseudoclass-lock.html @@ -23,7 +23,7 @@ window.onload = function() { let gInspectee = null; let gWalker = null; -async function setup(callback) { +async function setup() { const url = document.getElementById("inspectorContent").href; const { target, doc } = await attachURL(url); gInspectee = doc; diff --git a/devtools/server/tests/chrome/test_memory_allocations_04.html b/devtools/server/tests/chrome/test_memory_allocations_04.html index 8bb64c591c..ae50a38291 100644 --- a/devtools/server/tests/chrome/test_memory_allocations_04.html +++ b/devtools/server/tests/chrome/test_memory_allocations_04.html @@ -29,7 +29,7 @@ window.onload = function() { } } - const testProbability = async function(p, expected) { + const testProbability = async function(p) { info("probability = " + p); await memory.startRecordingAllocations({ probability: p, diff --git a/devtools/server/tests/chrome/test_suspendTimeouts.js b/devtools/server/tests/chrome/test_suspendTimeouts.js index 614ac60cdb..d8a993fa8d 100644 --- a/devtools/server/tests/chrome/test_suspendTimeouts.js +++ b/devtools/server/tests/chrome/test_suspendTimeouts.js @@ -131,7 +131,7 @@ window.onload = function () { }, 1000); } - function finish(message) { + function finish() { SimpleTest.info("suspendTimeouts_content.js", "called finish"); SimpleTest.finish(); } diff --git a/devtools/server/tests/xpcshell/registertestactors-lazy.js b/devtools/server/tests/xpcshell/registertestactors-lazy.js index ef04e7a8d2..3ab7ebb1a8 100644 --- a/devtools/server/tests/xpcshell/registertestactors-lazy.js +++ b/devtools/server/tests/xpcshell/registertestactors-lazy.js @@ -21,13 +21,13 @@ const lazySpec = generateActorSpec({ }); class LazyActor extends Actor { - constructor(conn, id) { + constructor(conn) { super(conn, lazySpec); Services.obs.notifyObservers(null, "actor", "instantiated"); } - hello(str) { + hello() { return "world"; } } diff --git a/devtools/server/tests/xpcshell/test_blackboxing-01.js b/devtools/server/tests/xpcshell/test_blackboxing-01.js index 6c549b908e..981692dd6c 100644 --- a/devtools/server/tests/xpcshell/test_blackboxing-01.js +++ b/devtools/server/tests/xpcshell/test_blackboxing-01.js @@ -114,7 +114,7 @@ function evalCode() { Cu.evalInSandbox( "" + function runTest() { // line 1 doStuff( // line 2 - Break here - function (n) { // line 3 - Step through `doStuff` to here + function () { // line 3 - Step through `doStuff` to here (() => {})(); // line 4 debugger; // line 5 } // line 6 diff --git a/devtools/server/tests/xpcshell/test_blackboxing-02.js b/devtools/server/tests/xpcshell/test_blackboxing-02.js index 66efaee6c8..b713b33187 100644 --- a/devtools/server/tests/xpcshell/test_blackboxing-02.js +++ b/devtools/server/tests/xpcshell/test_blackboxing-02.js @@ -80,7 +80,7 @@ function evalCode(debuggee) { Cu.evalInSandbox( "" + function runTest() { // line 1 doStuff( // line 2 - function(n) { // line 3 + function() { // line 3 debugger; // line 5 } // line 6 ); // line 7 diff --git a/devtools/server/tests/xpcshell/test_blackboxing-05.js b/devtools/server/tests/xpcshell/test_blackboxing-05.js index 388c87da88..0a9bbbbdae 100644 --- a/devtools/server/tests/xpcshell/test_blackboxing-05.js +++ b/devtools/server/tests/xpcshell/test_blackboxing-05.js @@ -81,7 +81,7 @@ function evalCode(debuggee) { "" + function runTest() { // line 1 doStuff( // line 2 - function(n) { // line 3 + function() { // line 3 debugger; // line 4 } // line 5 ); // line 6 diff --git a/devtools/server/tests/xpcshell/test_breakpoint-03.js b/devtools/server/tests/xpcshell/test_breakpoint-03.js index f598660a98..9b0dbe3c0b 100644 --- a/devtools/server/tests/xpcshell/test_breakpoint-03.js +++ b/devtools/server/tests/xpcshell/test_breakpoint-03.js @@ -50,7 +50,7 @@ add_task( Assert.equal(debuggee.b, undefined); // Remove the breakpoint. - bpClient.remove(function (response) { + bpClient.remove(function () { threadFront.resume().then(resolve); }); }); diff --git a/devtools/server/tests/xpcshell/test_breakpoint-05.js b/devtools/server/tests/xpcshell/test_breakpoint-05.js index f678b285b1..288f2d3a6e 100644 --- a/devtools/server/tests/xpcshell/test_breakpoint-05.js +++ b/devtools/server/tests/xpcshell/test_breakpoint-05.js @@ -35,7 +35,7 @@ add_task( Assert.equal(debuggee.b, undefined); // Remove the breakpoint. - bpClient.remove(function (response) { + bpClient.remove(function () { threadFront.resume().then(resolve); }); }); diff --git a/devtools/server/tests/xpcshell/test_breakpoint-06.js b/devtools/server/tests/xpcshell/test_breakpoint-06.js index 79ddcdc3d4..a2cdcd1340 100644 --- a/devtools/server/tests/xpcshell/test_breakpoint-06.js +++ b/devtools/server/tests/xpcshell/test_breakpoint-06.js @@ -35,7 +35,7 @@ add_task( Assert.equal(debuggee.b, undefined); // Remove the breakpoint. - bpClient.remove(function (response) { + bpClient.remove(function () { threadFront.resume().then(resolve); }); }); diff --git a/devtools/server/tests/xpcshell/test_breakpoint-07.js b/devtools/server/tests/xpcshell/test_breakpoint-07.js index e6391747bb..a6a1576c5c 100644 --- a/devtools/server/tests/xpcshell/test_breakpoint-07.js +++ b/devtools/server/tests/xpcshell/test_breakpoint-07.js @@ -35,7 +35,7 @@ add_task( Assert.equal(debuggee.b, undefined); // Remove the breakpoint. - bpClient.remove(function (response) { + bpClient.remove(function () { threadFront.resume().then(resolve); }); }); diff --git a/devtools/server/tests/xpcshell/test_breakpoint-08.js b/devtools/server/tests/xpcshell/test_breakpoint-08.js index bff0cc3b52..44a7c11803 100644 --- a/devtools/server/tests/xpcshell/test_breakpoint-08.js +++ b/devtools/server/tests/xpcshell/test_breakpoint-08.js @@ -43,7 +43,7 @@ add_task( Assert.equal(debuggee.b, undefined); // Remove the breakpoint. - response.bpClient.remove(function (response) { + response.bpClient.remove(function () { threadFront.resume().then(resolve); }); }); diff --git a/devtools/server/tests/xpcshell/test_breakpoint-09.js b/devtools/server/tests/xpcshell/test_breakpoint-09.js index 90b334102d..5dbf8be62a 100644 --- a/devtools/server/tests/xpcshell/test_breakpoint-09.js +++ b/devtools/server/tests/xpcshell/test_breakpoint-09.js @@ -43,7 +43,7 @@ add_task( await client.waitForRequestsToSettle(); done = true; - threadFront.once("paused", function (packet) { + threadFront.once("paused", function () { // The breakpoint should not be hit again. threadFront.resume().then(function () { Assert.ok(false); diff --git a/devtools/server/tests/xpcshell/test_breakpoint-11.js b/devtools/server/tests/xpcshell/test_breakpoint-11.js index a29cd2f768..341f983c39 100644 --- a/devtools/server/tests/xpcshell/test_breakpoint-11.js +++ b/devtools/server/tests/xpcshell/test_breakpoint-11.js @@ -9,7 +9,7 @@ */ add_task( - threadFrontTest(async ({ threadFront, client, debuggee }) => { + threadFrontTest(async ({ threadFront, debuggee }) => { const packet = await executeOnNextTickAndWaitForPause( () => evaluateTestCode(debuggee), threadFront diff --git a/devtools/server/tests/xpcshell/test_breakpoint-12.js b/devtools/server/tests/xpcshell/test_breakpoint-12.js index 44b524f1cf..05de46bb6f 100644 --- a/devtools/server/tests/xpcshell/test_breakpoint-12.js +++ b/devtools/server/tests/xpcshell/test_breakpoint-12.js @@ -23,7 +23,7 @@ add_task( ); const location = { line: debuggee.line0 + 3 }; - source.setBreakpoint(location).then(function ([response, bpClient]) { + source.setBreakpoint(location).then(function ([response]) { // Check that the breakpoint has properly skipped forward one line. Assert.equal(response.actualLocation.source.actor, source.actor); Assert.equal(response.actualLocation.line, location.line + 1); @@ -75,7 +75,7 @@ add_task( Assert.equal(debuggee.a, 1); Assert.equal(debuggee.b, undefined); - threadFront.once("paused", function (packet) { + threadFront.once("paused", function () { // We don't expect any more pauses after the breakpoint was hit once. Assert.ok(false); }); diff --git a/devtools/server/tests/xpcshell/test_breakpoint-14.js b/devtools/server/tests/xpcshell/test_breakpoint-14.js index 835edb1385..aa4c92bdd4 100644 --- a/devtools/server/tests/xpcshell/test_breakpoint-14.js +++ b/devtools/server/tests/xpcshell/test_breakpoint-14.js @@ -10,7 +10,7 @@ */ add_task( - threadFrontTest(async ({ threadFront, client, debuggee }) => { + threadFrontTest(async ({ threadFront, debuggee }) => { const packet = await executeOnNextTickAndWaitForPause( () => evaluateTestCode(debuggee), threadFront diff --git a/devtools/server/tests/xpcshell/test_breakpoint-16.js b/devtools/server/tests/xpcshell/test_breakpoint-16.js index a42306eee1..6ca098cef9 100644 --- a/devtools/server/tests/xpcshell/test_breakpoint-16.js +++ b/devtools/server/tests/xpcshell/test_breakpoint-16.js @@ -9,7 +9,7 @@ */ add_task( - threadFrontTest(async ({ threadFront, client, debuggee }) => { + threadFrontTest(async ({ threadFront, debuggee }) => { const packet = await executeOnNextTickAndWaitForPause( () => evaluateTestCode(debuggee), threadFront diff --git a/devtools/server/tests/xpcshell/test_client_request.js b/devtools/server/tests/xpcshell/test_client_request.js index 837bee5047..5c19884824 100644 --- a/devtools/server/tests/xpcshell/test_client_request.js +++ b/devtools/server/tests/xpcshell/test_client_request.js @@ -203,7 +203,7 @@ function test_client_request_after_close() { }); request.then( - response => { + () => { ok(false, "Request succeed even after client.close"); }, response => { diff --git a/devtools/server/tests/xpcshell/test_conditional_breakpoint-04.js b/devtools/server/tests/xpcshell/test_conditional_breakpoint-04.js index b270b92974..941b480347 100644 --- a/devtools/server/tests/xpcshell/test_conditional_breakpoint-04.js +++ b/devtools/server/tests/xpcshell/test_conditional_breakpoint-04.js @@ -9,7 +9,7 @@ */ add_task( - threadFrontTest(async ({ threadFront, debuggee, commands }) => { + threadFrontTest(async ({ threadFront, debuggee }) => { await threadFront.setBreakpoint( { sourceUrl: "conditional_breakpoint-04.js", line: 3 }, { condition: "throw new Error()" } diff --git a/devtools/server/tests/xpcshell/test_dbgglobal.js b/devtools/server/tests/xpcshell/test_dbgglobal.js index 407e270da4..6e6f40bb5d 100644 --- a/devtools/server/tests/xpcshell/test_dbgglobal.js +++ b/devtools/server/tests/xpcshell/test_dbgglobal.js @@ -69,14 +69,14 @@ function run_test() { ); client2.close(); }, - onTransportClosed(result) { + onTransportClosed() { client1.close(); }, }; client2.ready(); }, - onTransportClosed(result) { + onTransportClosed() { do_test_finished(); }, }; diff --git a/devtools/server/tests/xpcshell/test_forwardingprefix.js b/devtools/server/tests/xpcshell/test_forwardingprefix.js index e917350da5..fa9afbf2f0 100644 --- a/devtools/server/tests/xpcshell/test_forwardingprefix.js +++ b/devtools/server/tests/xpcshell/test_forwardingprefix.js @@ -51,7 +51,7 @@ function newConnection(prefix) { function createMainConnection() { ({ conn: gMainConnection, transport: gMainTransport } = newConnection()); gClient = new DevToolsClient(gMainTransport); - gClient.connect().then(([type, traits]) => run_next_test()); + gClient.connect().then(() => run_next_test()); } /* @@ -152,7 +152,7 @@ function createSubconnection1() { const { conn, transport } = newSubconnection("prefix1"); gSubconnection1 = conn; transport.ready(); - gClient.expectReply("prefix1/root", reply => run_next_test()); + gClient.expectReply("prefix1/root", () => run_next_test()); } // Establish forwarding, but don't put any actors in that server. @@ -165,7 +165,7 @@ function createSubconnection2() { const { conn, transport } = newSubconnection("prefix2"); gSubconnection2 = conn; transport.ready(); - gClient.expectReply("prefix2/root", reply => run_next_test()); + gClient.expectReply("prefix2/root", () => run_next_test()); } function TestForwardPrefix12OnlyRoot() { diff --git a/devtools/server/tests/xpcshell/test_framearguments-01.js b/devtools/server/tests/xpcshell/test_framearguments-01.js index 524d43f58c..9f6f433e70 100644 --- a/devtools/server/tests/xpcshell/test_framearguments-01.js +++ b/devtools/server/tests/xpcshell/test_framearguments-01.js @@ -33,6 +33,8 @@ function evalCode(debuggee) { debuggee.eval( "(" + function () { + // These arguments are tested. + // eslint-disable-next-line no-unused-vars function stopMe(number, bool, string, null_, undef, object) { debugger; } diff --git a/devtools/server/tests/xpcshell/test_framebindings-07.js b/devtools/server/tests/xpcshell/test_framebindings-07.js index 77d43dfba8..ee655da07a 100644 --- a/devtools/server/tests/xpcshell/test_framebindings-07.js +++ b/devtools/server/tests/xpcshell/test_framebindings-07.js @@ -4,7 +4,7 @@ "use strict"; add_task( - threadFrontTest(async ({ threadFront, debuggee, client }) => { + threadFrontTest(async ({ threadFront, debuggee }) => { const packet = await executeOnNextTickAndWaitForPause( () => evalCode(debuggee), threadFront diff --git a/devtools/server/tests/xpcshell/test_functiongrips-01.js b/devtools/server/tests/xpcshell/test_functiongrips-01.js index 5abce26875..ccadeba11d 100644 --- a/devtools/server/tests/xpcshell/test_functiongrips-01.js +++ b/devtools/server/tests/xpcshell/test_functiongrips-01.js @@ -8,6 +8,8 @@ add_task( // Test named function function evalCode() { debuggee.eval( + // These arguments are tested. + // eslint-disable-next-line no-unused-vars function stopMe(arg1) { debugger; }.toString() diff --git a/devtools/server/tests/xpcshell/test_getRuleText.js b/devtools/server/tests/xpcshell/test_getRuleText.js index fe53dca158..bc89da974c 100644 --- a/devtools/server/tests/xpcshell/test_getRuleText.js +++ b/devtools/server/tests/xpcshell/test_getRuleText.js @@ -16,6 +16,25 @@ const TEST_DATA = [ throws: true, }, { + desc: "Null input", + input: null, + line: 1, + column: 1, + throws: true, + }, + { + desc: "Missing loc", + input: "#id{color:red;background:yellow;}", + throws: true, + }, + { + desc: "No opening bracket", + input: "/* hey */", + line: 1, + column: 1, + throws: true, + }, + { desc: "Simplest test case", input: "#id{color:red;background:yellow;}", line: 1, @@ -39,18 +58,6 @@ const TEST_DATA = [ expected: { offset: 4, text: "color:red;background:yellow;" }, }, { - desc: "Null input", - input: null, - line: 1, - column: 1, - throws: true, - }, - { - desc: "Missing loc", - input: "#id{color:red;background:yellow;}", - throws: true, - }, - { desc: "Multi-lines CSS", input: [ "/* this is a multi line css */", @@ -61,7 +68,7 @@ const TEST_DATA = [ " /*something else here */", "* {", " color: purple;", - "}", + "} ", ].join("\n"), line: 7, column: 1, @@ -107,12 +114,60 @@ const TEST_DATA = [ }, }, { + desc: "Attribute selector containing a { character", + input: `div[data-x="{"]{color: gold}`, + line: 1, + column: 1, + expected: { + offset: 16, + text: "color: gold", + }, + }, + { desc: "Rule contains no tokens", input: "div{}", line: 1, column: 1, expected: { offset: 4, text: "" }, }, + { + desc: "Rule contains invalid declaration", + input: `#id{color;}`, + line: 1, + column: 1, + expected: { offset: 4, text: "color;" }, + }, + { + desc: "Rule contains invalid declaration", + input: `#id{-}`, + line: 1, + column: 1, + expected: { offset: 4, text: "-" }, + }, + { + desc: "Rule contains nested rule", + input: `#id{background: gold; .nested{color:blue;} color: tomato; }`, + line: 1, + column: 1, + expected: { + offset: 4, + text: "background: gold; .nested{color:blue;} color: tomato; ", + }, + }, + { + desc: "Rule contains nested rule with invalid declaration", + input: `#id{.nested{color;}}`, + line: 1, + column: 1, + expected: { offset: 4, text: ".nested{color;}" }, + }, + { + desc: "Rule contains unicode chars", + input: `#id /*🙃*/ {content: "☃️";}`, + line: 1, + column: 1, + expected: { offset: 12, text: `content: "☃️";` }, + }, ]; function run_test() { diff --git a/devtools/server/tests/xpcshell/test_interrupt.js b/devtools/server/tests/xpcshell/test_interrupt.js index 07593a7360..5431c8f508 100644 --- a/devtools/server/tests/xpcshell/test_interrupt.js +++ b/devtools/server/tests/xpcshell/test_interrupt.js @@ -4,7 +4,7 @@ "use strict"; add_task( - threadFrontTest(async ({ threadFront, debuggee, client, targetFront }) => { + threadFrontTest(async ({ threadFront }) => { const onPaused = waitForEvent(threadFront, "paused"); await threadFront.interrupt(); await onPaused; diff --git a/devtools/server/tests/xpcshell/test_logpoint-01.js b/devtools/server/tests/xpcshell/test_logpoint-01.js index a5cb4f2197..e2d3186d47 100644 --- a/devtools/server/tests/xpcshell/test_logpoint-01.js +++ b/devtools/server/tests/xpcshell/test_logpoint-01.js @@ -12,7 +12,7 @@ const Resources = require("resource://devtools/server/actors/resources/index.js" add_task( threadFrontTest(async ({ threadActor, threadFront, debuggee, client }) => { let lastMessage, lastExpression; - const targetActor = threadActor._parent; + const { targetActor } = threadActor; // Only Workers are evaluating through the WebConsoleActor. // Tabs will be evaluating directly via the frame object. targetActor._consoleActor = { diff --git a/devtools/server/tests/xpcshell/test_logpoint-02.js b/devtools/server/tests/xpcshell/test_logpoint-02.js index d84d3fc324..183478ee32 100644 --- a/devtools/server/tests/xpcshell/test_logpoint-02.js +++ b/devtools/server/tests/xpcshell/test_logpoint-02.js @@ -12,7 +12,7 @@ const Resources = require("resource://devtools/server/actors/resources/index.js" add_task( threadFrontTest(async ({ threadActor, threadFront, debuggee, client }) => { let lastMessage, lastExpression; - const targetActor = threadActor._parent; + const { targetActor } = threadActor; // Only Workers are evaluating through the WebConsoleActor. // Tabs will be evaluating directly via the frame object. targetActor._consoleActor = { diff --git a/devtools/server/tests/xpcshell/test_logpoint-03.js b/devtools/server/tests/xpcshell/test_logpoint-03.js index b5d4440889..0b6e37a9f6 100644 --- a/devtools/server/tests/xpcshell/test_logpoint-03.js +++ b/devtools/server/tests/xpcshell/test_logpoint-03.js @@ -10,9 +10,9 @@ const Resources = require("resource://devtools/server/actors/resources/index.js"); add_task( - threadFrontTest(async ({ threadActor, threadFront, debuggee, client }) => { + threadFrontTest(async ({ threadActor, threadFront, debuggee }) => { let lastMessage, lastExpression; - const targetActor = threadActor._parent; + const { targetActor } = threadActor; // Only Workers are evaluating through the WebConsoleActor. // Tabs will be evaluating directly via the frame object. targetActor._consoleActor = { diff --git a/devtools/server/tests/xpcshell/test_longstringgrips-01.js b/devtools/server/tests/xpcshell/test_longstringgrips-01.js index ac0b228c17..fc93a80495 100644 --- a/devtools/server/tests/xpcshell/test_longstringgrips-01.js +++ b/devtools/server/tests/xpcshell/test_longstringgrips-01.js @@ -66,6 +66,8 @@ function test_longstring_grip() { }); gDebuggee.eval( + // These arguments are tested. + // eslint-disable-next-line no-unused-vars function stopMe(arg1) { debugger; }.toString() diff --git a/devtools/server/tests/xpcshell/test_objectgrips-02.js b/devtools/server/tests/xpcshell/test_objectgrips-02.js index 810a5009c0..71bf7d2c27 100644 --- a/devtools/server/tests/xpcshell/test_objectgrips-02.js +++ b/devtools/server/tests/xpcshell/test_objectgrips-02.js @@ -29,6 +29,8 @@ add_task( function evalCode(debuggee) { debuggee.eval( + // These arguments are tested. + // eslint-disable-next-line no-unused-vars function stopMe(arg1) { debugger; }.toString() diff --git a/devtools/server/tests/xpcshell/test_objectgrips-03.js b/devtools/server/tests/xpcshell/test_objectgrips-03.js index c8a51d41d3..b41b478f5d 100644 --- a/devtools/server/tests/xpcshell/test_objectgrips-03.js +++ b/devtools/server/tests/xpcshell/test_objectgrips-03.js @@ -44,6 +44,8 @@ add_task( function evalCode(debuggee) { debuggee.eval( + // These arguments are tested. + // eslint-disable-next-line no-unused-vars function stopMe(arg1) { debugger; }.toString() diff --git a/devtools/server/tests/xpcshell/test_objectgrips-04.js b/devtools/server/tests/xpcshell/test_objectgrips-04.js index d08705db3c..eb3fb06103 100644 --- a/devtools/server/tests/xpcshell/test_objectgrips-04.js +++ b/devtools/server/tests/xpcshell/test_objectgrips-04.js @@ -46,6 +46,8 @@ add_task( function evalCode(debuggee) { debuggee.eval( + // These arguments are tested. + // eslint-disable-next-line no-unused-vars function stopMe(arg1) { debugger; }.toString() diff --git a/devtools/server/tests/xpcshell/test_objectgrips-05.js b/devtools/server/tests/xpcshell/test_objectgrips-05.js index 4c6f0f107a..916c9db6df 100644 --- a/devtools/server/tests/xpcshell/test_objectgrips-05.js +++ b/devtools/server/tests/xpcshell/test_objectgrips-05.js @@ -38,6 +38,8 @@ add_task( function evalCode(debuggee) { debuggee.eval( + // These arguments are tested. + // eslint-disable-next-line no-unused-vars function stopMe(arg1) { debugger; }.toString() diff --git a/devtools/server/tests/xpcshell/test_objectgrips-06.js b/devtools/server/tests/xpcshell/test_objectgrips-06.js index ef3d2b5b66..e6c0f835a2 100644 --- a/devtools/server/tests/xpcshell/test_objectgrips-06.js +++ b/devtools/server/tests/xpcshell/test_objectgrips-06.js @@ -38,6 +38,8 @@ add_task( function evalCode(debuggee) { debuggee.eval( + // These arguments are tested. + // eslint-disable-next-line no-unused-vars function stopMe(arg1) { debugger; }.toString() diff --git a/devtools/server/tests/xpcshell/test_objectgrips-07.js b/devtools/server/tests/xpcshell/test_objectgrips-07.js index 2a3a0bf00e..4ccd711fc7 100644 --- a/devtools/server/tests/xpcshell/test_objectgrips-07.js +++ b/devtools/server/tests/xpcshell/test_objectgrips-07.js @@ -43,6 +43,8 @@ add_task( function evalCode(debuggee) { debuggee.eval( + // These arguments are tested. + // eslint-disable-next-line no-unused-vars function stopMe(arg1) { debugger; }.toString() diff --git a/devtools/server/tests/xpcshell/test_objectgrips-08.js b/devtools/server/tests/xpcshell/test_objectgrips-08.js index 1a37f19fb8..892368d7fe 100644 --- a/devtools/server/tests/xpcshell/test_objectgrips-08.js +++ b/devtools/server/tests/xpcshell/test_objectgrips-08.js @@ -36,6 +36,8 @@ add_task( function evalCode(debuggee) { debuggee.eval( + // These arguments are tested. + // eslint-disable-next-line no-unused-vars function stopMe(arg1) { debugger; }.toString() diff --git a/devtools/server/tests/xpcshell/test_objectgrips-16.js b/devtools/server/tests/xpcshell/test_objectgrips-16.js index 785c3bc36d..5e66aa700b 100644 --- a/devtools/server/tests/xpcshell/test_objectgrips-16.js +++ b/devtools/server/tests/xpcshell/test_objectgrips-16.js @@ -29,6 +29,8 @@ add_task( function eval_code() { debuggee.eval( + // These arguments are tested. + // eslint-disable-next-line no-unused-vars function stopMe(arg1) { debugger; }.toString() diff --git a/devtools/server/tests/xpcshell/test_objectgrips-17.js b/devtools/server/tests/xpcshell/test_objectgrips-17.js index edaea88eaa..ed90cc7ae6 100644 --- a/devtools/server/tests/xpcshell/test_objectgrips-17.js +++ b/devtools/server/tests/xpcshell/test_objectgrips-17.js @@ -288,6 +288,8 @@ async function run_tests_in_principal( ) { const { debuggee } = options; debuggee.eval( + // These arguments are tested. + // eslint-disable-next-line no-unused-vars function stopMe(arg1, arg2) { debugger; }.toString() diff --git a/devtools/server/tests/xpcshell/test_objectgrips-18.js b/devtools/server/tests/xpcshell/test_objectgrips-18.js index 90c38d99a9..9e169b596c 100644 --- a/devtools/server/tests/xpcshell/test_objectgrips-18.js +++ b/devtools/server/tests/xpcshell/test_objectgrips-18.js @@ -31,6 +31,8 @@ add_task( function eval_code() { debuggee.eval( + // These arguments are tested. + // eslint-disable-next-line no-unused-vars function stopMe(arg1) { debugger; }.toString() diff --git a/devtools/server/tests/xpcshell/test_objectgrips-19.js b/devtools/server/tests/xpcshell/test_objectgrips-19.js index 655c7d0f43..9ee92638bc 100644 --- a/devtools/server/tests/xpcshell/test_objectgrips-19.js +++ b/devtools/server/tests/xpcshell/test_objectgrips-19.js @@ -9,8 +9,10 @@ registerCleanupFunction(() => { }); add_task( - threadFrontTest(async ({ threadFront, debuggee, client }) => { + threadFrontTest(async ({ threadFront, debuggee }) => { debuggee.eval( + // These arguments are tested. + // eslint-disable-next-line no-unused-vars function stopMe(arg1) { debugger; }.toString() diff --git a/devtools/server/tests/xpcshell/test_objectgrips-20.js b/devtools/server/tests/xpcshell/test_objectgrips-20.js index 5027ca31a7..f36ea20f7e 100644 --- a/devtools/server/tests/xpcshell/test_objectgrips-20.js +++ b/devtools/server/tests/xpcshell/test_objectgrips-20.js @@ -17,6 +17,8 @@ registerCleanupFunction(() => { add_task( threadFrontTest(async ({ threadFront, debuggee, client }) => { debuggee.eval( + // These arguments are tested. + // eslint-disable-next-line no-unused-vars function stopMe(arg1) { debugger; }.toString() diff --git a/devtools/server/tests/xpcshell/test_objectgrips-21.js b/devtools/server/tests/xpcshell/test_objectgrips-21.js index 88296f7786..daed1808c7 100644 --- a/devtools/server/tests/xpcshell/test_objectgrips-21.js +++ b/devtools/server/tests/xpcshell/test_objectgrips-21.js @@ -213,6 +213,8 @@ async function test_unsafe_grips( tests ) { debuggee.eval( + // These arguments are tested. + // eslint-disable-next-line no-unused-vars function stopMe(arg1, arg2) { debugger; }.toString() diff --git a/devtools/server/tests/xpcshell/test_objectgrips-22.js b/devtools/server/tests/xpcshell/test_objectgrips-22.js index 34264f5534..fd33030fef 100644 --- a/devtools/server/tests/xpcshell/test_objectgrips-22.js +++ b/devtools/server/tests/xpcshell/test_objectgrips-22.js @@ -40,6 +40,8 @@ add_task( function evalCode(debuggee) { debuggee.eval( + // These arguments are tested. + // eslint-disable-next-line no-unused-vars function stopMe(arg1) { debugger; }.toString() diff --git a/devtools/server/tests/xpcshell/test_objectgrips-25.js b/devtools/server/tests/xpcshell/test_objectgrips-25.js index f80572bb19..4fdce90805 100644 --- a/devtools/server/tests/xpcshell/test_objectgrips-25.js +++ b/devtools/server/tests/xpcshell/test_objectgrips-25.js @@ -12,6 +12,8 @@ registerCleanupFunction(() => { function evalCode(debuggee) { debuggee.eval( + // These arguments are tested. + // eslint-disable-next-line no-unused-vars function stopMe(obj) { debugger; }.toString() diff --git a/devtools/server/tests/xpcshell/test_objectgrips-fn-apply-01.js b/devtools/server/tests/xpcshell/test_objectgrips-fn-apply-01.js index f576f16a5e..a67b08b0d2 100644 --- a/devtools/server/tests/xpcshell/test_objectgrips-fn-apply-01.js +++ b/devtools/server/tests/xpcshell/test_objectgrips-fn-apply-01.js @@ -63,6 +63,8 @@ add_task( function evalCode(debuggee) { debuggee.eval( + // These arguments are tested. + // eslint-disable-next-line no-unused-vars function stopMe(arg1) { debugger; }.toString() diff --git a/devtools/server/tests/xpcshell/test_objectgrips-fn-apply-02.js b/devtools/server/tests/xpcshell/test_objectgrips-fn-apply-02.js index 743286281c..fda27f6874 100644 --- a/devtools/server/tests/xpcshell/test_objectgrips-fn-apply-02.js +++ b/devtools/server/tests/xpcshell/test_objectgrips-fn-apply-02.js @@ -41,6 +41,8 @@ add_task( function evalCode(debuggee) { debuggee.eval( + // These arguments are tested. + // eslint-disable-next-line no-unused-vars function stopMe(arg1) { debugger; }.toString() diff --git a/devtools/server/tests/xpcshell/test_objectgrips-fn-apply-03.js b/devtools/server/tests/xpcshell/test_objectgrips-fn-apply-03.js index 6a3e919661..69a317b5e0 100644 --- a/devtools/server/tests/xpcshell/test_objectgrips-fn-apply-03.js +++ b/devtools/server/tests/xpcshell/test_objectgrips-fn-apply-03.js @@ -38,6 +38,8 @@ add_task( function evalCode(debuggee) { debuggee.eval( + // These arguments are tested. + // eslint-disable-next-line no-unused-vars function stopMe(arg1) { debugger; }.toString() diff --git a/devtools/server/tests/xpcshell/test_objectgrips-nested-promise.js b/devtools/server/tests/xpcshell/test_objectgrips-nested-promise.js index b60b7328c2..e0de94d24f 100644 --- a/devtools/server/tests/xpcshell/test_objectgrips-nested-promise.js +++ b/devtools/server/tests/xpcshell/test_objectgrips-nested-promise.js @@ -38,6 +38,8 @@ add_task( function evalCode(debuggee) { debuggee.eval( + // These arguments are tested. + // eslint-disable-next-line no-unused-vars function stopMe(arg) { debugger; }.toString() diff --git a/devtools/server/tests/xpcshell/test_objectgrips-nested-proxy.js b/devtools/server/tests/xpcshell/test_objectgrips-nested-proxy.js index 5b0667c055..a5760f6144 100644 --- a/devtools/server/tests/xpcshell/test_objectgrips-nested-proxy.js +++ b/devtools/server/tests/xpcshell/test_objectgrips-nested-proxy.js @@ -37,6 +37,8 @@ add_task( function evalCode(debuggee) { debuggee.eval( + // These arguments are tested. + // eslint-disable-next-line no-unused-vars function stopMe(arg) { debugger; }.toString() diff --git a/devtools/server/tests/xpcshell/test_objectgrips-property-value-01.js b/devtools/server/tests/xpcshell/test_objectgrips-property-value-01.js index 69da96a741..c7588578f4 100644 --- a/devtools/server/tests/xpcshell/test_objectgrips-property-value-01.js +++ b/devtools/server/tests/xpcshell/test_objectgrips-property-value-01.js @@ -89,6 +89,8 @@ add_task( function evalCode(debuggee) { debuggee.eval( + // These arguments are tested. + // eslint-disable-next-line no-unused-vars function stopMe(arg1) { debugger; }.toString() diff --git a/devtools/server/tests/xpcshell/test_objectgrips-property-value-02.js b/devtools/server/tests/xpcshell/test_objectgrips-property-value-02.js index bc7337128c..6d00a6e04e 100644 --- a/devtools/server/tests/xpcshell/test_objectgrips-property-value-02.js +++ b/devtools/server/tests/xpcshell/test_objectgrips-property-value-02.js @@ -38,6 +38,8 @@ add_task( function evalCode(debuggee) { debuggee.eval( + // These arguments are tested. + // eslint-disable-next-line no-unused-vars function stopMe(arg1) { debugger; }.toString() diff --git a/devtools/server/tests/xpcshell/test_objectgrips-sparse-array.js b/devtools/server/tests/xpcshell/test_objectgrips-sparse-array.js index 76a6b32f4b..3448987420 100644 --- a/devtools/server/tests/xpcshell/test_objectgrips-sparse-array.js +++ b/devtools/server/tests/xpcshell/test_objectgrips-sparse-array.js @@ -32,6 +32,8 @@ add_task( function evalCode(debuggee) { debuggee.eval( + // These arguments are tested. + // eslint-disable-next-line no-unused-vars function stopMe(arr) { debugger; }.toString() diff --git a/devtools/server/tests/xpcshell/test_pause_exceptions-04.js b/devtools/server/tests/xpcshell/test_pause_exceptions-04.js index 6246b112e0..aa30002af8 100644 --- a/devtools/server/tests/xpcshell/test_pause_exceptions-04.js +++ b/devtools/server/tests/xpcshell/test_pause_exceptions-04.js @@ -12,7 +12,7 @@ const { waitForTick } = require("resource://devtools/shared/DevToolsUtils.js"); add_task( threadFrontTest( - async ({ threadFront, client, debuggee, commands }) => { + async ({ threadFront, debuggee, commands }) => { let onResume = null; let packet = null; diff --git a/devtools/server/tests/xpcshell/test_pauselifetime-02.js b/devtools/server/tests/xpcshell/test_pauselifetime-02.js index e936df6177..08495229e6 100644 --- a/devtools/server/tests/xpcshell/test_pauselifetime-02.js +++ b/devtools/server/tests/xpcshell/test_pauselifetime-02.js @@ -47,6 +47,8 @@ function evaluateTestCode(debuggee) { debuggee.eval( "(" + function () { + // These arguments are tested. + // eslint-disable-next-line no-unused-vars function stopMe(obj) { debugger; } diff --git a/devtools/server/tests/xpcshell/test_pauselifetime-03.js b/devtools/server/tests/xpcshell/test_pauselifetime-03.js index 558ac8b910..f79ade2e7e 100644 --- a/devtools/server/tests/xpcshell/test_pauselifetime-03.js +++ b/devtools/server/tests/xpcshell/test_pauselifetime-03.js @@ -54,6 +54,8 @@ function evaluateTestCode(debuggee) { debuggee.eval( "(" + function () { + // These arguments are tested. + // eslint-disable-next-line no-unused-vars function stopMe(obj) { debugger; } diff --git a/devtools/server/tests/xpcshell/test_pauselifetime-04.js b/devtools/server/tests/xpcshell/test_pauselifetime-04.js index 7d226260f0..a699a96709 100644 --- a/devtools/server/tests/xpcshell/test_pauselifetime-04.js +++ b/devtools/server/tests/xpcshell/test_pauselifetime-04.js @@ -30,6 +30,8 @@ function evaluateTestCode(debuggee) { debuggee.eval( "(" + function () { + // These arguments are tested. + // eslint-disable-next-line no-unused-vars function stopMe(obj) { debugger; } diff --git a/devtools/server/tests/xpcshell/test_promises_run_to_completion.js b/devtools/server/tests/xpcshell/test_promises_run_to_completion.js index 4d1e8745fe..7147d77e0d 100644 --- a/devtools/server/tests/xpcshell/test_promises_run_to_completion.js +++ b/devtools/server/tests/xpcshell/test_promises_run_to_completion.js @@ -44,7 +44,7 @@ function test_promises_run_to_completion() { const log = [""]; g.log = log; - dbg.onDebuggerStatement = function handleDebuggerStatement(frame) { + dbg.onDebuggerStatement = function handleDebuggerStatement() { dbg.onDebuggerStatement = undefined; // Exercise the promise machinery: resolve a promise and perform a microtask @@ -63,7 +63,7 @@ function test_promises_run_to_completion() { force_microtask_checkpoint(); log[0] += ")"; - Promise.resolve(42).then(v => { + Promise.resolve(42).then(() => { // The microtask running this callback should be handled as we leave the // onDebuggerStatement Debugger callback, and should not be interleaved // with debuggee microtasks. diff --git a/devtools/server/tests/xpcshell/test_restartFrame-01.js b/devtools/server/tests/xpcshell/test_restartFrame-01.js index cb13ae2d7e..51fbe75510 100644 --- a/devtools/server/tests/xpcshell/test_restartFrame-01.js +++ b/devtools/server/tests/xpcshell/test_restartFrame-01.js @@ -8,7 +8,7 @@ * restarted frame. */ -async function testFinish({ threadFront, devToolsClient }) { +async function testFinish({ devToolsClient }) { await close(devToolsClient); do_test_finished(); diff --git a/devtools/server/tests/xpcshell/test_source-01.js b/devtools/server/tests/xpcshell/test_source-01.js index 5cb7a6da52..cb57ad43e9 100644 --- a/devtools/server/tests/xpcshell/test_source-01.js +++ b/devtools/server/tests/xpcshell/test_source-01.js @@ -46,6 +46,8 @@ add_task( function evaluateTestCode(debuggee) { Cu.evalInSandbox( "" + + // These arguments are tested. + // eslint-disable-next-line no-unused-vars function stopMe(arg1) { debugger; }, diff --git a/devtools/server/tests/xpcshell/test_source-02.js b/devtools/server/tests/xpcshell/test_source-02.js index 9cb88cb0e4..88e91ee6e3 100644 --- a/devtools/server/tests/xpcshell/test_source-02.js +++ b/devtools/server/tests/xpcshell/test_source-02.js @@ -52,6 +52,8 @@ add_task( function evaluateTestCode(debuggee) { Cu.evalInSandbox( "" + + // These arguments are tested. + // eslint-disable-next-line no-unused-vars function stopMe(arg1) { debugger; }, diff --git a/devtools/server/tests/xpcshell/test_source-03.js b/devtools/server/tests/xpcshell/test_source-03.js index d0cd4839a0..bbee9d2949 100644 --- a/devtools/server/tests/xpcshell/test_source-03.js +++ b/devtools/server/tests/xpcshell/test_source-03.js @@ -51,7 +51,7 @@ add_task( // in the first global. let pausedOne = false; let onResumed = null; - threadFront.once("paused", function (packet) { + threadFront.once("paused", function () { pausedOne = true; onResumed = resume(threadFront); }); @@ -62,7 +62,7 @@ add_task( // Ensure that the breakpoint was properly applied to the JSScipt loaded // in the second global. let pausedTwo = false; - threadFront.once("paused", function (packet) { + threadFront.once("paused", function () { pausedTwo = true; onResumed = resume(threadFront); }); diff --git a/devtools/server/tests/xpcshell/test_source-04.js b/devtools/server/tests/xpcshell/test_source-04.js index a3e3bef25f..fd3e8c339c 100644 --- a/devtools/server/tests/xpcshell/test_source-04.js +++ b/devtools/server/tests/xpcshell/test_source-04.js @@ -39,7 +39,7 @@ add_task( // in the first global. let pausedOne = false; let onResumed = null; - threadFront.once("paused", function (packet) { + threadFront.once("paused", function () { pausedOne = true; onResumed = resume(threadFront); }); @@ -61,7 +61,7 @@ add_task( // Ensure that the breakpoint was properly applied to the JSScipt loaded // in the second global. let pausedTwo = false; - threadFront.once("paused", function (packet) { + threadFront.once("paused", function () { pausedTwo = true; onResumed = resume(threadFront); }); diff --git a/devtools/server/tests/xpcshell/test_stepping-01.js b/devtools/server/tests/xpcshell/test_stepping-01.js index 0c66404510..6231ed746e 100644 --- a/devtools/server/tests/xpcshell/test_stepping-01.js +++ b/devtools/server/tests/xpcshell/test_stepping-01.js @@ -8,7 +8,7 @@ * going to the function b's call-site. */ -async function testFinish({ threadFront, devToolsClient }) { +async function testFinish({ devToolsClient }) { await close(devToolsClient); do_test_finished(); diff --git a/devtools/server/tests/xpcshell/test_stepping-18.js b/devtools/server/tests/xpcshell/test_stepping-18.js index e8581835d3..4961173074 100644 --- a/devtools/server/tests/xpcshell/test_stepping-18.js +++ b/devtools/server/tests/xpcshell/test_stepping-18.js @@ -8,7 +8,7 @@ * going to the function b's call-site. */ -async function testFinish({ threadFront, devToolsClient }) { +async function testFinish({ devToolsClient }) { await close(devToolsClient); do_test_finished(); diff --git a/devtools/server/tests/xpcshell/test_stepping-19.js b/devtools/server/tests/xpcshell/test_stepping-19.js index 7ab21c7b66..c344816615 100644 --- a/devtools/server/tests/xpcshell/test_stepping-19.js +++ b/devtools/server/tests/xpcshell/test_stepping-19.js @@ -7,7 +7,7 @@ * Check that step out stops at the async parent's frame. */ -async function testFinish({ threadFront, devToolsClient }) { +async function testFinish({ devToolsClient }) { await close(devToolsClient); do_test_finished(); diff --git a/devtools/server/tests/xpcshell/test_threadlifetime-01.js b/devtools/server/tests/xpcshell/test_threadlifetime-01.js index d2e8234fb9..d8b2e3c53b 100644 --- a/devtools/server/tests/xpcshell/test_threadlifetime-01.js +++ b/devtools/server/tests/xpcshell/test_threadlifetime-01.js @@ -45,6 +45,8 @@ function evaluateTestCode(debuggee) { debuggee.eval( "(" + function () { + // These arguments are tested. + // eslint-disable-next-line no-unused-vars function stopMe(arg1) { debugger; debugger; diff --git a/devtools/server/tests/xpcshell/test_threadlifetime-02.js b/devtools/server/tests/xpcshell/test_threadlifetime-02.js index c35350a48c..310ffe6b9f 100644 --- a/devtools/server/tests/xpcshell/test_threadlifetime-02.js +++ b/devtools/server/tests/xpcshell/test_threadlifetime-02.js @@ -62,6 +62,8 @@ function evaluateTestCode(debuggee) { debuggee.eval( "(" + function () { + // These arguments are tested. + // eslint-disable-next-line no-unused-vars function stopMe(arg1) { debugger; debugger; diff --git a/devtools/server/tests/xpcshell/test_threadlifetime-04.js b/devtools/server/tests/xpcshell/test_threadlifetime-04.js index 6b815c7933..a78b70cc66 100644 --- a/devtools/server/tests/xpcshell/test_threadlifetime-04.js +++ b/devtools/server/tests/xpcshell/test_threadlifetime-04.js @@ -48,6 +48,8 @@ function test_thread_lifetime() { gDebuggee.eval( "(" + function () { + // These arguments are tested. + // eslint-disable-next-line no-unused-vars function stopMe(arg1) { debugger; } diff --git a/devtools/server/tests/xpcshell/test_wasm_source-01.js b/devtools/server/tests/xpcshell/test_wasm_source-01.js index fe8e43e236..4f6561d88d 100644 --- a/devtools/server/tests/xpcshell/test_wasm_source-01.js +++ b/devtools/server/tests/xpcshell/test_wasm_source-01.js @@ -13,7 +13,7 @@ var gThreadFront; add_task( threadFrontTest( - async ({ threadFront, debuggee, client }) => { + async ({ threadFront, debuggee }) => { gThreadFront = threadFront; gDebuggee = debuggee; @@ -90,7 +90,7 @@ const EXPECTED_CONTENT = String.fromCharCode( ); function test_source() { - gThreadFront.once("paused", function (packet) { + gThreadFront.once("paused", function () { gThreadFront.getSources().then(function (response) { Assert.ok(!!response); Assert.ok(!!response.sources); diff --git a/devtools/server/tests/xpcshell/testactors.js b/devtools/server/tests/xpcshell/testactors.js index af208fe93e..bbcd8abe6e 100644 --- a/devtools/server/tests/xpcshell/testactors.js +++ b/devtools/server/tests/xpcshell/testactors.js @@ -166,7 +166,7 @@ class TestTargetActor extends protocol.Actor { this._extraActors.threadActor = this.threadActor; this.makeDebugger = makeDebugger.bind(null, { findDebuggees: () => [this._global], - shouldAddNewGlobalAsDebuggee: g => gAllowNewThreadGlobals, + shouldAddNewGlobalAsDebuggee: () => gAllowNewThreadGlobals, }); this.dbg = this.makeDebugger(); this.notifyResources = this.notifyResources.bind(this); @@ -216,12 +216,12 @@ class TestTargetActor extends protocol.Actor { return { ...response, ...actors }; } - detach(request) { + detach() { this.threadActor.destroy(); return { type: "detached" }; } - reload(request) { + reload() { this.sourcesManager.reset(); this.threadActor.clearDebuggees(); this.threadActor.dbg.addDebuggees(); diff --git a/devtools/server/tracer/tests/browser/browser_document_tracer.js b/devtools/server/tracer/tests/browser/browser_document_tracer.js index 694842fa8b..dcf2c9eb4d 100644 --- a/devtools/server/tracer/tests/browser/browser_document_tracer.js +++ b/devtools/server/tracer/tests/browser/browser_document_tracer.js @@ -52,7 +52,7 @@ add_task(async function testTracingWorker() { const firstFrame = frames[0]; is(firstFrame.formatedDisplayName, "λ foo"); - is(firstFrame.currentDOMEvent, "DOM(click)"); + is(firstFrame.currentDOMEvent, "DOM | click"); const lastFrame = frames.at(-1); is(lastFrame.formatedDisplayName, "λ bar"); diff --git a/devtools/server/tracer/tests/browser/browser_worker_tracer.js b/devtools/server/tracer/tests/browser/browser_worker_tracer.js index 815da85853..f555bd06b0 100644 --- a/devtools/server/tracer/tests/browser/browser_worker_tracer.js +++ b/devtools/server/tracer/tests/browser/browser_worker_tracer.js @@ -52,7 +52,7 @@ add_task(async function testTracingWorker() { ok(lastFrame.frame); }); -function waitForWorkerDebugger(url, dbgUrl) { +function waitForWorkerDebugger(url) { return new Promise(function (resolve) { wdm.addListener({ onRegister(dbg) { diff --git a/devtools/server/tracer/tests/xpcshell/test_tracer.js b/devtools/server/tracer/tests/xpcshell/test_tracer.js index fe9a984aa8..0f38052ba5 100644 --- a/devtools/server/tracer/tests/xpcshell/test_tracer.js +++ b/devtools/server/tracer/tests/xpcshell/test_tracer.js @@ -238,3 +238,267 @@ add_task(async function testTracingFunctionReturnAndValues() { info("Stop tracing"); stopTracing(); }); + +add_task(async function testTracingStep() { + // Test the `traceStep` flag + const sandbox = Cu.Sandbox("https://example.com"); + const source = ` +function foo() { + bar(); /* line 3 */ + second(); /* line 4 */ +} +function bar() { + let res; /* line 7 */ + if (1 === 1) { /* line 8 */ + res = "string"; /* line 9 */ + } else { + res = "nope" + } + return res; /* line 13 */ +}; +function second() { + let x = 0; /* line 16 */ + for (let i = 0; i < 2; i++) { /* line 17 */ + x++; /* line 18 */ + } + return null; /* line 20 */ +}; +foo();`; + Cu.evalInSandbox(source, sandbox, null, "file.js", 1); + + // Pass an override method to catch all strings tentatively logged to stdout + const logs = []; + function loggingMethod(str) { + logs.push(str); + } + + info("Start tracing"); + startTracing({ + global: sandbox, + traceSteps: true, + loggingMethod, + }); + + info("Call some code"); + sandbox.foo(); + + Assert.equal(logs.length, 19); + Assert.equal(logs[0], "Start tracing JavaScript\n"); + Assert.stringContains(logs[1], "λ foo"); + Assert.stringContains(logs[1], "file.js:3:3"); + + // Each "step" only prints the location and nothing more + Assert.stringContains(logs[2], "file.js:3:3"); + + Assert.stringContains(logs[3], "λ bar"); + Assert.stringContains(logs[3], "file.js:6:16"); + + Assert.stringContains(logs[4], "file.js:8:7"); + + Assert.stringContains(logs[5], "file.js:9:5"); + + Assert.stringContains(logs[6], "file.js:13:3"); + + Assert.stringContains(logs[7], "file.js:4:3"); + + Assert.stringContains(logs[8], "λ second"); + Assert.stringContains(logs[8], "file.js:15:19"); + + Assert.stringContains(logs[9], "file.js:16:11"); + + // For loop + Assert.stringContains(logs[10], "file.js:17:16"); + + Assert.stringContains(logs[11], "file.js:17:19"); + + Assert.stringContains(logs[12], "file.js:18:5"); + + Assert.stringContains(logs[13], "file.js:17:26"); + + Assert.stringContains(logs[14], "file.js:17:19"); + + Assert.stringContains(logs[15], "file.js:18:5"); + + Assert.stringContains(logs[16], "file.js:17:26"); + + Assert.stringContains(logs[17], "file.js:17:19"); + // End of for loop + + Assert.stringContains(logs[18], "file.js:20:3"); + + info("Stop tracing"); + stopTracing(); +}); + +add_task(async function testTracingPauseOnStep() { + // Test the `pauseOnStep` flag + const sandbox = Cu.Sandbox("https://example.com"); + sandbox.dump = dump; + const source = `var counter = 0; function incrementCounter() { let x = 0; dump("++\\n"); counter++; };`; + Cu.evalInSandbox(source, sandbox); + + // Pass an override method to catch all strings tentatively logged to stdout + const logs = []; + let loggingMethodResolve; + function loggingMethod(str) { + logs.push(str); + if (loggingMethodResolve) { + loggingMethodResolve(); + } + } + + info("Start tracing without pause"); + startTracing({ + global: sandbox, + loggingMethod, + }); + + info("Call some code"); + sandbox.incrementCounter(); + + Assert.equal(logs.length, 2); + Assert.equal(logs[0], "Start tracing JavaScript\n"); + Assert.stringContains(logs[1], "λ incrementCounter"); + + info( + "When pauseOnStep isn't used, the traced code runs synchronously to completion" + ); + Assert.equal(sandbox.counter, 1); + + info("Stop tracing"); + stopTracing(); + + logs.length = 0; + sandbox.counter = 0; + + info("Start tracing with 0ms pause"); + startTracing({ + global: sandbox, + pauseOnStep: 0, + loggingMethod, + }); + + let onTraces = Promise.withResolvers(); + let onResumed = Promise.withResolvers(); + // This is used when receiving new traces in `loggingMethod()` + loggingMethodResolve = onTraces.resolve; + + info( + "Run the to-be-traced code in a distinct event loop as it would be paused synchronously and would prevent further test script execution" + ); + Services.tm.dispatchToMainThread(() => { + sandbox.incrementCounter(); + onResumed.resolve(); + }); + + info("Wait for tracer to call the listener"); + await onTraces.promise; + + Assert.equal(logs.length, 2); + Assert.equal(logs[0], "Start tracing JavaScript\n"); + Assert.stringContains(logs[1], "λ incrementCounter"); + + info( + "When pauseInStep is used, the tracer listener is called, but the traced function is paused and doesn't run synchronously to completion" + ); + Assert.equal( + sandbox.counter, + 0, + "The increment method was called but its execution flow was blocked and couldn't increment" + ); + + info("Wait for traced code to be resumed"); + await onResumed.promise; + info( + "If we release the event loop, we can see the traced function completion" + ); + Assert.equal(sandbox.counter, 1); + + info("Stop tracing"); + stopTracing(); + + logs.length = 0; + sandbox.counter = 0; + + info("Start tracing with 250ms pause"); + startTracing({ + global: sandbox, + pauseOnStep: 250, + loggingMethod, + }); + + onTraces = Promise.withResolvers(); + onResumed = Promise.withResolvers(); + // This is used when receiving new traces in `loggingMethod()` + loggingMethodResolve = onTraces.resolve; + + info( + "Run the to-be-traced code in a distinct event loop as it would be paused synchronously and would prevent further test script execution" + ); + const startTimestamp = Cu.now(); + Services.tm.dispatchToMainThread(() => { + sandbox.incrementCounter(); + onResumed.resolve(); + }); + + info("Wait for tracer to call the listener"); + await onTraces.promise; + + Assert.equal(logs.length, 2); + Assert.equal(logs[0], "Start tracing JavaScript\n"); + Assert.stringContains(logs[1], "λ incrementCounter"); + + info( + "When pauseInStep is used, the tracer lsitener is called, but the traced function is paused and doesn't run synchronously to completion" + ); + Assert.equal(sandbox.counter, 0); + + info("Wait for traced code to be resumed"); + await onResumed.promise; + info( + "If we release the event loop, we can see the traced function completion" + ); + Assert.equal(sandbox.counter, 1); + info("The thread should have paused at least the pauseOnStep's duration"); + Assert.greater(Cu.now() - startTimestamp, 250); + + info("Stop tracing"); + stopTracing(); +}); + +add_task(async function testTracingFilterSourceUrl() { + // Test the `filterFrameSourceUrl` flag + const sandbox = Cu.Sandbox("https://example.com"); + + // Use a unique global (sandbox), but with two distinct scripts (first.js and second.js) + const source1 = `function foo() { bar(); }`; + Cu.evalInSandbox(source1, sandbox, null, "first.js", 1); + + // Only code running in that second source should be traced. + const source2 = `function bar() { }`; + Cu.evalInSandbox(source2, sandbox, null, "second.js", 1); + + // Pass an override method to catch all strings tentatively logged to stdout + const logs = []; + function loggingMethod(str) { + logs.push(str); + } + + info("Start tracing"); + startTracing({ + global: sandbox, + filterFrameSourceUrl: "second", + loggingMethod, + }); + + info("Call some code"); + sandbox.foo(); + + Assert.equal(logs.length, 2); + Assert.equal(logs[0], "Start tracing JavaScript\n"); + Assert.stringContains(logs[1], "λ bar"); + Assert.stringContains(logs[1], "second.js:1:18"); + + info("Stop tracing"); + stopTracing(); +}); diff --git a/devtools/server/tracer/tracer.jsm b/devtools/server/tracer/tracer.jsm index 82c746bb57..955b25fe3a 100644 --- a/devtools/server/tracer/tracer.jsm +++ b/devtools/server/tracer/tracer.jsm @@ -25,6 +25,7 @@ const EXPORTED_SYMBOLS = [ "addTracingListener", "removeTracingListener", "NEXT_INTERACTION_MESSAGE", + "DOM_MUTATIONS", ]; const NEXT_INTERACTION_MESSAGE = @@ -43,8 +44,23 @@ const FRAME_EXIT_REASONS = { THROW: "throw", }; +const DOM_MUTATIONS = { + // Track all DOM Node being added + ADD: "add", + // Track all attributes being modified + ATTRIBUTES: "attributes", + // Track all DOM Node being removed + REMOVE: "remove", +}; + const listeners = new Set(); +// Detecting worker is different if this file is loaded via Common JS loader (isWorker global) +// or as a JSM (constructor name) +const isWorker = + globalThis.isWorker || + globalThis.constructor.name == "WorkerDebuggerGlobalScope"; + // This module can be loaded from the worker thread, where we can't use ChromeUtils. // So implement custom lazy getters (without XPCOMUtils ESM) from here. // Worker codepath in DevTools will pass a custom Debugger instance. @@ -60,7 +76,7 @@ const customLazy = { // (ex: from tracer actor module), // this module no longer has WorkerDebuggerGlobalScope as global, // but has to use require() to pull Debugger. - if (typeof isWorker == "boolean") { + if (isWorker) { return require("Debugger"); } const { addDebuggerToGlobal } = ChromeUtils.importESModule( @@ -113,25 +129,44 @@ const customLazy = { * @param {Boolean} options.traceDOMEvents * Optional setting to enable tracing all the DOM events being going through * dom/events/EventListenerManager.cpp's `EventListenerManager`. + * @param {Array<string>} options.traceDOMMutations + * Optional setting to enable tracing all the DOM mutations. + * This array may contains three strings: + * - "add": trace all new DOM Node being added, + * - "attributes": trace all DOM attribute modifications, + * - "delete": trace all DOM Node being removed. * @param {Boolean} options.traceValues * Optional setting to enable tracing all function call values as well, * as returned values (when we do log returned frames). * @param {Boolean} options.traceOnNextInteraction * Optional setting to enable when the tracing should only start when the * use starts interacting with the page. i.e. on next keydown or mousedown. + * @param {Boolean} options.traceSteps + * Optional setting to enable tracing each frame within a function execution. + * (i.e. not only function call and function returns [when traceFunctionReturn is true]) * @param {Boolean} options.traceFunctionReturn * Optional setting to enable when the tracing should notify about frame exit. * i.e. when a function call returns or throws. + * @param {String} options.filterFrameSourceUrl + * Optional setting to restrict all traces to only a given source URL. + * This is a loose check, so any source whose URL includes the passed string will be traced. * @param {Number} options.maxDepth * Optional setting to ignore frames when depth is greater than the passed number. * @param {Number} options.maxRecords * Optional setting to stop the tracer after having recorded at least * the passed number of top level frames. + * @param {Number} options.pauseOnStep + * Optional setting to delay each frame execution for a given amount of time in ms. */ class JavaScriptTracer { constructor(options) { this.onEnterFrame = this.onEnterFrame.bind(this); + // DevTools CommonJS Workers modules don't have access to AbortController + if (!isWorker) { + this.abortController = new AbortController(); + } + // By default, we would trace only JavaScript related to caller's global. // As there is no way to compute the caller's global default to the global of the // mandatory options argument. @@ -152,56 +187,48 @@ class JavaScriptTracer { if (!this.loggingMethod) { // On workers, `dump` can't be called with JavaScript on another object, // so bind it. - // Detecting worker is different if this file is loaded via Common JS loader (isWorker) - // or as a JSM (constructor name) - this.loggingMethod = - typeof isWorker == "boolean" || - globalThis.constructor.name == "WorkerDebuggerGlobalScope" - ? dump.bind(null) - : dump; + this.loggingMethod = isWorker ? dump.bind(null) : dump; } this.traceDOMEvents = !!options.traceDOMEvents; + + if (options.traceDOMMutations) { + if (!Array.isArray(options.traceDOMMutations)) { + throw new Error("'traceDOMMutations' attribute should be an array"); + } + const acceptedValues = Object.values(DOM_MUTATIONS); + if (!options.traceDOMMutations.every(e => acceptedValues.includes(e))) { + throw new Error( + `'traceDOMMutations' only accept array of strings whose values can be: ${acceptedValues}` + ); + } + this.traceDOMMutations = options.traceDOMMutations; + } + this.traceSteps = !!options.traceSteps; this.traceValues = !!options.traceValues; this.traceFunctionReturn = !!options.traceFunctionReturn; this.maxDepth = options.maxDepth; this.maxRecords = options.maxRecords; this.records = 0; + if ("pauseOnStep" in options) { + if (typeof options.pauseOnStep != "number") { + throw new Error("'pauseOnStep' attribute should be a number"); + } + this.pauseOnStep = options.pauseOnStep; + } + if ("filterFrameSourceUrl" in options) { + if (typeof options.filterFrameSourceUrl != "string") { + throw new Error("'filterFrameSourceUrl' attribute should be a string"); + } + this.filterFrameSourceUrl = options.filterFrameSourceUrl; + } // An increment used to identify function calls and their returned/exit frames this.frameId = 0; // This feature isn't supported on Workers as they aren't involving user events - if (options.traceOnNextInteraction && typeof isWorker !== "boolean") { - this.abortController = new AbortController(); - const listener = () => { - this.abortController.abort(); - // Avoid tracing if the users asked to stop tracing. - if (this.dbg) { - this.#startTracing(); - } - }; - const eventOptions = { - signal: this.abortController.signal, - capture: true, - }; - // Register the event listener on the Chrome Event Handler in order to receive the event first. - // When used for the parent process target, `tracedGlobal` is browser.xhtml's window, which doesn't have a chromeEventHandler. - const eventHandler = - this.tracedGlobal.docShell.chromeEventHandler || this.tracedGlobal; - eventHandler.addEventListener("mousedown", listener, eventOptions); - eventHandler.addEventListener("keydown", listener, eventOptions); - - // Significate to the user that the tracer is registered, but not tracing just yet. - let shouldLogToStdout = listeners.size == 0; - for (const l of listeners) { - if (typeof l.onTracingPending == "function") { - shouldLogToStdout |= l.onTracingPending(); - } - } - if (shouldLogToStdout) { - this.loggingMethod(this.prefix + NEXT_INTERACTION_MESSAGE + "\n"); - } + if (options.traceOnNextInteraction && !isWorker) { + this.#waitForNextInteraction(); } else { this.#startTracing(); } @@ -212,6 +239,44 @@ class JavaScriptTracer { isTracing = false; /** + * In case `traceOnNextInteraction` option is used, delay the actual start of tracing until a first user interaction. + */ + #waitForNextInteraction() { + // Use a dedicated Abort Controller as we are going to stop it as soon as we get the first user interaction, + // whereas other listeners would typically wait for tracer stop. + this.nextInteractionAbortController = new AbortController(); + + const listener = () => { + this.nextInteractionAbortController.abort(); + // Avoid tracing if the users asked to stop tracing while we were waiting for the user interaction. + if (this.dbg) { + this.#startTracing(); + } + }; + const eventOptions = { + signal: this.nextInteractionAbortController.signal, + capture: true, + }; + // Register the event listener on the Chrome Event Handler in order to receive the event first. + // When used for the parent process target, `tracedGlobal` is browser.xhtml's window, which doesn't have a chromeEventHandler. + const eventHandler = + this.tracedGlobal.docShell.chromeEventHandler || this.tracedGlobal; + eventHandler.addEventListener("mousedown", listener, eventOptions); + eventHandler.addEventListener("keydown", listener, eventOptions); + + // Significate to the user that the tracer is registered, but not tracing just yet. + let shouldLogToStdout = listeners.size == 0; + for (const l of listeners) { + if (typeof l.onTracingPending == "function") { + shouldLogToStdout |= l.onTracingPending(); + } + } + if (shouldLogToStdout) { + this.loggingMethod(this.prefix + NEXT_INTERACTION_MESSAGE + "\n"); + } + } + + /** * Actually really start watching for executions. * * This may be delayed when traceOnNextInteraction options is used. @@ -225,6 +290,10 @@ class JavaScriptTracer { if (this.traceDOMEvents) { this.startTracingDOMEvents(); } + // This feature isn't supported on Workers as they aren't interacting with the DOM Tree + if (this.traceDOMMutations?.length > 0 && !isWorker) { + this.startTracingDOMMutations(); + } // In any case, we consider the tracing as started this.notifyToggle(true); @@ -248,6 +317,97 @@ class JavaScriptTracer { this.currentDOMEvent = null; } + startTracingDOMMutations() { + this.tracedGlobal.document.devToolsWatchingDOMMutations = true; + + const eventOptions = { + signal: this.abortController.signal, + capture: true, + }; + if (this.traceDOMMutations.includes(DOM_MUTATIONS.ADD)) { + this.tracedGlobal.docShell.chromeEventHandler.addEventListener( + "devtoolschildinserted", + this.#onDOMMutation, + eventOptions + ); + } + if (this.traceDOMMutations.includes(DOM_MUTATIONS.ATTRIBUTES)) { + this.tracedGlobal.docShell.chromeEventHandler.addEventListener( + "devtoolsattrmodified", + this.#onDOMMutation, + eventOptions + ); + } + if (this.traceDOMMutations.includes(DOM_MUTATIONS.REMOVE)) { + this.tracedGlobal.docShell.chromeEventHandler.addEventListener( + "devtoolschildremoved", + this.#onDOMMutation, + eventOptions + ); + } + } + + stopTracingDOMMutations() { + this.tracedGlobal.document.devToolsWatchingDOMMutations = false; + // Note that the event listeners are all going to be unregistered via the AbortController. + } + + /** + * Called for any DOM Mutation done in the traced document. + * + * @param {DOM Event} event + */ + #onDOMMutation = event => { + // Ignore elements inserted by DevTools, like the inspector's highlighters + if (event.target.isNativeAnonymous) { + return; + } + + let type = ""; + switch (event.type) { + case "devtoolschildinserted": + type = DOM_MUTATIONS.ADD; + break; + case "devtoolsattrmodified": + type = DOM_MUTATIONS.ATTRIBUTES; + break; + case "devtoolschildremoved": + type = DOM_MUTATIONS.REMOVE; + break; + default: + throw new Error("Unexpected DOM Mutation event type: " + event.type); + } + + let shouldLogToStdout = true; + if (listeners.size > 0) { + shouldLogToStdout = false; + for (const listener of listeners) { + // If any listener return true, also log to stdout + if (typeof listener.onTracingDOMMutation == "function") { + shouldLogToStdout |= listener.onTracingDOMMutation({ + depth: this.depth, + prefix: this.prefix, + + type, + element: event.target, + caller: Components.stack.caller, + }); + } + } + } + + if (shouldLogToStdout) { + const padding = "—".repeat(this.depth + 1); + this.loggingMethod( + this.prefix + + padding + + `[DOM Mutation | ${type}] ` + + objectToString(event.target) + + "\n" + ); + } + }; + /** * Called by DebuggerNotificationObserver interface when a DOM event start being notified * and after it has been notified. @@ -277,7 +437,7 @@ class JavaScriptTracer { .makeDebuggeeValue(notification.event) .getProperty("type").return; } - this.currentDOMEvent = `DOM(${type})`; + this.currentDOMEvent = `DOM | ${type}`; } else { this.currentDOMEvent = notification.type; } @@ -306,14 +466,22 @@ class JavaScriptTracer { this.depth = 0; // Cancel the traceOnNextInteraction event listeners. - if (this.abortController) { - this.abortController.abort(); - this.abortController = null; + if (this.nextInteractionAbortController) { + this.nextInteractionAbortController.abort(); + this.nextInteractionAbortController = null; } if (this.traceDOMEvents) { this.stopTracingDOMEvents(); } + if (this.traceDOMMutations?.length > 0 && !isWorker) { + this.stopTracingDOMMutations(); + } + + // Unregister all event listeners + if (this.abortController) { + this.abortController.abort(); + } this.tracedGlobal = null; this.isTracing = false; @@ -406,10 +574,21 @@ class JavaScriptTracer { return; } try { + // If an optional filter is passed, ignore frames which aren't matching the filter string + if ( + this.filterFrameSourceUrl && + !frame.script.source.url?.includes(this.filterFrameSourceUrl) + ) { + return; + } + // Because of async frame which are popped and entered again on completion of the awaited async task, // we have to compute the depth from the frame. (and can't use a simple increment on enter/decrement on pop). const depth = getFrameDepth(frame); + // Save the current depth for the DOM Mutation handler + this.depth = depth; + // Ignore the frame if we reached the depth limit (if one is provided) if (this.maxDepth && depth >= this.maxDepth) { return; @@ -467,6 +646,39 @@ class JavaScriptTracer { this.logFrameEnteredToStdout(frame, depth); } + if (this.traceSteps) { + frame.onStep = () => { + // Spidermonkey steps on many intermediate positions which don't make sense to the user. + // `isStepStart` is close to each statement start, which is meaningful to the user. + const { isStepStart } = frame.script.getOffsetMetadata(frame.offset); + if (!isStepStart) { + return; + } + + shouldLogToStdout = true; + if (listeners.size > 0) { + shouldLogToStdout = false; + for (const listener of listeners) { + // If any listener return true, also log to stdout + if (typeof listener.onTracingFrameStep == "function") { + shouldLogToStdout |= listener.onTracingFrameStep({ + frame, + depth, + prefix: this.prefix, + }); + } + } + } + if (shouldLogToStdout) { + this.logFrameStepToStdout(frame, depth); + } + // Optionaly pause the frame execution by letting the other event loop to run in between. + if (typeof this.pauseOnStep == "number") { + syncPause(this.pauseOnStep); + } + }; + } + frame.onPop = completion => { // Special case async frames. We are exiting the current frame because of waiting for an async task. // (this is typically a `await foo()` from an async function) @@ -520,6 +732,11 @@ class JavaScriptTracer { this.logFrameExitedToStdout(frame, depth, why, rv); } }; + + // Optionaly pause the frame execution by letting the other event loop to run in between. + if (typeof this.pauseOnStep == "number") { + syncPause(this.pauseOnStep); + } } catch (e) { console.error("Exception while tracing javascript", e); } @@ -576,6 +793,20 @@ class JavaScriptTracer { } /** + * Display to stdout one given frame execution, which represents a step within a function execution. + * + * @param {Debugger.Frame} frame + * @param {Number} depth + */ + logFrameStepToStdout(frame, depth) { + const padding = "—".repeat(depth + 1); + + const message = `${padding}— ${getTerminalHyperLink(frame)}`; + + this.loggingMethod(this.prefix + message + "\n"); + } + + /** * Display to stdout the exit of a given frame execution, which represents a function return. * * @param {Debugger.Frame} frame @@ -787,6 +1018,27 @@ function getTerminalHyperLink(frame) { return `\x1B]8;;${href}\x1B\\${href}\x1B]8;;\x1B\\`; } +/** + * Helper function to synchronously pause the current frame execution + * for a given duration in ms. + * + * @param {Number} duration + */ +function syncPause(duration) { + let freeze = true; + const timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + timer.initWithCallback( + () => { + freeze = false; + }, + duration, + Ci.nsITimer.TYPE_ONE_SHOT + ); + Services.tm.spinEventLoopUntil("debugger-slow-motion", function () { + return !freeze; + }); +} + // This JSM may be execute as CommonJS when loaded in the worker thread if (typeof module == "object") { module.exports = { |