From a30e4325bcd1e335b8f20979f00ff3a105f07362 Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Wed, 25 Jun 2025 23:07:44 +0100 Subject: [PATCH 01/12] Add PoC support for device enum --- .../integration-test/device-enumeration.spec.js | 2 +- .../webcompat/config/device-enumeration.json | 14 ++++++++++++++ injected/src/features/web-compat.js | 2 +- 3 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 injected/integration-test/test-pages/webcompat/config/device-enumeration.json diff --git a/injected/integration-test/device-enumeration.spec.js b/injected/integration-test/device-enumeration.spec.js index 6428a61cc5..cedc4afeef 100644 --- a/injected/integration-test/device-enumeration.spec.js +++ b/injected/integration-test/device-enumeration.spec.js @@ -29,7 +29,7 @@ test.describe('Device Enumeration Feature', () => { }, featureSettings: { webCompat: { - enumerateDevices: 'enabled', + deviceEnumeration: 'enabled', }, }, }); diff --git a/injected/integration-test/test-pages/webcompat/config/device-enumeration.json b/injected/integration-test/test-pages/webcompat/config/device-enumeration.json new file mode 100644 index 0000000000..676f051404 --- /dev/null +++ b/injected/integration-test/test-pages/webcompat/config/device-enumeration.json @@ -0,0 +1,14 @@ +{ + "readme": "This config is used to test the device enumeration feature.", + "version": 1, + "unprotectedTemporary": [], + "features": { + "webCompat": { + "state": "enabled", + "exceptions": [], + "settings": { + "deviceEnumeration": "enabled" + } + } + } +} \ No newline at end of file diff --git a/injected/src/features/web-compat.js b/injected/src/features/web-compat.js index e0f812e86f..0248e801ed 100644 --- a/injected/src/features/web-compat.js +++ b/injected/src/features/web-compat.js @@ -141,7 +141,7 @@ export class WebCompat extends ContentFeature { if (this.getFeatureSettingEnabled('disableDeviceEnumeration')) { this.preventDeviceEnumeration(); } - if (this.getFeatureSettingEnabled('enumerateDevices')) { + if (this.getFeatureSettingEnabled('deviceEnumeration')) { this.deviceEnumerationFix(); } } From e2bf67f5468d3d8ca94672a153aea094baae5916 Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Tue, 1 Jul 2025 14:03:33 +0100 Subject: [PATCH 02/12] debugging --- injected/src/features/web-compat.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/injected/src/features/web-compat.js b/injected/src/features/web-compat.js index 0248e801ed..789108ef7c 100644 --- a/injected/src/features/web-compat.js +++ b/injected/src/features/web-compat.js @@ -141,9 +141,9 @@ export class WebCompat extends ContentFeature { if (this.getFeatureSettingEnabled('disableDeviceEnumeration')) { this.preventDeviceEnumeration(); } - if (this.getFeatureSettingEnabled('deviceEnumeration')) { + // if (this.getFeatureSettingEnabled('deviceEnumeration')) { this.deviceEnumerationFix(); - } + //} } /** @@ -914,6 +914,7 @@ export class WebCompat extends ContentFeature { */ apply: async (target, thisArg, args) => { try { + debugger; // Request device enumeration information from native /** @type {{willPrompt: boolean, videoInput: boolean, audioInput: boolean, audioOutput: boolean}} */ const response = await this.messaging.request(MSG_DEVICE_ENUMERATION, {}); From e2aa3c4506fdc117f2666ec9c37d11fcff7a2252 Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Fri, 4 Jul 2025 02:42:42 +0100 Subject: [PATCH 03/12] Add typing --- injected/src/features/web-compat.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/injected/src/features/web-compat.js b/injected/src/features/web-compat.js index 789108ef7c..cb41135e12 100644 --- a/injected/src/features/web-compat.js +++ b/injected/src/features/web-compat.js @@ -143,7 +143,7 @@ export class WebCompat extends ContentFeature { } // if (this.getFeatureSettingEnabled('deviceEnumeration')) { this.deviceEnumerationFix(); - //} + // } } /** @@ -914,7 +914,6 @@ export class WebCompat extends ContentFeature { */ apply: async (target, thisArg, args) => { try { - debugger; // Request device enumeration information from native /** @type {{willPrompt: boolean, videoInput: boolean, audioInput: boolean, audioOutput: boolean}} */ const response = await this.messaging.request(MSG_DEVICE_ENUMERATION, {}); From 8c5a6a2e52a655683dd446c1128811f0936882f5 Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Fri, 4 Jul 2025 02:58:02 +0100 Subject: [PATCH 04/12] Use input interface also --- injected/src/features/web-compat.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/injected/src/features/web-compat.js b/injected/src/features/web-compat.js index cb41135e12..ed9ef01632 100644 --- a/injected/src/features/web-compat.js +++ b/injected/src/features/web-compat.js @@ -893,7 +893,20 @@ export class WebCompat extends ContentFeature { enumerable: true, }, }); - + + // Set the prototype based on device type + if (kind === 'videoinput' || kind === 'audioinput') { + // Input devices should inherit from InputDeviceInfo.prototype if available + if (typeof InputDeviceInfo !== 'undefined' && InputDeviceInfo.prototype) { + Object.setPrototypeOf(deviceInfo, InputDeviceInfo.prototype); + } else { + Object.setPrototypeOf(deviceInfo, MediaDeviceInfo.prototype); + } + } else { + // Output devices inherit from MediaDeviceInfo.prototype + Object.setPrototypeOf(deviceInfo, MediaDeviceInfo.prototype); + } + return deviceInfo; } From 3cf8520dc6e508e12f36cf1f57555e51662bfd79 Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Fri, 4 Jul 2025 03:02:01 +0100 Subject: [PATCH 05/12] Lint fixes --- injected/src/features/web-compat.js | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/injected/src/features/web-compat.js b/injected/src/features/web-compat.js index ed9ef01632..0248e801ed 100644 --- a/injected/src/features/web-compat.js +++ b/injected/src/features/web-compat.js @@ -141,9 +141,9 @@ export class WebCompat extends ContentFeature { if (this.getFeatureSettingEnabled('disableDeviceEnumeration')) { this.preventDeviceEnumeration(); } - // if (this.getFeatureSettingEnabled('deviceEnumeration')) { + if (this.getFeatureSettingEnabled('deviceEnumeration')) { this.deviceEnumerationFix(); - // } + } } /** @@ -893,20 +893,7 @@ export class WebCompat extends ContentFeature { enumerable: true, }, }); - - // Set the prototype based on device type - if (kind === 'videoinput' || kind === 'audioinput') { - // Input devices should inherit from InputDeviceInfo.prototype if available - if (typeof InputDeviceInfo !== 'undefined' && InputDeviceInfo.prototype) { - Object.setPrototypeOf(deviceInfo, InputDeviceInfo.prototype); - } else { - Object.setPrototypeOf(deviceInfo, MediaDeviceInfo.prototype); - } - } else { - // Output devices inherit from MediaDeviceInfo.prototype - Object.setPrototypeOf(deviceInfo, MediaDeviceInfo.prototype); - } - + return deviceInfo; } From 94fde1c345befd08564be73e0b749fa78460e97f Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Fri, 4 Jul 2025 03:16:13 +0100 Subject: [PATCH 06/12] Add test case --- .../webcompat/pages/enumerate-devices-api-test.html | 9 ++++++--- injected/src/features/web-compat.js | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/injected/integration-test/test-pages/webcompat/pages/enumerate-devices-api-test.html b/injected/integration-test/test-pages/webcompat/pages/enumerate-devices-api-test.html index 05234a668a..dc1253d2bc 100644 --- a/injected/integration-test/test-pages/webcompat/pages/enumerate-devices-api-test.html +++ b/injected/integration-test/test-pages/webcompat/pages/enumerate-devices-api-test.html @@ -127,9 +127,12 @@ results.push({ name: 'Device properties are read-only', result: (() => { - const originalValue = devices[0].deviceId; - devices[0].deviceId = 'modified'; - return devices[0].deviceId === originalValue; + try { + devices[0].deviceId = 'modified'; + return false; + } catch (e) { + return true; + } })(), expected: true }); diff --git a/injected/src/features/web-compat.js b/injected/src/features/web-compat.js index 0248e801ed..e0f812e86f 100644 --- a/injected/src/features/web-compat.js +++ b/injected/src/features/web-compat.js @@ -141,7 +141,7 @@ export class WebCompat extends ContentFeature { if (this.getFeatureSettingEnabled('disableDeviceEnumeration')) { this.preventDeviceEnumeration(); } - if (this.getFeatureSettingEnabled('deviceEnumeration')) { + if (this.getFeatureSettingEnabled('enumerateDevices')) { this.deviceEnumerationFix(); } } From 6316e4b8ce82cc5699a81070ef70bd95ef7d5003 Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Wed, 9 Jul 2025 20:31:33 +0100 Subject: [PATCH 07/12] Fix test keys --- injected/integration-test/device-enumeration.spec.js | 2 +- .../test-pages/webcompat/config/device-enumeration.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/injected/integration-test/device-enumeration.spec.js b/injected/integration-test/device-enumeration.spec.js index cedc4afeef..6428a61cc5 100644 --- a/injected/integration-test/device-enumeration.spec.js +++ b/injected/integration-test/device-enumeration.spec.js @@ -29,7 +29,7 @@ test.describe('Device Enumeration Feature', () => { }, featureSettings: { webCompat: { - deviceEnumeration: 'enabled', + enumerateDevices: 'enabled', }, }, }); diff --git a/injected/integration-test/test-pages/webcompat/config/device-enumeration.json b/injected/integration-test/test-pages/webcompat/config/device-enumeration.json index 676f051404..00f7c4a3b0 100644 --- a/injected/integration-test/test-pages/webcompat/config/device-enumeration.json +++ b/injected/integration-test/test-pages/webcompat/config/device-enumeration.json @@ -7,7 +7,7 @@ "state": "enabled", "exceptions": [], "settings": { - "deviceEnumeration": "enabled" + "enumerateDevices": "enabled" } } } From 9ac631306a477afccff97165f1461d266e3b513f Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Wed, 9 Jul 2025 22:24:17 +0100 Subject: [PATCH 08/12] Define types on defineProperty --- injected/src/content-feature.js | 14 +-- .../fingerprinting-temporary-storage.js | 1 + injected/src/features/gpc.js | 2 + injected/src/features/navigator-interface.js | 1 + injected/src/features/web-compat.js | 99 ++++++++++--------- injected/src/utils.js | 10 -- injected/src/wrapper-utils.js | 23 ++++- 7 files changed, 88 insertions(+), 62 deletions(-) diff --git a/injected/src/content-feature.js b/injected/src/content-feature.js index 8923a66a4f..cb68f4d128 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, String(propertyName), /** @type {any} */ (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..514cf47a0b 100644 --- a/injected/src/features/web-compat.js +++ b/injected/src/features/web-compat.js @@ -1,3 +1,5 @@ +// TypeScript is disabled for this file due to intentional DOM polyfills (e.g., Notification) that are incompatible with the DOM lib types. + import ContentFeature from '../content-feature.js'; // eslint-disable-next-line no-redeclare import { URL } from '../captured-globals.js'; @@ -236,15 +238,23 @@ export class WebCompat extends ContentFeature { configurable: true, enumerable: false, }); - - this.defineProperty(window.Notification, 'permission', { - value: 'denied', - writable: false, + // window.Notification polyfill is intentionally incompatible with DOM lib types + this.defineProperty(/** @type {any} */ (window.Notification), 'requestPermission', { + value: () => { + return Promise.resolve('denied'); + }, + writable: true, configurable: true, enumerable: true, }); - this.defineProperty(window.Notification, 'maxActions', { + this.defineProperty(/** @type {any} */ (window.Notification), 'permission', { + get: () => 'denied', + configurable: true, + enumerable: false, + }); + + this.defineProperty(/** @type {any} */ (window.Notification), 'maxActions', { get: () => 2, configurable: true, enumerable: true, @@ -445,6 +455,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 +472,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 +848,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 +865,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..9557cd5177 100644 --- a/injected/src/wrapper-utils.js +++ b/injected/src/wrapper-utils.js @@ -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 */ From b1849c0fa3485e708198fc08a1ceb8cac2844bc5 Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Thu, 10 Jul 2025 22:24:44 +0100 Subject: [PATCH 09/12] Add test link --- injected/integration-test/test-pages/index.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/injected/integration-test/test-pages/index.html b/injected/integration-test/test-pages/index.html index d82db682e0..14adffe636 100644 --- a/injected/integration-test/test-pages/index.html +++ b/injected/integration-test/test-pages/index.html @@ -3,5 +3,8 @@

Integration page

This loads the injection file as if it were loaded through the content script.

+ From f0d557156548e29cd3f72a84b2e76dfd76f8bbee Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Sat, 6 Sep 2025 10:46:19 +0100 Subject: [PATCH 10/12] Clean up --- injected/integration-test/test-pages/index.html | 3 --- .../webcompat/config/device-enumeration.json | 14 -------------- .../pages/enumerate-devices-api-test.html | 9 +++------ injected/src/features/web-compat.js | 2 -- 4 files changed, 3 insertions(+), 25 deletions(-) delete mode 100644 injected/integration-test/test-pages/webcompat/config/device-enumeration.json diff --git a/injected/integration-test/test-pages/index.html b/injected/integration-test/test-pages/index.html index 14adffe636..d82db682e0 100644 --- a/injected/integration-test/test-pages/index.html +++ b/injected/integration-test/test-pages/index.html @@ -3,8 +3,5 @@

Integration page

This loads the injection file as if it were loaded through the content script.

- diff --git a/injected/integration-test/test-pages/webcompat/config/device-enumeration.json b/injected/integration-test/test-pages/webcompat/config/device-enumeration.json deleted file mode 100644 index 00f7c4a3b0..0000000000 --- a/injected/integration-test/test-pages/webcompat/config/device-enumeration.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "readme": "This config is used to test the device enumeration feature.", - "version": 1, - "unprotectedTemporary": [], - "features": { - "webCompat": { - "state": "enabled", - "exceptions": [], - "settings": { - "enumerateDevices": "enabled" - } - } - } -} \ No newline at end of file diff --git a/injected/integration-test/test-pages/webcompat/pages/enumerate-devices-api-test.html b/injected/integration-test/test-pages/webcompat/pages/enumerate-devices-api-test.html index dc1253d2bc..05234a668a 100644 --- a/injected/integration-test/test-pages/webcompat/pages/enumerate-devices-api-test.html +++ b/injected/integration-test/test-pages/webcompat/pages/enumerate-devices-api-test.html @@ -127,12 +127,9 @@ results.push({ name: 'Device properties are read-only', result: (() => { - try { - devices[0].deviceId = 'modified'; - return false; - } catch (e) { - return true; - } + const originalValue = devices[0].deviceId; + devices[0].deviceId = 'modified'; + return devices[0].deviceId === originalValue; })(), expected: true }); diff --git a/injected/src/features/web-compat.js b/injected/src/features/web-compat.js index 514cf47a0b..edd3a30a96 100644 --- a/injected/src/features/web-compat.js +++ b/injected/src/features/web-compat.js @@ -1,5 +1,3 @@ -// TypeScript is disabled for this file due to intentional DOM polyfills (e.g., Notification) that are incompatible with the DOM lib types. - import ContentFeature from '../content-feature.js'; // eslint-disable-next-line no-redeclare import { URL } from '../captured-globals.js'; From b33842a411e53f96c3403c519dcab5854af1c31d Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Sat, 6 Sep 2025 10:53:04 +0100 Subject: [PATCH 11/12] Reorder after rebase to reduce noise --- injected/src/features/web-compat.js | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/injected/src/features/web-compat.js b/injected/src/features/web-compat.js index edd3a30a96..a254a24261 100644 --- a/injected/src/features/web-compat.js +++ b/injected/src/features/web-compat.js @@ -236,20 +236,12 @@ export class WebCompat extends ContentFeature { configurable: true, enumerable: false, }); - // window.Notification polyfill is intentionally incompatible with DOM lib types - this.defineProperty(/** @type {any} */ (window.Notification), 'requestPermission', { - value: () => { - return Promise.resolve('denied'); - }, - writable: true, - configurable: true, - enumerable: true, - }); this.defineProperty(/** @type {any} */ (window.Notification), 'permission', { - get: () => 'denied', + value: 'denied', + writable: false, configurable: true, - enumerable: false, + enumerable: true, }); this.defineProperty(/** @type {any} */ (window.Notification), 'maxActions', { @@ -867,25 +859,25 @@ export class WebCompat extends ContentFeature { value: 'default', writable: false, configurable: false, - enumerable: true + enumerable: true, }); this.defineProperty(deviceInfo, 'kind', { value: kind, writable: false, configurable: false, - enumerable: true + enumerable: true, }); this.defineProperty(deviceInfo, 'label', { value: '', writable: false, configurable: false, - enumerable: true + enumerable: true, }); this.defineProperty(deviceInfo, 'groupId', { value: 'default-group', writable: false, configurable: false, - enumerable: true + enumerable: true, }); this.defineProperty(deviceInfo, 'toJSON', { value: function () { @@ -898,7 +890,7 @@ export class WebCompat extends ContentFeature { }, writable: false, configurable: false, - enumerable: false + enumerable: false, }); return /** @type {MediaDeviceInfo} */ (deviceInfo); From d3e59b3c4417b7371bf0d5192fcc75da62052250 Mon Sep 17 00:00:00 2001 From: laghee <20972610+laghee@users.noreply.github.com> Date: Thu, 11 Sep 2025 15:23:40 +0200 Subject: [PATCH 12/12] Fix defineProperty type casting issue causing test failures --- injected/src/content-feature.js | 2 +- injected/src/wrapper-utils.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/injected/src/content-feature.js b/injected/src/content-feature.js index cb68f4d128..5a3a3abaeb 100644 --- a/injected/src/content-feature.js +++ b/injected/src/content-feature.js @@ -281,7 +281,7 @@ export default class ContentFeature extends ConfigFeature { } }); - return defineProperty(object, String(propertyName), /** @type {any} */ (descriptor)); + return defineProperty(object, propertyName, /** @type {import('./wrapper-utils').StrictPropertyDescriptor} */ (descriptor)); } /** diff --git a/injected/src/wrapper-utils.js b/injected/src/wrapper-utils.js index 9557cd5177..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) {