Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions injected/src/features/api-manipulation.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
*/
import ContentFeature from '../content-feature.js';
// eslint-disable-next-line no-redeclare
import { hasOwnProperty } from '../captured-globals.js';
import { hasOwnProperty, getOwnPropertyDescriptor } from '../captured-globals.js';
import { processAttr } from '../utils.js';
import { toStringGetTrap } from '../wrapper-utils.js';

/**
* @internal
Expand Down Expand Up @@ -112,8 +113,21 @@ export default class ApiManipulation extends ContentFeature {
wrapApiDescriptor(api, key, change) {
const getterValue = change.getterValue;
if (getterValue) {
// If we are overriding an existing getter, we should preserve its `toString()` output
// to avoid exposing that the API was modified. If we are defining a new property,
// provide a generic "native code" getter string.
const origDescriptor = getOwnPropertyDescriptor(api, key);
const origGetter = origDescriptor?.get;

const getter = () => processAttr(getterValue, undefined);
const getterProxy = new Proxy(getter, {
get: toStringGetTrap(
typeof origGetter === 'function' ? origGetter : getter,
typeof origGetter === 'function' ? undefined : `function get ${key}() { [native code] }`,
),
});
const descriptor = {
get: () => processAttr(getterValue, undefined),
get: getterProxy,
};
if ('enumerable' in change) {
descriptor.enumerable = change.enumerable;
Expand Down
9 changes: 8 additions & 1 deletion injected/unit-test/features.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ describe('ApiManipulation', () => {
};
apiManipulation.wrapApiDescriptor(dummyTarget, 'definedByConfig', change);
expect(dummyTarget.definedByConfig).toBe('defined!');
const desc = Object.getOwnPropertyDescriptor(dummyTarget, 'definedByConfig');
expect(desc?.get?.toString()).toBe('function get definedByConfig() { [native code] }');
});

it('does not define a property if define is not set and property does not exist', () => {
Expand All @@ -140,17 +142,22 @@ describe('ApiManipulation', () => {
});

it('wraps an existing property if present', () => {
const origGetter = () => 4;
Object.defineProperty(dummyTarget, 'hardwareConcurrency', {
get: () => 4,
get: origGetter,
configurable: true,
enumerable: true,
});
const originalDescriptor = Object.getOwnPropertyDescriptor(dummyTarget, 'hardwareConcurrency');
const originalGetterToString = originalDescriptor?.get?.toString();
const change = {
type: 'descriptor',
getterValue: { type: 'number', value: 222 },
};
apiManipulation.wrapApiDescriptor(dummyTarget, 'hardwareConcurrency', change);
// The getter should now return 222
expect(dummyTarget.hardwareConcurrency).toBe(222);
const updatedDescriptor = Object.getOwnPropertyDescriptor(dummyTarget, 'hardwareConcurrency');
expect(updatedDescriptor?.get?.toString()).toBe(originalGetterToString);
});
});
Loading