diff --git a/lib/internal/navigator.js b/lib/internal/navigator.js index 2904779a47e64b..64e9600167f050 100644 --- a/lib/internal/navigator.js +++ b/lib/internal/navigator.js @@ -11,6 +11,7 @@ const { const { ERR_ILLEGAL_CONSTRUCTOR, + ERR_INVALID_THIS, } = require('internal/errors').codes; const { @@ -79,7 +80,8 @@ function getNavigatorPlatform(arch, platform) { } class Navigator { - // Private properties are used to avoid brand validations. + // Private properties are used to avoid brand validations: reading a private + // field from a non-Navigator receiver throws a TypeError on its own. #availableParallelism; #locks; #userAgent; @@ -113,6 +115,10 @@ class Navigator { * @returns {string} */ get language() { + // `language` does not read a private field, so brand-check explicitly + // to keep parity with the other getters when called on a non-Navigator + // receiver (e.g. `Navigator.prototype.language`). + if (!(#languages in this)) throw new ERR_INVALID_THIS('Navigator'); // The default locale might be changed dynamically, so always invoke the // binding. return getDefaultLocale() || 'en-US'; diff --git a/test/parallel/test-navigator.js b/test/parallel/test-navigator.js index 01c5595b93cd12..82e3fe903d455f 100644 --- a/test/parallel/test-navigator.js +++ b/test/parallel/test-navigator.js @@ -147,3 +147,19 @@ Object.defineProperty(navigator, 'language', { value: 'for-testing' }); assert.strictEqual(navigator.language, 'for-testing'); assert.strictEqual(navigator.languages.length, 1); assert.strictEqual(navigator.languages[0] !== 'for-testing', true); + +{ + const { Navigator } = globalThis; + for (const name of Object.keys(Navigator.prototype)) { + assert.throws( + () => Navigator.prototype[name], + { name: 'TypeError' }, + `expected TypeError when reading ${name} on Navigator.prototype`, + ); + assert.throws( + () => Reflect.get(Navigator.prototype, name, {}), + { name: 'TypeError' }, + `expected TypeError when reading ${name} on a plain object`, + ); + } +}