I'm currently trying to write a browser extension that catches all mailto links, instead of letting them being opened by the default mail application. The mechanism can be enabled and disabled using a simple toggle button.
When my extension loads everything seems to work, but I get a Error: Could not establish connection. Receiving end does not exist.
. Then when I use the toggle button to disable and enable it again I get the following:
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
Edit: And the disabling doesn't seem to work properly in the content script. The click handler is not properly removed for some reason...
So what could be causing all these errors? I checked this.sendMsgToTabs
in the debugger and it actually isn't undefined. I also don't have any unusual sites open, so I don't understand why there is a connection problem.
I've only tested my code in Firefox for now. But people say Chrome's API is essentially the same. Here is my code:
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()
content-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"
}
}