From 85ae42adbc9478fb6e01be356355f781a2826450 Mon Sep 17 00:00:00 2001 From: Kuitos Date: Mon, 16 Aug 2021 23:17:56 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20hijack=20document=20event=20listene?= =?UTF-8?q?r=20and=20destroy=20it=20after=20unmount=20(#1655)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/__tests__/utils.test.ts | 2 +- src/sandbox/patchers/windowListener.ts | 53 ++++++++++++++++++++------ src/sandbox/proxySandbox.ts | 4 +- src/utils.ts | 8 +--- 4 files changed, 46 insertions(+), 21 deletions(-) diff --git a/src/__tests__/utils.test.ts b/src/__tests__/utils.test.ts index ba829c0..a240848 100644 --- a/src/__tests__/utils.test.ts +++ b/src/__tests__/utils.test.ts @@ -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'); diff --git a/src/sandbox/patchers/windowListener.ts b/src/sandbox/patchers/windowListener.ts index 677ddde..58da3b4 100644 --- a/src/sandbox/patchers/windowListener.ts +++ b/src/sandbox/patchers/windowListener.ts @@ -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(); + const windowListenerMap = new Map(); + const docListenerMap = new Map(); 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; }; diff --git a/src/sandbox/proxySandbox.ts b/src/sandbox/proxySandbox.ts index 84ec491..1a1c554 100644 --- a/src/sandbox/proxySandbox.ts +++ b/src/sandbox/proxySandbox.ts @@ -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') { diff --git a/src/utils.ts b/src/utils.ts index a008ac0..9746a6d 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -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;