2 minute read

Chrome Extension JS 对比

JS 种类 可访问的 API DOM 访问情况 JS 访问情况 直接跨域
background js 可以访问绝大部分的 API,除了 devtools 系列 不能直接访问 不可以 可以
content scripts 只能访问 extension、runtime 等部分 API 可以访问 不可以 不可以
browser action 可以访问绝大部分的 API,除了 devtools 系列 不能直接访问 不可以 可以
inject script 和普通 JS 无任何差别,不能访问任何扩展 API 可以访问 可以访问 不可以

通信

content script 向扩展程序通信

// content script
chrome.runtime.sendMessage({ greeting: "您好" }, function (response) {
    console.log("收到来自后台的回复:" + response)
})

// background.js 或者 popup.js(browser action)
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
    console.log("收到来自content-script的消息:")
    console.log(request, sender, sendResponse)
    sendResponse("我是后台,我已收到你的消息:" + JSON.stringify(request))
})

注意: 如果多个页面都监听 onMessage 事件,对于某一次事件只有第一次调用 sendResponse() 能成功发出回应,所有其他回应将被忽略。

在 content script 中 使用 sendMessage 发送消息,在接收方使用 runtime.onMessage 或者 runtime.onMessageExternal ,向自己的扩展程序发送消息就接受方使用 onMessage,如果是向另一个扩展程序发送则使用 onMessageExternal 。

扩展程序向 coentent script 发送信息使用的就是 tabs.sendMessage

// 唯一的区别是您需要指定发送至哪一个标签页
chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
    chrome.tabs.sendMessage(
        tabs[0].id,
        { greeting: "您好" },
        function (response) {
            console.log(response.farewell)
        }
    )
})

注意事项: content_scripts 向 popup 主动发消息的前提是 popup 必须打开!否则需要利用 background 作中转; 如果 background 和 popup 同时监听,那么它们都可以同时收到消息,但是只有一个可以 sendResponse,一个先发送了,那么另外一个再发送就无效;

popup 与 background 的通信

popup 可以直接调用 background 中的 JS 方法,也可以直接访问 background 的 DOM:

// background.js
function test() {
    alert("我是background!")
}

// popup.js
var bg = chrome.extension.getBackgroundPage()
bg.test() // 访问bg的函数
alert(bg.document.body.innerHTML) // 访问bg的DOM

注意:不能有语法报错,不然会看不到输出

background 访问 popup 如下(前提是 popup 已经打开)

var views = chrome.extension.getViews({ type: "popup" })
console.log("---views---", views)
if (views.length > 0) {
    console.log(views[0].location.href)
}

调试步骤:

  • 先打开背景页,console tab 下会输出 ---views--- []
  • 点击你的扩展,弹出 popup 页面
  • 刷新背景页面的控制台,就可以看到输出的内容了

popup/background 与 content 通信

// background.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)
        })
    })
}
sendMessageToContentScript(
    { cmd: "test", value: "你好,我是popup!" },
    function (response) {
        console.log("来自content的回复:" + response)
    }
)
// content script
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
    // console.log(sender.tab ?"from a content script:" + sender.tab.url :"from the extension");
    if (request.cmd == "test") alert(request.value)
    sendResponse("我收到了你的消息!")
})

inject script 和 content script 通信

content-script 和页面内的脚本(injected-script 自然也属于页面内的脚本)之间唯一共享的东西就是页面的 DOM 元素,有 2 种方法可以实现二者通讯:

  1. 可以通过 window.postMessage 和 window.addEventListener 来实现二者消息通讯(推荐)
// inject script
window.postMessage({ test: "你好!" }, "*")

// content script
window.addEventListener(
    "message",
    function (e) {
        console.log(e.data)
    },
    false
)
  1. 通过自定义 DOM 事件来实现;
// inject script
var customEvent = document.createEvent("Event")
customEvent.initEvent("myCustomEvent", true, true)
function fireCustomEvent(data) {
    hiddenDiv = document.getElementById("myCustomEventDiv")
    hiddenDiv.innerText = data
    hiddenDiv.dispatchEvent(customEvent)
}
fireCustomEvent("你好,我是普通JS!")
// content script
var hiddenDiv = document.getElementById("myCustomEventDiv")
if (!hiddenDiv) {
    hiddenDiv = document.createElement("div")
    hiddenDiv.style.display = "none"
    document.body.appendChild(hiddenDiv)
}
hiddenDiv.addEventListener("myCustomEvent", function () {
    var eventData = document.getElementById("myCustomEventDiv").innerText
    console.log("收到自定义事件消息:" + eventData)
})

长连接

上面讲的都是属于短连接,一次性的。

要使用长连接,您可以分别使用 runtime.connect 或 tabs.connect 从您的内容脚本建立到扩展程序(或者反过来)的长时间连接。建立的通道可以有一个可选的名称,让您区分不同类型的连接。

建立连接时,两端都将获得一个 runtime.Port 对象,用来通过建立的连接发送和接收消息。

content script 中建立连接,发送并监听消息

var port = chrome.runtime.connect({ name: "敲门" })
port.postMessage({ joke: "敲门" })
port.onMessage.addListener(function (msg) {
    if (msg.question == "是谁?") port.postMessage({ answer: "女士" })
    else if (msg.question == "哪位女士?")
        port.postMessage({ answer: "Bovary 女士" })
})

在接收端通过 onConnect 事件监听

chrome.runtime.onConnect.addListener(function (port) {
    console.assert(port.name == "敲门")
    port.onMessage.addListener(function (msg) {
        if (msg.joke == "敲门") port.postMessage({ question: "是谁?" })
        else if (msg.answer == "女士")
            port.postMessage({ question: "哪位女士?" })
        else if (msg.answer == "Bovary 女士")
            port.postMessage({ question: "我没听清楚。" })
    })
})

您可能想知道连接何时关闭,例如您需要为每一个打开的端口单独保留状态。这种情况下您可以监听 runtime.Port.onDisconnect 事件,当连接的另一端调用 runtime.Port.disconnect 或包含该端口的页面已结束(例如标签页转到了另一个页面)时,对于每一个端口确保都会发生一次该事件。

总结

  1. content script 是依附于当前打开的页面的,所以由它向 background/popup 发送消息直接 runtime.sendMessage 就行
  2. bacground/popup 分别是扩展的后台页面和扩展展示页,它们是不知道往哪个打开页面(即 tab)的 content script 发送信息的,因此这里就通过 tabs.sendMessage
  3. injected script 和 content script 都属于页面内的脚本,由于 content script 不能获取页面自己的 js ,但是 inject script 是可以获取到页面 js ,而 content script 和 inject script 之间共享的东西就是页面的 DOM 元素,window 就是页面元素,所以可以用来传递消息
  4. background 和 popup 都是属于扩展自己的 js,这两者的通信就更简单了。

Updated: