diff --git a/injected/src/content-feature.js b/injected/src/content-feature.js index 8923a66a4f..5a3a3abaeb 100644 --- a/injected/src/content-feature.js +++ b/injected/src/content-feature.js @@ -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} 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 @@ -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)); } /** diff --git a/injected/src/features/fingerprinting-temporary-storage.js b/injected/src/features/fingerprinting-temporary-storage.js index e8f57d0846..306752a310 100644 --- a/injected/src/features/fingerprinting-temporary-storage.js +++ b/injected/src/features/fingerprinting-temporary-storage.js @@ -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, diff --git a/injected/src/features/gpc.js b/injected/src/features/gpc.js index 2a75dc9a29..fddab8ede3 100644 --- a/injected/src/features/gpc.js +++ b/injected/src/features/gpc.js @@ -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, @@ -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, diff --git a/injected/src/features/navigator-interface.js b/injected/src/features/navigator-interface.js index b2a83fb1af..3e95d00b87 100644 --- a/injected/src/features/navigator-interface.js +++ b/injected/src/features/navigator-interface.js @@ -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, diff --git a/injected/src/features/web-compat.js b/injected/src/features/web-compat.js index e0f812e86f..a254a24261 100644 --- a/injected/src/features/web-compat.js +++ b/injected/src/features/web-compat.js @@ -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, @@ -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, @@ -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, @@ -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 @@ -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); } /** diff --git a/injected/src/utils.js b/injected/src/utils.js index e33f1e3ce6..a2cde022e0 100644 --- a/injected/src/utils.js +++ b/injected/src/utils.js @@ -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(); diff --git a/injected/src/wrapper-utils.js b/injected/src/wrapper-utils.js index c543edd687..0aa1856fc5 100644 --- a/injected/src/wrapper-utils.js +++ b/injected/src/wrapper-utils.js @@ -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) { @@ -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 */