node/lib/internal/navigator.js
Mohamed Sayed ba7e6b3c47
lib: make Navigator#language getter throw on invalid this
Signed-off-by: Mohamed Sayed <k@3zrv.com>
PR-URL: https://github.com/nodejs/node/pull/63601
Fixes: https://github.com/nodejs/node/issues/63513
Reviewed-By: LiviaMedeiros <livia@cirno.name>
Reviewed-By: Matthew Aitken <maitken033380023@gmail.com>
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
2026-06-02 14:25:47 +02:00

164 lines
3.9 KiB
JavaScript

'use strict';
const {
ObjectDefineProperties,
ObjectFreeze,
StringPrototypeIndexOf,
StringPrototypeSlice,
StringPrototypeToUpperCase,
Symbol,
} = primordials;
const {
ERR_ILLEGAL_CONSTRUCTOR,
} = require('internal/errors').codes;
const {
kEnumerableProperty,
} = require('internal/util');
const {
getAvailableParallelism,
} = internalBinding('os');
const kInitialize = Symbol('kInitialize');
const {
platform,
arch,
version: nodeVersion,
} = require('internal/process/per_thread');
const {
getDefaultLocale,
} = internalBinding('config');
/**
* @param {string} arch
* @param {string} platform
* @returns {string}
*/
function getNavigatorPlatform(arch, platform) {
if (platform === 'darwin') {
// On macOS, modern browsers return 'MacIntel' even if running on Apple Silicon.
return 'MacIntel';
} else if (platform === 'win32') {
// On Windows, modern browsers return 'Win32' even if running on a 64-bit version of Windows.
// https://developer.mozilla.org/en-US/docs/Web/API/Navigator/platform#usage_notes
return 'Win32';
} else if (platform === 'linux') {
if (arch === 'ia32') {
return 'Linux i686';
} else if (arch === 'x64') {
return 'Linux x86_64';
}
return `Linux ${arch}`;
} else if (platform === 'freebsd') {
if (arch === 'ia32') {
return 'FreeBSD i386';
} else if (arch === 'x64') {
return 'FreeBSD amd64';
}
return `FreeBSD ${arch}`;
} else if (platform === 'openbsd') {
if (arch === 'ia32') {
return 'OpenBSD i386';
} else if (arch === 'x64') {
return 'OpenBSD amd64';
}
return `OpenBSD ${arch}`;
} else if (platform === 'sunos') {
if (arch === 'ia32') {
return 'SunOS i86pc';
}
return `SunOS ${arch}`;
} else if (platform === 'aix') {
return 'AIX';
}
return `${StringPrototypeToUpperCase(platform[0])}${StringPrototypeSlice(platform, 1)} ${arch}`;
}
class Navigator {
// 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;
#platform;
#languages;
constructor() {
if (arguments[0] === kInitialize) {
return;
}
throw new ERR_ILLEGAL_CONSTRUCTOR();
}
/**
* @returns {number}
*/
get hardwareConcurrency() {
this.#availableParallelism ??= getAvailableParallelism();
return this.#availableParallelism;
}
/**
* @returns {LockManager}
*/
get locks() {
this.#locks ??= require('internal/locks').locks;
return this.#locks;
}
/**
* @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`).
this.#languages; // eslint-disable-line no-unused-expressions
// The default locale might be changed dynamically, so always invoke the
// binding.
return getDefaultLocale() || 'en-US';
}
/**
* @returns {Array<string>}
*/
get languages() {
this.#languages ??= ObjectFreeze([this.language]);
return this.#languages;
}
/**
* @returns {string}
*/
get userAgent() {
this.#userAgent ??= `Node.js/${StringPrototypeSlice(nodeVersion, 1, StringPrototypeIndexOf(nodeVersion, '.'))}`;
return this.#userAgent;
}
/**
* @returns {string}
*/
get platform() {
this.#platform ??= getNavigatorPlatform(arch, platform);
return this.#platform;
}
}
ObjectDefineProperties(Navigator.prototype, {
hardwareConcurrency: kEnumerableProperty,
language: kEnumerableProperty,
languages: kEnumerableProperty,
userAgent: kEnumerableProperty,
platform: kEnumerableProperty,
locks: kEnumerableProperty,
});
module.exports = {
getNavigatorPlatform,
navigator: new Navigator(kInitialize),
Navigator,
};