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 #1

Closed
wants to merge 1 commit into from

Commits on Oct 19, 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 19, 2022
    Configuration menu
    Copy the full SHA
    1dc5415 View commit details
    Browse the repository at this point in the history