Commit Graph
535 Commits
Author SHA1 Message Date
Andrey Mnatsakanov af1e15f6c8 fix(js): make Event/CustomEvent constructors match WebIDL
The Event and CustomEvent constructors diverged from Chrome in three
observable ways:

- new Event() / new CustomEvent() with no argument did not throw, though the
  type argument is required (Chrome: "1 argument required, but only 0
  present."). initEvent/initMouseEvent already had this check; the
  constructors did not.
- the type argument was not coerced to a string, so new Event(123).type was
  the number 123 instead of "123".
- CustomEvent.detail defaulted to undefined; WebIDL CustomEventInit specifies
  `any detail = null`.

Add the required-argument guard to both constructors, coerce type via
String(t) (inherited by every subclass through super), and default detail to
null. createEvent() builds events with an explicit '' type, so the arity
guard leaves the type-"" uninitialized-event path intact.

Fixes #552
2026-08-04 11:00:01 +02:00
Andrey Mnatsakanov a0e1ba5f85 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
2026-08-04 10:35:24 +02:00
Andrey Mnatsakanov ae33543850 fix(dom): match setAttribute by qualified name, not local name only
Node::set_attribute looked up the existing attribute by local name alone,
while get_attribute and the remove_attribute op both match by qualified
name. A parsed namespaced attribute is stored with a separate prefix
(xlink:href -> prefix="xlink", local="href"), so setAttribute("xlink:href")
never matched it: the update was pushed as a second attribute, getAttribute
kept returning the stale original, and the element serialized xlink:href
twice.

Match set_attribute by qualified_name_eq, consistent with the read/remove
paths, so the update lands in place and no duplicate is created.

Fixes #547
2026-08-04 10:34:04 +02:00
Andrey Mnatsakanov 53295faa59 fix(js): surface a parsererror for malformed XML in DOMParser
DOMParser.parseFromString always HTML-parsed, even for XML mime types, so
malformed XML never produced the <parsererror> element Chrome emits and
error-detection code (doc.querySelector('parsererror')) never triggered. obscura
has no XML parser, so full XML semantics stay out of scope; instead, for XML
mime types run a conservative well-formedness check (tag balance, single root,
terminated comment/CDATA/PI, skipping quoted attribute regions) and, on a clear
error, make the document surface a <parsererror>. The check defaults to
well-formed when ambiguous, so valid XML is never falsely flagged.

Fixes #504
2026-08-04 08:43:45 +02:00
SGavrl f0541f40a4 fix(js): isolate bootstrap declarations (#544) 2026-08-03 16:36:19 +00:00
SGavrl 6a55b63fa6 fix(js): isolate bootstrap declarations 2026-08-03 16:31:30 +00:00
Tamish MhatreandSGavrl 23a8a3111b fix: add missing #[ignore] gate and local fixture for concurrent fetch test (#450)
* fix: add missing #[ignore] gate and local fixture for concurrent fetch test

* fix(test): make fetch fixture nonblocking

---------

Co-authored-by: SGavrl <277843151+SGavrl@users.noreply.github.com>
2026-08-03 16:11:48 +00:00
SGavrl 5912c37c57 Merge pull request #514 from mnaza/fix/attribute-ns-503
fix(js): make setAttributeNS/getAttributeNS/removeAttributeNS namespace-aware
2026-08-03 16:10:58 +00:00
SGavrl 73fe8b91ae test namespace style synchronization 2026-08-03 15:36:52 +00:00
SGavrl 1518ca2a70 Merge remote-tracking branch 'origin/main' into maintainer/pr-514-review
# Conflicts:
#	crates/obscura-js/js/bootstrap.js
2026-08-03 15:35:42 +00:00
SGavrl 42f7fad709 Merge pull request #507 from mnaza/fix/insert-adjacent-html-495
fix(js): case-insensitive insertAdjacentHTML that keeps table/select fragments
2026-08-03 15:33:34 +00:00
SGavrl 62ada5ebb9 Merge pull request #494 from mnaza/fix/clonenode-structural-490
fix(js): clone nodes structurally instead of via div.innerHTML
2026-08-03 15:32:18 +00:00
SGavrl 624b98c7ad Merge pull request #542 from lisa0314/fix/531-scroll-into-view-if-needed
feat(cdp): support DOM.scrollIntoViewIfNeeded
2026-08-03 15:31:59 +00:00
SGavrl becb8816e8 Merge pull request #539 from dgcavalcante-png/fix/stealth-honors-ssl-cert-file
fix(net): honor SSL_CERT_FILE / SSL_CERT_DIR in the stealth client (#525 follow-up)
2026-08-03 15:31:19 +00:00
SGavrl 57cedbf069 Merge pull request #540 from dgcavalcante-png/fix/webidl-globals-non-enumerable
fix(js): expose WebIDL interfaces as non-enumerable, like a real browser
2026-08-03 15:30:45 +00:00
lisa0314 84513d5bcc feat(cdp): support DOM.scrollIntoViewIfNeeded 2026-08-03 14:42:03 +08:00
Diego Cavalcante c7e7c703b7 fix(js): expose WebIDL interfaces as non-enumerable, like a real browser
`bootstrap.js` publishes its DOM interfaces with `globalThis.Node = Node`,
which defaults to `enumerable: true`. A real browser exposes every WebIDL
interface on the global as `enumerable: false`, so the difference is a
one-liner for any script that cares:

    Object.getOwnPropertyDescriptor(window, 'Node').enumerable
    // real Chrome: false     obscura: true

It also means `Object.keys(window)` and `for...in` over the global enumerate
22 names a browser would never list.

The file already has the mechanism for this -- `_preHideInternals()`
pre-declares internal globals with `enumerable: false`, and its own comment
notes that later `globalThis.x = value` assignments only update the value and
leave the descriptor intact. This just adds the WebIDL names to that list, so
no assignment site changes.

Before: 9 of 10 sampled interfaces enumerable. After: 0 of 10, matching Chrome.

Regression: interfaces still callable (`typeof Node === 'function'`),
`instanceof Node` / `instanceof Element` hold, `querySelector` works, Hacker
News still yields 30 results under `--stealth`, snapshot builds clean.

Composes with #537 -- verified with both applied: `var Node` pages run, the
JSF/RichFaces case loads (jQuery/A4J/RichFaces all live), and enumerability
stays at 0/10.
2026-08-02 01:26:42 -03:00
Diego Cavalcante fb42229200 fix(net): honor SSL_CERT_FILE / SSL_CERT_DIR in the stealth client
The #525 fix landed on the reqwest path (`client.rs`: `configured_root_paths()`
-> `add_root_certificate`) but did not cover `wreq_client.rs`. The stealth
client builds its `wreq::Client` without a certificate store, so those
variables are silently ignored whenever `--stealth` is on.

The consequence is inverted: the transport with the best TLS fingerprint is the
only one that cannot reach a host behind a private or incomplete CA chain --
precisely the environments #525 described.

Same binary on current main, same private-CA server, only the flag differs:

  SSL_CERT_FILE=ca.pem obscura           fetch https://127.0.0.1:8443/  -> ok
  SSL_CERT_FILE=ca.pem obscura --stealth fetch https://127.0.0.1:8443/  -> fails

`wreq` already exposes the hook -- `ClientBuilder::tls_cert_store(CertStore)`,
and `CertStore::builder().set_default_paths()` reads both variables. It is
simply never called.

Two constraints, both of which fail silently if got wrong:

* `tls_cert_store`, not `tls_options`. `emulation()` is documented as
  "This will overwrite the existing configuration" and internally calls
  `self.tls_options(emulation.tls_options)`; setting TLS options here would
  discard the Chrome fingerprint, which is the whole point of this client.
  `tls_cert_store` is a separate config field and composes with emulation.
* Opt-in only. Supplying a store REPLACES the webpki roots (see
  `set_cert_store` in `tls/conn/ext.rs`), it does not add to them. Applying it
  unconditionally would break ordinary sites whenever the bundle is
  incomplete. With neither variable set, behaviour is byte-for-byte unchanged.

Verified: with the variable, a private-CA host resolves under --stealth;
without it, the same host still fails; with a deliberately EMPTY bundle,
example.com fails -- which is what proves the store is actually applied rather
than the bundle merely happening to be right.

Refs #538, #525
2026-08-02 01:10:02 -03:00
SGavrl eab2a5eea1 Merge pull request #535 from h4ckf0r0day/fix/492-style-sync
Fix/492 style sync
2026-08-01 19:58:18 +00:00
SGavrl 108e777418 Merge remote-tracking branch 'origin/main' into maintainer/pr-492-fix 2026-08-01 19:51:52 +00:00
SGavrl 4b41d7c746 Merge pull request #534 from h4ckf0r0day/fix/517-string-timers
Fix/517 string timers
2026-08-01 19:51:23 +00:00
SGavrl 037e321a79 Merge remote-tracking branch 'origin/main' into maintainer/pr-517-tests 2026-08-01 19:47:51 +00:00
SGavrl 92576571ef Merge pull request #533 from h4ckf0r0day/fix/509-performance-clock
Fix/509 performance clock
2026-08-01 19:46:49 +00:00
SGavrl 2275d8fea0 Merge remote-tracking branch 'origin/main' into maintainer/pr-509-fix
# Conflicts:
#	crates/obscura-js/src/runtime.rs
2026-08-01 19:39:24 +00:00
SGavrl bbc4980eba Merge pull request #510 from mnaza/fix/childnode-coerce-498
fix(js): coerce non-string primitives in before/after/replaceWith
2026-08-01 17:18:11 +00:00
SGavrl 389de72d1b Merge remote-tracking branch 'origin/main' into maintainer/pr-510-review 2026-08-01 17:14:05 +00:00
SGavrl 31ac297a48 Merge pull request #508 from mnaza/fix/history-preserve-url-496
fix(js): keep the current URL when pushState/replaceState omit url
2026-08-01 17:12:59 +00:00
SGavrl e9f783b3cf Merge pull request #511 from mnaza/fix/quirks-mode-selectors-499
fix(dom): honor quirks mode for class/id selector case-sensitivity
2026-08-01 17:12:44 +00:00
SGavrl ec7334d91b Merge pull request #512 from mnaza/fix/remove-double-free-500
fix(dom): guard DomTree::remove() against OOB and double-free
2026-08-01 17:12:28 +00:00
SGavrl 0428099b0f Merge remote-tracking branch 'origin/main' into maintainer/pr-509-fix 2026-08-01 16:04:37 +00:00
SGavrl 76e5280c32 fix(dom): preserve namespaced attribute qualified names 2026-08-01 16:00:52 +00:00
SGavrl 1e305135e3 Merge remote-tracking branch 'origin/main' into maintainer/pr-514-review 2026-08-01 15:58:03 +00:00
SGavrl a2fd4d1735 fix(js): parse fragments in their insertion context 2026-08-01 15:54:51 +00:00
SGavrl 35fa61032b Merge remote-tracking branch 'origin/main' into maintainer/pr-507-review 2026-08-01 15:50:23 +00:00
SGavrl 907e15d85d perf(js): avoid per-element style sync objects 2026-08-01 15:46:25 +00:00
SGavrl 82aa320084 Merge remote-tracking branch 'origin/main' into maintainer/pr-492-fix 2026-08-01 15:44:40 +00:00
SGavrl 5d7a02041a fix(js): preserve inline styles when cloning 2026-08-01 15:41:45 +00:00
SGavrl c327354911 Merge remote-tracking branch 'origin/main' into maintainer/pr-494-review 2026-08-01 15:40:11 +00:00
SGavrl 16b6a09546 Merge remote-tracking branch 'origin/main' into maintainer/pr-517-tests 2026-08-01 15:37:17 +00:00
SGavrl dbc87e86f9 test(js): cover string timer handlers 2026-08-01 15:37:17 +00:00
SGavrl b5c2f9cc4e Merge branch 'maintainer/pr-508-review' into maintainer/pr-510-review
# Conflicts:
#	crates/obscura-js/src/runtime.rs
2026-08-01 15:30:32 +00:00
SGavrl caf51975bb Merge remote-tracking branch 'origin/main' into maintainer/pr-508-review 2026-08-01 15:28:07 +00:00
SGavrl 3f5dd23778 Merge remote-tracking branch 'origin/main' into maintainer/pr-510-review 2026-08-01 15:28:02 +00:00
SGavrl d93ff517fc fix(js): keep performance clock bounded 2026-08-01 15:20:35 +00:00
h4ckf0r0day 5634386488 Update README.md 2026-08-01 01:06:22 +02:00
SGavrl d66e5ec110 Merge pull request #526 from marcoripa96/native-ca-roots
feat(net): trust platform CA roots alongside bundled webpki roots
2026-07-31 17:44:01 +00:00
SGavrl bb21452f90 Merge pull request #532 from alectimison-maker/fix/dynamic-script-data-urls
fix(js): execute dynamic data URL scripts
2026-07-31 17:43:45 +00:00
SGavrl 0def838088 Merge pull request #523 from stack3mpty/fix/missing-event-constructors-521-522
fix(js): add global PromiseRejectionEvent and StorageEvent constructors
2026-07-31 17:43:25 +00:00
SGavrl f46f741e8c fix(net): load configured CA roots without default cost 2026-07-30 21:57:18 +00:00
SGavrl 0ff1ba0ea9 fix(js): match event constructor semantics 2026-07-30 21:43:33 +00:00