diff --git a/packages/@lwc/engine-core/src/framework/base-lightning-element.ts b/packages/@lwc/engine-core/src/framework/base-lightning-element.ts index 8c68712b6d..16ced798fc 100644 --- a/packages/@lwc/engine-core/src/framework/base-lightning-element.ts +++ b/packages/@lwc/engine-core/src/framework/base-lightning-element.ts @@ -20,9 +20,9 @@ import { freeze, hasOwnProperty, isFunction, - isString, isNull, isObject, + isString, isUndefined, KEY__SYNTHETIC_MODE, keys, @@ -35,7 +35,7 @@ import { applyAriaReflection } from '../libs/aria-reflection/aria-reflection'; import { HTMLElementOriginalDescriptors } from './html-properties'; import { getWrappedComponentsListener } from './component'; -import { vmBeingConstructed, isBeingConstructed, isInvokingRender } from './invoker'; +import { isBeingConstructed, isInvokingRender, vmBeingConstructed } from './invoker'; import { associateVM, getAssociatedVM, @@ -47,16 +47,17 @@ import { } from './vm'; import { componentValueObserved } from './mutation-tracker'; import { - patchShadowRootWithRestrictions, - patchLightningElementPrototypeWithRestrictions, patchCustomElementWithRestrictions, + patchLightningElementPrototypeWithRestrictions, + patchShadowRootWithRestrictions, } from './restrictions'; -import { Template, isUpdatingTemplate, getVMBeingRendered } from './template'; +import { getVMBeingRendered, isUpdatingTemplate, Template } from './template'; import { HTMLElementConstructor } from './base-bridge-element'; import { updateComponentValue } from './update-component-value'; import { markLockerLiveObject } from './membrane'; import { TemplateStylesheetFactories } from './stylesheet'; import { instrumentInstance } from './runtime-instrumentation'; +import { applyShadowMigrateMode } from './shadow-migration-mode'; /** * This operation is called with a descriptor of an standard html property @@ -289,6 +290,14 @@ function doAttachShadow(vm: VM): ShadowRoot { patchShadowRootWithRestrictions(shadowRoot); } + if ( + process.env.IS_BROWSER && + lwcRuntimeFlags.ENABLE_FORCE_SHADOW_MIGRATE_MODE && + vm.shadowMigrateMode + ) { + applyShadowMigrateMode(shadowRoot); + } + return shadowRoot; } diff --git a/packages/@lwc/engine-core/src/framework/shadow-migration-mode.ts b/packages/@lwc/engine-core/src/framework/shadow-migration-mode.ts new file mode 100644 index 0000000000..52b01b2e61 --- /dev/null +++ b/packages/@lwc/engine-core/src/framework/shadow-migration-mode.ts @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2023, salesforce.com, inc. + * All rights reserved. + * SPDX-License-Identifier: MIT + * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT + */ +import { logWarnOnce } from '../shared/logger'; + +let globalStylesheet: CSSStyleSheet | undefined; + +function isStyleElement(elm: Element): elm is HTMLStyleElement { + return elm.tagName === 'STYLE'; +} + +async function fetchStylesheet(elm: HTMLStyleElement | HTMLLinkElement) { + if (isStyleElement(elm)) { + return elm.textContent; + } else { + // + const { href } = elm; + try { + return await (await fetch(href)).text(); + } catch (err) { + logWarnOnce(`Ignoring cross-origin stylesheet in migrate mode: ${href}`); + // ignore errors with cross-origin stylesheets - nothing we can do for those + return ''; + } + } +} + +function initGlobalStylesheet() { + const stylesheet = new CSSStyleSheet(); + const elmsToPromises = new Map(); + let lastSeenLength = 0; + + const copyToGlobalStylesheet = () => { + const elms = document.head.querySelectorAll( + 'style:not([data-rendered-by-lwc]),link[rel="stylesheet"]' + ); + if (elms.length === lastSeenLength) { + return; // nothing to update + } + lastSeenLength = elms.length; + const promises = [...(elms as unknown as Iterable)].map( + (elm) => { + let promise = elmsToPromises.get(elm); + if (!promise) { + // Cache the promise + promise = fetchStylesheet(elm); + elmsToPromises.set(elm, promise); + } + return promise; + } + ); + Promise.all(promises).then((stylesheetTexts) => { + // When replaceSync() is called, the entire contents of the constructable stylesheet are replaced + // with the copied+concatenated styles. This means that any shadow root's adoptedStyleSheets that + // contains this constructable stylesheet will immediately get the new styles. + stylesheet.replaceSync(stylesheetTexts.join('\n')); + }); + }; + + const headObserver = new MutationObserver(copyToGlobalStylesheet); + + // By observing only the childList, note that we are not covering the case where someone changes an `href` + // on an existing `, or the textContent on an existing `