Search code examples
javascriptreactjsgoogle-chrome-extensionfirefox-addon

How to remove a listener of an extension popup (React component), when the popup is closed?


I have an extension that was build with react (legacy code), and I have been tracking a bug that I have finally cornered, but I cannot fixed.

When the icon of the extension (in the browser bar) is clicked a react Component is created, and a listener is added in its componentDidMount():

async componentDidMount(){
   ...
   // an object from the background is retrieved
   let background_object = this.props.getBackgroundObject();
   ...
   // code including await background_object.doSomething();
   ...
   // add event (eventemitter3 is used for the event management)
   background_object.event.on('onMusic', this.dance);
   ...
}

async dance() {
  this.setState({
    'music': true,
  })
}

However, I cannot figure out how to remove the listener once the Component disappear, e.g. by clicking somewhere else in the browser. I thought that componentWillUnmount was what I looking for, but it is never called:

componentWillUnmount(){
  // this is never called!!!
  background_object.event.removeListener('onDance', this.dance);
}

The problem is that everytime I open (and close) the extension popup, a new event is added to the background_object, so that dance() is called multiple times (as many as I open and close the popup).

For now, I have used once instead of on:

async componentDidMount(){
   ...
   // an object from the background is retrieved
   let background_object = this.props.getBackgroundObject();
   ...
   // code including await background_object.doSomething();
   ...
   // add event (eventemitter3 is used for the event management)
   background_object.event.once('onMusic', this.dance);
   ...
}

async dance() {
 // add the event again in case onMusic is called again
 background_object.event.once('onMusic', this.dance);
 this.setState({
   'music': true,
 })
}

In this way, at least, it's only called once. However, I am concerned that my component is being created multiple times and consuming memory in my browser.

How can I make sure that the component is actually being destroyed? How can I detect when the popup is closed in order to remove the event?


Solution

  • It is possible to use chrome.runtime.onConnect for this (thanks @wOxxOm):

    1. In the constructor of the React component open a connection:
      constructor(props){
        super(props)
        this.state = {
          dance: false,
        }
        ...
        var port = this.xbrowser.runtime.connect();
        ...
      }
    
    
    1. Add the event in componentDidMount of the react Component.
    async componentDidMount(){
       ...
       // an object from the background is retrieved
       let background_object = this.props.getBackgroundObject();
    
       ...
       // add event (eventemitter3 is used for the event management)
       background_object.event.on('onMusic', this.dance);
       ...
    }
    
    async dance() {
      this.setState({
        'music': true,
      })
    }
    
    1. Somewhere in the background (e.g background.js) listen to the connections to the browser, and remove the event when the connection is lost:
    chrome.runtime.onConnect.addListener(function (externalPort) {
      externalPort.onDisconnect.addListener(function () {
         let background_object = this.props.getBackgroundObject();
         background_object.event.removeListener('onSend'); 
      })
    })
    

    In my head, this is not very elegant, but it is doing the trick.