stealth: add HTMLMediaElement with canPlayType, Navigator constructor
canPlayType was undefined on <audio> and <video> elements. Adds HTMLMediaElement extending Element with canPlayType returning values that match Chrome 145 on Linux (probably/maybe/empty per MIME type). HTMLAudioElement and HTMLVideoElement extend HTMLMediaElement. _elementClassFor routes AUDIO and VIDEO tags to the correct class. Navigator: adds a global Navigator constructor so typeof Navigator !== 'undefined'. Links it into the navigator prototype chain (_wdProto = Object.create(Navigator.prototype)) so navigator instanceof Navigator returns true, matching Chrome. These are two of the signals in server-side fingerprint classifiers (fpCollect et al).
This commit is contained in:
1 parent
a59e756da4
commit
a7a7b0f4d8
1 file changed
+50
-3
Vendored
+50
-3
@@ -2383,9 +2383,51 @@ class DocumentType extends Node {
|
||||
}
|
||||
|
||||
const _cache = new Map();
|
||||
|
||||
// Media elements need canPlayType for codec detection fingerprinting.
|
||||
// Values match Chrome 145 on Linux x86_64 without proprietary codecs.
|
||||
class HTMLMediaElement extends Element {
|
||||
canPlayType(type) {
|
||||
if (!type || typeof type !== 'string') return '';
|
||||
const mime = type.split(';')[0].trim().toLowerCase();
|
||||
if (mime === 'video/mp4' || mime === 'video/webm' || mime === 'video/ogg') return 'probably';
|
||||
if (mime === 'video/x-matroska') return 'maybe';
|
||||
if (mime === 'audio/ogg' || mime === 'audio/webm' || mime === 'audio/wav' ||
|
||||
mime === 'audio/mpeg') return 'probably';
|
||||
if (mime === 'audio/mp4' || mime === 'audio/x-m4a' || mime === 'audio/aac') return 'maybe';
|
||||
return '';
|
||||
}
|
||||
load() {}
|
||||
play() { return Promise.resolve(); }
|
||||
pause() {}
|
||||
get paused() { return true; }
|
||||
get ended() { return false; }
|
||||
get readyState() { return 0; }
|
||||
get currentTime() { return 0; }
|
||||
set currentTime(v) {}
|
||||
get duration() { return NaN; }
|
||||
get volume() { return 1; }
|
||||
set volume(v) {}
|
||||
get muted() { return false; }
|
||||
set muted(v) {}
|
||||
get src() { return this.getAttribute('src') || ''; }
|
||||
set src(v) { this.setAttribute('src', v); }
|
||||
}
|
||||
_markNative(HTMLMediaElement.prototype.canPlayType);
|
||||
_markNative(HTMLMediaElement.prototype.play);
|
||||
_markNative(HTMLMediaElement.prototype.load);
|
||||
_markNative(HTMLMediaElement.prototype.pause);
|
||||
class HTMLVideoElement extends HTMLMediaElement {}
|
||||
class HTMLAudioElement extends HTMLMediaElement {}
|
||||
globalThis.HTMLMediaElement = HTMLMediaElement;
|
||||
globalThis.HTMLVideoElement = HTMLVideoElement;
|
||||
globalThis.HTMLAudioElement = HTMLAudioElement;
|
||||
|
||||
function _elementClassFor(nid) {
|
||||
const tag = _domParse("tag_name", nid);
|
||||
if (tag === "FORM" && globalThis.HTMLFormElement) return globalThis.HTMLFormElement;
|
||||
if (tag === "AUDIO") return HTMLAudioElement;
|
||||
if (tag === "VIDEO") return HTMLVideoElement;
|
||||
return Element;
|
||||
}
|
||||
function _wrap(nid) {
|
||||
@@ -2501,6 +2543,11 @@ function _registerIframe(iframeEl) {
|
||||
enumerable: false,
|
||||
});
|
||||
}
|
||||
// Navigator constructor so that typeof Navigator !== 'undefined' and
|
||||
// navigatorPrototype checks don't throw a ReferenceError.
|
||||
function Navigator() {}
|
||||
_markNative(Navigator);
|
||||
|
||||
// PluginArray must exist before navigator is built so the plugins getter can use it.
|
||||
function PluginArray(items) {
|
||||
for (var _pi = 0; _pi < items.length; _pi++) this[_pi] = items[_pi];
|
||||
@@ -2640,10 +2687,11 @@ globalThis.navigator = {
|
||||
// Move navigator.webdriver off the instance and onto a thin prototype so that
|
||||
// Object.getOwnPropertyDescriptor(navigator, 'webdriver') returns undefined,
|
||||
// matching Chrome where the property lives on Navigator.prototype.
|
||||
// Use Navigator.prototype as the chain base so navigator instanceof Navigator === true.
|
||||
(function() {
|
||||
var _wdGetter = function() { return false; };
|
||||
_markNative(_wdGetter);
|
||||
var _wdProto = Object.create(Object.getPrototypeOf(globalThis.navigator));
|
||||
var _wdProto = Object.create(Navigator.prototype);
|
||||
Object.defineProperty(_wdProto, 'webdriver', {get: _wdGetter, set: undefined, enumerable: true, configurable: true});
|
||||
Object.setPrototypeOf(globalThis.navigator, _wdProto);
|
||||
})();
|
||||
@@ -4574,8 +4622,7 @@ globalThis.HTMLLabelElement = Element;
|
||||
globalThis.HTMLTableElement = Element;
|
||||
globalThis.HTMLIFrameElement = Element;
|
||||
globalThis.HTMLCanvasElement = Element;
|
||||
globalThis.HTMLVideoElement = Element;
|
||||
globalThis.HTMLAudioElement = Element;
|
||||
// HTMLVideoElement and HTMLAudioElement are defined above with canPlayType support.
|
||||
globalThis.HTMLScriptElement = Element;
|
||||
globalThis.HTMLStyleElement = Element;
|
||||
globalThis.HTMLLinkElement = Element;
|
||||
|
||||
Reference in new issue
Block a user