From 9dfc67af74947e131d0c9486840a68a38c927c70 Mon Sep 17 00:00:00 2001 From: SGavrl <277843151+SGavrl@users.noreply.github.com> Date: Sun, 26 Jul 2026 20:07:45 +0000 Subject: [PATCH] dom: expose browser window constructor identity --- crates/obscura-js/js/bootstrap.js | 11 +++++++++++ crates/obscura-js/src/runtime.rs | 17 +++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/crates/obscura-js/js/bootstrap.js b/crates/obscura-js/js/bootstrap.js index dd4114c..c468791 100644 --- a/crates/obscura-js/js/bootstrap.js +++ b/crates/obscura-js/js/bootstrap.js @@ -3715,6 +3715,17 @@ Object.defineProperty(globalThis.Window, Symbol.hasInstance, { value(obj) { return obj === globalThis || (obj && obj.window === obj); }, configurable: true, }); +// A browser global is a Window object, not merely an object accepted by +// `Window[Symbol.hasInstance]`. Framework environment gates (including Ember's) +// also require the direct identity `self.constructor === Window`; leaving the +// inherited Object constructor makes them enter their server-rendering path +// and hand string selectors to DOM render operations. +Object.defineProperty(globalThis, 'constructor', { + value: globalThis.Window, + writable: true, + configurable: true, + enumerable: false, +}); // Remove the static _iframeRegistry and replace with dynamic getters. diff --git a/crates/obscura-js/src/runtime.rs b/crates/obscura-js/src/runtime.rs index a09a8a2..d1b1106 100644 --- a/crates/obscura-js/src/runtime.rs +++ b/crates/obscura-js/src/runtime.rs @@ -1809,6 +1809,23 @@ mod tests { assert_eq!(title, serde_json::json!("Test Page")); } + #[test] + fn global_window_has_browser_constructor_identity() { + let mut rt = setup_runtime(""); + let result = rt + .evaluate( + "return [window === self, self.constructor === Window,\ + window instanceof Window, self.document === document,\ + self.location === location, self.history === history,\ + self.navigator === navigator];", + ) + .unwrap(); + assert_eq!( + result, + serde_json::json!([true, true, true, true, true, true, true]) + ); + } + #[test] fn explicit_viewport_is_distinct_from_fingerprinted_screen() { let dom = parse_html("");