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

fix: Mitigate Safari memory leak for input element #4250

Closed
wants to merge 2 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
67 changes: 59 additions & 8 deletions packages/textfield/src/Textfield.ts
Expand Up @@ -204,6 +204,17 @@ export class TextfieldBase extends ManageHelpText(
return this.inputElement;
}

private _eventHandlers = {
input: this.handleInput.bind(this),
change: this.handleChange.bind(this),
focus: this.onFocus.bind(this),
blur: this.onBlur.bind(this),
};

protected _firstUpdateAfterConnected = false;

protected abortController?: AbortController;

/**
* Sets the start and end positions of the current selection.
*
Expand Down Expand Up @@ -316,10 +327,6 @@ export class TextfieldBase extends ManageHelpText(
pattern=${ifDefined(this.pattern)}
placeholder=${this.placeholder}
.value=${this.displayValue}
@change=${this.handleChange}
@input=${this.handleInput}
@focus=${this.onFocus}
@blur=${this.onBlur}
?disabled=${this.disabled}
?required=${this.required}
?readonly=${this.readonly}
Expand Down Expand Up @@ -351,10 +358,6 @@ export class TextfieldBase extends ManageHelpText(
pattern=${ifDefined(this.pattern)}
placeholder=${this.placeholder}
.value=${live(this.displayValue)}
@change=${this.handleChange}
@input=${this.handleInput}
@focus=${this.onFocus}
@blur=${this.onBlur}
?disabled=${this.disabled}
?required=${this.required}
?readonly=${this.readonly}
Expand Down Expand Up @@ -389,6 +392,54 @@ export class TextfieldBase extends ManageHelpText(
super.update(changedProperties);
}

public override connectedCallback(): void {
super.connectedCallback();
this._firstUpdateAfterConnected = true;
this.requestUpdate();
}

public override disconnectedCallback(): void {
// Cleanup all event listeners and and remove input element from DOM
this.abortController?.abort();
super.disconnectedCallback();
}

protected firstUpdateAfterConnected(): void {
this.abortController = new AbortController();
const { signal } = this.abortController;

this.inputElement.addEventListener(
'change',
this._eventHandlers['change'],
{ signal }
);
this.inputElement.addEventListener(
'input',
this._eventHandlers['input'],
{ signal }
);
this.inputElement.addEventListener(
'focus',
this._eventHandlers['focus'],
{ signal }
);
this.inputElement.addEventListener(
'blur',
this._eventHandlers['blur'] as EventListener,
{ signal }
);
}

protected override updated(changedProperties: PropertyValues): void {
super.updated(changedProperties);
// Adding this here instead of firstUpdated because we want to make sure
// this is called again on the first update after a previous disconnect
if (this._firstUpdateAfterConnected) {
this._firstUpdateAfterConnected = false;
this.firstUpdateAfterConnected();
}
}

public checkValidity(): boolean {
let validity = this.inputElement.checkValidity();
if (this.required || (this.value && this.pattern)) {
Expand Down