diff --git a/packages/icestark-sandbox/CHANGELOG.md b/packages/icestark-sandbox/CHANGELOG.md index af2d5372..f815e66e 100644 --- a/packages/icestark-sandbox/CHANGELOG.md +++ b/packages/icestark-sandbox/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 1.1.2 + +- [fix] hijacked eventListener were not been removed after sandbox unload. ([#295](https://github.com/ice-lab/icestark/issues/295)) +- [fix] never bind `eval` in sandbox. ([#4294](https://github.com/alibaba/ice/issues/4294)) +- [refact] misspelling of Sandbox types. + ## 1.1.1 - [fix] falsy values except `undefined` would be trapped by proxy window. ([#156](https://github.com/ice-lab/icestark/issues/156)) diff --git a/packages/icestark-sandbox/package.json b/packages/icestark-sandbox/package.json index a310ff06..ee6e0b08 100644 --- a/packages/icestark-sandbox/package.json +++ b/packages/icestark-sandbox/package.json @@ -1,6 +1,6 @@ { "name": "@ice/sandbox", - "version": "1.1.1", + "version": "1.1.3", "description": "sandbox for execute scripts", "main": "lib/index.js", "scripts": { diff --git a/packages/icestark-sandbox/src/index.ts b/packages/icestark-sandbox/src/index.ts index f03c2fc5..1f6b8ab3 100644 --- a/packages/icestark-sandbox/src/index.ts +++ b/packages/icestark-sandbox/src/index.ts @@ -2,15 +2,15 @@ export interface SandboxProps { multiMode?: boolean; } -export interface SandboxContructor { +export interface SandboxConstructor { new(): Sandbox; } -// check window contructor function, like Object Array +// check window constructor function, like Object Array function isConstructor(fn) { // generator function and has own prototype properties const hasConstructor = fn.prototype && fn.prototype.constructor === fn && Object.getOwnPropertyNames(fn.prototype).length > 1; - // unnecessary to call toString if it has contructor function + // unnecessary to call toString if it has constructor function const functionStr = !hasConstructor && fn.toString(); const upperCaseRegex = /^function\s+[A-Z]/; @@ -67,8 +67,9 @@ export default class Sandbox { // hijack addEventListener proxyWindow.addEventListener = (eventName, fn, ...rest) => { - const listeners = this.eventListeners[eventName] || []; - listeners.push(fn); + this.eventListeners[eventName] = (this.eventListeners[eventName] || []); + this.eventListeners[eventName].push(fn); + return originalAddEventListener.apply(originalWindow, [eventName, fn, ...rest]); }; // hijack removeEventListener @@ -96,11 +97,11 @@ export default class Sandbox { set(target: Window, p: PropertyKey, value: any): boolean { // eslint-disable-next-line no-prototype-builtins if (!originalWindow.hasOwnProperty(p)) { - // recorde value added in sandbox + // record value added in sandbox propertyAdded[p] = value; // eslint-disable-next-line no-prototype-builtins } else if (!originalValues.hasOwnProperty(p)) { - // if it is already been setted in orignal window, record it's original value + // if it is already been setted in original window, record it's original value originalValues[p] = originalWindow[p]; } // set new value to original window in case of jsonp, js bundle which will be execute outof sandbox @@ -140,6 +141,17 @@ export default class Sandbox { } const value = originalWindow[p]; + + /** + * use `eval` indirectly if you bind it. And if eval code is not being evaluated by a direct call, + * then initialise the execution context as if it was a global execution context. + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval + * https://262.ecma-international.org/5.1/#sec-10.4.2 + */ + if (p === 'eval') { + return value; + } + if (isWindowFunction(value)) { // fix Illegal invocation return value.bind(originalWindow); diff --git a/packages/icestark-sandbox/tests/index.spec.ts b/packages/icestark-sandbox/tests/index.spec.ts index 91054de4..1ac819ae 100644 --- a/packages/icestark-sandbox/tests/index.spec.ts +++ b/packages/icestark-sandbox/tests/index.spec.ts @@ -1,5 +1,8 @@ +import '@testing-library/jest-dom/extend-expect'; import Sandbox from '../src/index'; +const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); + describe('sandbox: excapeSandbox', () => { const sandbox = new Sandbox({}); const delay = (time) => new Promise((resolve) => setTimeout(() => resolve(), time)); @@ -35,7 +38,7 @@ describe('sandbox: default props', () => { }); }); -describe('sandbox: access contructor', () => { +describe('sandbox: access constructor', () => { const sandbox = new Sandbox(); test('execute global functions', () => { @@ -72,4 +75,59 @@ describe('sandbox: falsy values should be trapped.', () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any expect((window as any).a).toBe(undefined); }); -}); \ No newline at end of file +}); + +describe('sandbox: eventListener and setTimeout should be trapped', () => { + /** + * for some reason, set `setTimeout: false` to enable communicate with global. + */ + const sandbox = new Sandbox({ multiMode: false }); + + test('trap eventListener and setTimeout', async () => { + sandbox.execScriptInSandbox(` + window.count = 0; + window.addEventListener('popstate', (event) => { + console.warn('sandbox: onPopState count', count); + count += 1; + }); + history.pushState({page: 1}, "title 1", "?page=1"); + history.pushState({page: 2}, "title 2", "?page=2"); + history.pushState({page: 3}, "title 3", "?page=3"); + history.back(); + + window.id = setTimeout(() => { + expect(count).toEqual(1); + }, 100) + `); + + await delay(1000); + expect((window as any).count).toEqual(1); + sandbox.clear(); + history.back(); + await delay(1000); + expect((window as any).count).toEqual(undefined); + }); +}); + +describe('eval in sandbox', () => { + const sandbox = new Sandbox({ multiMode: true }); + + test('execution context is not global execution context', () => { + let error = null; + try { + sandbox.execScriptInSandbox( + ` + function bar (value) { + eval('console.log(value);'); + } + bar(1); + ` + ); + } catch (e) { + error = e.message; + } + + expect(error).toBe(null); + }); +}); +