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 `