hijack document event listener and destroy it after unmount (#1655)

This commit is contained in:
Kuitos 2021-08-16 23:17:56 +08:00 committed by GitHub
parent 3e101581af
commit 85ae42adbc
4 changed files with 46 additions and 21 deletions

View File

@ -110,7 +110,7 @@ test('should getXPathForElement work well', () => {
const xpath = getXPathForElement(testNode!, document);
expect(xpath).toEqual(
// 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');

View File

@ -6,40 +6,69 @@
import { noop } from 'lodash';
const rawAddEventListener = window.addEventListener;
const rawRemoveEventListener = window.removeEventListener;
const rawWindowAddEventListener = window.addEventListener;
const rawWindowRemoveEventListener = window.removeEventListener;
const rawDocAddEventListener = document.addEventListener;
const rawDocRemoveEventListener = document.removeEventListener;
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 = (
type: string,
listener: EventListenerOrEventListenerObject,
options?: boolean | AddEventListenerOptions,
) => {
const listeners = listenerMap.get(type) || [];
listenerMap.set(type, [...listeners, listener]);
return rawAddEventListener.call(window, type, listener, options);
const listeners = windowListenerMap.get(type) || [];
windowListenerMap.set(type, [...listeners, listener]);
return rawWindowAddEventListener.call(window, type, listener, options);
};
global.removeEventListener = (
type: string,
listener: EventListenerOrEventListenerObject,
options?: boolean | AddEventListenerOptions,
) => {
const storedTypeListeners = listenerMap.get(type);
const storedTypeListeners = windowListenerMap.get(type);
if (storedTypeListeners && storedTypeListeners.length && 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() {
listenerMap.forEach((listeners, type) =>
windowListenerMap.forEach((listeners, type) =>
[...listeners].forEach((listener) => global.removeEventListener(type, listener)),
);
global.addEventListener = rawAddEventListener;
global.removeEventListener = rawRemoveEventListener;
docListenerMap.forEach((listeners, type) =>
[...listeners].forEach((listener) => document.removeEventListener(type, listener)),
);
global.addEventListener = rawWindowAddEventListener;
global.removeEventListener = rawWindowRemoveEventListener;
document.addEventListener = rawDocAddEventListener;
document.removeEventListener = rawDocRemoveEventListener;
return noop;
};

View File

@ -215,14 +215,14 @@ export default class ProxySandbox implements SandBox {
},
get(target: FakeWindow, p: PropertyKey): any {
if (p === Symbol.unscopables) return unscopables;
setCurrentRunningSandboxProxy(proxy);
// 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
// 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));
if (p === Symbol.unscopables) return unscopables;
// 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
if (p === 'window' || p === 'self') {

View File

@ -199,16 +199,12 @@ export function getXPathForElement(el: Node, document: Document): string | void
tmpEle = tmpEle.previousSibling;
}
xpath = `*[name()='${element.nodeName}' and namespace-uri()='${
element.namespaceURI === null ? '' : element.namespaceURI
}'][${pos}]/${xpath}`;
xpath = `*[name()='${element.nodeName}'][${pos}]/${xpath}`;
element = element.parentNode!;
}
xpath = `/*[name()='${document.documentElement.nodeName}' and namespace-uri()='${
element.namespaceURI === null ? '' : element.namespaceURI
}']/${xpath}`;
xpath = `/*[name()='${document.documentElement.nodeName}']/${xpath}`;
xpath = xpath.replace(/\/$/, '');
return xpath;