Skip to content

Chrome插件-踩坑

本篇是对开发Chrome插件开发过程中一些功能实现所需到的问题进行梳理总结,如:文件的通信、插件图标和badgeText的动态设置,等等


插件只在指定的网页启用

“The Declarative Content API allows you to enable your extension's action depending on the URL of a web page, or if a CSS selector matches an element on the page, without needing to add host permissions or inject a content script."

通过Declarative设置URL或者CSS选择器匹配规则,可以实现在特定的网页开启插件功能。

首先,在manifest.json中声明permissions和指定page_action。注意:不是browser_action,它是针对所有页面都有效的一种设置图标的方式,不支持指定URL有效。

json
{
  "page_action": {
    "default_popup": "popup.html",
    "default_icon": {
      "16": "images/icon_16.png",
      "32": "images/icon_32.png",
      "48": "images/icon_48.png",
      "128": "images/icon_128.png"
    }
  },
  "permissions": [
    "declarativeContent"
  ]
}

然后,在background.js中调用chrome.declarativeContentAPI进行控制。

js
chrome.runtime.onInstalled.addListener(function() {
  chrome.declarativeContent.onPageChanged.removeRules(undefined, function() {
    chrome.declarativeContent.onPageChanged.addRules([{
      conditions: [new chrome.declarativeContent.PageStateMatcher({
        pageUrl: {hostEquals: 'developer.chrome.com'},
      })
      ],
          actions: [new chrome.declarativeContent.ShowPageAction()]
    }]);
  });
});
  • 通过pageUrl控制

  • 通过CSS选择器控制

完整使用参考 文档-chrome.declarativeContent

BrowserAction相关操作

  • 设置badgeText
js
function updateBadgeText(text = '', color = "#f50000") {
  chrome.browserAction.setBadgeText({ text: text });
  chrome.browserAction.setBadgeBackgroundColor({
    color: color,
  });
}
  • 修改icon
js
function updateActionIcon(iconPath) {
  chrome.browserAction.setIcon({
    path: iconPath,
  });
}

浏览器消息气泡通知

js
function sendNotifications(title, message, icon) {
  chrome.notifications.create(null, {
    type: "basic",
    iconUrl: icon || "static/img/logo.png",
    title: title,
    message: message,
  });
}

不同文件访问Chrome扩展API的限制

content_scripts

作为可注入到运行页面的脚本(还有CSS),可访问的API最少,常用的有下几个: runtime、storage、extension、i18n。 另外要注意的是,content-scripts和运行页面共享DOM,但是不共享js,如要访问页面js(例如某个js变量),只能通过injected js来实现。

background

是一个常驻的页面,它的生命周期是插件中所有类型页面中最长的,它随着浏览器的打开而打开,随着浏览器的关闭而关闭,所以通常把需要一直运行的、启动就运行的、全局的代码放在background里面。

background的权限非常高,几乎可以调用所有的Chrome扩展API,而且它可以无限制跨域,也就是可以跨域访问任何网站而无需要求对方设置CORS。

popup

它和background非常类似,它们之间最大的不同是生命周期的不同,popup中可以直接通过chrome.extension.getBackgroundPage()获取background的window对象。

通信 💬说 👂听

场景 content_script.js💬 popup.js👂

通常情况下,我们在content_script编写插件主代码,运行的临时数据都在这类js里缓存,当点开popup页面后,如何拿到这些数据,对UI进行更新,这里就需要用到两者通信了。

js
// content_script.js 向popup发送一条消息:下载进度
chrome.runtime.sendMessage(
  {
    cmd: "onprogress",
    value: {
      domId,
      progress,
    },
  },
  function (response) {
    console.log('接收到popup.js的[onprogress]事件应答:',response);
  }
);

popup通过chrome.runtime.onMessage.addListener接收消息,request是消息体,这里用request.cmd来区分不同的消息类型。

js
// popup.js 接收

chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
  const { value, cmd } = request;
  const { domId, progress } = value || {};
  if (!domId) {
    return;
  }

  if (cmd == "onprogress") {
    updateDownloadBtnUI(domId, "progress", progress);
  }

  if (cmd == "finished") {
    updateDownloadBtnUI(domId, "finished", 100);
  }

  if (cmd == "failed") {
    updateDownloadBtnUI(domId, "failed", 0);
  }

  sendResponse(
    "我是popup,我已收到content_sript的事件消息:" + JSON.stringify(request)
  );
});

场景 content_script.js💬 background.js👂

基本上和content_script.jspopup.js通信方式一致。

场景 popup.js💬 content_script.js👂

发送方popup.js获取当前tab,调用chrome.tabs.sendMessage发送消息,接收方content_script.js依然通过chrome.runtime.onMessage.addListener接收消息。

js
// popup.js

function sendMessageToContentScript(message, callback) {
  chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
    chrome.tabs.sendMessage(tabs[0].id, message, function (response) {
      if (callback) callback(response);
    });
  });
}

// 调用
testBtnDom.addEventListener("click", function () {
  sendMessageToContentScript(
    { cmd: "test", value: "show me the test code" },
    function (res) {
      console.log(res);
    }
  );
});
js
//content_script.js

chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
  if (request.cmd == "test") {
    console.log("content_script.state:", state);
    sendResponse({
      msg: "hello there",
    });
  }

  return true;
});

场景 popup.jsbackground.js通信

popup.js 中调用 background.js 中的方法。实际上是通过chrome.extension.getBackgroundPage()获取background的window对象。

var bg = chrome.extension.getBackgroundPage();
bg.doSomething(); //doSomething()是background中的一个方法

V2迁移V3

两个版本差异点还是挺多的,总的来说,对目前大部分插件都有影响,需要大量兼容。以下列举一些本人遇到问题。

全面禁止evel

V3无法再通过evel的方式运行代码字符串了,所有动态的字符串代码必须保存到指定的js文件中,并在manifest.json注册表中指定新的background,以前的background.scripts改为background.service_worker,且值为字符串。

json
{
    "background": {
        "service_worker": "background.js"
    }  
}

manifest.json改动

  • browser_actionor或者page_action,统一改为action。 * background.scripts改为background.service_worker。且service_worker字段采用字符串,而不是字符串数组。(service_worker必须在根级别注册:它们不能在嵌套目录中。)。
  • popup.js 中调用 background.js 中的方法,从之前的chrome.runtime.getBackgroundPage() 改为 background迁移
  • web_accessible_resources的值从之前的资源路径字符串数组改为对象数组,且每个对象配置至少指定两个字段。
json
// V3
"web_accessible_resources": [{
  "resources": [RESOURCE_PATHS],
  "matches": [MATCH_PATTERNS],
  "extension_ids": [EXTENSION_IDS],
  optional "use_dynamic_url": boolean
}]

其它一些没遇到的就不列举了,详见 mv3-迁移清单mv3-迁移

Released under the MIT License.