服务器端概述

连接到后端

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。对于每个 watchTargetswatchResources 方法调用,都会通过 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.mjsSUPPORTED_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 查询维护副本。