-
Notifications
You must be signed in to change notification settings - Fork 435
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
Disconnect loaded Frame Element while rendering #454
Conversation
@@ -29,6 +29,7 @@ export class FrameRenderer extends Renderer<FrameElement> { | |||
if (sourceRange) { | |||
sourceRange.selectNodeContents(frameElement) | |||
this.currentElement.appendChild(sourceRange.extractContents()) | |||
frameElement.disconnectedCallback() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure of a way to add a test to guard against a regression.
The getEventListeners() function used to uncover this leak is defined in the console's suite of tools, and isn't available to the document.
I'm open to ideas!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe attach a listener that increments a global you can check?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think that the code within the connectedCallback()
and disconnectedCallback()
fire any events directly. It's also worth noting that we're manually invoking these methods within Turbo itself, so they're kind of "synthetic" connections, which makes a MutationObserver
similarly unpredictable, since the underlying element itself might not be attached to the browser's document.
@dhh this feels like an important fix to ship as part of |
Closes [hotwired#453] --- While loading a matching `<turbo-frame>` element from a response body, the `FrameController` "activates" it by invoking `FrameElement.connectedCallback()` after importing it into the document. During that activation, several event listeners are attached, including some on the `document`. Unfortunately, that `FrameElement.connectedCallback()` invocation is never mirrored with a `FrameElement.disconnectedCallback()` invocation, so the attached listeners are never detached, causing an accumulation. This commit invokes the `disconnectedCallback()` after loaded element's contents are extracted into the browser's document. [hotwired#453]: hotwired#453
666429f
to
da43934
Compare
Add test coverage to [hotwired#454][]. While the original fix was important to ship, it was unclear how to guard against re-introducing the leaks. This commit introduces the `withoutChangingEventListenersCount()` test helper to wrap a block within the test harness. While executing within the block, the driven page's `document.addEventListener` and `document.removeEventListener` functions are replaced with decorated functions that increment and decrement a counter. At the start of the block, capture and return how many times `addEventListener` has been invoked without a matching `removeEventListener`. At the end, capture and return the differential. [hotwired#454]: hotwired#454
The fix introduced in [hotwired#454][] resolved the issue, but introduced a matching `disconnectedCallback()` far-off in the `FrameRenderer`. This commit moves that call to occur _immediately_ after it's paired `element.connectedCallback()`. [hotwired#454]: hotwired#454
Add test coverage to [hotwired#454][]. While the original fix was important to ship, it was unclear how to guard against re-introducing the leaks. This commit introduces the `withoutChangingEventListenersCount()` test helper to wrap a block within the test harness. While executing within the block, the driven page's `document.addEventListener` and `document.removeEventListener` functions are replaced with decorated functions that increment and decrement a counter. At the start of the block, capture and return how many times `addEventListener` has been invoked without a matching `removeEventListener`. At the end, capture and return the differential. [hotwired#454]: hotwired#454
The fix introduced in [hotwired#454][] resolved the issue, but introduced a matching `disconnectedCallback()` far-off in the `FrameRenderer`. This commit moves that call to occur _immediately_ after it's paired `element.connectedCallback()`. [hotwired#454]: hotwired#454
The fix introduced in [hotwired#454][] resolved the issue, but introduced a matching `disconnectedCallback()` far-off in the `FrameRenderer`. This commit moves that call to occur _immediately_ after it's paired `element.connectedCallback()`. [hotwired#454]: hotwired#454
* Add test coverage for event listener leaking Add test coverage to [#454][]. While the original fix was important to ship, it was unclear how to guard against re-introducing the leaks. This commit introduces the `withoutChangingEventListenersCount()` test helper to wrap a block within the test harness. While executing within the block, the driven page's `document.addEventListener` and `document.removeEventListener` functions are replaced with decorated functions that increment and decrement a counter. At the start of the block, capture and return how many times `addEventListener` has been invoked without a matching `removeEventListener`. At the end, capture and return the differential. [#454]: #454 * Move the `disconnectedCallback()` call The fix introduced in [#454][] resolved the issue, but introduced a matching `disconnectedCallback()` far-off in the `FrameRenderer`. This commit moves that call to occur _immediately_ after it's paired `element.connectedCallback()`. [#454]: #454 Co-authored-by: David Heinemeier Hansson <david@basecamp.com>
Closes #453
While loading a matching
<turbo-frame>
element from a response body,the
FrameController
"activates" it by invokingFrameElement.connectedCallback()
after importing it into the document.During that activation, several event listeners are attached, including
some on the
document
.Unfortunately, that
FrameElement.connectedCallback()
invocation isnever mirrored with a
FrameElement.disconnectedCallback()
invocation,so the attached listeners are never detached, causing an accumulation.
This commit invokes the
disconnectedCallback()
after loaded element'scontents are extracted into the browser's document.