diff options
Diffstat (limited to 'devtools/shared/commands/resource/legacy-listeners')
9 files changed, 461 insertions, 0 deletions
diff --git a/devtools/shared/commands/resource/legacy-listeners/console-messages.js b/devtools/shared/commands/resource/legacy-listeners/console-messages.js new file mode 100644 index 0000000000..ae3f81b4df --- /dev/null +++ b/devtools/shared/commands/resource/legacy-listeners/console-messages.js @@ -0,0 +1,59 @@ +/* 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 ResourceCommand = require("resource://devtools/shared/commands/resource/resource-command.js"); + +module.exports = async function ({ targetCommand, targetFront, onAvailable }) { + // Allow the top level target unconditionnally. + // Also allow frame, but only in content toolbox, i.e. still ignore them in + // the context of the browser toolbox as we inspect messages via the process + // targets + const listenForFrames = targetCommand.descriptorFront.isTabDescriptor; + + // Allow workers when messages aren't dispatched to the main thread. + const listenForWorkers = + !targetCommand.rootFront.traits + .workerConsoleApiMessagesDispatchedToMainThread; + + const acceptTarget = + targetFront.isTopLevel || + targetFront.targetType === targetCommand.TYPES.PROCESS || + (targetFront.targetType === targetCommand.TYPES.FRAME && listenForFrames) || + (targetFront.targetType === targetCommand.TYPES.WORKER && listenForWorkers); + + if (!acceptTarget) { + return; + } + + const webConsoleFront = await targetFront.getFront("console"); + if (webConsoleFront.isDestroyed()) { + return; + } + + // Request notifying about new messages + await webConsoleFront.startListeners(["ConsoleAPI"]); + + // Fetch already existing messages + // /!\ The actor implementation requires to call startListeners(ConsoleAPI) first /!\ + const { messages } = await webConsoleFront.getCachedMessages(["ConsoleAPI"]); + + for (const message of messages) { + message.resourceType = ResourceCommand.TYPES.CONSOLE_MESSAGE; + } + onAvailable(messages); + + // Forward new message events + webConsoleFront.on("consoleAPICall", message => { + // Ignore console messages that are cloned from the content process + // (they aren't relevant to toolboxes still using legacy listeners) + if (message.clonedFromContentProcess) { + return; + } + + message.resourceType = ResourceCommand.TYPES.CONSOLE_MESSAGE; + onAvailable([message]); + }); +}; diff --git a/devtools/shared/commands/resource/legacy-listeners/css-changes.js b/devtools/shared/commands/resource/legacy-listeners/css-changes.js new file mode 100644 index 0000000000..e9f3e17075 --- /dev/null +++ b/devtools/shared/commands/resource/legacy-listeners/css-changes.js @@ -0,0 +1,28 @@ +/* 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 ResourceCommand = require("resource://devtools/shared/commands/resource/resource-command.js"); + +module.exports = async function ({ targetFront, onAvailable }) { + if (!targetFront.hasActor("changes")) { + return; + } + + const changesFront = await targetFront.getFront("changes"); + + // Get all changes collected up to this point by the ChangesActor on the server, + // then fire each change as "add-change". + const changes = await changesFront.allChanges(); + await onAvailable(changes.map(change => toResource(change))); + + changesFront.on("add-change", change => onAvailable([toResource(change)])); +}; + +function toResource(change) { + return Object.assign(change, { + resourceType: ResourceCommand.TYPES.CSS_CHANGE, + }); +} diff --git a/devtools/shared/commands/resource/legacy-listeners/error-messages.js b/devtools/shared/commands/resource/legacy-listeners/error-messages.js new file mode 100644 index 0000000000..5ba898c917 --- /dev/null +++ b/devtools/shared/commands/resource/legacy-listeners/error-messages.js @@ -0,0 +1,62 @@ +/* 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 ResourceCommand = require("resource://devtools/shared/commands/resource/resource-command.js"); +const { MESSAGE_CATEGORY } = require("resource://devtools/shared/constants.js"); + +module.exports = async function ({ targetCommand, targetFront, onAvailable }) { + // Allow the top level target unconditionnally. + // Also allow frame, but only in content toolbox, i.e. still ignore them in + // the context of the browser toolbox as we inspect messages via the process + // targets + // Also ignore workers as they are not supported yet. (see bug 1592584) + const listenForFrames = targetCommand.descriptorFront.isTabDescriptor; + const isAllowed = + targetFront.isTopLevel || + targetFront.targetType === targetCommand.TYPES.PROCESS || + (targetFront.targetType === targetCommand.TYPES.FRAME && listenForFrames); + + if (!isAllowed) { + return; + } + + const webConsoleFront = await targetFront.getFront("console"); + if (webConsoleFront.isDestroyed()) { + return; + } + + // Request notifying about new messages. Here the "PageError" type start listening for + // both actual PageErrors (emitted as "pageError" events) as well as LogMessages ( + // emitted as "logMessage" events). This function only set up the listener on the + // webConsoleFront for "pageError". + await webConsoleFront.startListeners(["PageError"]); + + // Fetch already existing messages + // /!\ The actor implementation requires to call startListeners("PageError") first /!\ + let { messages } = await webConsoleFront.getCachedMessages(["PageError"]); + + // On server < v79, we're also getting CSS Messages that we need to filter out. + messages = messages.filter( + message => message.pageError.category !== MESSAGE_CATEGORY.CSS_PARSER + ); + + messages.forEach(message => { + message.resourceType = ResourceCommand.TYPES.ERROR_MESSAGE; + }); + // Cached messages don't have the same shape as live messages, + // so we need to transform them. + onAvailable(messages); + + webConsoleFront.on("pageError", message => { + // On server < v79, we're getting CSS Messages that we need to filter out. + if (message.pageError.category === MESSAGE_CATEGORY.CSS_PARSER) { + return; + } + + message.resourceType = ResourceCommand.TYPES.ERROR_MESSAGE; + onAvailable([message]); + }); +}; diff --git a/devtools/shared/commands/resource/legacy-listeners/moz.build b/devtools/shared/commands/resource/legacy-listeners/moz.build new file mode 100644 index 0000000000..6ffb469891 --- /dev/null +++ b/devtools/shared/commands/resource/legacy-listeners/moz.build @@ -0,0 +1,14 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +DevToolsModules( + "console-messages.js", + "css-changes.js", + "error-messages.js", + "platform-messages.js", + "reflow.js", + "root-node.js", + "source.js", + "thread-states.js", +) diff --git a/devtools/shared/commands/resource/legacy-listeners/platform-messages.js b/devtools/shared/commands/resource/legacy-listeners/platform-messages.js new file mode 100644 index 0000000000..729696275e --- /dev/null +++ b/devtools/shared/commands/resource/legacy-listeners/platform-messages.js @@ -0,0 +1,44 @@ +/* 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 ResourceCommand = require("resource://devtools/shared/commands/resource/resource-command.js"); + +module.exports = async function ({ targetCommand, targetFront, onAvailable }) { + // Only allow the top level target and processes. + // Frames can be ignored as logMessage are never sent to them anyway. + // Also ignore workers as they are not supported yet. (see bug 1592584) + const isAllowed = + targetFront.isTopLevel || + targetFront.targetType === targetCommand.TYPES.PROCESS; + if (!isAllowed) { + return; + } + + const webConsoleFront = await targetFront.getFront("console"); + if (webConsoleFront.isDestroyed()) { + return; + } + + // Request notifying about new messages. Here the "PageError" type start listening for + // both actual PageErrors (emitted as "pageError" events) as well as LogMessages ( + // emitted as "logMessage" events). This function only set up the listener on the + // webConsoleFront for "logMessage". + await webConsoleFront.startListeners(["PageError"]); + + // Fetch already existing messages + // /!\ The actor implementation requires to call startListeners("PageError") first /!\ + const { messages } = await webConsoleFront.getCachedMessages(["LogMessage"]); + + for (const message of messages) { + message.resourceType = ResourceCommand.TYPES.PLATFORM_MESSAGE; + } + onAvailable(messages); + + webConsoleFront.on("logMessage", message => { + message.resourceType = ResourceCommand.TYPES.PLATFORM_MESSAGE; + onAvailable([message]); + }); +}; diff --git a/devtools/shared/commands/resource/legacy-listeners/reflow.js b/devtools/shared/commands/resource/legacy-listeners/reflow.js new file mode 100644 index 0000000000..63802f510d --- /dev/null +++ b/devtools/shared/commands/resource/legacy-listeners/reflow.js @@ -0,0 +1,24 @@ +/* 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 ResourceCommand = require("resource://devtools/shared/commands/resource/resource-command.js"); + +module.exports = async function ({ targetFront, onAvailable }) { + if (!targetFront.getTrait("isBrowsingContext")) { + // The reflows only work with BrowsingContext targets + return; + } + const reflowFront = await targetFront.getFront("reflow"); + reflowFront.on("reflows", reflows => + onAvailable([ + { + resourceType: ResourceCommand.TYPES.REFLOW, + reflows, + }, + ]) + ); + await reflowFront.start(); +}; diff --git a/devtools/shared/commands/resource/legacy-listeners/root-node.js b/devtools/shared/commands/resource/legacy-listeners/root-node.js new file mode 100644 index 0000000000..6fa2bcbf22 --- /dev/null +++ b/devtools/shared/commands/resource/legacy-listeners/root-node.js @@ -0,0 +1,61 @@ +/* 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 ResourceCommand = require("resource://devtools/shared/commands/resource/resource-command.js"); + +module.exports = async function ({ targetFront, onAvailable, onDestroyed }) { + // XXX: When watching root node for a non top-level target, this will also + // ensure the inspector & walker fronts for the target are initialized. + // This also implies that we call reparentRemoteFrame on the new walker, which + // will create the link between the parent frame NodeFront and the inner + // document NodeFront. + // + // This is not something that will work when the resource is moved to the + // server. When it becomes a server side resource, a RootNode would be emitted + // directly by the target actor. + // + // This probably means that the root node resource cannot remain a NodeFront. + // It should not be a front and the client should be responsible for + // retrieving the corresponding NodeFront. + // + // The other thing that we are missing with this patch is that we should only + // create inspector & walker fronts (and call reparentRemoteFrame) when we get + // a RootNode which is directly under an iframe node which is currently + // visible and tracked in the markup view. + // + // For instance, with the following markup: + // html + // body + // div + // iframe + // remote doc + // + // If the markup view only sees nodes down to `div`, then the client is not + // currently tracking the nodeFront for the `iframe`, and getting a new root + // node for the remote document should NOT force the iframe to be tracked on + // on the client. + // + // When we get a RootNode resource, we will need a way to check this before + // initializing & reparenting the walker. + // + if (!targetFront.getTrait("isBrowsingContext")) { + // The root-node resource is only available on browsing-context targets. + return; + } + + const inspectorFront = await targetFront.getFront("inspector"); + inspectorFront.walker.on("root-available", node => { + node.resourceType = ResourceCommand.TYPES.ROOT_NODE; + return onAvailable([node]); + }); + + inspectorFront.walker.on("root-destroyed", node => { + node.resourceType = ResourceCommand.TYPES.ROOT_NODE; + return onDestroyed([node]); + }); + + await inspectorFront.walker.watchRootNode(); +}; diff --git a/devtools/shared/commands/resource/legacy-listeners/source.js b/devtools/shared/commands/resource/legacy-listeners/source.js new file mode 100644 index 0000000000..45ee62f70f --- /dev/null +++ b/devtools/shared/commands/resource/legacy-listeners/source.js @@ -0,0 +1,88 @@ +/* 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 ResourceCommand = require("resource://devtools/shared/commands/resource/resource-command.js"); + +/** + * Emit SOURCE resources, which represents a Javascript source and has the following attributes set on "available": + * + * - introductionType {null|String}: A string indicating how this source code was introduced into the system. + * This will typically be set to "scriptElement", "eval", ... + * But this may have many other values: + * https://searchfox.org/mozilla-central/rev/ac142717cc067d875e83e4b1316f004f6e063a46/dom/script/ScriptLoader.cpp#2628-2639 + * https://searchfox.org/mozilla-central/search?q=symbol:_ZN2JS14CompileOptions19setIntroductionTypeEPKc&redirect=false + * https://searchfox.org/mozilla-central/rev/ac142717cc067d875e83e4b1316f004f6e063a46/devtools/server/actors/source.js#160-169 + * - sourceMapBaseURL {String}: Base URL where to look for a source map. + * This isn't the source map URL. + * - sourceMapURL {null|String}: URL of the source map, if there is one. + * - url {null|String}: URL of the source, if it relates to a particular URL. + * Evaled sources won't have any related URL. + * - isBlackBoxed {Boolean}: Specifying whether the source actor's 'black-boxed' flag is set. + * - extensionName {null|String}: If the source comes from an add-on, the add-on name. + */ +module.exports = async function ({ targetCommand, targetFront, onAvailable }) { + const isBrowserToolbox = + targetCommand.descriptorFront.isBrowserProcessDescriptor; + const isNonTopLevelFrameTarget = + !targetFront.isTopLevel && + targetFront.targetType === targetCommand.TYPES.FRAME; + + if (isBrowserToolbox && isNonTopLevelFrameTarget) { + // In the BrowserToolbox, non-top-level frame targets are already + // debugged via content-process targets. + return; + } + + const threadFront = await targetFront.getFront("thread"); + + // Use a list of all notified SourceFront as we don't have a newSource event for all sources + // but we sometime get sources notified both via newSource event *and* sources() method... + // We store actor ID instead of SourceFront as it appears that multiple SourceFront for the same + // actor are created... + const sourcesActorIDCache = new Set(); + + // Forward new sources (but also existing ones, see next comment) + threadFront.on("newSource", ({ source }) => { + if (sourcesActorIDCache.has(source.actor)) { + return; + } + sourcesActorIDCache.add(source.actor); + // source is a SourceActor's form, add the resourceType attribute on it + source.resourceType = ResourceCommand.TYPES.SOURCE; + onAvailable([source]); + }); + + // Forward already existing sources + // Note that calling `sources()` will end up emitting `newSource` event for all existing sources. + // But not in some cases, for example, when the thread is already paused. + // (And yes, it means that already existing sources can be transfered twice over the wire) + // + // Also, browser_ext_devtools_inspectedWindow_targetSwitch.js creates many top level targets, + // for which the SourceMapURLService will fetch sources. But these targets are destroyed while + // the test is running and when they are, we purge all pending requests, including this one. + // So ignore any error if this request failed on destruction. + let sources; + try { + sources = await threadFront.sources(); + } catch (e) { + if (threadFront.isDestroyed()) { + return; + } + throw e; + } + + // Note that `sources()` doesn't encapsulate SourceFront into a `source` attribute + // while `newSource` event does. + sources = sources.filter(source => { + return !sourcesActorIDCache.has(source.actor); + }); + for (const source of sources) { + sourcesActorIDCache.add(source.actor); + // source is a SourceActor's form, add the resourceType attribute on it + source.resourceType = ResourceCommand.TYPES.SOURCE; + } + onAvailable(sources); +}; diff --git a/devtools/shared/commands/resource/legacy-listeners/thread-states.js b/devtools/shared/commands/resource/legacy-listeners/thread-states.js new file mode 100644 index 0000000000..42c922072a --- /dev/null +++ b/devtools/shared/commands/resource/legacy-listeners/thread-states.js @@ -0,0 +1,81 @@ +/* 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 ResourceCommand = require("resource://devtools/shared/commands/resource/resource-command.js"); + +module.exports = async function ({ targetCommand, targetFront, onAvailable }) { + const isBrowserToolbox = + targetCommand.descriptorFront.isBrowserProcessDescriptor; + const isNonTopLevelFrameTarget = + !targetFront.isTopLevel && + targetFront.targetType === targetCommand.TYPES.FRAME; + + if (isBrowserToolbox && isNonTopLevelFrameTarget) { + // In the BrowserToolbox, non-top-level frame targets are already + // debugged via content-process targets. + return; + } + + // Wait for the thread actor to be attached, otherwise getFront(thread) will throw for worker targets + // This is because worker target are still kind of descriptors and are only resolved into real target + // after being attached. And the thread actor ID is only retrieved and available after being attached. + await targetFront.onThreadAttached; + + if (targetFront.isDestroyed()) { + return; + } + const threadFront = await targetFront.getFront("thread"); + + let isInterrupted = false; + const onPausedPacket = packet => { + // If paused by an explicit interrupt, which are generated by the + // slow script dialog and internal events such as setting + // breakpoints, ignore the event. + const { why } = packet; + if (why.type === "interrupted" && !why.onNext) { + isInterrupted = true; + return; + } + + // Ignore attached events because they are not useful to the user. + if (why.type == "alreadyPaused" || why.type == "attached") { + return; + } + + onAvailable([ + { + resourceType: ResourceCommand.TYPES.THREAD_STATE, + state: "paused", + why, + frame: packet.frame, + }, + ]); + }; + threadFront.on("paused", onPausedPacket); + + threadFront.on("resumed", packet => { + // NOTE: the client suppresses resumed events while interrupted + // to prevent unintentional behavior. + // see [client docs](devtools/client/debugger/src/client/README.md#interrupted) for more information. + if (isInterrupted) { + isInterrupted = false; + return; + } + + onAvailable([ + { + resourceType: ResourceCommand.TYPES.THREAD_STATE, + state: "resumed", + }, + ]); + }); + + // Notify about already paused thread + const pausedPacket = threadFront.getLastPausePacket(); + if (pausedPacket) { + onPausedPacket(pausedPacket); + } +}; |