Skip to content

Commit dd42830

Browse files
committed
fix(core): restore shadow dom mode
1 parent fc2292f commit dd42830

File tree

4 files changed

+219
-5
lines changed

4 files changed

+219
-5
lines changed

dev/main/src/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ let defaultConfig: interfaces.Options = {
4545
protectVariable: ['MonitoringInstance', 'Garfish'],
4646
sandbox: {
4747
open: true,
48+
strictIsolation: true,
4849
},
4950

5051
// beforeMount(appInfo) {

packages/core/src/module/app.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,7 @@ export class App {
413413
private async renderTemplate() {
414414
const { appInfo, entryManager, resources } = this;
415415
const { url: baseUrl, DOMApis } = entryManager;
416-
const { htmlNode, appContainer } = createAppContainer(appInfo.name);
416+
const { htmlNode, appContainer } = createAppContainer(appInfo);
417417

418418
// Transformation relative path
419419
this.htmlNode = htmlNode;

packages/utils/src/container.ts

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,47 @@
11
import { interfaces } from '@garfish/core';
2+
import { dispatchEvents } from './dispatchEvents';
23
import { __MockHtml__ } from './garfish';
34
import { assert, createKey } from './utils';
45

5-
export function createAppContainer(name: string) {
6+
// Guarantee in strict isolation mode, the shadow in the dom popup window, floating layer depends on the body style of work
7+
function asyncNodeAttribute(from: Element, to: Element) {
8+
const MutationObserver = window.MutationObserver;
9+
const observer = new MutationObserver((mutations) => {
10+
mutations.forEach(({ type, target, attributeName }) => {
11+
if (target) {
12+
const tag = target.nodeName?.toLowerCase();
13+
if (
14+
type === 'attributes' &&
15+
attributeName === 'style' &&
16+
(target === from || tag === 'body')
17+
) {
18+
const style = (target as any)?.getAttribute('style');
19+
if (style) {
20+
to.setAttribute('style', style);
21+
}
22+
}
23+
}
24+
});
25+
});
26+
observer.observe(from, { attributes: true, subtree: true });
27+
}
28+
29+
export function createAppContainer(appInfo: interfaces.AppInfo) {
30+
const name = appInfo.name;
631
// Create a temporary node, which is destroyed by the module itself
732
const htmlNode = document.createElement('div');
833
const appContainer = document.createElement('div');
934

10-
htmlNode.setAttribute(__MockHtml__, '');
11-
appContainer.id = `garfish_app_for_${name}_${createKey()}`;
12-
appContainer.appendChild(htmlNode);
35+
if (appInfo.sandbox && appInfo.sandbox.strictIsolation) {
36+
const root = appContainer.attachShadow({ mode: 'open' });
37+
root.appendChild(htmlNode);
38+
asyncNodeAttribute(htmlNode, document.body);
39+
dispatchEvents(root);
40+
} else {
41+
htmlNode.setAttribute(__MockHtml__, '');
42+
appContainer.id = `garfish_app_for_${name}_${createKey()}`;
43+
appContainer.appendChild(htmlNode);
44+
}
1345

1446
return {
1547
htmlNode,

packages/utils/src/dispatchEvents.ts

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
import { hasOwn } from './utils';
2+
3+
// reference https://zh-hans.reactjs.org/docs/events.html
4+
const reactEvents = [
5+
'onAbort',
6+
'onAnimationCancel',
7+
'onAnimationEnd',
8+
'onAnimationIteration',
9+
'onAuxClick',
10+
'onBlur',
11+
'onChange',
12+
'onClick',
13+
'onClose',
14+
'onContextMenu',
15+
'onDoubleClick',
16+
'onError',
17+
'onFocus',
18+
'onGotPointerCapture',
19+
'onInput',
20+
'onKeyDown',
21+
'onKeyPress',
22+
'onKeyUp',
23+
'onLoad',
24+
'onLoadEnd',
25+
'onLoadStart',
26+
'onLostPointerCapture',
27+
'onMouseDown',
28+
'onMouseMove',
29+
'onMouseOut',
30+
'onMouseOver',
31+
'onMouseUp',
32+
'onPointerCancel',
33+
'onPointerDown',
34+
'onPointerEnter',
35+
'onPointerLeave',
36+
'onPointerMove',
37+
'onPointerOut',
38+
'onPointerOver',
39+
'onPointerUp',
40+
'onReset',
41+
'onResize',
42+
'onScroll',
43+
'onSelect',
44+
'onSelectionChange',
45+
'onSelectStart',
46+
'onSubmit',
47+
'onTouchCancel',
48+
'onTouchMove',
49+
'onTouchStart',
50+
'onTouchEnd',
51+
'onTransitionCancel',
52+
'onTransitionEnd',
53+
'onDrag',
54+
'onDragEnd',
55+
'onDragEnter',
56+
'onDragExit',
57+
'onDragLeave',
58+
'onDragOver',
59+
'onDragStart',
60+
'onDrop',
61+
'onFocusOut',
62+
];
63+
64+
const divergentNativeEvents = {
65+
onDoubleClick: 'dblclick',
66+
};
67+
68+
const mimickedReactEvents = {
69+
onInput: 'onChange',
70+
onFocusOut: 'onBlur',
71+
onSelectionChange: 'onSelect',
72+
};
73+
74+
export function dispatchEvents(shadowRoot) {
75+
const removeEventListeners = [];
76+
77+
reactEvents.forEach(function (reactEventName) {
78+
const nativeEventName = getNativeEventName(reactEventName);
79+
80+
function retargetEvent(event) {
81+
const path =
82+
event.path ||
83+
(event.composedPath && event.composedPath()) ||
84+
composedPath(event.target);
85+
86+
for (let i = 0; i < path.length; i++) {
87+
const el = path[i];
88+
let props = null;
89+
const reactComponent = findReactComponent(el);
90+
const eventHandlers = findReactEventHandlers(el);
91+
92+
if (!eventHandlers) {
93+
props = findReactProps(reactComponent);
94+
} else {
95+
props = eventHandlers;
96+
}
97+
98+
if (reactComponent && props) {
99+
dispatchEvent(event, reactEventName, props);
100+
}
101+
102+
if (reactComponent && props && mimickedReactEvents[reactEventName]) {
103+
dispatchEvent(event, mimickedReactEvents[reactEventName], props);
104+
}
105+
106+
if (event.cancelBubble) {
107+
break;
108+
}
109+
110+
if (el === shadowRoot) {
111+
break;
112+
}
113+
}
114+
}
115+
116+
shadowRoot.addEventListener(nativeEventName, retargetEvent, false);
117+
118+
removeEventListeners.push(function () {
119+
shadowRoot.removeEventListener(nativeEventName, retargetEvent, false);
120+
});
121+
});
122+
123+
return function () {
124+
removeEventListeners.forEach(function (removeEventListener) {
125+
removeEventListener();
126+
});
127+
};
128+
}
129+
130+
function findReactEventHandlers(item) {
131+
return findReactProperty(item, '__reactEventHandlers');
132+
}
133+
134+
function findReactComponent(item) {
135+
return findReactProperty(item, '_reactInternal');
136+
}
137+
138+
function findReactProperty(item, propertyPrefix) {
139+
for (const key in item) {
140+
if (hasOwn(item, key) && key.indexOf(propertyPrefix) !== -1) {
141+
return item[key];
142+
}
143+
}
144+
}
145+
146+
function findReactProps(component) {
147+
if (!component) return undefined;
148+
if (component.memoizedProps) return component.memoizedProps; // React 16 Fiber
149+
if (component._currentElement && component._currentElement.props)
150+
return component._currentElement.props; // React <=15
151+
}
152+
153+
function dispatchEvent(event, eventType, componentProps) {
154+
event.persist = function () {
155+
event.isPersistent = () => true;
156+
};
157+
158+
if (componentProps[eventType]) {
159+
componentProps[eventType](event);
160+
}
161+
}
162+
163+
function getNativeEventName(reactEventName) {
164+
if (divergentNativeEvents[reactEventName]) {
165+
return divergentNativeEvents[reactEventName];
166+
}
167+
return reactEventName.replace(/^on/, '').toLowerCase();
168+
}
169+
170+
function composedPath(el) {
171+
const path = [];
172+
while (el) {
173+
path.push(el);
174+
if (el.tagName === 'HTML') {
175+
path.push(document);
176+
path.push(window);
177+
return path;
178+
}
179+
el = el.parentElement;
180+
}
181+
}

0 commit comments

Comments
 (0)