From e637895c386d5853179a9a7b6f0297b19a9360f2 Mon Sep 17 00:00:00 2001 From: Elliott Marquez <5981958+e111077@users.noreply.github.com> Date: Wed, 20 Dec 2023 13:55:29 -0800 Subject: [PATCH 1/2] expose functions to force the polyfill and make not exception with polyfills applied --- src/element-internals.ts | 59 +++++++++++++++++++++++++++++++-------- src/index.ts | 28 ++++++++++++++++++- src/mutation-observers.ts | 17 ++++++++--- 3 files changed, 88 insertions(+), 16 deletions(-) diff --git a/src/element-internals.ts b/src/element-internals.ts index dc8ab72..85210b0 100644 --- a/src/element-internals.ts +++ b/src/element-internals.ts @@ -306,7 +306,49 @@ export function isElementInternalsSupported(): boolean { ].every(prop => prop in featureDetectionElement.internals); } -if (!isElementInternalsSupported()) { +let hasElementInternalsPolyfillBeenApplied = false; +let hasCustomStateSetPolyfillBeenApplied = false; + +/** + * Forcibly applies the polyfill for CustomStateSet. + * + * https://developer.mozilla.org/en-US/docs/Web/API/CustomStateSet + */ +export function forceCustomStateSetPolyfill(attachInternals?: HTMLElement['attachInternals']) { + if (hasCustomStateSetPolyfillBeenApplied) { + return; + } + + hasCustomStateSetPolyfillBeenApplied = true; + window.CustomStateSet = CustomStateSet; + + if (attachInternals) { + HTMLElement.prototype.attachInternals = function(...args) { + const internals = attachInternals.call(this, args); + internals.states = new CustomStateSet(this); + return internals; + } + } +} + +/** + * Forcibly applies the polyfill for ElementInternals. Useful for situations + * like Chrome extensions where Chrome supports ElementInternals, but the + * CustomElements polyfill is required. + * + * https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals + * + * @param forceCustomStateSet Optional: when true, forces the + * [CustomStateSet](https://developer.mozilla.org/en-US/docs/Web/API/CustomStateSet) + * polyfill as well. + */ +export function forceElementInternalsPolyfill(forceCustomStateSet = true) { + if (hasElementInternalsPolyfillBeenApplied) { + return; + } + + hasElementInternalsPolyfillBeenApplied = true; + if (typeof window !== 'undefined') { /** @ts-expect-error: we need to replace the default ElementInternals */ window.ElementInternals = ElementInternals; @@ -391,15 +433,10 @@ if (!isElementInternalsSupported()) { patchFormPrototype(); } - if (typeof window !== 'undefined' && !window.CustomStateSet) { - window.CustomStateSet = CustomStateSet; - } -} else if (typeof window !== 'undefined' && !window.CustomStateSet) { - window.CustomStateSet = CustomStateSet; - const attachInternals = HTMLElement.prototype.attachInternals; - HTMLElement.prototype.attachInternals = function(...args) { - const internals = attachInternals.call(this, args); - internals.states = new CustomStateSet(this); - return internals; + if ( + forceCustomStateSet || + (typeof window !== "undefined" && !window.CustomStateSet) + ) { + forceCustomStateSetPolyfill(); } } diff --git a/src/index.ts b/src/index.ts index a334026..475f06e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,17 @@ -import { ElementInternals } from './element-internals.js'; +import { + ElementInternals, + forceCustomStateSetPolyfill, + forceElementInternalsPolyfill, + isElementInternalsSupported, +} from './element-internals.js'; import { CustomStateSet } from './CustomStateSet.js'; import './element-internals.js'; import { IElementInternals } from './types.js'; export * from './types.js'; +export { + forceCustomStateSetPolyfill, + forceElementInternalsPolyfill, +} from './element-internals.js'; declare global { interface Window { @@ -18,3 +27,20 @@ declare global { attachInternals(): ElementInternals&IElementInternals; } } + +// Deteermine whether the webcomponents polyfill has been applied. +const isCePolyfill = !!( + customElements as unknown as { + polyfillWrapFlushCallback: () => void; + } +).polyfillWrapFlushCallback; + +// custom elements polyfill is on. Do not auto-apply. User should determine +// whether to force or not. +if (!isCePolyfill) { + if (!isElementInternalsSupported()) { + forceElementInternalsPolyfill(false); + } else if (typeof window !== "undefined" && !window.CustomStateSet) { + forceCustomStateSetPolyfill(HTMLElement.prototype.attachInternals); + } +} diff --git a/src/mutation-observers.ts b/src/mutation-observers.ts index 8e324cc..a14cca2 100644 --- a/src/mutation-observers.ts +++ b/src/mutation-observers.ts @@ -164,10 +164,19 @@ export function fragmentObserverCallback(mutationList: MutationRecord[]): void { * @param fragment {DocumentFragment} */ export const deferUpgrade = (fragment: DocumentFragment) => { - const observer = new MutationObserver(fragmentObserverCallback) - observer.observe?.(fragment, { childList: true }); - documentFragmentMap.set(fragment, observer); -}; + const observer = new MutationObserver(fragmentObserverCallback); + // is this using shady DOM and is not actually a DocumentFragment? + if ( + window?.ShadyDOM?.inUse && + (fragment as unknown as { mode: string }).mode && + (fragment as unknown as { host: HTMLElement | null }).host + ) { + // using shady DOM polyfill. Best to just observe the host. + fragment = (fragment as ShadowRoot).host as unknown as DocumentFragment; + } + observer.observe?.(fragment, { childList: true }); + documentFragmentMap.set(fragment, observer); + }; export const observer = mutationObserverExists() ? new MutationObserver(observerCallback) : {} as MutationObserver; export const observerConfig: MutationObserverInit = { From 38bb7882075548bd082babfbc401a0663e55972b Mon Sep 17 00:00:00 2001 From: Elliott Marquez <5981958+e111077@users.noreply.github.com> Date: Wed, 20 Dec 2023 13:59:12 -0800 Subject: [PATCH 2/2] fix indentation --- src/mutation-observers.ts | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/mutation-observers.ts b/src/mutation-observers.ts index a14cca2..fdf0036 100644 --- a/src/mutation-observers.ts +++ b/src/mutation-observers.ts @@ -164,18 +164,18 @@ export function fragmentObserverCallback(mutationList: MutationRecord[]): void { * @param fragment {DocumentFragment} */ export const deferUpgrade = (fragment: DocumentFragment) => { - const observer = new MutationObserver(fragmentObserverCallback); - // is this using shady DOM and is not actually a DocumentFragment? - if ( - window?.ShadyDOM?.inUse && - (fragment as unknown as { mode: string }).mode && - (fragment as unknown as { host: HTMLElement | null }).host - ) { - // using shady DOM polyfill. Best to just observe the host. - fragment = (fragment as ShadowRoot).host as unknown as DocumentFragment; - } - observer.observe?.(fragment, { childList: true }); - documentFragmentMap.set(fragment, observer); + const observer = new MutationObserver(fragmentObserverCallback); + // is this using shady DOM and is not actually a DocumentFragment? + if ( + window?.ShadyDOM?.inUse && + (fragment as unknown as { mode: string }).mode && + (fragment as unknown as { host: HTMLElement | null }).host + ) { + // using shady DOM polyfill. Best to just observe the host. + fragment = (fragment as ShadowRoot).host as unknown as DocumentFragment; + } + observer.observe?.(fragment, { childList: true }); + documentFragmentMap.set(fragment, observer); }; export const observer = mutationObserverExists() ? new MutationObserver(observerCallback) : {} as MutationObserver;