我目前正在尝试编写一个浏览器扩展程序来捕获所有mailto链接,而不是让它们被默认邮件应用程序打开。可以使用简单的切换按钮启用和禁用该机制
当我的扩展程序加载时,一切似乎都有效,但我得到Error: Could not establish connection. Receiving end does not exist.
。然后,当我使用切换按钮禁用并再次启用它时,我得到以下内容:
23:52:15.247 function sendMsgToTabs(msg) background.js:16:9
23:52:15.256 this.sendMsgToTabs(...) is undefined background.js:17
23:52:15.302 Error: Could not establish connection. Receiving end does not exist. (unknown)
23:53:29.347 TypeError: this._recipeManager is null[Learn More] LoginManagerParent.jsm:77:9
修改:禁用似乎无法在内容脚本中正常运行。由于某种原因,单击处理程序未正确删除...
那么可能导致所有这些错误的原因是什么?我在调试器中检查了this.sendMsgToTabs
,但它实际上并未定义。我也没有打开任何不寻常的网站,所以我不明白为什么会出现连接问题。
我目前只在Firefox中测试过我的代码。但人们说Chrome的API基本相同。这是我的代码:
background.js
'use strict'
class MyWebExtensionBackend {
constructor() {
this.icons = {
enabled: '/icons/on.png',
disabled: '/icons/off.png'
}
this.isEnabled = true
browser.browserAction.onClicked.addListener(this.toggle.bind(this)) //toolbar button
browser.runtime.onMessage.addListener(this.msgListener.bind(this))
this.enable()
}
enable() {
browser.browserAction.setIcon({ path: this.icons.enabled })
this.isEnabled = true
console.log(this.sendMsgToTabs)
this.sendMsgToTabs({isEnabled: this.isEnabled}).catch(console.error)
}
disable() {
browser.browserAction.setIcon({ path: this.icons.disabled })
this.isEnabled = false
this.sendMsgToTabs({isEnabled: this.isEnabled}).catch(console.error)
}
toggle() {
if (this.isEnabled)
this.disable()
else
this.enable()
}
sendMsgToTabs(msg) {
return browser.tabs.query({}, tabs => {
let msgPromises = []
for (let tab of tabs) {
let msgPromise = browser.tabs.sendMessage(tab.id, msg)
msgPromises.push(msgPromise)
}
return Promise.all(msgPromises)
})
}
msgListener(msg, sender, sendResponse) {
console.log(msg.link)
/* browser.notifications.create({ // doesn't work
"type": "basic",
"iconUrl": browser.extension.getURL("icons/on.png"),
"title": 'url opened',
"message": msg.link
}); */
}
}
let myWebExtensionBackend = new MyWebExtensionBackend()
内容-的script.js
'use strict'
class MyWebExtensionFrontend {
constructor() {
this.isEnabled = false
browser.runtime.onMessage.addListener(this.msgListener.bind(this))
}
linkHandler(event) {
if (event.target.tagName !== 'A')
return
let link = event.target.href
if (link.startsWith('mailto:')) {
event.preventDefault() // doesn't appear to have an effect
console.log(link)
browser.runtime.sendMessage({'link': link}).catch(console.error)
//using a promise here felt kind of wrong
//because event handler functions can't really deal with that
//from what I can tell
return false // doesn't appear to have an effect
}
}
enable() {
console.log('enable frontend')
window.addEventListener('click', this.linkHandler.bind(this))
this.isEnabled = true
}
disable() {
console.log('disable frontend')
window.removeEventListener('click', this.linkHandler.bind(this))
this.isEnabled = false
}
msgListener(req) {
if (req.isEnabled)
this.enable()
else
this.disable()
return Promise.resolve({res: ''})
}
}
let myWebExtensionFrontend = new MyWebExtensionFrontend()
的manifest.json
{
"description": "A basic toggle button",
"manifest_version": 2,
"name": "toggle-button",
"version": "1.0",
"homepage_url": "https://github.com/TODO",
"icons": {
"48": "icons/on.png"
},
"background": {
"scripts": ["background.js"]
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content-script.js"]
}
],
"browser_action": {
"default_icon": "icons/on.png"
}
}
答案 0 :(得分:2)
您的代码有两个问题。一个特定于WebExtensions,另一个特定于DOM和JavaScript。
在Firefox中,WebExtensions有两个名称空间,chrome.
和browser.
。 chrome.
的设计与Chrome的扩展程序API非常接近,而browser.
则有一些增强功能。特别是,异步browser.
API可能会返回一个promise。只有在没有使用回调函数调用方法时才会返回promise。如果它确实收到回调作为参数,则browser.
API的行为类似于chrome.
。以下是一些例子:
// These have a callback and return undefined.
chrome.tabs.query({}, function(tabs) { /* ... */ });
browser.tabs.query({}, function(tabs) { /* ... */ });
chrome.tabs.query({}); // No callback, returns undefined (this is quite useless).
browser.tabs.query({}); // No callback, returns a Promise<Array>.
browser.tabs.query({}).then(function(tabs) { /* ... */});
请注意,您收到的错误消息显示返回值未定义:
this.sendMsgToTabs(...) is undefined
如果方法未定义(如您所愿),Firefox将打印以下错误:
this.sendMsgToTabs is not a function
您的案例的修复方法是使用promises,如下所示:
sendMsgToTabs(msg) {
// Note: Changed ", tabs =>" to ").then(tabs =>"
return browser.tabs.query({}).then(tabs => {
let msgPromises = []
for (let tab of tabs) {
let msgPromise = browser.tabs.sendMessage(tab.id, msg)
msgPromises.push(msgPromise)
}
return Promise.all(msgPromises)
});
}
如果有任何没有内容脚本的标签,则承诺将被拒绝。如果您不关心承诺的返回值,请在let msgPromise
之后添加以下内容:
msgPromise = msgPromise.catch(() => {}); // Ignore errors.
您的代码如下所示:
// your enable() function:
window.addEventListener('click', this.linkHandler.bind(this))
// your disable() function:
window.removeEventListener('click', this.linkHandler.bind(this))
问题是bind
会返回新函数。因此,当调用enable()
方法时,会创建一个新函数并将其用作事件侦听器。调用disable()
时,会创建一个新函数并将其传递给removeEventListener
。由于此新函数与任何其他函数(特别是前一个参数addEventListener
)不同,因此结果是不会删除单击处理程序。
有三种方法可以使用所需的this
值注册DOM事件:
在构造类时,用绑定函数替换实例方法:
// In the constructor:
this.linkHandler = this.linkHandler.bind(this);
// In your enable() function:
window.addEventListener('click', this.linkHandler);
// In your disable() function:
window.removeEventListener('click', this.linkHandler);
在调用enable()
时,如果需要,创建一个绑定函数:
// In your enable() function:
if (!this.linkHandlerBound) {
this.linkHandlerBound = this.linkHandler.bind(this);
}
window.addEventListener('click', this.linkHandlerBound);
// In your disable() function:
window.removeEventListener('click', this.linkHandlerBound);
// If wanted (but not needed), run: this.linkHandlerBound = null;
使用handleEvent
method传递对象:
// A method of your class
handleEvent(event) {
if (event.type === 'click') {
this.linkHandler(event);
}
}
// In your enable() function:
window.addEventListener('click', this);
// In your disable() function:
window.removeEventListener('click', this);
注意, not 使用handleEvent
方法犯下类似错误的错误如下:
// Do NOT do this! You won't be able to remove the listener because
// you did not store a reference to the event handler object.
window.addEventListener('click', {
handleEvent: this.linkHandler.bind(this)
});
答案 1 :(得分:0)
我刚刚注意到您还没有分享处理连接的方式,但您可能需要检查Long-lived connections以区分不同类型的连接以及如何建立连接。
如给定链接中所述,
建立连接时,每个端都有一个
runtime.Port
对象,用于通过该连接发送和接收消息。
以下是有关如何从内容脚本打开频道以及发送和收听消息的示例:
var port = chrome.runtime.connect({name: "knockknock"});
port.postMessage({joke: "Knock knock"});
port.onMessage.addListener(function(msg) {
if (msg.question == "Who's there?")
port.postMessage({answer: "Madame"});
else if (msg.question == "Madame who?")
port.postMessage({answer: "Madame... Bovary"});
});
此外,您可能还想查看相关SO post中的建议解决方案,看看哪些适用于您。