/** * @author Kuitos * @since 2020-10-13 */ import type { Freer } from '../../../interfaces'; import { nativeGlobal } from '../../../utils'; import { getCurrentRunningApp } from '../../common'; import type { ContainerConfig } from './common'; import { calcAppCount, getAppWrapperHeadElement, isAllAppsUnmounted, isHijackingTag, patchHTMLDynamicAppendPrototypeFunctions, rawHeadAppendChild, rebuildCSSRules, recordStyledComponentsCSSRules, styleElementTargetSymbol, } from './common'; // Get native global window with a sandbox disgusted way, thus we could share it between qiankun instances🤪 Object.defineProperty(nativeGlobal, '__proxyAttachContainerConfigMap__', { enumerable: false, writable: true }); // Share proxyAttachContainerConfigMap between multiple qiankun instance, thus they could access the same record nativeGlobal.__proxyAttachContainerConfigMap__ = nativeGlobal.__proxyAttachContainerConfigMap__ || new WeakMap(); const proxyAttachContainerConfigMap: WeakMap = nativeGlobal.__proxyAttachContainerConfigMap__; const elementAttachContainerConfigMap = new WeakMap(); const docCreatePatchedMap = new WeakMap(); function patchDocumentCreateElement() { const docCreateElementFnBeforeOverwrite = docCreatePatchedMap.get(document.createElement); if (!docCreateElementFnBeforeOverwrite) { const rawDocumentCreateElement = document.createElement; Document.prototype.createElement = function createElement( this: Document, tagName: K, options?: ElementCreationOptions, ): HTMLElement { const element = rawDocumentCreateElement.call(this, tagName, options); if (isHijackingTag(tagName)) { const { window: currentRunningSandboxProxy } = getCurrentRunningApp() || {}; if (currentRunningSandboxProxy) { const proxyContainerConfig = proxyAttachContainerConfigMap.get(currentRunningSandboxProxy); if (proxyContainerConfig) { elementAttachContainerConfigMap.set(element, proxyContainerConfig); } } } return element; }; // It means it have been overwritten while createElement is an own property of document if (document.hasOwnProperty('createElement')) { document.createElement = Document.prototype.createElement; } docCreatePatchedMap.set(Document.prototype.createElement, rawDocumentCreateElement); } return function unpatch() { if (docCreateElementFnBeforeOverwrite) { Document.prototype.createElement = docCreateElementFnBeforeOverwrite; document.createElement = docCreateElementFnBeforeOverwrite; } }; } export function patchStrictSandbox( appName: string, appWrapperGetter: () => HTMLElement | ShadowRoot, proxy: Window, mounting = true, scopedCSS = false, excludeAssetFilter?: CallableFunction, speedySandbox = false, ): Freer { let containerConfig = proxyAttachContainerConfigMap.get(proxy); if (!containerConfig) { containerConfig = { appName, proxy, appWrapperGetter, dynamicStyleSheetElements: [], strictGlobal: true, speedySandbox, excludeAssetFilter, scopedCSS, }; proxyAttachContainerConfigMap.set(proxy, containerConfig); } // all dynamic style sheets are stored in proxy container const { dynamicStyleSheetElements } = containerConfig; const unpatchDocumentCreate = patchDocumentCreateElement(); const unpatchDynamicAppendPrototypeFunctions = patchHTMLDynamicAppendPrototypeFunctions( (element) => elementAttachContainerConfigMap.has(element), (element) => elementAttachContainerConfigMap.get(element)!, ); if (!mounting) calcAppCount(appName, 'increase', 'bootstrapping'); if (mounting) calcAppCount(appName, 'increase', 'mounting'); return function free() { if (!mounting) calcAppCount(appName, 'decrease', 'bootstrapping'); if (mounting) calcAppCount(appName, 'decrease', 'mounting'); // release the overwritten prototype after all the micro apps unmounted if (isAllAppsUnmounted()) { unpatchDynamicAppendPrototypeFunctions(); unpatchDocumentCreate(); } recordStyledComponentsCSSRules(dynamicStyleSheetElements); // As now the sub app content all wrapped with a special id container, // the dynamic style sheet would be removed automatically while unmoutting return function rebuild() { rebuildCSSRules(dynamicStyleSheetElements, (stylesheetElement) => { const appWrapper = appWrapperGetter(); if (!appWrapper.contains(stylesheetElement)) { const mountDom = stylesheetElement[styleElementTargetSymbol] === 'head' ? getAppWrapperHeadElement(appWrapper) : appWrapper; rawHeadAppendChild.call(mountDom, stylesheetElement); return true; } return false; }); }; }; }