From 086c044dc34dfc0f74fbe41f4ecb402b2cd34884 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 03:13:33 +0200 Subject: Merging upstream version 125.0.1. Signed-off-by: Daniel Baumann --- devtools/server/actors/accessibility/accessible.js | 8 +- devtools/server/actors/addon/addons.js | 10 +- .../actors/addon/webextension-inspected-window.js | 4 +- devtools/server/actors/animation-type-longhand.js | 1 + devtools/server/actors/animation.js | 2 +- devtools/server/actors/breakpoint.js | 2 +- devtools/server/actors/descriptors/tab.js | 13 +- devtools/server/actors/descriptors/webextension.js | 6 +- devtools/server/actors/descriptors/worker.js | 3 +- devtools/server/actors/device.js | 3 +- devtools/server/actors/heap-snapshot-file.js | 2 +- .../actors/highlighters/css/highlighters.css | 4 +- devtools/server/actors/highlighters/shapes.js | 19 +- .../server/actors/highlighters/tabbing-order.js | 8 +- devtools/server/actors/inspector/constants.js | 2 +- .../server/actors/inspector/event-collector.js | 2 +- devtools/server/actors/inspector/inspector.js | 7 +- devtools/server/actors/inspector/walker.js | 44 ++- devtools/server/actors/manifest.js | 10 +- .../actors/network-monitor/channel-event-sink.js | 5 +- .../actors/network-monitor/network-event-actor.js | 19 +- devtools/server/actors/object.js | 10 +- devtools/server/actors/object/previewers.js | 24 ++ devtools/server/actors/object/property-iterator.js | 32 ++ devtools/server/actors/objects-manager.js | 2 +- devtools/server/actors/page-style.js | 6 +- devtools/server/actors/preference.js | 2 +- devtools/server/actors/reflow.js | 4 +- .../extensions-backgroundscript-status.js | 2 +- .../actors/resources/network-events-content.js | 14 +- .../actors/resources/network-events-stacktraces.js | 19 +- devtools/server/actors/resources/network-events.js | 27 +- .../resources/parent-process-document-event.js | 2 +- .../server/actors/resources/server-sent-events.js | 2 +- devtools/server/actors/resources/sources.js | 14 +- .../actors/resources/storage/extension-storage.js | 10 +- .../server/actors/resources/storage/indexed-db.js | 12 +- devtools/server/actors/resources/thread-states.js | 18 +- .../resources/utils/content-process-storage.js | 12 +- .../utils/nsi-console-listener-watcher.js | 10 +- .../resources/utils/parent-process-storage.js | 3 +- devtools/server/actors/resources/websockets.js | 4 +- devtools/server/actors/root.js | 2 - devtools/server/actors/source.js | 6 +- devtools/server/actors/target-configuration.js | 3 +- devtools/server/actors/targets/content-process.js | 12 +- .../targets/session-data-processors/blackboxing.js | 2 +- .../targets/session-data-processors/breakpoints.js | 14 +- .../session-data-processors/event-breakpoints.js | 2 +- .../targets/session-data-processors/resources.js | 2 +- .../target-configuration.js | 9 +- .../thread-configuration.js | 21 +- .../session-data-processors/xhr-breakpoints.js | 2 +- .../actors/targets/target-actor-registry.sys.mjs | 2 +- devtools/server/actors/targets/window-global.js | 26 +- devtools/server/actors/targets/worker.js | 2 +- devtools/server/actors/thread-configuration.js | 26 +- devtools/server/actors/thread.js | 111 +++--- devtools/server/actors/tracer.js | 154 +++++++-- devtools/server/actors/utils/custom-formatters.js | 4 +- .../actors/utils/inactive-property-helper.js | 39 ++- devtools/server/actors/utils/logEvent.js | 4 +- devtools/server/actors/utils/sources-manager.js | 13 +- .../server/actors/utils/stylesheets-manager.js | 17 +- devtools/server/actors/watcher.js | 21 +- .../server/actors/watcher/WatcherRegistry.sys.mjs | 84 ++++- .../content-process-jsprocessactor-startup.js | 26 ++ .../actors/watcher/target-helpers/frame-helper.js | 11 +- .../server/actors/watcher/target-helpers/moz.build | 1 + .../watcher/target-helpers/process-helper.js | 372 +++------------------ devtools/server/actors/webbrowser.js | 14 +- devtools/server/actors/webconsole.js | 2 +- .../webconsole/commands/experimental-commands.ftl | 22 +- .../server/actors/webconsole/commands/manager.js | 35 +- .../actors/webconsole/eager-ecma-allowlist.js | 23 +- .../server/actors/webconsole/eval-with-debugger.js | 20 +- .../webconsole/listeners/console-file-activity.js | 2 +- .../actors/webconsole/listeners/document-events.js | 10 +- .../worker/service-worker-registration-list.js | 7 +- .../actors/worker/service-worker-registration.js | 5 +- .../actors/worker/worker-descriptor-actor-list.js | 3 +- 81 files changed, 838 insertions(+), 696 deletions(-) create mode 100644 devtools/server/actors/watcher/target-helpers/content-process-jsprocessactor-startup.js (limited to 'devtools/server/actors') 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 = []; @@ -1069,6 +1071,32 @@ class WalkerActor extends Actor { return new NodeListActor(this, nodeList); } + /** + * 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. 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; @@ -265,12 +274,112 @@ class TracerActor extends Actor { return false; } + /** + * 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 + */ 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 */ -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, -- cgit v1.2.3