diff --git a/src/sandbox/__tests__/common.test.ts b/src/sandbox/__tests__/common.test.ts index fe5e290..429e21e 100644 --- a/src/sandbox/__tests__/common.test.ts +++ b/src/sandbox/__tests__/common.test.ts @@ -59,4 +59,24 @@ describe('getTargetValue', () => { const boundFn = getTargetValue(window, callableFunction); expect(boundFn.prototype).toBe(callableFunction.prototype); }); + + it("should work well while function's toString()'s return value keeps the same as the origin", () => { + function callableFunction1() {} + function callableFunction2() {} + function callableFunction3() {} + callableFunction2.toString = () => 'instance toString'; + Object.defineProperty(callableFunction3, 'toString', { + get() { + return () => 'instance toString'; + }, + }); + + const boundFn1 = getTargetValue(window, callableFunction1); + const boundFn2 = getTargetValue(window, callableFunction2); + const boundFn3 = getTargetValue(window, callableFunction3); + + expect(boundFn1.toString()).toBe(callableFunction1.toString()); + expect(boundFn2.toString()).toBe(callableFunction2.toString()); + expect(boundFn3.toString()).toBe(callableFunction3.toString()); + }); }); diff --git a/src/sandbox/common.ts b/src/sandbox/common.ts index ebe3366..863727f 100644 --- a/src/sandbox/common.ts +++ b/src/sandbox/common.ts @@ -51,6 +51,25 @@ export function getTargetValue(target: any, value: any): any { Object.defineProperty(boundValue, 'prototype', { value: value.prototype, enumerable: false, writable: true }); } + // Some util, like `function isNative() { return typeof Ctor === 'function' && /native code/.test(Ctor.toString()) }` relies on the original `toString()` result + // but bound functions will always return "function() {[native code]}" for `toString`, which is misleading + if (typeof value.toString === 'function') { + const valueHasInstanceToString = value.hasOwnProperty('toString') && !boundValue.hasOwnProperty('toString'); + const boundValueHasPrototypeToString = boundValue.toString === Function.prototype.toString; + + if (valueHasInstanceToString || boundValueHasPrototypeToString) { + const originToStringDescriptor = Object.getOwnPropertyDescriptor( + valueHasInstanceToString ? value : Function.prototype, + 'toString', + ); + + Object.defineProperty(boundValue, 'toString', { + ...originToStringDescriptor, + ...(originToStringDescriptor?.get ? null : { value: () => value.toString() }), + }); + } + } + functionBoundedValueMap.set(value, boundValue); return boundValue; }