0

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?

HoldOffHunger
  • 18,769
  • 10
  • 104
  • 133
toto_tico
  • 17,977
  • 9
  • 97
  • 116
  • why are the functions async? I think the problem could be related to this. componentWillUnmount should be called. Your approach with removing listener on unmount is correct. – oshell Nov 11 '19 at 13:21
  • 1
    The popup is terminated immediately so no unload code will run. Use a dummy port connection in the background script instead, [more info](/a/15801294), [example](/a/39756934). – wOxxOm Nov 11 '19 at 13:22
  • @oshell, it is an async, because there is some await functions in the `...` parts. I read about this, and it is normal (as I said, this is legacy code, so cannot five you a full answer) – toto_tico Nov 11 '19 at 13:25
  • @wOxxOm, that seems like a solution (sort of makes sense, except I was expecting something more graceful from React). I will try in a few hours, and post about the result. What is the `componentWillUnmount` for then? Maybe it just does not work for extensions? – toto_tico Nov 11 '19 at 13:30
  • 1
    The popup is terminated immediately so no unload/unmount code will run. – wOxxOm Nov 11 '19 at 13:36
  • try not making the function async and put the async logic you want in there in a self executing async function. at least the componentWillUnmount should be triggered – oshell Nov 11 '19 at 14:08
  • @wOxxOm, that did not work. Ehe event triggers when `runtime.connect` is called, which i cannot find anywhere in the code. – toto_tico Nov 11 '19 at 15:37
  • oh, wait, i see, that is exactly the point. I need to connect, and when the popup is terminated, the connection will die. That is a bit hackish, but will do. Thanks – toto_tico Nov 11 '19 at 15:43

1 Answers1

0

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.

toto_tico
  • 17,977
  • 9
  • 97
  • 116