xpe 0.0.0-alpha.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +627 -71
- package/dist/child/app-client.d.ts +44 -0
- package/dist/child/app-client.js +151 -0
- package/dist/child/index.d.ts +3 -0
- package/dist/child/index.js +3 -0
- package/dist/child/lifecycle.d.ts +12 -0
- package/dist/child/lifecycle.js +47 -0
- package/dist/child/mock-host.d.ts +8 -0
- package/dist/child/mock-host.js +41 -0
- package/dist/core/bridge.d.ts +28 -0
- package/dist/core/bridge.js +19 -0
- package/dist/core/events.d.ts +13 -0
- package/dist/core/events.js +56 -0
- package/dist/core/index.d.ts +3 -0
- package/dist/core/index.js +3 -0
- package/dist/core/types.d.ts +102 -0
- package/dist/core/types.js +20 -0
- package/dist/dev-shell/index.d.ts +26 -0
- package/dist/dev-shell/index.js +56 -0
- package/dist/host/app-path-router.d.ts +33 -0
- package/dist/host/app-path-router.js +183 -0
- package/dist/host/host-runtime.d.ts +29 -0
- package/dist/host/host-runtime.js +176 -0
- package/dist/host/index.d.ts +1 -0
- package/dist/host/index.js +1 -0
- package/dist/host/xpe-host.d.ts +41 -0
- package/dist/host/xpe-host.js +61 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/kernel/common.d.ts +62 -0
- package/dist/kernel/common.js +207 -0
- package/dist/kernel/constant.d.ts +26 -0
- package/dist/kernel/constant.js +42 -0
- package/dist/kernel/effect.d.ts +32 -0
- package/dist/kernel/effect.js +529 -0
- package/dist/kernel/entry.d.ts +43 -0
- package/dist/kernel/entry.js +224 -0
- package/dist/kernel/event.d.ts +22 -0
- package/dist/kernel/event.js +120 -0
- package/dist/kernel/iframe.d.ts +141 -0
- package/dist/kernel/iframe.js +1104 -0
- package/dist/kernel/index.d.ts +25 -0
- package/dist/kernel/index.js +86 -0
- package/dist/kernel/plugin.d.ts +36 -0
- package/dist/kernel/plugin.js +68 -0
- package/dist/kernel/proxy.d.ts +17 -0
- package/dist/kernel/proxy.js +440 -0
- package/dist/kernel/runtime-engine.d.ts +154 -0
- package/dist/kernel/runtime-engine.js +209 -0
- package/dist/kernel/sandbox.d.ts +235 -0
- package/dist/kernel/sandbox.js +879 -0
- package/dist/kernel/shadow.d.ts +64 -0
- package/dist/kernel/shadow.js +385 -0
- package/dist/kernel/sync.d.ts +21 -0
- package/dist/kernel/sync.js +185 -0
- package/dist/kernel/template.d.ts +67 -0
- package/dist/kernel/template.js +226 -0
- package/dist/kernel/tracker.d.ts +44 -0
- package/dist/kernel/tracker.js +92 -0
- package/dist/kernel/utils.d.ts +100 -0
- package/dist/kernel/utils.js +325 -0
- package/dist/kernel/xpe-bridge.d.ts +27 -0
- package/dist/kernel/xpe-bridge.js +205 -0
- package/dist/shared/index.d.ts +1 -0
- package/dist/shared/index.js +3 -0
- package/package.json +79 -57
- package/LICENSE +0 -21
- package/framework/es/core/Application.d.ts +0 -126
- package/framework/es/core/Command/Command.d.ts +0 -7
- package/framework/es/core/Container/Container.d.ts +0 -80
- package/framework/es/core/Contracts/AxiosAdapter.d.ts +0 -12
- package/framework/es/core/Helper/Helper.d.ts +0 -43
- package/framework/es/core/Helper/Storage/WebStorage.d.ts +0 -71
- package/framework/es/core/Libs/Exception.d.ts +0 -24
- package/framework/es/core/Libs/Lib.d.ts +0 -72
- package/framework/es/core/Provider/CommandProvider.d.ts +0 -4
- package/framework/es/core/Provider/HelperProvider.d.ts +0 -4
- package/framework/es/core/Provider/HttpProvider.d.ts +0 -13
- package/framework/es/core/Provider/ServiceProvider.d.ts +0 -15
- package/framework/es/core/Service/HttpService.d.ts +0 -133
- package/framework/es/core/Service/Service.d.ts +0 -6
- package/framework/es/core/Spanner/RESTful.d.ts +0 -30
- package/framework/es/core/Transformer/Transformer.d.ts +0 -46
- package/framework/es/core/Type/adapters.d.ts +0 -25
- package/framework/es/core/Type/types.d.ts +0 -161
- package/framework/es/core/TypeFunc/index.d.ts +0 -6
- package/framework/es/core/Wrenches/EventBus.d.ts +0 -56
- package/framework/es/core/Wrenches/is.d.ts +0 -19
- package/framework/es/ege.js +0 -4
- package/framework/es/ege.js.map +0 -1
- package/framework/es/index.d.ts +0 -13
- package/framework/es/pdd.js +0 -4
- package/framework/es/pdd.js.map +0 -1
- package/framework/es/src/core/Application.d.ts +0 -127
- package/framework/es/src/core/Application.d.ts.map +0 -1
- package/framework/es/src/core/Command/Command.d.ts +0 -8
- package/framework/es/src/core/Command/Command.d.ts.map +0 -1
- package/framework/es/src/core/Container/Container.d.ts +0 -81
- package/framework/es/src/core/Container/Container.d.ts.map +0 -1
- package/framework/es/src/core/Contracts/AxiosAdapter.d.ts +0 -13
- package/framework/es/src/core/Contracts/AxiosAdapter.d.ts.map +0 -1
- package/framework/es/src/core/Helper/Helper.d.ts +0 -45
- package/framework/es/src/core/Helper/Helper.d.ts.map +0 -1
- package/framework/es/src/core/Helper/Storage/WebStorage.d.ts +0 -72
- package/framework/es/src/core/Helper/Storage/WebStorage.d.ts.map +0 -1
- package/framework/es/src/core/Libs/Exception.d.ts +0 -25
- package/framework/es/src/core/Libs/Exception.d.ts.map +0 -1
- package/framework/es/src/core/Libs/Lib.d.ts +0 -73
- package/framework/es/src/core/Libs/Lib.d.ts.map +0 -1
- package/framework/es/src/core/Provider/CommandProvider.d.ts +0 -5
- package/framework/es/src/core/Provider/CommandProvider.d.ts.map +0 -1
- package/framework/es/src/core/Provider/HelperProvider.d.ts +0 -5
- package/framework/es/src/core/Provider/HelperProvider.d.ts.map +0 -1
- package/framework/es/src/core/Provider/HttpProvider.d.ts +0 -14
- package/framework/es/src/core/Provider/HttpProvider.d.ts.map +0 -1
- package/framework/es/src/core/Provider/ServiceProvider.d.ts +0 -16
- package/framework/es/src/core/Provider/ServiceProvider.d.ts.map +0 -1
- package/framework/es/src/core/Service/HttpService.d.ts +0 -128
- package/framework/es/src/core/Service/HttpService.d.ts.map +0 -1
- package/framework/es/src/core/Service/Service.d.ts +0 -7
- package/framework/es/src/core/Service/Service.d.ts.map +0 -1
- package/framework/es/src/core/Spanner/RESTful.d.ts +0 -32
- package/framework/es/src/core/Spanner/RESTful.d.ts.map +0 -1
- package/framework/es/src/core/Transformer/Transformer.d.ts +0 -47
- package/framework/es/src/core/Transformer/Transformer.d.ts.map +0 -1
- package/framework/es/src/core/Type/adapters.d.ts +0 -26
- package/framework/es/src/core/Type/adapters.d.ts.map +0 -1
- package/framework/es/src/core/Type/types.d.ts +0 -186
- package/framework/es/src/core/Type/types.d.ts.map +0 -1
- package/framework/es/src/core/TypeFunc/index.d.ts +0 -8
- package/framework/es/src/core/TypeFunc/index.d.ts.map +0 -1
- package/framework/es/src/core/Wrenches/EventBus.d.ts +0 -57
- package/framework/es/src/core/Wrenches/EventBus.d.ts.map +0 -1
- package/framework/es/src/core/Wrenches/is.d.ts +0 -20
- package/framework/es/src/core/Wrenches/is.d.ts.map +0 -1
- package/framework/es/src/index.d.ts +0 -14
- package/framework/es/src/index.d.ts.map +0 -1
- package/framework/es/src/utils.d.ts +0 -17
- package/framework/es/src/utils.d.ts.map +0 -1
- package/framework/es/utils.d.ts +0 -16
- package/framework/umd/core/Application.d.ts +0 -126
- package/framework/umd/core/Command/Command.d.ts +0 -7
- package/framework/umd/core/Container/Container.d.ts +0 -80
- package/framework/umd/core/Contracts/AxiosAdapter.d.ts +0 -12
- package/framework/umd/core/Helper/Helper.d.ts +0 -43
- package/framework/umd/core/Helper/Storage/WebStorage.d.ts +0 -71
- package/framework/umd/core/Libs/Exception.d.ts +0 -24
- package/framework/umd/core/Libs/Lib.d.ts +0 -72
- package/framework/umd/core/Provider/CommandProvider.d.ts +0 -4
- package/framework/umd/core/Provider/HelperProvider.d.ts +0 -4
- package/framework/umd/core/Provider/HttpProvider.d.ts +0 -13
- package/framework/umd/core/Provider/ServiceProvider.d.ts +0 -15
- package/framework/umd/core/Service/HttpService.d.ts +0 -133
- package/framework/umd/core/Service/Service.d.ts +0 -6
- package/framework/umd/core/Spanner/RESTful.d.ts +0 -30
- package/framework/umd/core/Transformer/Transformer.d.ts +0 -46
- package/framework/umd/core/Type/adapters.d.ts +0 -25
- package/framework/umd/core/Type/types.d.ts +0 -161
- package/framework/umd/core/TypeFunc/index.d.ts +0 -6
- package/framework/umd/core/Wrenches/EventBus.d.ts +0 -56
- package/framework/umd/core/Wrenches/is.d.ts +0 -19
- package/framework/umd/ege.js +0 -4
- package/framework/umd/ege.js.map +0 -1
- package/framework/umd/index.d.ts +0 -13
- package/framework/umd/pdd.js +0 -4
- package/framework/umd/pdd.js.map +0 -1
- package/framework/umd/src/core/Application.d.ts +0 -127
- package/framework/umd/src/core/Application.d.ts.map +0 -1
- package/framework/umd/src/core/Command/Command.d.ts +0 -8
- package/framework/umd/src/core/Command/Command.d.ts.map +0 -1
- package/framework/umd/src/core/Container/Container.d.ts +0 -81
- package/framework/umd/src/core/Container/Container.d.ts.map +0 -1
- package/framework/umd/src/core/Contracts/AxiosAdapter.d.ts +0 -13
- package/framework/umd/src/core/Contracts/AxiosAdapter.d.ts.map +0 -1
- package/framework/umd/src/core/Helper/Helper.d.ts +0 -45
- package/framework/umd/src/core/Helper/Helper.d.ts.map +0 -1
- package/framework/umd/src/core/Helper/Storage/WebStorage.d.ts +0 -72
- package/framework/umd/src/core/Helper/Storage/WebStorage.d.ts.map +0 -1
- package/framework/umd/src/core/Libs/Exception.d.ts +0 -25
- package/framework/umd/src/core/Libs/Exception.d.ts.map +0 -1
- package/framework/umd/src/core/Libs/Lib.d.ts +0 -73
- package/framework/umd/src/core/Libs/Lib.d.ts.map +0 -1
- package/framework/umd/src/core/Provider/CommandProvider.d.ts +0 -5
- package/framework/umd/src/core/Provider/CommandProvider.d.ts.map +0 -1
- package/framework/umd/src/core/Provider/HelperProvider.d.ts +0 -5
- package/framework/umd/src/core/Provider/HelperProvider.d.ts.map +0 -1
- package/framework/umd/src/core/Provider/HttpProvider.d.ts +0 -14
- package/framework/umd/src/core/Provider/HttpProvider.d.ts.map +0 -1
- package/framework/umd/src/core/Provider/ServiceProvider.d.ts +0 -16
- package/framework/umd/src/core/Provider/ServiceProvider.d.ts.map +0 -1
- package/framework/umd/src/core/Service/HttpService.d.ts +0 -128
- package/framework/umd/src/core/Service/HttpService.d.ts.map +0 -1
- package/framework/umd/src/core/Service/Service.d.ts +0 -7
- package/framework/umd/src/core/Service/Service.d.ts.map +0 -1
- package/framework/umd/src/core/Spanner/RESTful.d.ts +0 -32
- package/framework/umd/src/core/Spanner/RESTful.d.ts.map +0 -1
- package/framework/umd/src/core/Transformer/Transformer.d.ts +0 -47
- package/framework/umd/src/core/Transformer/Transformer.d.ts.map +0 -1
- package/framework/umd/src/core/Type/adapters.d.ts +0 -26
- package/framework/umd/src/core/Type/adapters.d.ts.map +0 -1
- package/framework/umd/src/core/Type/types.d.ts +0 -186
- package/framework/umd/src/core/Type/types.d.ts.map +0 -1
- package/framework/umd/src/core/TypeFunc/index.d.ts +0 -8
- package/framework/umd/src/core/TypeFunc/index.d.ts.map +0 -1
- package/framework/umd/src/core/Wrenches/EventBus.d.ts +0 -57
- package/framework/umd/src/core/Wrenches/EventBus.d.ts.map +0 -1
- package/framework/umd/src/core/Wrenches/is.d.ts +0 -20
- package/framework/umd/src/core/Wrenches/is.d.ts.map +0 -1
- package/framework/umd/src/index.d.ts +0 -14
- package/framework/umd/src/index.d.ts.map +0 -1
- package/framework/umd/src/utils.d.ts +0 -17
- package/framework/umd/src/utils.d.ts.map +0 -1
- package/framework/umd/utils.d.ts +0 -16
- package/index.d.ts +0 -724
|
@@ -0,0 +1,1104 @@
|
|
|
1
|
+
import { renderElementToContainer } from "./shadow.js";
|
|
2
|
+
import { syncUrlToWindow } from "./sync.js";
|
|
3
|
+
import { fixElementCtrSrcOrHref, isConstructable, anchorElementGenerator, isMatchSyncQueryById, isFunction, warn, error, execHooks, getCurUrl, getAbsolutePath, setAttrsToElement, setTagToScript, getTagFromScript, } from "./utils.js";
|
|
4
|
+
import { documentProxyProperties, rawAddEventListener, rawRemoveEventListener, rawDocumentQuerySelector, mainDocumentAddEventListenerEvents, mainAndAppAddEventListenerEvents, appDocumentAddEventListenerEvents, appDocumentOnEvents, appWindowAddEventListenerEvents, appWindowOnEvent, windowProxyProperties, windowRegWhiteList, rawWindowAddEventListener, rawWindowRemoveEventListener, } from "./common.js";
|
|
5
|
+
import { getJsLoader } from "./plugin.js";
|
|
6
|
+
import { XPE_TIPS_SCRIPT_ERROR_REQUESTED, XPE_DATA_FLAG } from "./constant.js";
|
|
7
|
+
import { ensureXpeEventListeners, getXpeInternalSandbox, getXpeMount, getXpeRawWindow, installXpeRuntimeBridge, } from "./xpe-bridge.js";
|
|
8
|
+
const extraInstanceofConstructorNames = new Set([
|
|
9
|
+
"ClipboardEvent",
|
|
10
|
+
"CSSStyleDeclaration",
|
|
11
|
+
"DataTransfer",
|
|
12
|
+
"DOMImplementation",
|
|
13
|
+
"DOMMatrix",
|
|
14
|
+
"DOMMatrixReadOnly",
|
|
15
|
+
"DOMParser",
|
|
16
|
+
"DOMPoint",
|
|
17
|
+
"DOMPointReadOnly",
|
|
18
|
+
"DOMQuad",
|
|
19
|
+
"DOMRect",
|
|
20
|
+
"DOMRectList",
|
|
21
|
+
"DOMRectReadOnly",
|
|
22
|
+
"DOMStringList",
|
|
23
|
+
"DOMStringMap",
|
|
24
|
+
"DOMTokenList",
|
|
25
|
+
"HTMLCollection",
|
|
26
|
+
"MediaList",
|
|
27
|
+
"NamedNodeMap",
|
|
28
|
+
"Range",
|
|
29
|
+
"Selection",
|
|
30
|
+
"StyleSheet",
|
|
31
|
+
"StyleSheetList",
|
|
32
|
+
"TextDecoder",
|
|
33
|
+
"TextEncoder",
|
|
34
|
+
"TimeRanges",
|
|
35
|
+
]);
|
|
36
|
+
/**
|
|
37
|
+
* 修改window对象的事件监听,只有路由事件采用iframe的事件
|
|
38
|
+
*/
|
|
39
|
+
function patchIframeEvents(iframeWindow) {
|
|
40
|
+
const sandbox = getXpeInternalSandbox(iframeWindow);
|
|
41
|
+
const eventListeners = ensureXpeEventListeners(iframeWindow);
|
|
42
|
+
iframeWindow.addEventListener = function addEventListener(type, listener, options) {
|
|
43
|
+
const activeSandbox = getActiveSandbox(iframeWindow);
|
|
44
|
+
if (!activeSandbox) {
|
|
45
|
+
return rawWindowAddEventListener.call(iframeWindow, type, listener, options);
|
|
46
|
+
}
|
|
47
|
+
// 运行插件钩子函数
|
|
48
|
+
execHooks(activeSandbox.plugins, "windowAddEventListenerHook", iframeWindow, type, listener, options);
|
|
49
|
+
// 相同参数多次调用 addEventListener 不会导致重复注册,所以用set。
|
|
50
|
+
eventListeners.add({ type, listener, options });
|
|
51
|
+
if (appWindowAddEventListenerEvents.concat(activeSandbox.iframeAddEventListeners).includes(type) ||
|
|
52
|
+
(typeof options === "object" && options.targetWindow)) {
|
|
53
|
+
const targetWindow = typeof options === "object" && options.targetWindow ? options?.targetWindow : iframeWindow;
|
|
54
|
+
return rawWindowAddEventListener.call(targetWindow, type, listener, options);
|
|
55
|
+
}
|
|
56
|
+
// 在子应用嵌套场景使用window.window获取真实window
|
|
57
|
+
rawWindowAddEventListener.call(getXpeRawWindow(window) || window, type, listener, options);
|
|
58
|
+
};
|
|
59
|
+
iframeWindow.removeEventListener = function removeEventListener(type, listener, options) {
|
|
60
|
+
const activeSandbox = getActiveSandbox(iframeWindow);
|
|
61
|
+
if (!activeSandbox) {
|
|
62
|
+
return rawWindowRemoveEventListener.call(iframeWindow, type, listener, options);
|
|
63
|
+
}
|
|
64
|
+
// 运行插件钩子函数
|
|
65
|
+
execHooks(activeSandbox.plugins, "windowRemoveEventListenerHook", iframeWindow, type, listener, options);
|
|
66
|
+
eventListeners.forEach((o) => {
|
|
67
|
+
// 这里严格一点,确保子应用销毁的时候都能销毁
|
|
68
|
+
if (o.listener === listener && o.type === type && options == o.options) {
|
|
69
|
+
eventListeners.delete(o);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
if (appWindowAddEventListenerEvents.concat(activeSandbox.iframeAddEventListeners).includes(type) ||
|
|
73
|
+
(typeof options === "object" && options.targetWindow)) {
|
|
74
|
+
const targetWindow = typeof options === "object" && options.targetWindow ? options?.targetWindow : iframeWindow;
|
|
75
|
+
return rawWindowRemoveEventListener.call(targetWindow, type, listener, options);
|
|
76
|
+
}
|
|
77
|
+
rawWindowRemoveEventListener.call(getXpeRawWindow(window) || window, type, listener, options);
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function getActiveSandbox(iframeWindow) {
|
|
81
|
+
const sandbox = getXpeInternalSandbox(iframeWindow);
|
|
82
|
+
if (!sandbox || sandbox.isDestroyed())
|
|
83
|
+
return undefined;
|
|
84
|
+
return sandbox;
|
|
85
|
+
}
|
|
86
|
+
function patchIframeVariable(iframeWindow, sandbox, appHostPath) {
|
|
87
|
+
installXpeRuntimeBridge(iframeWindow, sandbox);
|
|
88
|
+
iframeWindow.__XPE_PUBLIC_PATH__ = appHostPath + "/";
|
|
89
|
+
iframeWindow.__XPE_RAW_WINDOW__ = iframeWindow;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* 对iframe的history的pushState和replaceState进行修改
|
|
93
|
+
* 将从location劫持后的数据修改回来,防止跨域错误
|
|
94
|
+
* 同步路由到主应用
|
|
95
|
+
* @param iframeWindow
|
|
96
|
+
* @param appHostPath 子应用的 host path
|
|
97
|
+
* @param mainHostPath 主应用的 host path
|
|
98
|
+
*/
|
|
99
|
+
function patchIframeHistory(iframeWindow, appHostPath, mainHostPath) {
|
|
100
|
+
const history = iframeWindow.history;
|
|
101
|
+
const rawHistoryPushState = history.pushState;
|
|
102
|
+
const rawHistoryReplaceState = history.replaceState;
|
|
103
|
+
history.pushState = function (data, title, url) {
|
|
104
|
+
const baseUrl = mainHostPath + iframeWindow.location.pathname + iframeWindow.location.search + iframeWindow.location.hash;
|
|
105
|
+
const mainUrl = getAbsolutePath(url?.replace(appHostPath, ""), baseUrl);
|
|
106
|
+
const ignoreFlag = url === undefined;
|
|
107
|
+
rawHistoryPushState.call(history, data, title, ignoreFlag ? undefined : mainUrl);
|
|
108
|
+
if (ignoreFlag)
|
|
109
|
+
return;
|
|
110
|
+
updateBase(iframeWindow, appHostPath, mainHostPath);
|
|
111
|
+
syncUrlToWindow(iframeWindow);
|
|
112
|
+
};
|
|
113
|
+
history.replaceState = function (data, title, url) {
|
|
114
|
+
const baseUrl = mainHostPath + iframeWindow.location.pathname + iframeWindow.location.search + iframeWindow.location.hash;
|
|
115
|
+
const mainUrl = getAbsolutePath(url?.replace(appHostPath, ""), baseUrl);
|
|
116
|
+
const ignoreFlag = url === undefined;
|
|
117
|
+
rawHistoryReplaceState.call(history, data, title, ignoreFlag ? undefined : mainUrl);
|
|
118
|
+
if (ignoreFlag)
|
|
119
|
+
return;
|
|
120
|
+
updateBase(iframeWindow, appHostPath, mainHostPath);
|
|
121
|
+
syncUrlToWindow(iframeWindow);
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* 动态的修改iframe的base地址
|
|
126
|
+
* @param iframeWindow
|
|
127
|
+
* @param appHostPath
|
|
128
|
+
* @param mainHostPath
|
|
129
|
+
*/
|
|
130
|
+
function updateBase(iframeWindow, appHostPath, mainHostPath) {
|
|
131
|
+
const baseUrl = new URL(iframeWindow.location.href?.replace(mainHostPath, ""), appHostPath);
|
|
132
|
+
const baseElement = rawDocumentQuerySelector.call(iframeWindow.document, "base");
|
|
133
|
+
if (baseElement)
|
|
134
|
+
baseElement.setAttribute("href", appHostPath + baseUrl.pathname);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* patch iframe window effect
|
|
138
|
+
* @param iframeWindow
|
|
139
|
+
*/
|
|
140
|
+
// TODO 继续改进
|
|
141
|
+
export function patchWindowEffect(iframeWindow) {
|
|
142
|
+
const sandbox = getXpeInternalSandbox(iframeWindow);
|
|
143
|
+
// 属性处理函数
|
|
144
|
+
function processWindowProperty(key) {
|
|
145
|
+
const value = iframeWindow[key];
|
|
146
|
+
try {
|
|
147
|
+
if (typeof value === "function" && !isConstructable(value)) {
|
|
148
|
+
iframeWindow[key] = window[key].bind(window);
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
iframeWindow[key] = window[key];
|
|
152
|
+
}
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
catch (e) {
|
|
156
|
+
warn(e.message);
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
Object.getOwnPropertyNames(iframeWindow).forEach((key) => {
|
|
161
|
+
// 特殊处理
|
|
162
|
+
if (key === "getSelection") {
|
|
163
|
+
Object.defineProperty(iframeWindow, key, {
|
|
164
|
+
get: () => {
|
|
165
|
+
const sandbox = getXpeInternalSandbox(iframeWindow);
|
|
166
|
+
// 降级模式:可见 DOM 在渲染 iframe,getSelection 需读 sandbox.document
|
|
167
|
+
if (sandbox?.degrade && sandbox.document) {
|
|
168
|
+
return sandbox.document.getSelection.bind(sandbox.document);
|
|
169
|
+
}
|
|
170
|
+
return iframeWindow.document[key];
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
// 单独属性
|
|
176
|
+
if (windowProxyProperties.includes(key)) {
|
|
177
|
+
processWindowProperty(key);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
// 正则匹配,可以一次处理多个
|
|
181
|
+
windowRegWhiteList.some((reg) => {
|
|
182
|
+
if (reg.test(key) && key in iframeWindow.parent) {
|
|
183
|
+
return processWindowProperty(key);
|
|
184
|
+
}
|
|
185
|
+
return false;
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
// onEvent set
|
|
189
|
+
const windowOnEvents = Object.getOwnPropertyNames(window)
|
|
190
|
+
.filter((p) => /^on/.test(p))
|
|
191
|
+
.filter((e) => !appWindowOnEvent.concat(sandbox.iframeOnEvents).includes(e));
|
|
192
|
+
// 走主应用window
|
|
193
|
+
windowOnEvents.forEach((e) => {
|
|
194
|
+
const descriptor = Object.getOwnPropertyDescriptor(iframeWindow, e) || {
|
|
195
|
+
enumerable: true,
|
|
196
|
+
writable: true,
|
|
197
|
+
};
|
|
198
|
+
try {
|
|
199
|
+
Object.defineProperty(iframeWindow, e, {
|
|
200
|
+
enumerable: descriptor.enumerable,
|
|
201
|
+
configurable: true,
|
|
202
|
+
get: () => window[e],
|
|
203
|
+
set: descriptor.writable || descriptor.set
|
|
204
|
+
? (handler) => {
|
|
205
|
+
// 首次写入时记录主 window 上 onXXX 的原始值;destroy 时通过 setter
|
|
206
|
+
// 还原(accessor 不能用 defineProperty descriptor 直接还原内部 handler),
|
|
207
|
+
// 防止主应用 window 被 dangling handler 长期污染。
|
|
208
|
+
const tracker = getXpeInternalSandbox(iframeWindow)?.eventCleanupTracker;
|
|
209
|
+
tracker?.trackWindowOnEvent(e, window[e], Object.prototype.hasOwnProperty.call(window, e));
|
|
210
|
+
window[e] = typeof handler === "function" ? handler.bind(iframeWindow) : handler;
|
|
211
|
+
}
|
|
212
|
+
: undefined,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
catch (e) {
|
|
216
|
+
warn(e.message);
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
// 降级模式 DOM 在渲染 iframe,instanceof 需在 document 就绪后由 patchDegradeInstanceofAcrossRealms 处理
|
|
220
|
+
if (!sandbox.degrade) {
|
|
221
|
+
patchInstanceofAcrossRealms(iframeWindow);
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
execHooks(sandbox.plugins, "windowPropertyOverride", iframeWindow);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
function isDomConstructor(name, ctor, peerWindow) {
|
|
228
|
+
const prototype = ctor.prototype;
|
|
229
|
+
if (!prototype)
|
|
230
|
+
return false;
|
|
231
|
+
if (ctor === peerWindow.EventTarget || ctor === peerWindow.Event)
|
|
232
|
+
return true;
|
|
233
|
+
if (prototype instanceof peerWindow.EventTarget || prototype instanceof peerWindow.Event)
|
|
234
|
+
return true;
|
|
235
|
+
if (/^(HTML|SVG|MathML).+Element$/.test(name))
|
|
236
|
+
return true;
|
|
237
|
+
return extraInstanceofConstructorNames.has(name);
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* 让 targetWindow 上的 DOM 构造函数 instanceof 同时认可 peerWindow realm 的对象。
|
|
241
|
+
* 非降级:targetWindow=子应用 JS iframe,peerWindow=主应用 window(DOM 在 shadowRoot)。
|
|
242
|
+
* 降级:在 patchDegradeInstanceofAcrossRealms 中对渲染 iframe 与执行 iframe 双向调用。
|
|
243
|
+
*/
|
|
244
|
+
export function patchInstanceofAcrossRealms(targetWindow, peerWindow = window) {
|
|
245
|
+
// DOM 构造函数之间存在继承链(HTMLIFrameElement -> HTMLElement -> Element -> Node ...),
|
|
246
|
+
// 对构造函数的属性读取会沿这条链向上查找。因此 _hasPatch / Symbol.hasInstance 必须用 own
|
|
247
|
+
// 语义判断,否则会读到已被 patch 的祖先构造函数的值,导致 patch 被跳过或判断串味到祖先 realm。
|
|
248
|
+
const nativeHasInstance = Function.prototype[Symbol.hasInstance];
|
|
249
|
+
Object.getOwnPropertyNames(targetWindow).forEach((name) => {
|
|
250
|
+
let targetConstructor;
|
|
251
|
+
let peerConstructor;
|
|
252
|
+
try {
|
|
253
|
+
targetConstructor = targetWindow[name];
|
|
254
|
+
peerConstructor = peerWindow[name];
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
if (typeof targetConstructor !== "function" || typeof peerConstructor !== "function")
|
|
260
|
+
return;
|
|
261
|
+
if (targetConstructor === peerConstructor || Object.prototype.hasOwnProperty.call(targetConstructor, "_hasPatch"))
|
|
262
|
+
return;
|
|
263
|
+
if (!isDomConstructor(name, peerConstructor, peerWindow))
|
|
264
|
+
return;
|
|
265
|
+
try {
|
|
266
|
+
Object.defineProperties(targetConstructor, {
|
|
267
|
+
[Symbol.hasInstance]: {
|
|
268
|
+
configurable: true,
|
|
269
|
+
value(element) {
|
|
270
|
+
// 用 this 而非闭包变量,确保命中的始终是当前构造函数自己的判断。
|
|
271
|
+
// 对端也用原生 hasInstance,避免双向 patch 时 element instanceof peerConstructor 递归栈溢出。
|
|
272
|
+
if (nativeHasInstance.call(this, element))
|
|
273
|
+
return true;
|
|
274
|
+
return nativeHasInstance.call(peerConstructor, element);
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
_hasPatch: { value: true },
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
catch (error) {
|
|
281
|
+
console.warn(error);
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
const sandbox = getXpeInternalSandbox(targetWindow);
|
|
285
|
+
if (sandbox) {
|
|
286
|
+
execHooks(sandbox.plugins, "windowPropertyOverride", targetWindow);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* 降级模式:DOM 在渲染 iframe、JS 在执行 iframe,对两侧 window 双向 patch instanceof。
|
|
291
|
+
* 需在 sandbox.document(渲染 document)就绪后调用,勿在 patchWindowEffect 阶段调用。
|
|
292
|
+
*/
|
|
293
|
+
export function patchDegradeInstanceofAcrossRealms(appWindow, renderWindow) {
|
|
294
|
+
if (!renderWindow || appWindow === renderWindow)
|
|
295
|
+
return;
|
|
296
|
+
patchInstanceofAcrossRealms(renderWindow, appWindow);
|
|
297
|
+
patchInstanceofAcrossRealms(appWindow, renderWindow);
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* 补齐 iframe realm 节点移动到宿主 shadowRoot 后的元素事件。
|
|
301
|
+
*
|
|
302
|
+
* Vue/React 等框架会通过子应用 iframe 的 Node.prototype.addEventListener
|
|
303
|
+
* 给 iframe document 创建的节点绑定事件;这些节点随后会被挂进宿主
|
|
304
|
+
* document 的 shadowRoot。Chromium 下这种跨 document 节点用 iframe
|
|
305
|
+
* realm 的原生 addEventListener 注册后,宿主 document 中的真实点击不会触发。
|
|
306
|
+
* 因此元素级事件统一用宿主原生 Node.prototype 注册,同时保留缓存供降级和恢复链路使用。
|
|
307
|
+
*/
|
|
308
|
+
function patchNodeEventListeners(iframeWindow) {
|
|
309
|
+
const handlerCallbackMap = new WeakMap();
|
|
310
|
+
iframeWindow.Node.prototype.addEventListener = function (type, handler, options) {
|
|
311
|
+
const activeSandbox = getActiveSandbox(iframeWindow);
|
|
312
|
+
if (!activeSandbox)
|
|
313
|
+
return rawAddEventListener.call(this, type, handler, options);
|
|
314
|
+
const callback = getOrCreateElementEventCallback(handlerCallbackMap, handler, iframeWindow);
|
|
315
|
+
// 添加事件缓存
|
|
316
|
+
const elementListenerList = activeSandbox.elementEventCacheMap.get(this);
|
|
317
|
+
if (elementListenerList) {
|
|
318
|
+
if (!elementListenerList.find((listener) => listener.type === type && listener.handler === handler)) {
|
|
319
|
+
elementListenerList.push({ type, handler, callback, options });
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
else
|
|
323
|
+
activeSandbox.elementEventCacheMap.set(this, [{ type, handler, callback, options }]);
|
|
324
|
+
return rawAddEventListener.call(this, type, callback, options);
|
|
325
|
+
};
|
|
326
|
+
iframeWindow.Node.prototype.removeEventListener = function (type, handler, options) {
|
|
327
|
+
const activeSandbox = getActiveSandbox(iframeWindow);
|
|
328
|
+
if (!activeSandbox)
|
|
329
|
+
return rawRemoveEventListener.call(this, type, handler, options);
|
|
330
|
+
// 清除缓存
|
|
331
|
+
const elementListenerList = activeSandbox.elementEventCacheMap.get(this);
|
|
332
|
+
if (elementListenerList) {
|
|
333
|
+
const index = elementListenerList?.findIndex((ele) => ele.type === type && ele.handler === handler);
|
|
334
|
+
if (index !== -1)
|
|
335
|
+
elementListenerList.splice(index, 1);
|
|
336
|
+
}
|
|
337
|
+
if (!elementListenerList?.length) {
|
|
338
|
+
activeSandbox.elementEventCacheMap.delete(this);
|
|
339
|
+
}
|
|
340
|
+
const callback = handlerCallbackMap.get(handler) ?? handler;
|
|
341
|
+
return rawRemoveEventListener.call(this, type, callback, options);
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
function getOrCreateElementEventCallback(handlerCallbackMap, handler, iframeWindow) {
|
|
345
|
+
const cached = handlerCallbackMap.get(handler);
|
|
346
|
+
if (cached)
|
|
347
|
+
return cached;
|
|
348
|
+
const callback = typeof handler === "function"
|
|
349
|
+
? function elementEventCallback(event) {
|
|
350
|
+
patchEventForIframeRealm(event, iframeWindow);
|
|
351
|
+
handler.call(this, event);
|
|
352
|
+
}
|
|
353
|
+
: {
|
|
354
|
+
handleEvent(event) {
|
|
355
|
+
patchEventForIframeRealm(event, iframeWindow);
|
|
356
|
+
handler.handleEvent(event);
|
|
357
|
+
},
|
|
358
|
+
};
|
|
359
|
+
handlerCallbackMap.set(handler, callback);
|
|
360
|
+
return callback;
|
|
361
|
+
}
|
|
362
|
+
function patchEventForIframeRealm(event, iframeWindow) {
|
|
363
|
+
const frameworkEvent = event;
|
|
364
|
+
if (frameworkEvent._vts === undefined) {
|
|
365
|
+
frameworkEvent._vts = Date.now();
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* 恢复节点的监听事件
|
|
370
|
+
*/
|
|
371
|
+
export function recoverEventListeners(rootElement, iframeWindow) {
|
|
372
|
+
const sandbox = getActiveSandbox(iframeWindow);
|
|
373
|
+
if (!sandbox)
|
|
374
|
+
return;
|
|
375
|
+
const walkerDocument = rootElement.ownerDocument ?? document;
|
|
376
|
+
const ElementIterator = walkerDocument.createTreeWalker(rootElement, NodeFilter.SHOW_ELEMENT, null, false);
|
|
377
|
+
let nextElement = ElementIterator.currentNode;
|
|
378
|
+
while (nextElement) {
|
|
379
|
+
const elementListenerList = sandbox.elementEventCacheMap.get(nextElement);
|
|
380
|
+
if (elementListenerList?.length) {
|
|
381
|
+
elementListenerList.forEach((listener) => {
|
|
382
|
+
rawRemoveEventListener.call(nextElement, listener.type, listener.callback, listener.options);
|
|
383
|
+
rawAddEventListener.call(nextElement, listener.type, listener.callback, listener.options);
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
nextElement = ElementIterator.nextNode();
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* 恢复根节点的监听事件
|
|
391
|
+
*/
|
|
392
|
+
export function recoverDocumentListeners(oldRootElement, newRootElement, iframeWindow) {
|
|
393
|
+
const sandbox = getActiveSandbox(iframeWindow);
|
|
394
|
+
if (!sandbox)
|
|
395
|
+
return;
|
|
396
|
+
const elementListenerList = sandbox.elementEventCacheMap.get(oldRootElement);
|
|
397
|
+
if (elementListenerList?.length) {
|
|
398
|
+
sandbox.elementEventCacheMap.delete(oldRootElement);
|
|
399
|
+
sandbox.elementEventCacheMap.set(newRootElement, elementListenerList);
|
|
400
|
+
elementListenerList.forEach((listener) => {
|
|
401
|
+
rawRemoveEventListener.call(newRootElement, listener.type, listener.callback, listener.options);
|
|
402
|
+
rawAddEventListener.call(newRootElement, listener.type, listener.callback, listener.options);
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
export function recoverShadowRootEventListeners(iframeWindow) {
|
|
407
|
+
const sandbox = getActiveSandbox(iframeWindow);
|
|
408
|
+
if (!sandbox?.shadowRoot)
|
|
409
|
+
return;
|
|
410
|
+
const walker = document.createTreeWalker(sandbox.shadowRoot, NodeFilter.SHOW_ELEMENT, null, false);
|
|
411
|
+
let nextElement = walker.currentNode;
|
|
412
|
+
while (nextElement) {
|
|
413
|
+
const elementListenerList = sandbox.elementEventCacheMap.get(nextElement);
|
|
414
|
+
elementListenerList?.forEach((listener) => {
|
|
415
|
+
rawRemoveEventListener.call(nextElement, listener.type, listener.callback, listener.options);
|
|
416
|
+
rawAddEventListener.call(nextElement, listener.type, listener.callback, listener.options);
|
|
417
|
+
});
|
|
418
|
+
recoverFrameworkEventInvokers(nextElement);
|
|
419
|
+
nextElement = walker.nextNode();
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
function recoverFrameworkEventInvokers(element) {
|
|
423
|
+
if (element.nodeType !== Node.ELEMENT_NODE)
|
|
424
|
+
return;
|
|
425
|
+
Object.getOwnPropertySymbols(element)
|
|
426
|
+
.filter((symbol) => String(symbol) === "Symbol(_vei)")
|
|
427
|
+
.forEach((symbol) => {
|
|
428
|
+
const invokers = element[symbol];
|
|
429
|
+
if (!invokers)
|
|
430
|
+
return;
|
|
431
|
+
Object.keys(invokers).forEach((key) => {
|
|
432
|
+
const invoker = invokers[key];
|
|
433
|
+
if (!invoker)
|
|
434
|
+
return;
|
|
435
|
+
const eventType = toDomEventType(key);
|
|
436
|
+
if (!eventType)
|
|
437
|
+
return;
|
|
438
|
+
rawRemoveEventListener.call(element, eventType, invoker);
|
|
439
|
+
rawAddEventListener.call(element, eventType, invoker);
|
|
440
|
+
});
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
function toDomEventType(frameworkEventKey) {
|
|
444
|
+
const match = frameworkEventKey.match(/^on([A-Z].*)/);
|
|
445
|
+
if (!match)
|
|
446
|
+
return null;
|
|
447
|
+
return match[1].replace(/Once|Passive|Capture/g, "").toLowerCase();
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* 修复vue绑定事件e.timeStamp < attachedTimestamp 的情况
|
|
451
|
+
*/
|
|
452
|
+
export function patchEventTimeStamp(targetWindow, iframeWindow) {
|
|
453
|
+
Object.defineProperty(targetWindow.Event.prototype, "timeStamp", {
|
|
454
|
+
get: () => {
|
|
455
|
+
return iframeWindow.document.createEvent("Event").timeStamp;
|
|
456
|
+
},
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* patch document effect
|
|
461
|
+
* @param iframeWindow
|
|
462
|
+
*/
|
|
463
|
+
// TODO 继续改进
|
|
464
|
+
export function patchDocumentEffect(iframeWindow) {
|
|
465
|
+
const sandbox = getXpeInternalSandbox(iframeWindow);
|
|
466
|
+
/**
|
|
467
|
+
* 处理 addEventListener和removeEventListener
|
|
468
|
+
* 由于这个劫持导致 handler 的this发生改变,所以需要handler.bind(document)
|
|
469
|
+
* 但是这样会导致removeEventListener无法正常工作,因为handler => handler.bind(document)
|
|
470
|
+
* 这个地方保存callback = handler.bind(document) 方便removeEventListener
|
|
471
|
+
*/
|
|
472
|
+
const handlerCallbackMap = new WeakMap();
|
|
473
|
+
const handlerTypeMap = new WeakMap();
|
|
474
|
+
iframeWindow.Document.prototype.addEventListener = function (type, handler, options) {
|
|
475
|
+
if (!handler)
|
|
476
|
+
return;
|
|
477
|
+
let callback = handlerCallbackMap.get(handler);
|
|
478
|
+
const typeList = handlerTypeMap.get(handler);
|
|
479
|
+
// 设置 handlerCallbackMap
|
|
480
|
+
if (!callback) {
|
|
481
|
+
callback = typeof handler === "function" ? handler.bind(this) : handler;
|
|
482
|
+
handlerCallbackMap.set(handler, callback);
|
|
483
|
+
}
|
|
484
|
+
// 设置 handlerTypeMap
|
|
485
|
+
if (typeList) {
|
|
486
|
+
if (!typeList.includes(type))
|
|
487
|
+
typeList.push(type);
|
|
488
|
+
}
|
|
489
|
+
else {
|
|
490
|
+
handlerTypeMap.set(handler, [type]);
|
|
491
|
+
}
|
|
492
|
+
// 运行插件钩子函数
|
|
493
|
+
execHooks(sandbox.plugins, "documentAddEventListenerHook", iframeWindow, type, callback, options);
|
|
494
|
+
if (appDocumentAddEventListenerEvents.includes(type)) {
|
|
495
|
+
return rawAddEventListener.call(this, type, callback, options);
|
|
496
|
+
}
|
|
497
|
+
// 降级统一走 sandbox.document
|
|
498
|
+
if (sandbox.degrade)
|
|
499
|
+
return sandbox.document.addEventListener(type, callback, options);
|
|
500
|
+
if (mainDocumentAddEventListenerEvents.includes(type)) {
|
|
501
|
+
// 登记到清理跟踪器,destroy 时反向解绑,避免 handler 闭包永久钉住 iframeWindow
|
|
502
|
+
sandbox.eventCleanupTracker?.trackMainDocumentListener({ type, callback, options });
|
|
503
|
+
return window.document.addEventListener(type, callback, options);
|
|
504
|
+
}
|
|
505
|
+
if (mainAndAppAddEventListenerEvents.includes(type)) {
|
|
506
|
+
sandbox.eventCleanupTracker?.trackMainDocumentListener({ type, callback, options });
|
|
507
|
+
window.document.addEventListener(type, callback, options);
|
|
508
|
+
sandbox.shadowRoot.addEventListener(type, callback, options);
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
sandbox.shadowRoot.addEventListener(type, callback, options);
|
|
512
|
+
};
|
|
513
|
+
iframeWindow.Document.prototype.removeEventListener = function (type, handler, options) {
|
|
514
|
+
const callback = handlerCallbackMap.get(handler);
|
|
515
|
+
const typeList = handlerTypeMap.get(handler);
|
|
516
|
+
if (callback) {
|
|
517
|
+
if (typeList?.includes(type)) {
|
|
518
|
+
typeList.splice(typeList.indexOf(type), 1);
|
|
519
|
+
if (!typeList.length) {
|
|
520
|
+
handlerCallbackMap.delete(handler);
|
|
521
|
+
handlerTypeMap.delete(handler);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
// 运行插件钩子函数
|
|
525
|
+
execHooks(sandbox.plugins, "documentRemoveEventListenerHook", iframeWindow, type, callback, options);
|
|
526
|
+
if (appDocumentAddEventListenerEvents.includes(type)) {
|
|
527
|
+
return rawRemoveEventListener.call(this, type, callback, options);
|
|
528
|
+
}
|
|
529
|
+
if (sandbox.degrade)
|
|
530
|
+
return sandbox.document.removeEventListener(type, callback, options);
|
|
531
|
+
if (mainDocumentAddEventListenerEvents.includes(type)) {
|
|
532
|
+
sandbox.eventCleanupTracker?.untrackMainDocumentListener({ type, callback, options });
|
|
533
|
+
return window.document.removeEventListener(type, callback, options);
|
|
534
|
+
}
|
|
535
|
+
if (mainAndAppAddEventListenerEvents.includes(type)) {
|
|
536
|
+
sandbox.eventCleanupTracker?.untrackMainDocumentListener({ type, callback, options });
|
|
537
|
+
window.document.removeEventListener(type, callback, options);
|
|
538
|
+
sandbox.shadowRoot.removeEventListener(type, callback, options);
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
sandbox.shadowRoot.removeEventListener(type, callback, options);
|
|
542
|
+
}
|
|
543
|
+
};
|
|
544
|
+
// 处理onEvent
|
|
545
|
+
const elementOnEvents = Object.keys(iframeWindow.HTMLElement.prototype).filter((ele) => /^on/.test(ele));
|
|
546
|
+
const documentOnEvent = Object.keys(iframeWindow.Document.prototype)
|
|
547
|
+
.filter((ele) => /^on/.test(ele))
|
|
548
|
+
.filter((ele) => !appDocumentOnEvents.includes(ele));
|
|
549
|
+
elementOnEvents
|
|
550
|
+
.filter((e) => documentOnEvent.includes(e))
|
|
551
|
+
.forEach((e) => {
|
|
552
|
+
const descriptor = Object.getOwnPropertyDescriptor(iframeWindow.Document.prototype, e) || {
|
|
553
|
+
enumerable: true,
|
|
554
|
+
writable: true,
|
|
555
|
+
};
|
|
556
|
+
try {
|
|
557
|
+
Object.defineProperty(iframeWindow.Document.prototype, e, {
|
|
558
|
+
enumerable: descriptor.enumerable,
|
|
559
|
+
configurable: true,
|
|
560
|
+
get: () => sandbox.degrade ? sandbox.document[e] : (sandbox.shadowRoot.firstElementChild?.[e] ?? null),
|
|
561
|
+
set: descriptor.writable || descriptor.set
|
|
562
|
+
? (handler) => {
|
|
563
|
+
const val = typeof handler === "function" ? handler.bind(iframeWindow.document) : handler;
|
|
564
|
+
if (sandbox.degrade) {
|
|
565
|
+
sandbox.document[e] = val;
|
|
566
|
+
}
|
|
567
|
+
else {
|
|
568
|
+
// shadowRoot 尚无首个子元素(渲染初期 / 已清空)时跳过,避免对 null 赋值抛错
|
|
569
|
+
const firstElement = sandbox.shadowRoot.firstElementChild;
|
|
570
|
+
if (firstElement)
|
|
571
|
+
firstElement[e] = val;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
: undefined,
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
catch (e) {
|
|
578
|
+
warn(e.message);
|
|
579
|
+
}
|
|
580
|
+
});
|
|
581
|
+
// 处理属性get
|
|
582
|
+
const { ownerProperties, modifyProperties, shadowProperties, shadowMethods, documentProperties, documentMethods, documentEvents, } = documentProxyProperties;
|
|
583
|
+
modifyProperties.concat(shadowProperties, shadowMethods, documentProperties, documentMethods).forEach((propKey) => {
|
|
584
|
+
const descriptor = Object.getOwnPropertyDescriptor(iframeWindow.Document.prototype, propKey) || {
|
|
585
|
+
enumerable: true,
|
|
586
|
+
writable: true,
|
|
587
|
+
};
|
|
588
|
+
try {
|
|
589
|
+
Object.defineProperty(iframeWindow.Document.prototype, propKey, {
|
|
590
|
+
enumerable: descriptor.enumerable,
|
|
591
|
+
configurable: true,
|
|
592
|
+
get: () => sandbox.proxyDocument[propKey],
|
|
593
|
+
set: undefined,
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
catch (e) {
|
|
597
|
+
warn(e.message);
|
|
598
|
+
}
|
|
599
|
+
});
|
|
600
|
+
// 处理 document 专属事件(onfullscreenchange / onpointerlockchange 等)。
|
|
601
|
+
//
|
|
602
|
+
// 这类事件浏览器只 dispatch 到主 document 上,子应用形如
|
|
603
|
+
// `document.onfullscreenchange = handler` 的写法需要被转发到主 window.document。
|
|
604
|
+
// 实现要点:
|
|
605
|
+
// 1) 每个 propKey 只允许一个 active listener;setter 内部用同一份 bound 引用
|
|
606
|
+
// 做 add / remove / track,避免出现 "存进 map 的 bound 与实际注册的 bound
|
|
607
|
+
// 不是同一个" 而无法 remove;
|
|
608
|
+
// 2) 接入 eventCleanupTracker,sandbox.destroy() 时反向解绑,否则 bound 闭包
|
|
609
|
+
// 会持有 iframeWindow.document 永远挂在主 document 上;
|
|
610
|
+
// 3) handler = null/非函数:仅解绑不重绑,与原生 onXXX = null 语义一致。
|
|
611
|
+
const documentEventActiveListeners = new Map();
|
|
612
|
+
documentEvents.forEach((propKey) => {
|
|
613
|
+
const descriptor = Object.getOwnPropertyDescriptor(iframeWindow.Document.prototype, propKey) || {
|
|
614
|
+
enumerable: true,
|
|
615
|
+
writable: true,
|
|
616
|
+
};
|
|
617
|
+
if (!(descriptor.writable || descriptor.set))
|
|
618
|
+
return;
|
|
619
|
+
// documentEvents 形如 "onfullscreenchange",对应事件名去掉前缀 "on"
|
|
620
|
+
const eventType = propKey.slice(2);
|
|
621
|
+
try {
|
|
622
|
+
Object.defineProperty(iframeWindow.Document.prototype, propKey, {
|
|
623
|
+
enumerable: descriptor.enumerable,
|
|
624
|
+
configurable: true,
|
|
625
|
+
get: () => (sandbox.degrade ? sandbox : window).document[propKey],
|
|
626
|
+
set: (handler) => {
|
|
627
|
+
const targetDoc = (sandbox.degrade ? sandbox : window).document;
|
|
628
|
+
const previous = documentEventActiveListeners.get(propKey);
|
|
629
|
+
if (previous) {
|
|
630
|
+
targetDoc.removeEventListener(eventType, previous);
|
|
631
|
+
sandbox.eventCleanupTracker?.untrackMainDocumentListener({ type: eventType, callback: previous });
|
|
632
|
+
documentEventActiveListeners.delete(propKey);
|
|
633
|
+
}
|
|
634
|
+
if (typeof handler === "function") {
|
|
635
|
+
const bound = handler.bind(iframeWindow.document);
|
|
636
|
+
documentEventActiveListeners.set(propKey, bound);
|
|
637
|
+
targetDoc.addEventListener(eventType, bound);
|
|
638
|
+
sandbox.eventCleanupTracker?.trackMainDocumentListener({ type: eventType, callback: bound });
|
|
639
|
+
}
|
|
640
|
+
// handler 为 null/undefined/非函数:只解绑不重绑(与原生 onXXX = null 语义一致)
|
|
641
|
+
},
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
catch (e) {
|
|
645
|
+
warn(e.message);
|
|
646
|
+
}
|
|
647
|
+
});
|
|
648
|
+
// process owner property
|
|
649
|
+
ownerProperties.forEach((propKey) => {
|
|
650
|
+
Object.defineProperty(iframeWindow.document, propKey, {
|
|
651
|
+
enumerable: true,
|
|
652
|
+
configurable: true,
|
|
653
|
+
get: () => sandbox.proxyDocument[propKey],
|
|
654
|
+
set: undefined,
|
|
655
|
+
});
|
|
656
|
+
});
|
|
657
|
+
// 运行插件钩子函数
|
|
658
|
+
execHooks(sandbox.plugins, "documentPropertyOverride", iframeWindow);
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* patch Node effect
|
|
662
|
+
* 1、处理 getRootNode
|
|
663
|
+
* 2、处理 appendChild、insertBefore,当插入的节点为 svg 时,createElement 的 patch 会被去除,需要重新 patch
|
|
664
|
+
* @param iframeWindow
|
|
665
|
+
*/
|
|
666
|
+
function patchNodeEffect(iframeWindow) {
|
|
667
|
+
const sandbox = getXpeInternalSandbox(iframeWindow);
|
|
668
|
+
const rawGetRootNode = iframeWindow.Node.prototype.getRootNode;
|
|
669
|
+
const rawAppendChild = iframeWindow.Node.prototype.appendChild;
|
|
670
|
+
const rawInsertBefore = iframeWindow.Node.prototype.insertBefore;
|
|
671
|
+
const rawRemoveChild = iframeWindow.Node.prototype.removeChild;
|
|
672
|
+
iframeWindow.Node.prototype.getRootNode = function (options) {
|
|
673
|
+
const rootNode = rawGetRootNode.call(this, options);
|
|
674
|
+
if (rootNode === sandbox.shadowRoot)
|
|
675
|
+
return iframeWindow.document;
|
|
676
|
+
else
|
|
677
|
+
return rootNode;
|
|
678
|
+
};
|
|
679
|
+
iframeWindow.Node.prototype.appendChild = function (node) {
|
|
680
|
+
const res = rawAppendChild.call(this, node);
|
|
681
|
+
patchElementEffect(node, iframeWindow);
|
|
682
|
+
recoverEventListeners(this, iframeWindow);
|
|
683
|
+
return res;
|
|
684
|
+
};
|
|
685
|
+
iframeWindow.Node.prototype.insertBefore = function (node, child) {
|
|
686
|
+
const res = rawInsertBefore.call(this, node, child);
|
|
687
|
+
patchElementEffect(node, iframeWindow);
|
|
688
|
+
recoverEventListeners(this, iframeWindow);
|
|
689
|
+
return res;
|
|
690
|
+
};
|
|
691
|
+
iframeWindow.Node.prototype.removeChild = function (node) {
|
|
692
|
+
let res;
|
|
693
|
+
try {
|
|
694
|
+
res = rawRemoveChild.call(this, node);
|
|
695
|
+
}
|
|
696
|
+
catch (e) {
|
|
697
|
+
console.warn(`Failed to removeChild: ${node.nodeName.toLowerCase()} is not a child of ${this.nodeName.toLowerCase()}, try again with parentNode attribute. `);
|
|
698
|
+
if (node.isConnected && isFunction(node.parentNode?.removeChild)) {
|
|
699
|
+
node.parentNode.removeChild(node);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
patchElementEffect(node, iframeWindow);
|
|
703
|
+
return res;
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* 修复资源元素的相对路径问题
|
|
708
|
+
* @param iframeWindow
|
|
709
|
+
*/
|
|
710
|
+
function patchRelativeUrlEffect(iframeWindow) {
|
|
711
|
+
fixElementCtrSrcOrHref(iframeWindow, iframeWindow.HTMLImageElement, "src");
|
|
712
|
+
fixElementCtrSrcOrHref(iframeWindow, iframeWindow.HTMLAnchorElement, "href");
|
|
713
|
+
fixElementCtrSrcOrHref(iframeWindow, iframeWindow.HTMLSourceElement, "src");
|
|
714
|
+
fixElementCtrSrcOrHref(iframeWindow, iframeWindow.HTMLLinkElement, "href");
|
|
715
|
+
fixElementCtrSrcOrHref(iframeWindow, iframeWindow.HTMLScriptElement, "src");
|
|
716
|
+
fixElementCtrSrcOrHref(iframeWindow, iframeWindow.HTMLMediaElement, "src");
|
|
717
|
+
}
|
|
718
|
+
function wrapInlineScriptWithSandbox(code) {
|
|
719
|
+
return `(function(window, self, global, location) {
|
|
720
|
+
${code}
|
|
721
|
+
}).bind(window.__XPE_INTERNAL_SANDBOX__.proxy)(
|
|
722
|
+
window.__XPE_INTERNAL_SANDBOX__.proxy,
|
|
723
|
+
window.__XPE_INTERNAL_SANDBOX__.proxy,
|
|
724
|
+
window.__XPE_INTERNAL_SANDBOX__.proxy,
|
|
725
|
+
window.__XPE_INTERNAL_SANDBOX__.proxyLocation,
|
|
726
|
+
);`;
|
|
727
|
+
}
|
|
728
|
+
function createExecNextScriptContent() {
|
|
729
|
+
return [
|
|
730
|
+
"const sandbox = window.__XPE_INTERNAL_SANDBOX__;",
|
|
731
|
+
"if(sandbox.execQueue && sandbox.execQueue.length){ sandbox.execQueue.shift()()}",
|
|
732
|
+
].join("");
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* 初始化 base 标签,供 document 内相对路径资源解析使用。
|
|
736
|
+
* @param pathname 可选;降级渲染 iframe 的 location 为 about:blank,需传入 proxyLocation.pathname
|
|
737
|
+
*/
|
|
738
|
+
export function initBase(iframeWindow, url, pathname) {
|
|
739
|
+
const iframeDocument = iframeWindow.document;
|
|
740
|
+
if (!iframeDocument.head || iframeDocument.head.querySelector("base"))
|
|
741
|
+
return;
|
|
742
|
+
const baseElement = iframeDocument.createElement("base");
|
|
743
|
+
const iframeUrlElement = anchorElementGenerator(iframeWindow.location.href);
|
|
744
|
+
const appUrlElement = anchorElementGenerator(url);
|
|
745
|
+
const resolvedPathname = pathname ?? iframeUrlElement.pathname;
|
|
746
|
+
baseElement.setAttribute("href", appUrlElement.protocol + "//" + appUrlElement.host + resolvedPathname);
|
|
747
|
+
iframeDocument.head.insertBefore(baseElement, iframeDocument.head.firstChild);
|
|
748
|
+
}
|
|
749
|
+
/**
|
|
750
|
+
* 初始化iframe的dom结构
|
|
751
|
+
* @param iframeWindow
|
|
752
|
+
* @param sandbox
|
|
753
|
+
* @param mainHostPath
|
|
754
|
+
* @param appHostPath
|
|
755
|
+
*/
|
|
756
|
+
function initIframeDom(iframeWindow, sandbox, mainHostPath, appHostPath) {
|
|
757
|
+
const iframeDocument = iframeWindow.document;
|
|
758
|
+
const newDoc = window.document.implementation.createHTMLDocument("");
|
|
759
|
+
const newDocumentElement = iframeDocument.importNode(newDoc.documentElement, true);
|
|
760
|
+
iframeDocument.documentElement
|
|
761
|
+
? iframeDocument.replaceChild(newDocumentElement, iframeDocument.documentElement)
|
|
762
|
+
: iframeDocument.appendChild(newDocumentElement);
|
|
763
|
+
iframeWindow.__XPE_RAW_DOCUMENT_HEAD__ = iframeDocument.head;
|
|
764
|
+
iframeWindow.__XPE_RAW_DOCUMENT_QUERY_SELECTOR__ = iframeWindow.Document.prototype.querySelector;
|
|
765
|
+
iframeWindow.__XPE_RAW_DOCUMENT_QUERY_SELECTOR_ALL__ = iframeWindow.Document.prototype.querySelectorAll;
|
|
766
|
+
iframeWindow.__XPE_RAW_DOCUMENT_CREATE_ELEMENT__ = iframeWindow.Document.prototype.createElement;
|
|
767
|
+
iframeWindow.__XPE_RAW_DOCUMENT_CREATE_TEXT_NODE__ = iframeWindow.Document.prototype.createTextNode;
|
|
768
|
+
initBase(iframeWindow, sandbox.url);
|
|
769
|
+
patchIframeHistory(iframeWindow, appHostPath, mainHostPath);
|
|
770
|
+
patchIframeEvents(iframeWindow);
|
|
771
|
+
patchNodeEventListeners(iframeWindow);
|
|
772
|
+
syncIframeUrlToWindow(iframeWindow);
|
|
773
|
+
patchWindowEffect(iframeWindow);
|
|
774
|
+
patchDocumentEffect(iframeWindow);
|
|
775
|
+
patchNodeEffect(iframeWindow);
|
|
776
|
+
patchRelativeUrlEffect(iframeWindow);
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* 防止运行主应用的js代码,给子应用带来很多副作用
|
|
780
|
+
*
|
|
781
|
+
* options.fallbackSrc 表示 iframe 是用 srcdoc 启动的(不发请求加载主应用 host)
|
|
782
|
+
* 此时需要通过 document.open()/close() 在主应用上下文里把 iframe 的 URL
|
|
783
|
+
* 由 about:srcdoc 改写成主应用 URL,否则 location.origin 不是主应用同源,
|
|
784
|
+
* 子应用的 router/fetch 等都会出问题。
|
|
785
|
+
*
|
|
786
|
+
* 关键时序:srcdoc 是异步 navigation,appendChild 之后 iframe.contentWindow.document
|
|
787
|
+
* 还是初始 about:blank,立刻 open() 会被随后到来的 srcdoc 文档替换掉。
|
|
788
|
+
* 因此 srcdoc 分支必须等 iframe 的 load 事件触发(srcdoc 文档已就位)再做 trick。
|
|
789
|
+
*
|
|
790
|
+
* 如果 trick 在当前浏览器上失败(极少见),会兜底到 fallbackSrc 真实加载,
|
|
791
|
+
* 此时由于不再走 srcdoc,需要切换到 stopIframeLoading 的"立即 stop"分支。
|
|
792
|
+
*/
|
|
793
|
+
function stopIframeLoading(iframe, options) {
|
|
794
|
+
const iframeWindow = iframe.contentWindow;
|
|
795
|
+
return new Promise((resolve) => {
|
|
796
|
+
if (!iframeWindow) {
|
|
797
|
+
resolve();
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
// srcdoc 路径:等 srcdoc 文档就位(load 事件),然后做一次 document.open() trick
|
|
801
|
+
if (options) {
|
|
802
|
+
let done = false;
|
|
803
|
+
const runTrick = () => {
|
|
804
|
+
if (done)
|
|
805
|
+
return;
|
|
806
|
+
done = true;
|
|
807
|
+
const newDoc = iframeWindow.document;
|
|
808
|
+
const previousHref = iframeWindow.location.href;
|
|
809
|
+
newDoc.open();
|
|
810
|
+
newDoc.close();
|
|
811
|
+
// 按 HTML spec,document.open() 同步改写当前 document 的 URL,无需轮询
|
|
812
|
+
if (iframeWindow.location.href !== previousHref) {
|
|
813
|
+
resolve();
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
// 极少数浏览器未按 spec 同步改 URL,兜底走 fallbackSrc 真实加载
|
|
817
|
+
warn(`xpe: srcdoc + document.open() trick failed, fallback to load ${options.fallbackSrc} this time.`);
|
|
818
|
+
// HTML spec 规定 srcdoc 优先级高于 src,必须先移除 srcdoc 才能让 src 生效
|
|
819
|
+
iframe.removeAttribute("srcdoc");
|
|
820
|
+
iframe.src = options.fallbackSrc;
|
|
821
|
+
stopIframeLoading(iframe, false).then(resolve);
|
|
822
|
+
};
|
|
823
|
+
iframe.addEventListener("load", runTrick, { once: true });
|
|
824
|
+
// 5s 安全网:load 理论上必定触发,加一层保险避免诡异挂死
|
|
825
|
+
setTimeout(runTrick, 5e3);
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
// fallback 真实加载路径:仍需轮询,赶在页面真正加载完成前 stop()
|
|
829
|
+
const oldDoc = iframeWindow.document;
|
|
830
|
+
const loopDeadline = Date.now() + 5e3;
|
|
831
|
+
function loop() {
|
|
832
|
+
setTimeout(() => {
|
|
833
|
+
let newDoc;
|
|
834
|
+
try {
|
|
835
|
+
newDoc = iframeWindow.document;
|
|
836
|
+
}
|
|
837
|
+
catch (err) {
|
|
838
|
+
newDoc = null;
|
|
839
|
+
}
|
|
840
|
+
if ((!newDoc || newDoc == oldDoc) && Date.now() < loopDeadline) {
|
|
841
|
+
loop();
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
iframeWindow.stop ? iframeWindow.stop() : newDoc.execCommand("Stop");
|
|
845
|
+
resolve();
|
|
846
|
+
}, 1);
|
|
847
|
+
}
|
|
848
|
+
loop();
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
/**
|
|
852
|
+
* 给子应用元素打上 baseURI / ownerDocument 补丁,让它在主应用 DOM 中也保留子应用
|
|
853
|
+
* 的 location / document 语义。
|
|
854
|
+
*
|
|
855
|
+
* 闭包持有策略:用 WeakRef<Window> 间接持有 iframeWindow,proxyLocation / plugins
|
|
856
|
+
* 都通过 iframeWindow 上的内部沙盒全局动态访问。这样一来,当子应用 element 被业务移到
|
|
857
|
+
* 主应用 DOM 下(portal / 弹窗 / 拖拽等),sandbox.destroy() 清理内部沙盒全局后,
|
|
858
|
+
* getter 会自动降级到主 document,element 不会把整个子应用上下文钉在内存中。
|
|
859
|
+
*
|
|
860
|
+
* WeakRef 是 ES2021 标准(Chrome 84+ / Node 14.6+);旧环境降级为强引用以保兼容。
|
|
861
|
+
*/
|
|
862
|
+
export function patchElementEffect(element, iframeWindow) {
|
|
863
|
+
if (element._hasPatch)
|
|
864
|
+
return;
|
|
865
|
+
const HasWeakRef = typeof globalThis.WeakRef === "function";
|
|
866
|
+
const iframeWindowRef = HasWeakRef
|
|
867
|
+
? new globalThis.WeakRef(iframeWindow)
|
|
868
|
+
: { deref: () => iframeWindow };
|
|
869
|
+
try {
|
|
870
|
+
Object.defineProperties(element, {
|
|
871
|
+
baseURI: {
|
|
872
|
+
configurable: true,
|
|
873
|
+
get: () => {
|
|
874
|
+
const win = iframeWindowRef.deref();
|
|
875
|
+
const proxyLocation = win ? getXpeInternalSandbox(win)?.proxyLocation : undefined;
|
|
876
|
+
if (!proxyLocation)
|
|
877
|
+
return window.document.baseURI;
|
|
878
|
+
return proxyLocation.protocol + "//" + proxyLocation.host + proxyLocation.pathname;
|
|
879
|
+
},
|
|
880
|
+
set: undefined,
|
|
881
|
+
},
|
|
882
|
+
ownerDocument: {
|
|
883
|
+
configurable: true,
|
|
884
|
+
get: () => {
|
|
885
|
+
const win = iframeWindowRef.deref();
|
|
886
|
+
// 内部沙盒全局被清理(destroy 后)或 win 本身已 GC 时降级到主 document,
|
|
887
|
+
// 防止 element 永久把 iframeWindow 钉在内存中。
|
|
888
|
+
const sandbox = win ? getXpeInternalSandbox(win) : undefined;
|
|
889
|
+
if (!win || !sandbox)
|
|
890
|
+
return window.document;
|
|
891
|
+
// 降级模式:节点已挂到渲染 iframe,ownerDocument 需与可见 DOM 一致,
|
|
892
|
+
// 否则 wangEditor LO/RO(node.ownerDocument.defaultView instanceof)会失败。
|
|
893
|
+
if (sandbox.degrade && sandbox.document) {
|
|
894
|
+
return sandbox.document;
|
|
895
|
+
}
|
|
896
|
+
return win.document;
|
|
897
|
+
},
|
|
898
|
+
},
|
|
899
|
+
_hasPatch: { get: () => true },
|
|
900
|
+
});
|
|
901
|
+
}
|
|
902
|
+
catch (error) {
|
|
903
|
+
console.warn(error);
|
|
904
|
+
}
|
|
905
|
+
const sandbox = getXpeInternalSandbox(iframeWindow);
|
|
906
|
+
if (sandbox)
|
|
907
|
+
execHooks(sandbox.plugins, "patchElementHook", element, iframeWindow);
|
|
908
|
+
}
|
|
909
|
+
/**
|
|
910
|
+
* 子应用前进后退,同步路由到主应用
|
|
911
|
+
* @param iframeWindow
|
|
912
|
+
*/
|
|
913
|
+
export function syncIframeUrlToWindow(iframeWindow) {
|
|
914
|
+
iframeWindow.addEventListener("hashchange", () => syncUrlToWindow(iframeWindow));
|
|
915
|
+
iframeWindow.addEventListener("popstate", () => {
|
|
916
|
+
syncUrlToWindow(iframeWindow);
|
|
917
|
+
});
|
|
918
|
+
}
|
|
919
|
+
/**
|
|
920
|
+
* iframe插入脚本
|
|
921
|
+
* @param scriptResult script请求结果
|
|
922
|
+
* @param iframeWindow
|
|
923
|
+
* @param rawElement 原始的脚本
|
|
924
|
+
*/
|
|
925
|
+
export function insertScriptToIframe(scriptResult, iframeWindow, rawElement) {
|
|
926
|
+
const { src, module, content, crossorigin, crossoriginType, async, attrs, callback, onload } = scriptResult;
|
|
927
|
+
const sandbox = getActiveSandbox(iframeWindow);
|
|
928
|
+
const container = rawDocumentQuerySelector.call(iframeWindow.document, "head");
|
|
929
|
+
const execNextScript = () => {
|
|
930
|
+
if (async || !container)
|
|
931
|
+
return;
|
|
932
|
+
const nextScriptElement = iframeWindow.document.createElement("script");
|
|
933
|
+
nextScriptElement.textContent = createExecNextScriptContent();
|
|
934
|
+
container.appendChild(nextScriptElement);
|
|
935
|
+
};
|
|
936
|
+
if (!sandbox || !container) {
|
|
937
|
+
onload?.();
|
|
938
|
+
execNextScript();
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
const scriptElement = iframeWindow.document.createElement("script");
|
|
942
|
+
const { replace, plugins, proxyLocation } = sandbox;
|
|
943
|
+
const jsLoader = getJsLoader({ plugins, replace });
|
|
944
|
+
let code = jsLoader(content, src, getCurUrl(proxyLocation));
|
|
945
|
+
// 添加属性
|
|
946
|
+
attrs &&
|
|
947
|
+
Object.keys(attrs)
|
|
948
|
+
.filter((key) => !Object.keys(scriptResult).includes(key))
|
|
949
|
+
.forEach((key) => scriptElement.setAttribute(key, String(attrs[key])));
|
|
950
|
+
// 内联脚本
|
|
951
|
+
if (content) {
|
|
952
|
+
// patch location
|
|
953
|
+
if (!sandbox.degrade && !module && attrs?.type !== "importmap") {
|
|
954
|
+
code = wrapInlineScriptWithSandbox(code);
|
|
955
|
+
}
|
|
956
|
+
const descriptor = Object.getOwnPropertyDescriptor(scriptElement, "src");
|
|
957
|
+
// 部分浏览器 src 不可配置 取不到descriptor表示无该属性,可写
|
|
958
|
+
if (descriptor?.configurable || !descriptor) {
|
|
959
|
+
// 解决 webpack publicPath 为 auto 无法加载资源的问题
|
|
960
|
+
try {
|
|
961
|
+
Object.defineProperty(scriptElement, "src", { get: () => src || "" });
|
|
962
|
+
}
|
|
963
|
+
catch (error) {
|
|
964
|
+
console.warn(error);
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
else {
|
|
969
|
+
src && scriptElement.setAttribute("src", src);
|
|
970
|
+
crossorigin && scriptElement.setAttribute("crossorigin", crossoriginType);
|
|
971
|
+
}
|
|
972
|
+
module && scriptElement.setAttribute("type", "module");
|
|
973
|
+
scriptElement.textContent = code || "";
|
|
974
|
+
let isExecScriptCompleted = false;
|
|
975
|
+
const afterExecScript = () => {
|
|
976
|
+
if (isExecScriptCompleted)
|
|
977
|
+
return;
|
|
978
|
+
isExecScriptCompleted = true;
|
|
979
|
+
onload?.();
|
|
980
|
+
execNextScript();
|
|
981
|
+
};
|
|
982
|
+
// 错误情况处理
|
|
983
|
+
if (/^<!DOCTYPE html/i.test(code)) {
|
|
984
|
+
error(XPE_TIPS_SCRIPT_ERROR_REQUESTED, scriptResult);
|
|
985
|
+
return execNextScript();
|
|
986
|
+
}
|
|
987
|
+
// 打标记
|
|
988
|
+
if (rawElement) {
|
|
989
|
+
setTagToScript(scriptElement, getTagFromScript(rawElement));
|
|
990
|
+
// rawElement 不为空表示这是 effect.ts 转发的动态 <script>(webpack 异步 chunk 等)。
|
|
991
|
+
// 登记到 sandbox.dynamicScriptElements,由 destroy() 统一清理,避免 iframe 残留 detach。
|
|
992
|
+
const sandboxForCleanup = getXpeInternalSandbox(iframeWindow);
|
|
993
|
+
if (sandboxForCleanup && Array.isArray(sandboxForCleanup.dynamicScriptElements)) {
|
|
994
|
+
sandboxForCleanup.dynamicScriptElements.push(scriptElement);
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
// 外联脚本执行后的处理
|
|
998
|
+
const isOutlineScript = !content && src;
|
|
999
|
+
if (isOutlineScript) {
|
|
1000
|
+
scriptElement.onload = afterExecScript;
|
|
1001
|
+
scriptElement.onerror = afterExecScript;
|
|
1002
|
+
}
|
|
1003
|
+
container.appendChild(scriptElement);
|
|
1004
|
+
if (isOutlineScript && module) {
|
|
1005
|
+
waitForModuleLifecycleReady(iframeWindow, afterExecScript);
|
|
1006
|
+
}
|
|
1007
|
+
// 调用回调
|
|
1008
|
+
callback?.(iframeWindow);
|
|
1009
|
+
// 执行 hooks
|
|
1010
|
+
execHooks(plugins, "appendOrInsertElementHook", scriptElement, iframeWindow, rawElement);
|
|
1011
|
+
// 内联脚本执行后的处理
|
|
1012
|
+
!isOutlineScript && afterExecScript();
|
|
1013
|
+
}
|
|
1014
|
+
function waitForModuleLifecycleReady(iframeWindow, callback) {
|
|
1015
|
+
const sandbox = getActiveSandbox(iframeWindow);
|
|
1016
|
+
if (!sandbox)
|
|
1017
|
+
return;
|
|
1018
|
+
const startedAt = performance.now();
|
|
1019
|
+
const tick = () => {
|
|
1020
|
+
if (!getActiveSandbox(iframeWindow))
|
|
1021
|
+
return;
|
|
1022
|
+
if (isFunction(getXpeMount(iframeWindow)) || performance.now() - startedAt > 5000) {
|
|
1023
|
+
callback();
|
|
1024
|
+
return;
|
|
1025
|
+
}
|
|
1026
|
+
window.setTimeout(tick, 16);
|
|
1027
|
+
};
|
|
1028
|
+
tick();
|
|
1029
|
+
}
|
|
1030
|
+
/**
|
|
1031
|
+
* 加载iframe替换子应用
|
|
1032
|
+
* @param src 地址
|
|
1033
|
+
* @param element
|
|
1034
|
+
* @param degradeAttrs
|
|
1035
|
+
*/
|
|
1036
|
+
export function renderIframeReplaceApp(src, element, degradeAttrs = {}) {
|
|
1037
|
+
const iframe = window.document.createElement("iframe");
|
|
1038
|
+
const defaultStyle = "height:100%;width:100%";
|
|
1039
|
+
setAttrsToElement(iframe, { ...degradeAttrs, src, style: [defaultStyle, degradeAttrs.style].join(";") });
|
|
1040
|
+
renderElementToContainer(iframe, element);
|
|
1041
|
+
}
|
|
1042
|
+
// 沙箱 iframe 启动时的空白文档内容
|
|
1043
|
+
// srcdoc 文档的 origin 由 spec 保证继承自 embedder(即主应用),
|
|
1044
|
+
// 这样既不发网络请求,也保证主应用能访问 contentDocument。
|
|
1045
|
+
const SANDBOX_EMPTY_SRCDOC = "<!DOCTYPE html><html><head></head><body></body></html>";
|
|
1046
|
+
/**
|
|
1047
|
+
* js沙箱
|
|
1048
|
+
* 创建和主应用同源的iframe,路径携带了子路由的路由信息
|
|
1049
|
+
* iframe必须禁止加载html,防止进入主应用的路由逻辑
|
|
1050
|
+
*
|
|
1051
|
+
* 统一使用 srcdoc 加载空白文档:
|
|
1052
|
+
* - 不发任何请求加载主应用 host 资源(解决 issue #54)
|
|
1053
|
+
* - origin 继承自 embedder,主应用可以正常 patch contentDocument
|
|
1054
|
+
* - 之后通过 document.open() 把 iframe 的 location 改写到主应用 URL,
|
|
1055
|
+
* 使 location.origin、history、router 等行为与主应用同源一致
|
|
1056
|
+
*
|
|
1057
|
+
* attrs.src 不再作为 iframe 的初始 src(HTML spec 规定 srcdoc 优先级高于 src,
|
|
1058
|
+
* 即便保留 src 浏览器也会忽略它)。它被重新解释为「srcdoc trick 失败时的兜底空白页 URL」,
|
|
1059
|
+
* 用户可指向自己提供的 `/empty` 静态文件或 Service Worker 端点;不传则兜底 mainHostPath。
|
|
1060
|
+
*/
|
|
1061
|
+
export function iframeGenerator(sandbox, attrs, mainHostPath, appHostPath, appRoutePath) {
|
|
1062
|
+
// 把用户传入的 src 拆出来作为 fallback 用,不再作为 iframe 的初始 src 直接挂载
|
|
1063
|
+
const { src: userFallbackSrc, ...restAttrs } = attrs || {};
|
|
1064
|
+
const fallbackSrc = userFallbackSrc || mainHostPath;
|
|
1065
|
+
const iframe = window.document.createElement("iframe");
|
|
1066
|
+
const attrsMerge = {
|
|
1067
|
+
style: "display: none",
|
|
1068
|
+
...restAttrs,
|
|
1069
|
+
name: sandbox.id,
|
|
1070
|
+
[XPE_DATA_FLAG]: "",
|
|
1071
|
+
srcdoc: SANDBOX_EMPTY_SRCDOC,
|
|
1072
|
+
};
|
|
1073
|
+
setAttrsToElement(iframe, attrsMerge);
|
|
1074
|
+
window.document.body.appendChild(iframe);
|
|
1075
|
+
const iframeWindow = iframe.contentWindow;
|
|
1076
|
+
if (!iframeWindow) {
|
|
1077
|
+
throw new Error("XPE runtime iframe window is unavailable");
|
|
1078
|
+
}
|
|
1079
|
+
// 变量需要提前注入,在入口函数通过变量防止死循环
|
|
1080
|
+
patchIframeVariable(iframeWindow, sandbox, appHostPath);
|
|
1081
|
+
sandbox.iframeReady = stopIframeLoading(iframe, { fallbackSrc }).then(() => {
|
|
1082
|
+
if (sandbox.isDestroyed())
|
|
1083
|
+
return;
|
|
1084
|
+
const activeIframeWindow = iframe.contentWindow;
|
|
1085
|
+
if (!activeIframeWindow) {
|
|
1086
|
+
throw new Error("XPE runtime iframe window is unavailable");
|
|
1087
|
+
}
|
|
1088
|
+
if (!getXpeInternalSandbox(activeIframeWindow)) {
|
|
1089
|
+
patchIframeVariable(activeIframeWindow, sandbox, appHostPath);
|
|
1090
|
+
}
|
|
1091
|
+
const activeSandbox = getXpeInternalSandbox(activeIframeWindow);
|
|
1092
|
+
if (!activeSandbox) {
|
|
1093
|
+
throw new Error("XPE runtime sandbox bridge is unavailable");
|
|
1094
|
+
}
|
|
1095
|
+
initIframeDom(activeIframeWindow, sandbox, mainHostPath, appHostPath);
|
|
1096
|
+
/**
|
|
1097
|
+
* 如果有同步优先同步,非同步从url读取
|
|
1098
|
+
*/
|
|
1099
|
+
if (!isMatchSyncQueryById(activeSandbox.id)) {
|
|
1100
|
+
activeIframeWindow.history.replaceState(null, "", mainHostPath + appRoutePath);
|
|
1101
|
+
}
|
|
1102
|
+
});
|
|
1103
|
+
return iframe;
|
|
1104
|
+
}
|