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); 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');

View File

@ -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;
}; };

View File

@ -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') {

View File

@ -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;