实现函数

实现 API 函数至少需要两个不同的部分:模式中函数的定义,以及实际实现函数的 Javascript 代码。

在 API 模式中声明函数

一个简单函数的 API 模式定义如下所示

[
  {
    "namespace": "myapi",
    "functions": [
      {
        "name": "add",
        "type": "function",
        "description": "Adds two numbers together.",
        "async": true,
        "parameters": [
          {
            "name": "x",
            "type": "number",
            "description": "The first number to add."
          },
          {
            "name": "y",
            "type": "number",
            "description": "The second number to add."
          }
        ]
      }
    ]
  }
]

typedescription 属性在上面已经描述过了。 name 属性是在给定命名空间中显示的函数名称。也就是说,上面的片段创建了一个可以从扩展中调用的函数,例如 browser.myapi.add()parameters 属性描述了函数接收的参数。参数被指定为 Javascript 类型数组,其中每个参数都是前面部分描述的受约束的 Javascript 值。

每个参数还可以包含其他属性 optionaldefault。如果存在 optional,它必须是布尔值(并且参数默认情况下不是可选的,因此此属性通常仅在值为 true 时添加)。 default 属性仅对可选参数有意义,它指定在不带该参数的情况下调用函数时应为可选参数使用的值。没有显式 default 属性的可选参数将接收 null 的默认值。尽管在任何位置创建可选参数都是合法的(即,可选参数可以出现在必需参数之前),但这样做会导致难以使用的函数,并且鼓励 API 设计师改为使用具有可选命名属性的对象值参数,或者如果必须使用可选参数,则谨慎使用并将它们放在参数列表的末尾。

布尔值 async 属性指定函数是否为异步函数。对于异步函数,WebExtensions 框架负责自动生成一个 Promise,然后在函数实现完成时解析 Promise(如果实现抛出 Error 则拒绝 Promise)。由于扩展可以在子进程中运行,因此在父进程中(部分或完全)实现的任何 API 函数都必须是异步的。

当在 API 模式中声明函数时,会自动创建一个函数包装器并将其注入到适当的扩展 Javascript 上下文中。此包装器会自动根据模式中声明的形式参数验证传递给函数的参数,并在传递无效参数时立即抛出 Error。它还会处理可选参数并在需要时插入默认值。因此,API 实现通常不需要编写太多样板代码来验证和解释参数。

在主进程中实现函数

如果异步函数未在子进程中实现,则从模式生成的包装器会自动编组函数参数,将请求发送到父进程,并在那里调用实现。当该函数完成时,返回值将发送回子进程,并且函数调用的 Promise 将使用该值解析。

基于此,我们上面编写的模式的函数实现如下所示

this.myapi = class extends ExtensionAPI {
  getAPI(context) {
    return {
      myapi: {
        add(x, y) { return x+y; }
      }
    }
  }
}

API 函数的实现包含在 ExtensionAPI 类的子类中。ExtensionAPI 的每个子类都必须实现 getAPI() 方法,该方法返回一个对象,其结构反映了 API 公开的函数和事件的结构。传递给 getAPI()context 对象是 BaseContext 的实例,其中包含许多有用的属性和方法。

如果 API 函数实现返回 Promise,则当 Promise 完成时,其结果将发送回子进程。任何其他返回类型都将直接发送回子进程。函数实现也可以引发 Error。但默认情况下,从 API 实现函数内部抛出的 Error 不会暴露给调用该函数的扩展代码——它会被转换为带有消息“发生意外错误”的通用错误。要向扩展抛出特定错误,请使用 ExtensionError

this.myapi = class extends ExtensionAPI {
  getAPI(context) {
    return {
      myapi: {
        doSomething() {
          if (cantDoSomething) {
            throw new ExtensionError("Cannot call doSomething at this time");
          }
          return something();
        }
      }
    }
  }
}

此步骤的目的是避免 API 实现中的错误将有关实现的详细信息暴露给扩展。当抛出不是 ExtensionError 实例的 Error 时,原始错误将记录到 浏览器控制台,这在开发新 API 时可能很有用。

在子进程中实现函数

大多数函数都在主进程中实现,但有时也需要在子进程中实现函数,例如

  • 该函数具有一到多个无法使用结构化克隆算法自动发送到主进程的类型的参数。

  • 该函数实现与浏览器内部的某些部分交互,而这些部分只能从子进程访问。

  • 该函数可以在子进程中实现得更高效。

要在子进程中实现函数,只需包含在适当的上下文中加载的 ExtensionAPI 子类(例如 addon_childcontent_child 等),如“基础”部分所述。子进程中 ExtensionAPI 子类内部的代码可以使用 API 上下文中的方法如下所示调用父进程中函数的实现

this.myapi = class extends ExtensionAPI {
  getAPI(context) {
    return {
      myapi: {
        async doSomething(arg) {
          let result = await context.childManager.callParentAsyncFunction("anothernamespace.functionname", [arg]);
          /* do something with result */
          return ...;
        }
      }
    }
  }
}

正如您所料, callParentAsyncFunction() 使用给定的参数在主进程中调用给定的函数,并返回一个 Promise,该 Promise 使用函数的结果解析。这与自动生成的函数包装器用于在子进程中没有提供实现的异步函数的机制相同。

可以在主进程和子进程中都定义相同的函数,并让子进程中的实现调用父进程中同名的函数。当特定函数的实现需要主进程和子进程中的某些代码时,这是一种常见模式。