分类
chrome

Chrome Extension Manifest V3 升级笔记

关于 MV3 的知识,可以看这篇前文:

《Chrome 扩展大升级 Manifest V3:变化》

近期尝试把一个浏览器扩展升级到 MV3,然后发现了很多问题,写篇博客记录一下。

与文档不符

  • 很多号称返回 Promise 实例的方法实际上不会返回结果,或者在没有 callback 的时候会报错

文档未说明

  • service worker 里无法使用 chrome.i18n.getMessage 或者 chrome.i18n.getUILanguage。于是目前右键菜单都无法 i18n 了。
  • chrome.contextMenus.create 无法向 browser_action 添加菜单项

未实现 Promise 或者有 bug

  • chrome.storage.local.get(keys) Promise 结果固定返回 undefined,不可用,需用回 callback
  • chrome.permissions.request 未 Promise 化,无返回值
  • 必须传入 callback,不然报错
    • chrome.permissions.contains
    • chrome.contextMenus.remove

文档写了,但我没看见(>_<!)

  • 在 service worker 里使用 chrome.contextMenus.create 时,必须传入 id,且不能使用 onclick 函数。只能侦听 chrome.contextMenus.onClicked.addListener,然后做进一步判断。

MV2 存在的功能,但无法迁移至 MV3

(有些可能是暂时的)

因:不允许使用 remote JS

MV3 不支持使用外部 JS,所以 Google Analytics 和 Facebook 分享按钮之类的 SDK 都无法使用。也即是说,现在扩展不能使用 GA,也不能嵌入 Facebook Like,也不能使用其它需要引用外部 SDK 才能实现的功能。

因:Service worker 取代 background page/JS

无法获取系统 light/dark mode

Service worker 没有 window 对象,也就没有那些 window 对象上才有的方法,比如 matchMedia()。所以我们就无法在 service worker 里检查系统的 dark mode,实现动态修改 icon 的功能。

无法使用 Image 解析图像

Service worker 也没有 Image 对象,只能使用 createImageBitmap 获取 ImageBitmap,无法转换成 ImageData,也就无法使用一些依赖 ImageData 的仓库,比如没法解析二维码。(可以通过手动解析二进制的方式绕过)

因为 createImageBitmap 无法解析 SVG blob,所以原则上,所有 SVG 的操作均无法执行。

只能通过 chrome.tabs.create() 的方式传出内容

自然,alert()prompt() 都无法使用,也无法手动弹出 popup。所以如果我们想从 service worker 里向外传出内容,只有两个选择:

  1. 使用通知 notification API
  2. 直接打开页面

而 notification API 需要权限,所以可能只好选后者。

分类
chrome

Chrome 扩展里实现 SSO

周五打算给客户发版,结果在这里卡了大半天,写篇博客记录下。

0. SSO 的实现

SSO,Single sign-on,单点登录,即统一处理用户登录、提供用户身份凭据的功能。使用 SSO,可以只维护一套用户体系,容易开发维护;对用户来说,只需要登录一次就能使用该开发商的全部产品,也很轻松方便。

一般来说,SSO 的流程是:

  1. 用户使用 A产品,域名是 pa.mydomain.com,登录服务(S)位于 login.mydomain.com
  2. 用户使用提供服务的 A产品,A产品需要登录,用户选择登录
  3. 来到登录服务,完成登录
  4. S服务将用户指回 A产品,返回的 URL 里包含一个 token
  5. A产品拿到 token,请求 S服务,验证 token,获取部分用户信息(比如邮箱,一般只用来展示
  6. A产品生成自己所需的身份凭据,并以此验证用户身份

我厂的产品也是这么实现的。

1. Chrome 扩展遇到的问题

本地调试一切正常,但是加载成扩展之后,从登录服务跳回扩展会遇到 ERR_BLOCKED_BY_CLIENT 错误,URL 也被重定向到 chrome://invalid/。我在这里卡了很久,主要是不知道该怎么定位问题和搜索答案。

后来经过反复尝试,我终于发现,只有从登录页面跳转回去插件页面的时候,即 location.href='chrome-extension://{id}/ui/index.html 的时候,才会报错,所以立刻换用 chrome extension href ERR_BLOCKED_BY_CLIENT 作为关键词,立刻找到了答案:redirect to chrome-extension:// results in ERR_BLOCKED_BY_CLIENT

然后阅读文档:Manifest – Web Accessible Resources(可由 Web 访问的资源),得知需要在 manifest.json 里添加对应的配置:

{
  ...  "web_accessible_resources": [
    "ui/index.html"
  ],
  ...
}

添加后 SSO 就正常了。

2. 后记

不过我没想明白的是,这个配置意义何在?配置写在扩展里,防止 web 访问扩展里的文件,似乎并没有什么帮助,也没什么安全性的顾虑。也许是我还没遇到吧。

分类
chrome

让 Chrome API 支持 Promise

Chrome API 都是回调型,连续使用非常不方便,希望能改成 Promise 型。Chrome 本身不提供 promisify,不过可以自己写一个:

export default function promisify(original) {
  return function (...args) {
    return new Promise((resolve, reject) => {
      original(...args, (...results) => {
        if (chrome.runtime.lastError) {
          reject(chrome.runtime.lastError.message);
        } else {
          resolve(...results);
        }
      });
    });
  }
}

这里有几个注意事项:

  1. 参数使用 ...args 进行拆解和合并,方便调用
  2. 需要检查 runtime.lastError,不然出错的时候浏览器会报错,影响体验

使用的时候,我比较喜欢只修改需要使用的函数,不打算 promisify 全部函数,大概是这样:

import promisify from './index';

/* global chrome */

export const update = promisify(chrome.tabs.update);
export const remove = promisify(chrome.tabs.remove);
export const get = promisify(chrome.tabs.get);
// 我觉得 `close tab` 看起来更合理
export const close = remove;
// 封装一个 `goto` 的快捷方式
export const goto = function (tabId, url) {
  return update(tabId, {url});
}
// 全部导入,好处是简单,坏处是不方便 tree-shaking
import * as ChromeTabs from '../chrome-promisify/chrome.tabs';

ChromeTabs.goto(tabId, 'https://blog.meathill.com');

// 需要哪个导入哪个
import {goto} from '../chrome-promisify/chrome.tabs';

goto(tabId, 'https://github.com/meathill');
分类
chrome

Chrome Devtool Protocol 开发笔记

周末动笔写教程,越写越长暂时收不了笔;周日发现上一台 iMac 回收被坑了,显示器从烧屏变闪屏了,心情大坏,简直写不动了,哎……

Chrome Devtool Protocol(下面简称 CDP)是一个非常强大工具,简单来说,它可以揭开束缚 Chrome 的各种封印,从浏览器角度深入页面(及其它领域,包括 worker),完成一些平日里难以完成的操作。

我目前研究它主要是想优化我厂官网的首屏 CSS,顺便为将来 QA 插件的深入开发做准备。

不过 CDP 的文档、资料各种不全,Google 也没什么结果,所以我会把日常踩到的坑记到这里,以备回顾。

分类
chrome

Chrome 扩展存储

以前写的有问题,编辑下。

研究了半天,原来 Chrome 扩展不支持文件存储,只能使用 chrome.storage 保存持久化数据。

提供两种形式,一种可以在浏览器间自动同步,适用登录用户保存设置,比如微博的“眼不见心不烦”,chrome.storage.sync,提供一个 key 8K,最大512个 key,总数据量100K(即不可能512个 key 都装满)的存储。这个方式对读写频率也有限制,想想也好理解,比精要往 Google 的云同步嘛。

另外一种则更接近平时用的 localStorage,叫 chrome.storage.local。它的限制很少,只要总量不大于5M即可(可以通过设置 unlimitedStorage: true 来取消上限)。

使用的时候,需要在 manifest.json 里声明权限

    {
      "permissions": [
        "storage"
      ]
    }

这样的话就比较符合我的预期了,用户可以任意保存字幕到本地,太多了自己删掉就是,如果希望云同步就付费或者看广告。

分类
chrome

保持 Chrome 插件 popup.html 长期打开

开发 Chrome 插件时,如果使用 popup.html,调试时反复审查元素很麻烦。此时可以用

chrome-extension://插件id/popup.html

在新标签页打开,就简单多了。