title: Messaging problems between content script and background script (WebExtensions)
body: <p>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.<br>
  When my extension loads everything seems to work, but I get a <code>Error: Could not establish connection. Receiving end does not exist.</code>. Then when I use the toggle button to disable and enable it again I get the following:</p>
  
  <pre><code>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
  </code></pre>
  
  <p><strong>Edit</strong>: And the disabling doesn't seem to work properly in the content script. The click handler is not properly removed for some reason...  </p>
  
  <p><a href="https://i.stack.imgur.com/o0CDi.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/o0CDi.png" alt="enter image description here"></a></p>
  
  <p>So what could be causing all these errors? I checked <code>this.sendMsgToTabs</code> 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.</p>
  
  <p>I've only tested my code in Firefox for now. But people say Chrome's API is essentially the same. Here is my code:</p>
  
  <p><strong>background.js</strong></p>
  
  <pre><code>'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 =&gt; {
              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()
  </code></pre>
  
  <p><strong>content-script.js</strong></p>
  
  <pre><code>'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()
  </code></pre>
  
  <p><strong>manifest.json</strong></p>
  
  <pre><code>{
  
    "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": ["&lt;all_urls&gt;"],
        "js": ["content-script.js"]
      }
    ],
  
    "browser_action": {
      "default_icon": "icons/on.png"
    }
  
  }
  </code></pre>