Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clear dangling EventListeners and Detached Nodes when a controller is removed from the DOM #592

Merged
merged 1 commit into from Oct 31, 2022

Commits on Oct 31, 2022

  1. Clear dangling EventListeners and Detached Nodes when a controller is…

    … removed from the DOM
    
    `eventListenerMaps` maps HTML elements handled by Stimulus to `eventListenerMap`, a map of Stimulus
    EventListeners in this way: `{ HTMLelement: { eventName: EventListener } }`.
    When a controller HTML is removed from the DOM the dangling EventListeners aren't removed,
    moreover, the removed HTML elements are still referenced via the `eventListenerMaps` keys.
    This leads to having multiple detached HTMLelements and eventListeners which can't be GCed
    hence a memory leak. The leak is fixed by removing the dangling eventListeners and clearing
    unused `eventListenerMaps` keys. When the HTMLelement is attached again to the DOM,
    the `eventListenerMaps` and the related `eventListeners` are automatically re-created
    by Stimulus via the MutationObservers, no data is lost by doing this.
    
    Below is an example to reproduce the issue:
    
    ```
    <html>
      <head>
        <title>EventListenerMaps memory leak</title>
        <script type="module">
          import { Application, Controller } from "https://unpkg.com/@hotwired/stimulus/dist/stimulus.js"
          window.Stimulus = Application.start()
          Stimulus.register("boosts", class extends Controller {
            doClick() { process(500) }
          })
        </script>
        <script>
          function process(count) {
            let i = 0
            let handler = setInterval(function(){
              if (++i > count) {
                clearInterval(handler)
              } else {
                document.body.replaceWith(document.body.cloneNode(true))
              }
            }, 1)
          }
        </script>
    </head>
    <body>
      <div data-controller="boosts">
        To reproduce:
        <ul>
          <li>Check heap snapshot</li>
          <li>Click "trigger leak" button</li>
          <li>Check heap snapshot again</li>
        </ul>
        <button data-action="click->boosts#doClick">trigger leak</button>
      </div>
    </body>
    </html>
    ```
    intrip committed Oct 31, 2022
    Copy the full SHA
    a912acc View commit details
    Browse the repository at this point in the history