Skip to content
14 changes: 8 additions & 6 deletions injected/src/content-feature.js
Original file line number Diff line number Diff line change
Expand Up @@ -258,9 +258,11 @@ export default class ContentFeature extends ConfigFeature {
/**
* Define a property descriptor with debug flags.
* Mainly used for defining new properties. For overriding existing properties, consider using wrapProperty(), wrapMethod() and wrapConstructor().
* @param {any} object - object whose property we are wrapping (most commonly a prototype, e.g. globalThis.BatteryManager.prototype)
* @param {string} propertyName
* @param {import('./wrapper-utils').StrictPropertyDescriptor} descriptor - requires all descriptor options to be defined because we can't validate correctness based on TS types
* @template Obj
* @template {keyof Obj} Key
* @param {Obj} object - object whose property we are wrapping (most commonly a prototype, e.g. globalThis.BatteryManager.prototype)
* @param {Key} propertyName
* @param {import('./wrapper-utils.js').StrictPropertyDescriptorGeneric<Obj, Key>} descriptor - requires all descriptor options to be defined because we can't validate correctness based on TS types
*/
defineProperty(object, propertyName, descriptor) {
// make sure to send a debug flag when the property is used
Expand All @@ -270,16 +272,16 @@ export default class ContentFeature extends ConfigFeature {
if (typeof descriptorProp === 'function') {
const addDebugFlag = this.addDebugFlag.bind(this);
const wrapper = new Proxy(descriptorProp, {
apply(_, thisArg, argumentsList) {
apply(target, thisArg, argumentsList) {
addDebugFlag();
return Reflect.apply(descriptorProp, thisArg, argumentsList);
return target.apply(thisArg, argumentsList);
},
});
descriptor[k] = wrapToString(wrapper, descriptorProp);
}
});

return defineProperty(object, propertyName, descriptor);
return defineProperty(object, propertyName, /** @type {import('./wrapper-utils').StrictPropertyDescriptor} */ (descriptor));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export default class FingerprintingTemporaryStorage extends ContentFeature {
// @ts-expect-error https://app.asana.com/0/1201614831475344/1203979574128023/f
org.call(navigator.webkitTemporaryStorage, modifiedCallback, err);
};
// @ts-expect-error This doesn't exist in the DOM lib
this.defineProperty(Navigator.prototype, 'webkitTemporaryStorage', {
get: () => tStorage,
enumerable: true,
Expand Down
2 changes: 2 additions & 0 deletions injected/src/features/gpc.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export default class GlobalPrivacyControl extends ContentFeature {
if (args.globalPrivacyControlValue) {
// @ts-expect-error https://app.asana.com/0/1201614831475344/1203979574128023/f
if (navigator.globalPrivacyControl) return;
// @ts-expect-error This doesn't exist in the DOM lib
this.defineProperty(Navigator.prototype, 'globalPrivacyControl', {
get: () => true,
configurable: true,
Expand All @@ -18,6 +19,7 @@ export default class GlobalPrivacyControl extends ContentFeature {
// this may be overwritten by the user agent or other extensions
// @ts-expect-error https://app.asana.com/0/1201614831475344/1203979574128023/f
if (typeof navigator.globalPrivacyControl !== 'undefined') return;
// @ts-expect-error This doesn't exist in the DOM lib
this.defineProperty(Navigator.prototype, 'globalPrivacyControl', {
get: () => false,
configurable: true,
Expand Down
1 change: 1 addition & 0 deletions injected/src/features/navigator-interface.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export default class NavigatorInterface extends ContentFeature {
if (!args.platform || !args.platform.name) {
return;
}
// @ts-expect-error This doesn't exist in the DOM lib
this.defineProperty(Navigator.prototype, 'duckduckgo', {
value: {
platform: args.platform.name,
Expand Down
83 changes: 41 additions & 42 deletions injected/src/features/web-compat.js
Original file line number Diff line number Diff line change
Expand Up @@ -237,14 +237,14 @@ export class WebCompat extends ContentFeature {
enumerable: false,
});

this.defineProperty(window.Notification, 'permission', {
this.defineProperty(/** @type {any} */ (window.Notification), 'permission', {
value: 'denied',
writable: false,
configurable: true,
enumerable: true,
});

this.defineProperty(window.Notification, 'maxActions', {
this.defineProperty(/** @type {any} */ (window.Notification), 'maxActions', {
get: () => 2,
configurable: true,
enumerable: true,
Expand Down Expand Up @@ -445,6 +445,7 @@ export class WebCompat extends ContentFeature {
};
// TODO: original property is an accessor descriptor
this.defineProperty(Navigator.prototype, 'credentials', {
// @ts-expect-error validate this
value,
configurable: true,
enumerable: true,
Expand All @@ -461,6 +462,7 @@ export class WebCompat extends ContentFeature {
if (window.safari) {
return;
}
// @ts-expect-error https://app.asana.com/0/1201614831475344/1203979574128023/f
this.defineProperty(window, 'safari', {
value: {},
writable: true,
Expand Down Expand Up @@ -836,7 +838,7 @@ export class WebCompat extends ContentFeature {
/**
* Creates a valid MediaDeviceInfo or InputDeviceInfo object that passes instanceof checks
* @param {'videoinput' | 'audioinput' | 'audiooutput'} kind - The device kind
* @returns {MediaDeviceInfo | InputDeviceInfo}
* @returns {MediaDeviceInfo}
*/
createMediaDeviceInfo(kind) {
// Create an empty object with the correct prototype
Expand All @@ -853,48 +855,45 @@ export class WebCompat extends ContentFeature {
deviceInfo = Object.create(MediaDeviceInfo.prototype);
}

// Define read-only properties from the start
Object.defineProperties(deviceInfo, {
deviceId: {
value: 'default',
writable: false,
configurable: false,
enumerable: true,
},
kind: {
value: kind,
writable: false,
configurable: false,
enumerable: true,
},
label: {
value: '',
writable: false,
configurable: false,
enumerable: true,
},
groupId: {
value: 'default-group',
writable: false,
configurable: false,
enumerable: true,
},
toJSON: {
value: function () {
return {
deviceId: this.deviceId,
kind: this.kind,
label: this.label,
groupId: this.groupId,
};
},
writable: false,
configurable: false,
enumerable: true,
this.defineProperty(deviceInfo, 'deviceId', {
value: 'default',
writable: false,
configurable: false,
enumerable: true,
});
this.defineProperty(deviceInfo, 'kind', {
value: kind,
writable: false,
configurable: false,
enumerable: true,
});
this.defineProperty(deviceInfo, 'label', {
value: '',
writable: false,
configurable: false,
enumerable: true,
});
this.defineProperty(deviceInfo, 'groupId', {
value: 'default-group',
writable: false,
configurable: false,
enumerable: true,
});
this.defineProperty(deviceInfo, 'toJSON', {
value: function () {
return {
deviceId: this.deviceId,
kind: this.kind,
label: this.label,
groupId: this.groupId,
};
},
writable: false,
configurable: false,
enumerable: false,
});

return deviceInfo;
return /** @type {MediaDeviceInfo} */ (deviceInfo);
}

/**
Expand Down
10 changes: 0 additions & 10 deletions injected/src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -489,16 +489,6 @@ export class DDGProxy {
overload() {
this.objectScope[this.property] = this.internal;
}

overloadDescriptor() {
// TODO: this is not always correct! Use wrap* or shim* methods instead
this.feature.defineProperty(this.objectScope, this.property, {
value: this.internal,
writable: true,
enumerable: true,
configurable: true,
});
}
}

const maxCounter = new Map();
Expand Down
25 changes: 23 additions & 2 deletions injected/src/wrapper-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const ddgShimMark = Symbol('ddgShimMark');
* FIXME: this function is not needed anymore after FF xray removal
* Like Object.defineProperty, but with support for Firefox's mozProxies.
* @param {any} object - object whose property we are wrapping (most commonly a prototype, e.g. globalThis.BatteryManager.prototype)
* @param {string} propertyName
* @param {string | number | symbol} propertyName
* @param {import('./wrapper-utils').StrictPropertyDescriptor} descriptor - requires all descriptor options to be defined because we can't validate correctness based on TS types
*/
export function defineProperty(object, propertyName, descriptor) {
Expand Down Expand Up @@ -366,16 +366,37 @@ export function shimProperty(baseObject, propertyName, implInstance, readOnly, d
*/

/**
* @typedef {Object} BaseStrictPropertyDescriptor
* A generic property descriptor for a property of an object, with correct `this` context for accessors.
*
* @template Obj The object type
* @template {keyof Obj} Key The property key
* @typedef {Object} StrictPropertyDescriptorGeneric
* @property {boolean} configurable
* @property {boolean} enumerable
* @property {boolean} [writable]
* @property {(function(this: Obj): Obj[Key]) |Obj[Key]} [value]
* @property {(function(this: Obj): Obj[Key])} [get]
* @property {(function(this: Obj, Obj[Key]): void)} [set]
*/

/**
* @typedef {Object} BaseStrictPropertyDescriptor
* @property {boolean} configurable
* @property {boolean} enumerable
*/
/**
* @typedef {BaseStrictPropertyDescriptor & { value: any; writable: boolean }} StrictDataDescriptor
*/
/**
* @typedef {BaseStrictPropertyDescriptor & { get: () => any; set: (v: any) => void }} StrictAccessorDescriptor
*/
/**
* @typedef {BaseStrictPropertyDescriptor & { get: () => any }} StrictGetDescriptor
*/
/**
* @typedef {BaseStrictPropertyDescriptor & { set: (v: any) => void }} StrictSetDescriptor
*/
/**
* @typedef {StrictDataDescriptor | StrictAccessorDescriptor | StrictGetDescriptor | StrictSetDescriptor} StrictPropertyDescriptor
*/

Expand Down
Loading