From a0e1ba5f854b3ef0871a7e4b418833cd9d976e15 Mon Sep 17 00:00:00 2001 From: Andrey Mnatsakanov Date: Tue, 4 Aug 2026 10:35:24 +0200 Subject: [PATCH] fix(js): expose CSSStyleDeclaration as a real global interface CSSStyleDeclaration was added to the _preHideInternals non-enumerable pre-declaration list (commit c7e7c70) on the assumption that its later `globalThis.X = X` assignment would fill in the value. But it was the only one of the 22 WebIDL interfaces with no such assignment, so its value stayed undefined: `typeof window.CSSStyleDeclaration` was "undefined" while `'CSSStyleDeclaration' in window` was true, and because it is the type of element.style / getComputedStyle(), `el.style instanceof CSSStyleDeclaration` threw "Right-hand side of 'instanceof' is not callable" (Chrome returns true). Assign globalThis.CSSStyleDeclaration alongside the other interface globals. The property was already declared writable/configurable, so the assignment only updates the value and it remains enumerable:false, like a real browser. Fixes #546 --- crates/obscura-js/js/bootstrap.js | 6 ++++++ crates/obscura-js/src/runtime.rs | 14 ++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/crates/obscura-js/js/bootstrap.js b/crates/obscura-js/js/bootstrap.js index 4ffed6c..a835cc2 100644 --- a/crates/obscura-js/js/bootstrap.js +++ b/crates/obscura-js/js/bootstrap.js @@ -6041,6 +6041,12 @@ globalThis.DocumentType = DocumentType; globalThis.Node = Node; globalThis.Element = Element; globalThis.Document = Document; +// CSSStyleDeclaration is the type of element.style and getComputedStyle(); it is +// pre-declared non-enumerable in _preHideInternals, but unlike the other WebIDL +// interfaces it had no value assignment, leaving `window.CSSStyleDeclaration` +// undefined (so `el.style instanceof CSSStyleDeclaration` threw). Assigning here +// only fills the value; the property stays enumerable:false, matching Chrome. +globalThis.CSSStyleDeclaration = CSSStyleDeclaration; globalThis.XPathResult = globalThis.XPathResult || class XPathResult {}; Object.assign(globalThis.XPathResult, { ANY_TYPE: 0, diff --git a/crates/obscura-js/src/runtime.rs b/crates/obscura-js/src/runtime.rs index 03a12fc..e3fcb95 100644 --- a/crates/obscura-js/src/runtime.rs +++ b/crates/obscura-js/src/runtime.rs @@ -3484,6 +3484,20 @@ mod tests { assert_eq!(kb, serde_json::json!(true)); } + #[test] + fn cssstyledeclaration_is_a_usable_global_interface() { + // CSSStyleDeclaration was pre-declared non-enumerable but never assigned + // a value (the only WebIDL interface missing its globalThis.X = X line), + // so it was `undefined` while `'CSSStyleDeclaration' in window` was true, + // and `el.style instanceof CSSStyleDeclaration` threw. It must be a real + // constructor, non-enumerable like a browser, and the type of .style. + let mut rt = setup_runtime(""); + let v = rt + .evaluate("(function(){var d=Object.getOwnPropertyDescriptor(window,'CSSStyleDeclaration');return (typeof window.CSSStyleDeclaration)+'|'+(document.body.style instanceof CSSStyleDeclaration)+'|'+(d?d.enumerable:'missing');})()") + .unwrap(); + assert_eq!(v, serde_json::json!("function|true|false")); + } + #[test] fn test_create_event_unknown_type_returns_event() { let mut rt = setup_runtime("");