Skip to content

Commit 80ac60d

Browse files
Define types on defineProperty
1 parent dc8dd1c commit 80ac60d

File tree

7 files changed

+76
-38
lines changed

7 files changed

+76
-38
lines changed

injected/src/content-feature.js

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -235,9 +235,11 @@ export default class ContentFeature extends ConfigFeature {
235235
/**
236236
* Define a property descriptor with debug flags.
237237
* Mainly used for defining new properties. For overriding existing properties, consider using wrapProperty(), wrapMethod() and wrapConstructor().
238-
* @param {any} object - object whose property we are wrapping (most commonly a prototype, e.g. globalThis.BatteryManager.prototype)
239-
* @param {string} propertyName
240-
* @param {import('./wrapper-utils').StrictPropertyDescriptor} descriptor - requires all descriptor options to be defined because we can't validate correctness based on TS types
238+
* @template Obj
239+
* @template {keyof Obj} Key
240+
* @param {Obj} object - object whose property we are wrapping (most commonly a prototype, e.g. globalThis.BatteryManager.prototype)
241+
* @param {Key} propertyName
242+
* @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
241243
*/
242244
defineProperty(object, propertyName, descriptor) {
243245
// make sure to send a debug flag when the property is used
@@ -247,16 +249,16 @@ export default class ContentFeature extends ConfigFeature {
247249
if (typeof descriptorProp === 'function') {
248250
const addDebugFlag = this.addDebugFlag.bind(this);
249251
const wrapper = new Proxy(descriptorProp, {
250-
apply(_, thisArg, argumentsList) {
252+
apply(target, thisArg, argumentsList) {
251253
addDebugFlag();
252-
return Reflect.apply(descriptorProp, thisArg, argumentsList);
254+
return target.apply(thisArg, argumentsList);
253255
},
254256
});
255257
descriptor[k] = wrapToString(wrapper, descriptorProp);
256258
}
257259
});
258260

259-
return defineProperty(object, propertyName, descriptor);
261+
return defineProperty(object, String(propertyName), /** @type {any} */ (descriptor));
260262
}
261263

262264
/**

injected/src/features/fingerprinting-temporary-storage.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export default class FingerprintingTemporaryStorage extends ContentFeature {
2626
// @ts-expect-error https://app.asana.com/0/1201614831475344/1203979574128023/f
2727
org.call(navigator.webkitTemporaryStorage, modifiedCallback, err);
2828
};
29+
// @ts-expect-error This doesn't exist in the DOM lib
2930
this.defineProperty(Navigator.prototype, 'webkitTemporaryStorage', {
3031
get: () => tStorage,
3132
enumerable: true,

injected/src/features/gpc.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export default class GlobalPrivacyControl extends ContentFeature {
88
if (args.globalPrivacyControlValue) {
99
// @ts-expect-error https://app.asana.com/0/1201614831475344/1203979574128023/f
1010
if (navigator.globalPrivacyControl) return;
11+
// @ts-expect-error This doesn't exist in the DOM lib
1112
this.defineProperty(Navigator.prototype, 'globalPrivacyControl', {
1213
get: () => true,
1314
configurable: true,
@@ -18,6 +19,7 @@ export default class GlobalPrivacyControl extends ContentFeature {
1819
// this may be overwritten by the user agent or other extensions
1920
// @ts-expect-error https://app.asana.com/0/1201614831475344/1203979574128023/f
2021
if (typeof navigator.globalPrivacyControl !== 'undefined') return;
22+
// @ts-expect-error This doesn't exist in the DOM lib
2123
this.defineProperty(Navigator.prototype, 'globalPrivacyControl', {
2224
get: () => false,
2325
configurable: true,

injected/src/features/navigator-interface.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export default class NavigatorInterface extends ContentFeature {
2222
if (!args.platform || !args.platform.name) {
2323
return;
2424
}
25+
// @ts-expect-error This doesn't exist in the DOM lib
2526
this.defineProperty(Navigator.prototype, 'duckduckgo', {
2627
value: {
2728
platform: args.platform.name,

injected/src/features/web-compat.js

Lines changed: 42 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// TypeScript is disabled for this file due to intentional DOM polyfills (e.g., Notification) that are incompatible with the DOM lib types.
2+
13
import ContentFeature from '../content-feature.js';
24
// eslint-disable-next-line no-redeclare
35
import { URL } from '../captured-globals.js';
@@ -193,15 +195,16 @@ export class WebCompat extends ContentFeature {
193195
}
194196
// Expose the API
195197
this.defineProperty(window, 'Notification', {
198+
// @ts-expect-error window.Notification polyfill is intentionally incompatible with DOM lib types
196199
value: () => {
197200
// noop
198201
},
199202
writable: true,
200203
configurable: true,
201204
enumerable: false,
202205
});
203-
204-
this.defineProperty(window.Notification, 'requestPermission', {
206+
// window.Notification polyfill is intentionally incompatible with DOM lib types
207+
this.defineProperty(/** @type {any} */ (window.Notification), 'requestPermission', {
205208
value: () => {
206209
return Promise.resolve('denied');
207210
},
@@ -210,13 +213,13 @@ export class WebCompat extends ContentFeature {
210213
enumerable: true,
211214
});
212215

213-
this.defineProperty(window.Notification, 'permission', {
216+
this.defineProperty(/** @type {any} */ (window.Notification), 'permission', {
214217
get: () => 'denied',
215218
configurable: true,
216219
enumerable: false,
217220
});
218221

219-
this.defineProperty(window.Notification, 'maxActions', {
222+
this.defineProperty(/** @type {any} */ (window.Notification), 'maxActions', {
220223
get: () => 2,
221224
configurable: true,
222225
enumerable: true,
@@ -400,6 +403,7 @@ export class WebCompat extends ContentFeature {
400403
};
401404
// TODO: original property is an accessor descriptor
402405
this.defineProperty(Navigator.prototype, 'credentials', {
406+
// @ts-expect-error validate this
403407
value,
404408
configurable: true,
405409
enumerable: true,
@@ -416,6 +420,7 @@ export class WebCompat extends ContentFeature {
416420
if (window.safari) {
417421
return;
418422
}
423+
// @ts-expect-error https://app.asana.com/0/1201614831475344/1203979574128023/f
419424
this.defineProperty(window, 'safari', {
420425
value: {},
421426
writable: true,
@@ -791,31 +796,47 @@ export class WebCompat extends ContentFeature {
791796
/**
792797
* Creates a valid MediaDeviceInfo or InputDeviceInfo object that passes instanceof checks
793798
* @param {'videoinput' | 'audioinput' | 'audiooutput'} kind - The device kind
794-
* @returns {MediaDeviceInfo | InputDeviceInfo}
799+
* @returns {MediaDeviceInfo}
795800
*/
796801
createMediaDeviceInfo(kind) {
797-
// Create a simple object that looks like MediaDeviceInfo
798-
const deviceInfo = {
799-
deviceId: 'default',
800-
kind,
801-
label: '',
802-
groupId: 'default-group',
803-
toJSON() {
802+
const deviceInfo = /** @type {MediaDeviceInfo} */ ({});
803+
804+
this.defineProperty(deviceInfo, 'deviceId', {
805+
value: 'default',
806+
writable: false,
807+
configurable: false,
808+
enumerable: true
809+
});
810+
this.defineProperty(deviceInfo, 'kind', {
811+
value: kind,
812+
writable: false,
813+
configurable: false,
814+
enumerable: true
815+
});
816+
this.defineProperty(deviceInfo, 'label', {
817+
value: '',
818+
writable: false,
819+
configurable: false,
820+
enumerable: true
821+
});
822+
this.defineProperty(deviceInfo, 'groupId', {
823+
value: 'default-group',
824+
writable: false,
825+
configurable: false,
826+
enumerable: true
827+
});
828+
this.defineProperty(deviceInfo, 'toJSON', {
829+
value: function () {
804830
return {
805831
deviceId: this.deviceId,
806832
kind: this.kind,
807833
label: this.label,
808834
groupId: this.groupId,
809835
};
810836
},
811-
};
812-
813-
// Make properties read-only to match MediaDeviceInfo behavior
814-
Object.defineProperties(deviceInfo, {
815-
deviceId: { writable: false, configurable: false },
816-
kind: { writable: false, configurable: false },
817-
label: { writable: false, configurable: false },
818-
groupId: { writable: false, configurable: false },
837+
writable: false,
838+
configurable: false,
839+
enumerable: false
819840
});
820841

821842
// Set the prototype based on device type
@@ -831,7 +852,7 @@ export class WebCompat extends ContentFeature {
831852
Object.setPrototypeOf(deviceInfo, MediaDeviceInfo.prototype);
832853
}
833854

834-
return deviceInfo;
855+
return /** @type {MediaDeviceInfo} */ (deviceInfo);
835856
}
836857

837858
/**

injected/src/utils.js

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -458,16 +458,6 @@ export class DDGProxy {
458458
overload() {
459459
this.objectScope[this.property] = this.internal;
460460
}
461-
462-
overloadDescriptor() {
463-
// TODO: this is not always correct! Use wrap* or shim* methods instead
464-
this.feature.defineProperty(this.objectScope, this.property, {
465-
value: this.internal,
466-
writable: true,
467-
enumerable: true,
468-
configurable: true,
469-
});
470-
}
471461
}
472462

473463
const maxCounter = new Map();

injected/src/wrapper-utils.js

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,16 +366,37 @@ export function shimProperty(baseObject, propertyName, implInstance, readOnly, d
366366
*/
367367

368368
/**
369-
* @typedef {Object} BaseStrictPropertyDescriptor
369+
* A generic property descriptor for a property of an object, with correct `this` context for accessors.
370+
*
371+
* @template Obj The object type
372+
* @template {keyof Obj} Key The property key
373+
* @typedef {Object} StrictPropertyDescriptorGeneric
370374
* @property {boolean} configurable
371375
* @property {boolean} enumerable
376+
* @property {boolean} [writable]
377+
* @property {(function(this: Obj): Obj[Key]) |Obj[Key]} [value]
378+
* @property {(function(this: Obj): Obj[Key])} [get]
379+
* @property {(function(this: Obj, Obj[Key]): void)} [set]
372380
*/
373381

382+
/**
383+
* @typedef {Object} BaseStrictPropertyDescriptor
384+
* @property {boolean} configurable
385+
* @property {boolean} enumerable
386+
*/
374387
/**
375388
* @typedef {BaseStrictPropertyDescriptor & { value: any; writable: boolean }} StrictDataDescriptor
389+
*/
390+
/**
376391
* @typedef {BaseStrictPropertyDescriptor & { get: () => any; set: (v: any) => void }} StrictAccessorDescriptor
392+
*/
393+
/**
377394
* @typedef {BaseStrictPropertyDescriptor & { get: () => any }} StrictGetDescriptor
395+
*/
396+
/**
378397
* @typedef {BaseStrictPropertyDescriptor & { set: (v: any) => void }} StrictSetDescriptor
398+
*/
399+
/**
379400
* @typedef {StrictDataDescriptor | StrictAccessorDescriptor | StrictGetDescriptor | StrictSetDescriptor} StrictPropertyDescriptor
380401
*/
381402

0 commit comments

Comments
 (0)