✨ hijack document event listener and destroy it after unmount (#1655)
This commit is contained in:
parent
3e101581af
commit
85ae42adbc
|
|
@ -110,7 +110,7 @@ test('should getXPathForElement work well', () => {
|
||||||
const xpath = getXPathForElement(testNode!, document);
|
const xpath = getXPathForElement(testNode!, document);
|
||||||
expect(xpath).toEqual(
|
expect(xpath).toEqual(
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
`/*[name()='HTML' and namespace-uri()='http://www.w3.org/1999/xhtml']/*[name()='BODY' and namespace-uri()='http://www.w3.org/1999/xhtml'][1]/*[name()='ARTICLE' and namespace-uri()='http://www.w3.org/1999/xhtml'][1]/*[name()='DIV' and namespace-uri()='http://www.w3.org/1999/xhtml'][1]/*[name()='DIV' and namespace-uri()='http://www.w3.org/1999/xhtml'][2]`,
|
`/*[name()='HTML']/*[name()='BODY'][1]/*[name()='ARTICLE'][1]/*[name()='DIV'][1]/*[name()='DIV'][2]`,
|
||||||
);
|
);
|
||||||
|
|
||||||
const virtualDOM = document.createElement('div');
|
const virtualDOM = document.createElement('div');
|
||||||
|
|
|
||||||
|
|
@ -6,40 +6,69 @@
|
||||||
|
|
||||||
import { noop } from 'lodash';
|
import { noop } from 'lodash';
|
||||||
|
|
||||||
const rawAddEventListener = window.addEventListener;
|
const rawWindowAddEventListener = window.addEventListener;
|
||||||
const rawRemoveEventListener = window.removeEventListener;
|
const rawWindowRemoveEventListener = window.removeEventListener;
|
||||||
|
const rawDocAddEventListener = document.addEventListener;
|
||||||
|
const rawDocRemoveEventListener = document.removeEventListener;
|
||||||
|
|
||||||
export default function patch(global: WindowProxy) {
|
export default function patch(global: WindowProxy) {
|
||||||
const listenerMap = new Map<string, EventListenerOrEventListenerObject[]>();
|
const windowListenerMap = new Map<string, EventListenerOrEventListenerObject[]>();
|
||||||
|
const docListenerMap = new Map<string, EventListenerOrEventListenerObject[]>();
|
||||||
|
|
||||||
global.addEventListener = (
|
global.addEventListener = (
|
||||||
type: string,
|
type: string,
|
||||||
listener: EventListenerOrEventListenerObject,
|
listener: EventListenerOrEventListenerObject,
|
||||||
options?: boolean | AddEventListenerOptions,
|
options?: boolean | AddEventListenerOptions,
|
||||||
) => {
|
) => {
|
||||||
const listeners = listenerMap.get(type) || [];
|
const listeners = windowListenerMap.get(type) || [];
|
||||||
listenerMap.set(type, [...listeners, listener]);
|
windowListenerMap.set(type, [...listeners, listener]);
|
||||||
return rawAddEventListener.call(window, type, listener, options);
|
return rawWindowAddEventListener.call(window, type, listener, options);
|
||||||
};
|
};
|
||||||
|
|
||||||
global.removeEventListener = (
|
global.removeEventListener = (
|
||||||
type: string,
|
type: string,
|
||||||
listener: EventListenerOrEventListenerObject,
|
listener: EventListenerOrEventListenerObject,
|
||||||
options?: boolean | AddEventListenerOptions,
|
options?: boolean | AddEventListenerOptions,
|
||||||
) => {
|
) => {
|
||||||
const storedTypeListeners = listenerMap.get(type);
|
const storedTypeListeners = windowListenerMap.get(type);
|
||||||
if (storedTypeListeners && storedTypeListeners.length && storedTypeListeners.indexOf(listener) !== -1) {
|
if (storedTypeListeners && storedTypeListeners.length && storedTypeListeners.indexOf(listener) !== -1) {
|
||||||
storedTypeListeners.splice(storedTypeListeners.indexOf(listener), 1);
|
storedTypeListeners.splice(storedTypeListeners.indexOf(listener), 1);
|
||||||
}
|
}
|
||||||
return rawRemoveEventListener.call(window, type, listener, options);
|
return rawWindowRemoveEventListener.call(window, type, listener, options);
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener = (
|
||||||
|
type: string,
|
||||||
|
listener: EventListenerOrEventListenerObject,
|
||||||
|
options?: boolean | AddEventListenerOptions,
|
||||||
|
) => {
|
||||||
|
const listeners = docListenerMap.get(type) || [];
|
||||||
|
docListenerMap.set(type, [...listeners, listener]);
|
||||||
|
return rawDocAddEventListener.call(document, type, listener, options);
|
||||||
|
};
|
||||||
|
document.removeEventListener = (
|
||||||
|
type: string,
|
||||||
|
listener: EventListenerOrEventListenerObject,
|
||||||
|
options?: boolean | AddEventListenerOptions,
|
||||||
|
) => {
|
||||||
|
const storedTypeListeners = docListenerMap.get(type);
|
||||||
|
if (storedTypeListeners && storedTypeListeners.length && storedTypeListeners.indexOf(listener) !== -1) {
|
||||||
|
storedTypeListeners.splice(storedTypeListeners.indexOf(listener), 1);
|
||||||
|
}
|
||||||
|
return rawDocRemoveEventListener.call(document, type, listener, options);
|
||||||
};
|
};
|
||||||
|
|
||||||
return function free() {
|
return function free() {
|
||||||
listenerMap.forEach((listeners, type) =>
|
windowListenerMap.forEach((listeners, type) =>
|
||||||
[...listeners].forEach((listener) => global.removeEventListener(type, listener)),
|
[...listeners].forEach((listener) => global.removeEventListener(type, listener)),
|
||||||
);
|
);
|
||||||
global.addEventListener = rawAddEventListener;
|
docListenerMap.forEach((listeners, type) =>
|
||||||
global.removeEventListener = rawRemoveEventListener;
|
[...listeners].forEach((listener) => document.removeEventListener(type, listener)),
|
||||||
|
);
|
||||||
|
|
||||||
|
global.addEventListener = rawWindowAddEventListener;
|
||||||
|
global.removeEventListener = rawWindowRemoveEventListener;
|
||||||
|
document.addEventListener = rawDocAddEventListener;
|
||||||
|
document.removeEventListener = rawDocRemoveEventListener;
|
||||||
|
|
||||||
return noop;
|
return noop;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -215,14 +215,14 @@ export default class ProxySandbox implements SandBox {
|
||||||
},
|
},
|
||||||
|
|
||||||
get(target: FakeWindow, p: PropertyKey): any {
|
get(target: FakeWindow, p: PropertyKey): any {
|
||||||
|
if (p === Symbol.unscopables) return unscopables;
|
||||||
|
|
||||||
setCurrentRunningSandboxProxy(proxy);
|
setCurrentRunningSandboxProxy(proxy);
|
||||||
// FIXME if you have any other good ideas
|
// FIXME if you have any other good ideas
|
||||||
// remove the mark in next tick, thus we can identify whether it in micro app or not
|
// remove the mark in next tick, thus we can identify whether it in micro app or not
|
||||||
// this approach is just a workaround, it could not cover all complex cases, such as the micro app runs in the same task context with master in some case
|
// this approach is just a workaround, it could not cover all complex cases, such as the micro app runs in the same task context with master in some case
|
||||||
nextTask(() => setCurrentRunningSandboxProxy(null));
|
nextTask(() => setCurrentRunningSandboxProxy(null));
|
||||||
|
|
||||||
if (p === Symbol.unscopables) return unscopables;
|
|
||||||
|
|
||||||
// avoid who using window.window or window.self to escape the sandbox environment to touch the really window
|
// avoid who using window.window or window.self to escape the sandbox environment to touch the really window
|
||||||
// see https://github.com/eligrey/FileSaver.js/blob/master/src/FileSaver.js#L13
|
// see https://github.com/eligrey/FileSaver.js/blob/master/src/FileSaver.js#L13
|
||||||
if (p === 'window' || p === 'self') {
|
if (p === 'window' || p === 'self') {
|
||||||
|
|
|
||||||
|
|
@ -199,16 +199,12 @@ export function getXPathForElement(el: Node, document: Document): string | void
|
||||||
tmpEle = tmpEle.previousSibling;
|
tmpEle = tmpEle.previousSibling;
|
||||||
}
|
}
|
||||||
|
|
||||||
xpath = `*[name()='${element.nodeName}' and namespace-uri()='${
|
xpath = `*[name()='${element.nodeName}'][${pos}]/${xpath}`;
|
||||||
element.namespaceURI === null ? '' : element.namespaceURI
|
|
||||||
}'][${pos}]/${xpath}`;
|
|
||||||
|
|
||||||
element = element.parentNode!;
|
element = element.parentNode!;
|
||||||
}
|
}
|
||||||
|
|
||||||
xpath = `/*[name()='${document.documentElement.nodeName}' and namespace-uri()='${
|
xpath = `/*[name()='${document.documentElement.nodeName}']/${xpath}`;
|
||||||
element.namespaceURI === null ? '' : element.namespaceURI
|
|
||||||
}']/${xpath}`;
|
|
||||||
xpath = xpath.replace(/\/$/, '');
|
xpath = xpath.replace(/\/$/, '');
|
||||||
|
|
||||||
return xpath;
|
return xpath;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user