服务器端概述¶
连接到后端¶
DevTools 后端公开了一个 RDP 服务器,客户端可以查询该服务器。请参阅客户端 API <client-api.md>
选择特定的调试上下文¶
客户端通常会查询根 Actor 以获取一个特定的描述符 Actor,该 Actor 将指定要调试的浏览器部分。请参阅 <actor-hierarchy.md>。
典型的场景是客户端查询 TabDescriptorActor 以调试特定的标签页。
Watcher Actor¶
然后,一旦您设置了特定的调试上下文,您将检索一个 WatcherActor 实例。此 Actor 是 DevTools 后端的支柱,因为它将协调对所有内容的观察。
可调试上下文 / DevTools 目标¶
首先,您将使用 WatcherActor.watchTarget(String targetType)
方法来定义您感兴趣的子调试上下文。此方法只有在收到所有现有可调试上下文的通知后才会解析。然后,对于稍后创建的每个新的可调试上下文,都会在 WatcherActor 上发出 target-available-form
RDP 事件。以及当任何可调试上下文被销毁时,发出 target-destroyed-form
。
调试上下文在 DevTools 行话中称为目标,可以是
“frame”
浏览器中任何位置运行的任何文档实例。每个非常具体的文档实例都将通过专用的 WindowGlobalTargetActor 实例通知客户端。这意味着,如果您重新加载页面,您将拥有与重新加载次数一样多的目标 Actor。此外,如果存在 <iframe>,您将为每个 iframe 获取一个 frame 目标。
“worker”、 “service_worker”、 “shared_worker”
浏览器中任何位置运行的任何工作线程实例。每个工作线程实例都将通过专用的 WorkerTargetActor 实例通知客户端。
“process”
浏览器中运行的任何进程。这仅用于调试 Firefox,而不是调试网页时。每个进程都将通过专用的 ProcessTargetActor 通知客户端。您将收到与正在运行的内容进程一样多的进程目标的通知。
目标类型字符串在 devtools/server/actors/targets/index.js 中定义。
目标 Actor 公开了许多其他重要的 Actor,例如 Inspector、WebConsole、线程 Actor。请参阅 <actor-hierarchy.md>。
资源¶
一旦您开始监视某些目标类型,您可以使用 WatcherActor.watchResources(Array<String> resourceTypes)
方法来获取每个活动目标/可调试上下文的资源通知。此方法只有在收到所有现有资源的通知后才会解析。资源不会由 watchResources 方法返回。相反,它们通过在 Watcher Actor 或许多目标 Actor 之一上发出的 resources-available-array
RDP 事件通知客户端。某些资源也可能支持
resources-updated-array
RDP 事件,当任何资源更新(如样式表或网络事件)时,resources-destroyed-array
RDP 事件,当任何资源被销毁(如样式表)时。
资源可以是
“console-message”
任何
console.log()
、console.error()
、... 方法调用,在任何可调试上下文中,都将通过此类“console-message”资源通知客户端。“source”
任何可调试上下文中的任何 JavaScript 或 Wasm 源代码都将通过此类“source”资源通知客户端。
“stylesheet”
任何由“frame”可调试上下文定义的样式表都将通过此类“stylesheet”资源通知客户端。
还有很多其他东西。
您可以在 devtools/server/resources/ 文件夹中找到所有资源的列表。
资源类型字符串在 devtools/server/actors/resources/index.js 中定义。
资源将是一个 JSON 对象,其中包含特定于每种资源类型的属性。每个资源在 devtools/server/actors/resources/ 文件夹中都有一个专用的 ResourceWatcher
类。
根据资源类型,它们可能在以下位置被观察到:
父进程(无论目标在哪里运行)
目标进程的主线程
工作线程(如果我们有工作线程目标)
此行为也在 devtools/server/actors/resources/index.js 中通过以下方式定义:
ParentProcessResources
ResourceWatcher 将在父进程中实例化。
FrameTargetResources
ResourceWatcher 将为“frame”目标实例化,在 frame 运行的主线程中。
ProcessTargetResources
ResourceWatcher 将为“process”目标实例化,在其主线程中。
WorkerTargetResources
ResourceWatcher 将为“worker”目标实例化,在工作线程中。
然后每个资源都应该实现以下接口
class MyResourceWatcher {
/**
* Start watching for my resource for a given context.
*
* This class will be instantiated only once when registered in `ParentProcessResources` and running in the parent process.
* Also, the first argument `watcherOrTargetActor` will be a reference to the WatcherActor instance.
* In all the other cases, this class will be instantiated once per active Target instance.
* In any other case, it will be a reference to a TargetActor.
*
* Then, the onAvailable, onUpdated and onDestroyed should be called according to their names
* for each resource.
*
* /!\ This method should only resolve **after** having notified via onAvailable about all the existing resource instances.
*/
async watch(watcherOrTargetActor, { onAvailable, onUpdated, onDestroyed }) {
// Each method except a list of updates
// onAvailable expects a list of JSON object being Resources Object being passed as-is to the client
// There must be a `resourceType` attribute.
const {
TYPES: { MY_RESOURCE },
} = require("devtools/server/actors/resources/index");
onAvailable([
{
resourceType: MY_RESOURCE,
resourceId: 123, // Mandatory when using onUpdated and/or onDestroyed, otherwise optional
myResourceSpecificData: 42,
some: { nested : { object : "foo" } },
},
]);
// onUpdated expects a list of updates against many resource objects previously notified via onAvailable
onUpdated([
{
resourceType: MY_RESOURCE,
resourceId, // Same id passed to onAvailable
// This field allows to update top attributes of your resource object
resourceUpdates: {
myResourceSpecificData: 43,
},
// This advanced field allows to update any nested object attribute
nestedResourceUpdates: [
{
path: ["some", "nested", "object"],
value: "bar",
},
],
},
]);
// onDestroyed expects a list of resource to be notified as destroyed to the client
onDestroyed([
{
resourceType: MY_RESOURCE,
resourceId, // Same id passed to onAvailable
}
]);
}
/**
* Stop observing these resources. Unregister any listener to prevent any leak.
*/
destroy() {
}
Watcher Actor 如何处理进程/线程并实例化目标 Actor?¶
Watcher Actor 在父进程中运行,需要访问所有内容进程和线程才能与所有可调试上下文交互。为了访问所有内容进程,Watcher Actor 使用 JS Process Actor API。请参阅 <dom/docs/ipc/jsactors.rst>。它注册了一个名为“DevToolsProcess”的 JS Process Actor。对于每个 watchTargets
和 watchResources
方法调用,都会通过 JS Process Actor 向所有进程发送一个新的查询。JS Process Actor API 由两个不同的模块组成
一个在父进程中运行。 DevToolsProcessParent.sys.mjs
此模块很简单。当 watcher actor 调用其某些方法时,它主要将查询转发到内容进程。它还将接收来自内容进程的消息,并使用 ParentProcessWatcherRegistry 来检索相关的 Watcher Actor 实例并将其通知传入的消息。
一个在内容进程中运行。 DevToolsProcessChild.sys.mjs
此模块包含更多逻辑。在收到监视新目标和资源类型的请求时,这将把这些请求委派给许多目标监视器类。这些类特定于每种目标类型
这些类将
每次创建新的匹配的可调试上下文时,都会实例化一个新的目标 Actor。
上下文被销毁时,销毁相关目标 Actor。
将 SessionData 更新分派到目标 Actor。(这包括被监视的资源,它们是 SessionData 属性)
对于工作线程,此类还将访问不同的工作线程以执行所有这三个要点,但从工作线程中执行。
与父进程代码库类似, ContentProcessWatcherRegistry 维持一个对象列表,这些对象表示每个活动监视器 Actor。这有助于在内容进程中存储状态,这将特定于每个监视器。
会话数据¶
每个 Watcher Actor 都维护一个专用的会话数据对象。这是一个可 JSON 序列化的对象,旨在跨所有进程和线程共享。这很容易从任何服务器端代码访问。修改仅在父进程中完成,但其状态在进程和线程之间同步。
支持的属性列表在 SessionDataHelpers.sys.mjs 的 SUPPORTED_DATA
变量中维护。存储在会话数据对象中的值是对象的数组。但这些数组旨在像集合一样工作。原始值(字符串、数字等)每个数组只能存在一次。对于对象,SessionDataHelper 模块中的 DATA_KEY_FUNCTION
变量将提供一个唯一键来识别数组的每个元素。
会话数据对象由 ParentProcessWatcherRegistry 在父进程中维护。此模块将存储每个 Watcher Actor 的会话数据对象。然后,Watcher Actor 将使用 JS Process Actor 将对会话数据对象所做的更新传达给所有内容进程。工作线程目标监视器也将把更新中继到所有工作线程。
所有监视器 actor 的 Session Data 对象也将存储在GlobalProcessScript.sharedData 中。这样做是为了在新的内容进程启动时,为DevToolsProcessChild.sys.mjs 提供 Session Data。我们需要在启动序列的早期阶段访问 Session Data 以便设置断点。我们不能等待 JS Process Actor 查询,而是需要立即访问它。在进程启动时,我们将从sharedData
读取 Session Data,然后通过 JS Process Actor 查询维护副本。