Skip to content

Commit a8d88e8

Browse files
author
Mohamed Sayed
committed
lib: brand-check Navigator getters to throw ERR_INVALID_THIS
Fixes: #63513 Signed-off-by: Mohamed Sayed <k@3zrv.com>
1 parent df52c21 commit a8d88e8

2 files changed

Lines changed: 40 additions & 1 deletion

File tree

lib/internal/navigator.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const {
1111

1212
const {
1313
ERR_ILLEGAL_CONSTRUCTOR,
14+
ERR_INVALID_THIS,
1415
} = require('internal/errors').codes;
1516

1617
const {
@@ -79,7 +80,9 @@ function getNavigatorPlatform(arch, platform) {
7980
}
8081

8182
class Navigator {
82-
// Private properties are used to avoid brand validations.
83+
// Private properties also serve as a brand check via the `#field in obj`
84+
// operator in each getter, so callers get a proper TypeError instead of
85+
// an internal "private field" error when invoked on a non-Navigator `this`.
8386
#availableParallelism;
8487
#locks;
8588
#userAgent;
@@ -97,6 +100,7 @@ class Navigator {
97100
* @returns {number}
98101
*/
99102
get hardwareConcurrency() {
103+
if (!(#availableParallelism in this)) throw new ERR_INVALID_THIS('Navigator');
100104
this.#availableParallelism ??= getAvailableParallelism();
101105
return this.#availableParallelism;
102106
}
@@ -105,6 +109,7 @@ class Navigator {
105109
* @returns {LockManager}
106110
*/
107111
get locks() {
112+
if (!(#locks in this)) throw new ERR_INVALID_THIS('Navigator');
108113
this.#locks ??= require('internal/locks').locks;
109114
return this.#locks;
110115
}
@@ -113,6 +118,7 @@ class Navigator {
113118
* @returns {string}
114119
*/
115120
get language() {
121+
if (!(#languages in this)) throw new ERR_INVALID_THIS('Navigator');
116122
// The default locale might be changed dynamically, so always invoke the
117123
// binding.
118124
return getDefaultLocale() || 'en-US';
@@ -122,6 +128,7 @@ class Navigator {
122128
* @returns {Array<string>}
123129
*/
124130
get languages() {
131+
if (!(#languages in this)) throw new ERR_INVALID_THIS('Navigator');
125132
this.#languages ??= ObjectFreeze([this.language]);
126133
return this.#languages;
127134
}
@@ -130,6 +137,7 @@ class Navigator {
130137
* @returns {string}
131138
*/
132139
get userAgent() {
140+
if (!(#userAgent in this)) throw new ERR_INVALID_THIS('Navigator');
133141
this.#userAgent ??= `Node.js/${StringPrototypeSlice(nodeVersion, 1, StringPrototypeIndexOf(nodeVersion, '.'))}`;
134142
return this.#userAgent;
135143
}
@@ -138,6 +146,7 @@ class Navigator {
138146
* @returns {string}
139147
*/
140148
get platform() {
149+
if (!(#platform in this)) throw new ERR_INVALID_THIS('Navigator');
141150
this.#platform ??= getNavigatorPlatform(arch, platform);
142151
return this.#platform;
143152
}

test/parallel/test-navigator.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,3 +147,33 @@ Object.defineProperty(navigator, 'language', { value: 'for-testing' });
147147
assert.strictEqual(navigator.language, 'for-testing');
148148
assert.strictEqual(navigator.languages.length, 1);
149149
assert.strictEqual(navigator.languages[0] !== 'for-testing', true);
150+
151+
{
152+
const { Navigator } = globalThis;
153+
const getterNames = [
154+
'hardwareConcurrency',
155+
'locks',
156+
'language',
157+
'languages',
158+
'userAgent',
159+
'platform',
160+
];
161+
for (const name of getterNames) {
162+
assert.throws(
163+
() => Navigator.prototype[name],
164+
{
165+
name: 'TypeError',
166+
code: 'ERR_INVALID_THIS',
167+
},
168+
`expected TypeError when reading ${name} on Navigator.prototype`,
169+
);
170+
assert.throws(
171+
() => Reflect.get(Navigator.prototype, name, {}),
172+
{
173+
name: 'TypeError',
174+
code: 'ERR_INVALID_THIS',
175+
},
176+
`expected TypeError when reading ${name} on a plain object`,
177+
);
178+
}
179+
}

0 commit comments

Comments
 (0)