dommy-js-quickjs 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: df093cff1000de6ad73f27367c17845c2b6846b87785913d70695b0d38938daf
4
- data.tar.gz: aab8821f4e6974306d7e47b35730f32d7340cec51437d0926e94986e692bbb4d
3
+ metadata.gz: e78c3781c9d334b0ec8bd70fb68b0fa285685e5de516ff5b72d073aa74800ca1
4
+ data.tar.gz: 9278f706bd5d0929b3c74130e188c90f5bb07e2c8213f84130a21a0b3acce671
5
5
  SHA512:
6
- metadata.gz: efe4b423fc15fc33f2e3a77108f145cadf0d4d072b69c9447f958cbdcb872b367cd7857ce07a975bacb2d7a64a36c3bee760a4070ebfcc893bff8e6df8ea8441
7
- data.tar.gz: c5fa56f132d9df6e4c067f96f0fa5c1e915c63d9ff641832c72aac75307e6fad57d7e671e4585e0cdffe73419cba0e965a08d750e2c50e5dfbad223b5987c6f1
6
+ metadata.gz: e09091050fc6e2d6512708a5cf105da37ae9e30b3edc33da6258d3ac83981837ba52b6d4c4d152e099f7c19cdb146d2e8046b005227227cb54fc98e12447b45b
7
+ data.tar.gz: cd6dd057505db8978757a52d7a52a99d2060b957ea3538db112013d4129178b81a9535bb9ec69ef2c88bc4dccc29ab8aeaa99a847e801c127d0898587dbcbbba
data/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.12.0 — 2026-10-10
4
+
5
+ Requires `dommy >= 0.16.0, < 0.17` and `quickjs ~> 0.22.0`.
6
+
7
+ ### Added
8
+
9
+ - A bare `frameElement` global, read from the window on each access (a frame's container can change). It is a plain readonly attribute, so unlike the [Replaceable] ones below an assignment does not replace it.
10
+
11
+ ### Changed
12
+
13
+ - Requires `dommy >= 0.16.0, < 0.17` (was `>= 0.15.0, < 0.16`). dommy 0.16.0 needs makiri 0.16.0 and changes what `Session#click_link` and `#click_button` return; the suite passes against it, and the boot-cost bound below needs its lazy interface seeding.
14
+ - The realm's bootstrap bundles no longer hand their completion value to Ruby. `window_builtins.js` ends by assigning `window.globalThis`, so every page load converted the whole global object to a Ruby value: 184 host reads and about 6 ms. `Backend#run_bundle` now discards it (the compiled bundle ends with `void 0`). `test/dommy/js/test_window_boot.rb` checks that lazily seeded interfaces resolve like eager ones and bounds the bridge crossings of opening a window (`install_window` under 60; it was 424).
15
+ - The bare timer globals follow the IDL: `setTimeout(handler, timeout = 0, ...arguments)` and `setInterval` hand the extra arguments to the handler, and every timer global has the IDL's `length` (`setTimeout.length` is 1, `clearTimeout.length` 0).
16
+ - The bare forwards to window methods (`structuredClone`, `fetch`, `postMessage`, `addEventListener`, …) carry the operation's name and `length` from dommy's generated WebIDL signatures, instead of being 0-length `(...args)` wrappers.
17
+ - `innerWidth`, `innerHeight`, `scrollX`/`scrollY`, `pageXOffset`/`pageYOffset` and `devicePixelRatio` are read from the window on each access rather than copied once at boot, and an assignment replaces them ([Replaceable]).
18
+ - The WPT file runner boots its browser with `navigable: true`, so an `<iframe src>` loads through dommy's child navigables and the browser's resources (dommy now gives every iframe an initial `about:blank` document and navigates it from a task). `dom/nodes/Element-matches.html` and `Element-webkitMatchesSelector.html` are back to 669/669 and `ParentNode-querySelector-All.html` to 1975/1975.
19
+ - Tests follow dommy's spec changes: a srcless or `about:blank` iframe fires `load` during `appendChild`; boot runs HTML's "the end" to the load task, so a `setTimeout(0)` queued while parsing has fired before `Dommy::Browser.new` / `Session#visit` return, even with `settle: false` (which still leaves what the load handler queued pending); an unhandled rejection fails from the queued "notify about rejected promises" task, at the next `settle`, not from `execute`; and the custom element construction-stack test defines through a `CustomElementRegistry` host object and runs `__rbHost.ceUpgrade`.
20
+
21
+ ### Fixed
22
+
23
+ - The bare `localStorage` and `sessionStorage` globals are read from the window on each access instead of being copied at boot, so a window on an opaque origin (`about:blank`, which `Dommy::Browser.visit` starts from, `data:`, a sandboxed frame) no longer fails to install its globals with `SecurityError: Storage is disabled for an opaque origin`; the page gets that `SecurityError` when it reads them, as the HTML spec says. They are no longer enumerable own properties of the global.
24
+
3
25
  ## 0.11.0 — 2026-10-04
4
26
 
5
27
  Requires `dommy >= 0.15.0, < 0.16` and `quickjs ~> 0.22.0`.
@@ -96,8 +96,14 @@ module Dommy
96
96
  @bundle_cache = {}
97
97
  @bundle_mutex = Mutex.new
98
98
 
99
+ # A bundle runs for its side effects, so its completion value is
100
+ # discarded (the trailing `void 0`, on a line of its own so a final
101
+ # line comment cannot swallow it). Whatever its last statement
102
+ # evaluated to would otherwise be converted to a Ruby value on every
103
+ # VM — for window_builtins.js, whose last statement assigns
104
+ # `window.globalThis`, the entire global object.
99
105
  def self.compiled_bundle(cache_key, source)
100
- @bundle_mutex.synchronize { @bundle_cache[cache_key] ||= compile(source, filename: cache_key.to_s) }
106
+ @bundle_mutex.synchronize { @bundle_cache[cache_key] ||= compile("#{source}\n;void 0;", filename: cache_key.to_s) }
101
107
  end
102
108
 
103
109
  # Execute precompiled bytecode (a Quickjs::Runnable) on this VM in global
@@ -11,26 +11,83 @@ globalThis.frames = window;
11
11
  globalThis.location = window.location;
12
12
  globalThis.history = window.history;
13
13
  globalThis.navigator = window.navigator;
14
- globalThis.sessionStorage = window.sessionStorage;
15
- globalThis.localStorage = window.localStorage;
14
+ // Web Storage is read from the window on each access, not copied at boot: the
15
+ // sessionStorage / localStorage getters throw a SecurityError when "obtain a
16
+ // session/local storage bottle map" fails (HTML, Web storage), which it does
17
+ // for an opaque origin (Storage Standard, "obtain a storage key": an opaque
18
+ // origin is failure). A page on about:blank, data: or a sandboxed frame must
19
+ // see that when it reads `localStorage`, and it must not abort installing the
20
+ // rest of the globals. Not enumerable: on a real window they are accessors of
21
+ // Window.prototype, not own keys of the global — and an enumerable throwing
22
+ // getter would break whatever walks the global's own properties (handing
23
+ // `globalThis` to the host, as window_builtins.js does, reads each of them).
24
+ for (const __n of ["sessionStorage", "localStorage"]) {
25
+ Object.defineProperty(globalThis, __n, {
26
+ get() { return window[__n]; },
27
+ enumerable: false,
28
+ configurable: true,
29
+ });
30
+ }
16
31
  globalThis.CSS = window.CSS;
17
- globalThis.getComputedStyle = (...args) => window.getComputedStyle(...args);
18
- globalThis.matchMedia = (...args) => window.matchMedia(...args);
19
- globalThis.fetch = (...args) => window.fetch(...args);
20
- globalThis.addEventListener = (...args) => window.addEventListener(...args);
21
- globalThis.removeEventListener = (...args) => window.removeEventListener(...args);
22
- globalThis.dispatchEvent = (event) => window.dispatchEvent(event);
32
+ // A bare window method forwards to the window, named and sized as the IDL
33
+ // declares it (`structuredClone.length` is 1, not a rest wrapper's 0): the
34
+ // generated signatures dommy loads give each operation's required-argument
35
+ // count, looked up along the interface's inheritance (EventTarget's
36
+ // addEventListener).
37
+ const __rbWindowOperation = (name) => {
38
+ const signatures = globalThis.__rbIdlSignatures || {};
39
+ for (let iface = "Window"; iface && signatures[iface]; iface = signatures[iface].inherits) {
40
+ const operation = (signatures[iface].operations || {})[name];
41
+ if (operation) return operation;
42
+ }
43
+ return null;
44
+ };
45
+ const __rbForwardWindowMethod = (name) => {
46
+ const forward = function (...args) { return window[name](...args); };
47
+ const operation = __rbWindowOperation(name);
48
+ Object.defineProperty(forward, "name", { value: name });
49
+ Object.defineProperty(forward, "length", { value: operation ? operation.required : 0 });
50
+ globalThis[name] = forward;
51
+ };
52
+ for (const __m of ["getComputedStyle", "matchMedia", "fetch",
53
+ "addEventListener", "removeEventListener", "dispatchEvent"]) {
54
+ __rbForwardWindowMethod(__m);
55
+ }
23
56
 
24
57
  // More bare globals frameworks read directly (e.g. performance.now(), crypto,
25
- // screen). Objects/values are aliased by reference; methods are wrapped so
26
- // `this` binds to the window. All already exist on window.
58
+ // screen). Objects the window always returns the same of are aliased by
59
+ // reference; methods forward (above) so `this` binds to the window.
27
60
  for (const __n of ["performance", "crypto", "screen", "visualViewport",
28
- "indexedDB", "caches", "devicePixelRatio",
29
- "innerWidth", "innerHeight", "scrollX", "scrollY", "pageXOffset"]) {
61
+ "indexedDB", "caches"]) {
30
62
  try { globalThis[__n] = window[__n]; } catch (__e) {}
31
63
  }
64
+ // Values that change (a resize, a scroll) are read from the window each time
65
+ // rather than copied once at boot. They are [Replaceable]: an assignment
66
+ // replaces the accessor with the assigned value, as on a real window.
67
+ for (const __n of ["devicePixelRatio", "innerWidth", "innerHeight",
68
+ "scrollX", "scrollY", "pageXOffset", "pageYOffset"]) {
69
+ try {
70
+ Object.defineProperty(globalThis, __n, {
71
+ get() { return window[__n]; },
72
+ set(value) {
73
+ Object.defineProperty(globalThis, __n, { value, writable: true, enumerable: true, configurable: true });
74
+ },
75
+ enumerable: true,
76
+ configurable: true,
77
+ });
78
+ } catch (__e) {}
79
+ }
80
+ // `frameElement` is a plain readonly attribute: read from the window each
81
+ // time (a frame's container can change), never replaced by an assignment.
82
+ try {
83
+ Object.defineProperty(globalThis, "frameElement", {
84
+ get() { return window.frameElement; },
85
+ enumerable: false,
86
+ configurable: true,
87
+ });
88
+ } catch (__e) {}
32
89
  for (const __m of ["scrollTo", "scrollBy", "requestIdleCallback", "cancelIdleCallback",
33
90
  "getSelection", "structuredClone", "reportError", "btoa", "atob",
34
91
  "alert", "confirm", "prompt", "open", "postMessage"]) {
35
- try { globalThis[__m] = (...args) => window[__m](...args); } catch (__e) {}
92
+ try { __rbForwardWindowMethod(__m); } catch (__e) {}
36
93
  }
@@ -30,17 +30,28 @@ globalThis.__rbFetchTimerOrigin = (id) => {
30
30
  __rbTimerOrigins.delete(id);
31
31
  return origin.stack || "";
32
32
  };
33
- globalThis.setTimeout = (fn, delay) => __rbDefer((f, d) => window.setTimeout(f, d), fn, delay);
34
- globalThis.clearTimeout = (id) => window.clearTimeout(id);
35
- globalThis.setInterval = (fn, delay) => __rbDefer((f, d) => window.setInterval(f, d), fn, delay);
36
- globalThis.clearInterval = (id) => window.clearInterval(id);
37
- globalThis.requestAnimationFrame = (fn) => __rbDefer((f) => window.requestAnimationFrame(f), fn);
38
- globalThis.cancelAnimationFrame = (id) => window.cancelAnimationFrame(id);
33
+ // The signatures are the IDL's (HTML "timers"): `setTimeout(handler, timeout
34
+ // = 0, ...arguments)` hands the extra arguments to the handler, and the
35
+ // defaults keep each function's `length` the IDL's (1, 0, 1, 0, 1, 1).
36
+ globalThis.setTimeout = function setTimeout(handler, timeout = 0, ...args) {
37
+ return __rbDefer((f, d) => window.setTimeout(f, d, ...args), handler, timeout);
38
+ };
39
+ globalThis.clearTimeout = function clearTimeout(id = 0) { return window.clearTimeout(id); };
40
+ globalThis.setInterval = function setInterval(handler, timeout = 0, ...args) {
41
+ return __rbDefer((f, d) => window.setInterval(f, d, ...args), handler, timeout);
42
+ };
43
+ globalThis.clearInterval = function clearInterval(id = 0) { return window.clearInterval(id); };
44
+ globalThis.requestAnimationFrame = function requestAnimationFrame(callback) {
45
+ return __rbDefer((f) => window.requestAnimationFrame(f), callback);
46
+ };
47
+ globalThis.cancelAnimationFrame = function cancelAnimationFrame(handle) {
48
+ return window.cancelAnimationFrame(handle);
49
+ };
39
50
  // queueMicrotask must share the engine's promise-job (microtask) queue so its
40
51
  // callbacks are FIFO-ordered with Promise reactions (the WHATWG
41
52
  // single-microtask-queue model). Routing through the Ruby scheduler instead
42
53
  // would drain on a separate pass, reordering it after all native promise jobs.
43
- globalThis.queueMicrotask = (fn) => {
44
- if (typeof fn !== "function") throw new TypeError("queueMicrotask requires a function");
45
- Promise.resolve().then(() => { fn(); });
54
+ globalThis.queueMicrotask = function queueMicrotask(callback) {
55
+ if (typeof callback !== "function") throw new TypeError("queueMicrotask requires a function");
56
+ Promise.resolve().then(() => { callback(); });
46
57
  };
@@ -3,7 +3,7 @@
3
3
  module Dommy
4
4
  module Js
5
5
  module Quickjs
6
- VERSION = "0.11.0"
6
+ VERSION = "0.12.0"
7
7
  end
8
8
  end
9
9
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dommy-js-quickjs
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.0
4
+ version: 0.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - takahashim
@@ -29,20 +29,20 @@ dependencies:
29
29
  requirements:
30
30
  - - ">="
31
31
  - !ruby/object:Gem::Version
32
- version: 0.15.0
32
+ version: 0.16.0
33
33
  - - "<"
34
34
  - !ruby/object:Gem::Version
35
- version: '0.16'
35
+ version: '0.17'
36
36
  type: :runtime
37
37
  prerelease: false
38
38
  version_requirements: !ruby/object:Gem::Requirement
39
39
  requirements:
40
40
  - - ">="
41
41
  - !ruby/object:Gem::Version
42
- version: 0.15.0
42
+ version: 0.16.0
43
43
  - - "<"
44
44
  - !ruby/object:Gem::Version
45
- version: '0.16'
45
+ version: '0.17'
46
46
  description: |
47
47
  dommy-js-quickjs lets JavaScript drive a Dommy DOM by embedding QuickJS (via
48
48
  the quickjs gem) and bridging DOM nodes to JS through an ES Proxy that routes