From b57f302d99b756946a7ebcabae1253387cbd84bb Mon Sep 17 00:00:00 2001 From: Gaubee Date: Wed, 21 Jan 2026 13:56:22 +0800 Subject: [PATCH] fix(wujie-core): proxy document.adoptedStyleSheets to shadowRoot Proxy document.adoptedStyleSheets to shadowRoot.adoptedStyleSheets in non-degrade mode. This fixes the issue where constructed stylesheets cannot be shared between the iframe document and shadowRoot, causing WebComponents using adoptedStyleSheets (like IconPark) to fail with 'Sharing constructed stylesheets in multiple documents is not allowed' error. fix #209 --- .../integration/adoptedStyleSheets.test.ts | 81 +++++++++++++++++++ packages/wujie-core/src/shadow.ts | 22 +++++ 2 files changed, 103 insertions(+) create mode 100644 packages/wujie-core/__test__/integration/adoptedStyleSheets.test.ts diff --git a/packages/wujie-core/__test__/integration/adoptedStyleSheets.test.ts b/packages/wujie-core/__test__/integration/adoptedStyleSheets.test.ts new file mode 100644 index 000000000..f0cfe2891 --- /dev/null +++ b/packages/wujie-core/__test__/integration/adoptedStyleSheets.test.ts @@ -0,0 +1,81 @@ +import { awaitConsoleLogMessage } from "./utils"; +import { reactMainAppInfoMap, vueMainAppInfoMap, vueMainAppNameList, reactMainAppNameList } from "./common"; + +const generateTest = ( + AppInfoMap: typeof reactMainAppInfoMap | typeof vueMainAppInfoMap, + AppNameList: typeof vueMainAppNameList | typeof reactMainAppNameList, +) => { + AppNameList.slice(0, 1).forEach((appName) => { + it("adoptedStyleSheets proxy test", async () => { + const childApplicationMountedPromise = awaitConsoleLogMessage(page, AppInfoMap[appName].mountedMessage); + await page.click(AppInfoMap[appName].linkSelector); + await childApplicationMountedPromise; + + const result = await page.evaluate((childName) => { + const childWindowCollection = [window[0], window[1], window[2], window[3], window[4], window[5]]; + const childWindow: any = childWindowCollection.find((itemWindow) => itemWindow.name === childName); + const sandbox = childWindow.__WUJIE; + + if (sandbox.degrade) { + return { skipped: true, reason: "degrade mode" }; + } + + const shadowRoot = sandbox.shadowRoot as ShadowRoot; + const iframeDocument = childWindow.document; + + const sheet = new childWindow.CSSStyleSheet(); + sheet.replaceSync("body { --test-var: 1; }"); + + iframeDocument.adoptedStyleSheets = [sheet]; + + const isSameReference = iframeDocument.adoptedStyleSheets === shadowRoot.adoptedStyleSheets; + const hasSheet = shadowRoot.adoptedStyleSheets.length === 1; + + iframeDocument.adoptedStyleSheets = []; + const isCleared = shadowRoot.adoptedStyleSheets.length === 0; + + return { + skipped: false, + isSameReference, + hasSheet, + isCleared, + }; + }, appName); + + if (result.skipped) { + console.log(`Skipped: ${result.reason}`); + return; + } + + expect(result.isSameReference).toBe(true); + expect(result.hasSheet).toBe(true); + expect(result.isCleared).toBe(true); + }); + }); +}; + +describe("main react adoptedStyleSheets", () => { + beforeAll(async () => { + await page.evaluateOnNewDocument(() => { + localStorage.clear(); + localStorage.setItem("preload", "false"); + localStorage.setItem("degrade", "false"); + }); + await page.goto("http://localhost:7700/"); + }); + + generateTest(reactMainAppInfoMap, reactMainAppNameList); +}); + +describe("main vue adoptedStyleSheets", () => { + beforeAll(async () => { + await page.evaluateOnNewDocument(() => { + localStorage.clear(); + localStorage.setItem("preload", "false"); + localStorage.setItem("degrade", "false"); + }); + await page.goto("http://localhost:8000/"); + }); + + generateTest(vueMainAppInfoMap, vueMainAppNameList); +}); diff --git a/packages/wujie-core/src/shadow.ts b/packages/wujie-core/src/shadow.ts index 130b0654c..15465fbc3 100644 --- a/packages/wujie-core/src/shadow.ts +++ b/packages/wujie-core/src/shadow.ts @@ -46,6 +46,28 @@ export function defineWujieWebComponent() { const sandbox = getWujieById(this.getAttribute(WUJIE_APP_ID)); patchElementEffect(shadowRoot, sandbox.iframe.contentWindow); sandbox.shadowRoot = shadowRoot; + // 将 document.adoptedStyleSheets 代理到 shadowRoot.adoptedStyleSheets + if (!sandbox.degrade) { + const iframeWindow = sandbox.iframe.contentWindow; + const descriptor: PropertyDescriptor = { + configurable: true, + enumerable: true, + get: () => shadowRoot.adoptedStyleSheets, + set: (sheets: CSSStyleSheet[]) => { + shadowRoot.adoptedStyleSheets = sheets; + }, + }; + try { + Object.defineProperty(iframeWindow.Document.prototype, "adoptedStyleSheets", descriptor); + } catch (e) { + /* ignore */ + } + try { + Object.defineProperty(iframeWindow.document, "adoptedStyleSheets", descriptor); + } catch (e) { + /* ignore */ + } + } } disconnectedCallback(): void {