fix(overlays): fix memory leaks of adopt-styles and OverlayMixin
* fix(overlays): fix memory leaks of adopt-styles and OverlayMixin * fix(overlays): support reconnecting the OverlayController to the OverlayManager * chore: move unregister logic to OverlayController --------- Co-authored-by: Thijs Louisse <Thijs.Louisse@ing.com>
This commit is contained in:
parent
2a989f47cf
commit
86ca2e072e
5 changed files with 57 additions and 10 deletions
5
.changeset/quiet-apricots-sneeze.md
Normal file
5
.changeset/quiet-apricots-sneeze.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'@lion/ui': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
[overlays] Fixed memory leak caused adopted style cache and the `OverlayMixin` not releasing the `OverlayController` on teardown
|
||||||
|
|
@ -192,7 +192,6 @@ export class OverlayController extends EventTarget {
|
||||||
zIndex: 9999,
|
zIndex: 9999,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.manager.add(this);
|
|
||||||
/** @protected */
|
/** @protected */
|
||||||
this._contentId = `overlay-content--${Math.random().toString(36).slice(2, 10)}`;
|
this._contentId = `overlay-content--${Math.random().toString(36).slice(2, 10)}`;
|
||||||
/** @private */
|
/** @private */
|
||||||
|
|
@ -475,6 +474,14 @@ export class OverlayController extends EventTarget {
|
||||||
this._init();
|
this._init();
|
||||||
/** @private */
|
/** @private */
|
||||||
this.__elementToFocusAfterHide = undefined;
|
this.__elementToFocusAfterHide = undefined;
|
||||||
|
|
||||||
|
if (!this.#isRegisteredOnManager()) {
|
||||||
|
this.manager.add(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#isRegisteredOnManager() {
|
||||||
|
return Boolean(this.manager.list.find(ctrl => this === ctrl));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1354,6 +1361,10 @@ export class OverlayController extends EventTarget {
|
||||||
this.__handleOverlayStyles({ phase: 'teardown' });
|
this.__handleOverlayStyles({ phase: 'teardown' });
|
||||||
this._handleFeatures({ phase: 'teardown' });
|
this._handleFeatures({ phase: 'teardown' });
|
||||||
this.__wrappingDialogNode?.removeEventListener('cancel', this.__cancelHandler);
|
this.__wrappingDialogNode?.removeEventListener('cancel', this.__cancelHandler);
|
||||||
|
|
||||||
|
if (this.#isRegisteredOnManager()) {
|
||||||
|
this.manager.remove(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @private */
|
/** @private */
|
||||||
|
|
|
||||||
|
|
@ -182,11 +182,13 @@ export const OverlayMixinImplementation = superclass =>
|
||||||
this._setupOverlayCtrl();
|
this._setupOverlayCtrl();
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnectedCallback() {
|
async disconnectedCallback() {
|
||||||
super.disconnectedCallback();
|
super.disconnectedCallback();
|
||||||
if (this._overlayCtrl) {
|
|
||||||
this._teardownOverlayCtrl();
|
if (!this._overlayCtrl) return;
|
||||||
}
|
if (await this.#isMovingInDom()) return;
|
||||||
|
|
||||||
|
this._teardownOverlayCtrl();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -253,6 +255,7 @@ export const OverlayMixinImplementation = superclass =>
|
||||||
_teardownOverlayCtrl() {
|
_teardownOverlayCtrl() {
|
||||||
this._teardownOpenCloseListeners();
|
this._teardownOpenCloseListeners();
|
||||||
this.__teardownSyncFromOverlayController();
|
this.__teardownSyncFromOverlayController();
|
||||||
|
|
||||||
/** @type {OverlayController} */
|
/** @type {OverlayController} */
|
||||||
(this._overlayCtrl).teardown();
|
(this._overlayCtrl).teardown();
|
||||||
}
|
}
|
||||||
|
|
@ -387,5 +390,15 @@ export const OverlayMixinImplementation = superclass =>
|
||||||
ctrl._popper.update();
|
ctrl._popper.update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When we're moving around in dom, disconnectedCallback gets called.
|
||||||
|
* Before we decide to teardown, let's wait to see if we were not just moving nodes around.
|
||||||
|
* @return {Promise<boolean>}
|
||||||
|
*/
|
||||||
|
async #isMovingInDom() {
|
||||||
|
await this.updateComplete;
|
||||||
|
return this.isConnected;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
export const OverlayMixin = dedupeMixin(OverlayMixinImplementation);
|
export const OverlayMixin = dedupeMixin(OverlayMixinImplementation);
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ export const _adoptStyleUtils = {
|
||||||
adoptStyles: undefined,
|
adoptStyles: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
const styleCache = new Map();
|
const styleCache = new WeakMap();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {CSSStyleSheet} cssStyleSheet
|
* @param {CSSStyleSheet} cssStyleSheet
|
||||||
|
|
@ -81,10 +81,10 @@ function adoptStyleWhenAdoptedStylesheetsNotSupported(
|
||||||
*/
|
*/
|
||||||
function handleCache(renderRoot, style, { teardown = false } = {}) {
|
function handleCache(renderRoot, style, { teardown = false } = {}) {
|
||||||
let haltFurtherExecution = false;
|
let haltFurtherExecution = false;
|
||||||
if (!styleCache.has(renderRoot)) {
|
if (renderRoot && !styleCache.has(renderRoot)) {
|
||||||
styleCache.set(renderRoot, []);
|
styleCache.set(renderRoot, []);
|
||||||
}
|
}
|
||||||
const addedStylesForRoot = styleCache.get(renderRoot);
|
const addedStylesForRoot = styleCache.get(renderRoot) ?? [];
|
||||||
const foundStyle = addedStylesForRoot.find(
|
const foundStyle = addedStylesForRoot.find(
|
||||||
(/** @type {import("lit").CSSResultOrNative} */ addedStyle) => style === addedStyle,
|
(/** @type {import("lit").CSSResultOrNative} */ addedStyle) => style === addedStyle,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,14 @@ async function mimicEscapePress(element) {
|
||||||
await aTimeout(0);
|
await aTimeout(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {OverlayController} ctrlToFind
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
function isRegisteredOnManager(ctrlToFind) {
|
||||||
|
return Boolean(ctrlToFind.manager?.list?.find(ctrl => ctrlToFind === ctrl));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make sure that all browsers serialize html in a similar way
|
* Make sure that all browsers serialize html in a similar way
|
||||||
* (Firefox tends to output empty style attrs)
|
* (Firefox tends to output empty style attrs)
|
||||||
|
|
@ -275,8 +283,18 @@ describe('OverlayController', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: Add teardown feature tests
|
// TODO: Add more teardown feature tests
|
||||||
describe('Teardown', () => {});
|
describe('Teardown', () => {
|
||||||
|
it('unregisters itself from overlayManager', async () => {
|
||||||
|
const ctrl = new OverlayController(withGlobalTestConfig());
|
||||||
|
|
||||||
|
expect(isRegisteredOnManager(ctrl)).to.be.true;
|
||||||
|
|
||||||
|
ctrl.teardown();
|
||||||
|
|
||||||
|
expect(isRegisteredOnManager(ctrl)).to.be.false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('Node Configuration', () => {
|
describe('Node Configuration', () => {
|
||||||
describe('Content', async () => {
|
describe('Content', async () => {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue