dommy-js-quickjs 0.10.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: 56e600a474810ea778587cf249cbb7c200d9947c28270449837550971e5674de
4
- data.tar.gz: ff9e6cf37ed37fb89f148163e38ee6fdd63e8bd5fc1e81669f8538eebc84d3ea
3
+ metadata.gz: e78c3781c9d334b0ec8bd70fb68b0fa285685e5de516ff5b72d073aa74800ca1
4
+ data.tar.gz: 9278f706bd5d0929b3c74130e188c90f5bb07e2c8213f84130a21a0b3acce671
5
5
  SHA512:
6
- metadata.gz: ffa632c98fe6c8bedba9bc8e189cda13a1c9baef1c4bd0224fa1f7f9ffd31bdcaad8bcb84ffdbdf2b2436ec3a29bb68d0d8ceaa7879602f5cc45d8edbc1633ba
7
- data.tar.gz: 22272e499d364204ecba6edb91cb6c01953d72d46990652b3f14907e2141686e8bbc8d94de3d5125e2f96b21e9e1a34b8d5c887c41af3dbe8cd693e33a2370bd
6
+ metadata.gz: e09091050fc6e2d6512708a5cf105da37ae9e30b3edc33da6258d3ac83981837ba52b6d4c4d152e099f7c19cdb146d2e8046b005227227cb54fc98e12447b45b
7
+ data.tar.gz: cd6dd057505db8978757a52d7a52a99d2060b957ea3538db112013d4129178b81a9535bb9ec69ef2c88bc4dccc29ab8aeaa99a847e801c127d0898587dbcbbba
data/CHANGELOG.md CHANGED
@@ -1,5 +1,46 @@
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
+
25
+ ## 0.11.0 — 2026-10-04
26
+
27
+ Requires `dommy >= 0.15.0, < 0.16` and `quickjs ~> 0.22.0`.
28
+
29
+ ### Added
30
+
31
+ - ES modules can be preloaded as bytecode. `Runtime.register_module(name, source:)` registers a module once per process (wrapping `Quickjs.register_module`), and `Runtime.new(preload_modules: [name, …])` reads registered modules into the new VM, so a later page loads them without parsing their source again. A preloaded module is found before the module loader is asked for its name, and a loader that returns `{as: name}` for another specifier lands on it too. dommy's `Dommy::Js::ModulePreload` drives both: with it enabled, a page that imports Turbo as an ES module boots in about 11 ms instead of 24 ms from the third page on.
32
+
33
+ ### Changed
34
+
35
+ - Requires `dommy >= 0.15.0, < 0.16` (was `>= 0.14.0, < 0.15`), the release whose `ModulePreload` uses the API above and whose behavior the suite checks.
36
+ - Requires `quickjs ~> 0.22.0` (was `~> 0.21.0`). 0.22.0 releases [hmsk/quickjs.rb#141](https://github.com/hmsk/quickjs.rb/pull/141): an unhandled rejection is reported at the end of the microtask checkpoint, as HTML requires, so `Promise.reject(x).catch(...)` and `try { await rejecting() } catch {}` are no longer misreported. On a RubyGems install, `on_unhandled_rejection` reports again, a strict `Dommy::Browser` fails on a rejection the page leaves unhandled, and the page's `unhandledrejection` event fires. `event.reason` and `event.promise` are still not the page's own values there, and `rejectionhandled` does not fire: those need the JS rejection hook, which no quickjs release has yet.
37
+ - The development Gemfile pins the quickjs fork that carries the hook by tag ([`v0.22.0-rejection-hook.1`](https://github.com/takahashim/quickjs.rb/tree/v0.22.0-rejection-hook.1)) rather than by commit, so the fork's branch can be rebased onto each upstream release without stranding the ref an older commit pins.
38
+ - The suite no longer takes `DOMMY_BACKEND`: dommy 0.15 has Makiri as its only backend.
39
+
40
+ ### Removed
41
+
42
+ - The 0.21.0 workaround is gone: `Backend.premature_rejection_reports?`, its once-per-process probe, and the warning `on_unhandled_rejection` gave in place of reporting. No quickjs the gemspec allows behaves that way.
43
+
3
44
  ## 0.10.0 — 2026-10-01
4
45
 
5
46
  Requires `dommy >= 0.14.0, < 0.15` and `quickjs ~> 0.21.0`.
data/README.md CHANGED
@@ -110,15 +110,37 @@ The pieces it relies on are public Runtime API:
110
110
  - `Runtime#on_unhandled_rejection { |err| }` — surface promise rejections that
111
111
  reach the microtask queue with no handler. Frameworks swallow these; `err.backtrace`
112
112
  carries the JS stack, which is the difference between blind and one-shot debugging.
113
- The released quickjs (0.21.0) reports a rejection before a handler attached in
114
- the same microtask checkpoint can catch it, so `.catch` and `try { await … }
115
- catch` would look unhandled; on such an engine nothing is reported here (a
116
- warning says so once), so a strict `Dommy::Browser` does not fail on
117
- rejections and the page's `unhandledrejection` event (and
118
- `window.onunhandledrejection`) never fires. A quickjs with
119
- [hmsk/quickjs.rb#141](https://github.com/hmsk/quickjs.rb/pull/141) reports them.
120
113
  - `Runtime#on_log { |log| }` — observe `console.*` (`log.severity` / `log.to_s`).
121
114
 
115
+ ### Preloading ES modules
116
+
117
+ A module the loader hands over as source is parsed again by every Runtime that
118
+ imports it. Register it once per process and later Runtimes read it in as
119
+ bytecode instead:
120
+
121
+ ```ruby
122
+ Dommy::Js::Quickjs::Runtime.register_module("https://example.test/vendor/turbo.js", source: turbo_js)
123
+
124
+ rt = Dommy::Js::Quickjs::Runtime.new(preload_modules: ["https://example.test/vendor/turbo.js"])
125
+ rt.module_loader = ->(specifier, _importer) {
126
+ {as: "https://example.test/vendor/turbo.js"} if specifier == "@hotwired/turbo"
127
+ }
128
+ ```
129
+
130
+ - The name is the module's canonical name: the VM keys its module map by it,
131
+ and the module's relative imports resolve against it. Register a page's
132
+ module under its resolved URL.
133
+ - `source:` may be a Proc returning the source; it is read when a Runtime first
134
+ preloads the module, and the bytecode is shared from then on. Each Runtime
135
+ still gets its own instance, with its own module-level state.
136
+ - A preloaded module is found before the loader is asked for its name. A loader
137
+ that returns `{as: name}` for another specifier (an importmap entry, say)
138
+ lands on it too.
139
+ - A preloaded module's own imports are not preloaded; register each one you
140
+ want cached. Naming a module that is not registered raises `ArgumentError`.
141
+ - Preloading bypasses the loader for that name, so do not preload a module the
142
+ loader would refuse to some importers.
143
+
122
144
  ### Capybara
123
145
 
124
146
  Requiring the adapter enables `execute_script` / `evaluate_script` on
@@ -45,42 +45,9 @@ module Dommy
45
45
  # the top-level tag); either way it maps to Dommy::Bridge::UNDEFINED. No
46
46
  # normalization is needed here.
47
47
  class Backend
48
- # Whether this quickjs reports a promise rejection the moment it
49
- # happens, before a handler attached later in the same microtask
50
- # checkpoint gets the chance — the released quickjs (0.21.0) does, so
51
- # `Promise.reject(x).catch(f)` and `try { await p } catch {}` both
52
- # come out as unhandled. HTML reports at the end of the checkpoint
53
- # (hmsk/quickjs.rb#141, merged upstream but unreleased).
54
- #
55
- # Decided by behavior, not by version — a git checkout of the fixed
56
- # engine still says 0.21.0 — on a throwaway VM, once per process.
57
- def self.premature_rejection_reports?
58
- return @premature_rejection_reports if defined?(@premature_rejection_reports)
59
-
60
- @premature_rejection_reports = probe_premature_rejection_reports
61
- end
62
-
63
- def self.probe_premature_rejection_reports
64
- vm = ::Quickjs::VM.new
65
- reported = false
66
- vm.on_unhandled_rejection { |_error| reported = true }
67
- vm.eval_code(<<~JS, async: false)
68
- Promise.reject(1).catch(function () {});
69
- (async function () { try { await Promise.reject(2); } catch (e) {} })();
70
- 0
71
- JS
72
- vm.drain_jobs!
73
- reported
74
- rescue ::StandardError
75
- # A probe that cannot run says nothing either way; keep reporting.
76
- false
77
- ensure
78
- vm&.dispose!
79
- end
80
- private_class_method :probe_premature_rejection_reports
81
-
82
48
  # Timeout and memory ceiling both differ from the gem's defaults, for
83
- # reasons Config documents.
49
+ # reasons Config documents. Every other option goes to the VM as is,
50
+ # `preload_modules:` among them (see Runtime#initialize).
84
51
  def initialize(**vm_opts)
85
52
  vm_opts = {timeout_msec: Config.timeout_msec, memory_limit: Config.memory_limit}.merge(vm_opts)
86
53
  @vm = ::Quickjs::VM.new(**vm_opts)
@@ -114,6 +81,14 @@ module Dommy
114
81
  ::Quickjs.compile(source, filename: filename)
115
82
  end
116
83
 
84
+ # Register an ES module process-wide under its canonical name: compiled
85
+ # to bytecode once, on the first VM that preloads it, and read into each
86
+ # VM that names it in `preload_modules:`. `source:` is a String or a Proc
87
+ # returning one.
88
+ def self.register_module(name, source:)
89
+ ::Quickjs.register_module(name, source: source)
90
+ end
91
+
117
92
  # Process-global cache for the engine-internal runtime bundles run via
118
93
  # #run_bundle (host_runtime.js, observable_runtime.js). Kept separate
119
94
  # from ScriptCache (user-facing external scripts) so the two concerns
@@ -121,8 +96,14 @@ module Dommy
121
96
  @bundle_cache = {}
122
97
  @bundle_mutex = Mutex.new
123
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.
124
105
  def self.compiled_bundle(cache_key, source)
125
- @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) }
126
107
  end
127
108
 
128
109
  # Execute precompiled bytecode (a Quickjs::Runnable) on this VM in global
@@ -143,8 +124,8 @@ module Dommy
143
124
  def eval_awaited(js) = guarded { @vm.eval_code(js, async: true) }
144
125
 
145
126
  # Install the ESM module resolver: a callable `(specifier, importer) ->
146
- # source String | { code:, as: } | nil` the engine consults for every
147
- # static/dynamic `import`. nil clears it (engine default loader).
127
+ # source String | { code:, as: } | { as: } | nil` the engine consults for
128
+ # every static/dynamic `import`. nil clears it (engine default loader).
148
129
  def module_loader=(callable)
149
130
  @vm.module_loader = callable
150
131
  end
@@ -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
  };
@@ -21,8 +21,23 @@ module Dommy
21
21
  # What stays here is the port itself: script evaluation, and the wiring
22
22
  # that decides which collaborator answers a host's call.
23
23
  class Runtime
24
- def initialize(**vm_opts)
25
- @backend = Backend.new(**vm_opts)
24
+ # Register an ES module process-wide, so a later Runtime can preload it
25
+ # as bytecode instead of parsing its source again. `name` is the
26
+ # module's canonical name — the one the VM's module map keys it by and
27
+ # its relative imports resolve against — so a page module is best
28
+ # registered under its resolved URL. `source:` is a String or a Proc
29
+ # returning one (read only when a Runtime first preloads it).
30
+ def self.register_module(name, source:)
31
+ Backend.register_module(name, source: source)
32
+ end
33
+
34
+ # `preload_modules:` names registered modules (see .register_module) to
35
+ # read into this Runtime's VM as it is made. A preloaded module is found
36
+ # before the module loader is asked for its name, and a loader that
37
+ # returns `{as: name}` for another specifier lands on it too. A name not
38
+ # registered raises ArgumentError. Other options go to the VM.
39
+ def initialize(preload_modules: [], **vm_opts)
40
+ @backend = Backend.new(preload_modules: preload_modules, **vm_opts)
26
41
  @bridge = Dommy::Js::HostBridge.new(@backend)
27
42
  @environment = BrowserEnvironment.new(@backend)
28
43
  @errors = ErrorTranslator.new(@backend, @bridge)
@@ -113,7 +128,9 @@ module Dommy
113
128
  end
114
129
 
115
130
  # Install the ESM module resolver (see Backend#module_loader=). A
116
- # callable `(specifier, importer) -> source | {code:, as:} | nil`.
131
+ # callable `(specifier, importer) -> source | {code:, as:} | {as:} | nil`;
132
+ # `{as: name}` with no code redirects to a module the VM already has,
133
+ # such as one in `preload_modules:`.
117
134
  def module_loader=(callable)
118
135
  @backend.module_loader = callable
119
136
  end
@@ -195,19 +212,11 @@ module Dommy
195
212
  # Surface otherwise-swallowed JS promise rejections (see Backend).
196
213
  # Goes quiet once the JS hook is installed: both would fire for the same
197
214
  # rejection, and the hook carries strictly more.
198
- #
199
- # Goes quiet, too, on an engine that reports a rejection before a
200
- # handler could attach (Backend.premature_rejection_reports?): there
201
- # every `.catch` and `try { await … } catch` would surface as an
202
- # unhandled rejection and fail a strict host on correct code. Reporting
203
- # none is the lesser loss — a rejection the page truly leaves unhandled
204
- # goes unreported until the engine is fixed.
205
215
  def on_unhandled_rejection(&block)
206
216
  # Decided when a rejection actually arrives, not now: a host registers
207
217
  # this before #install_window, which is where the hook goes in.
208
218
  relay = lambda do |error|
209
219
  next if js_rejection_hook?
210
- next self.class.warn_premature_rejection_reports if Backend.premature_rejection_reports?
211
220
 
212
221
  block.call(@track_rejections ? @errors.enrich_rejection(error) : error)
213
222
  end
@@ -215,17 +224,6 @@ module Dommy
215
224
  self
216
225
  end
217
226
 
218
- # Said once per process, on the first rejection left unreported.
219
- def self.warn_premature_rejection_reports
220
- return if @warned_premature_rejection_reports
221
-
222
- @warned_premature_rejection_reports = true
223
- warn "dommy-js-quickjs: this quickjs (#{::Quickjs::VERSION}) reports a promise rejection before " \
224
- "a handler can attach, so unhandled rejections are not reported. A quickjs with " \
225
- "hmsk/quickjs.rb#141 (unhandled-rejection timing) reports them."
226
- nil
227
- end
228
-
229
227
  # Whether rejections arrive through the JS hook. A host that also relays
230
228
  # #on_unhandled_rejection would otherwise report each rejection twice.
231
229
  def js_rejection_hook? = !!@js_rejection_hook
@@ -3,7 +3,7 @@
3
3
  module Dommy
4
4
  module Js
5
5
  module Quickjs
6
- VERSION = "0.10.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.10.0
4
+ version: 0.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - takahashim
@@ -15,34 +15,34 @@ dependencies:
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: 0.21.0
18
+ version: 0.22.0
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: 0.21.0
25
+ version: 0.22.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: dommy
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
30
  - - ">="
31
31
  - !ruby/object:Gem::Version
32
- version: 0.14.0
32
+ version: 0.16.0
33
33
  - - "<"
34
34
  - !ruby/object:Gem::Version
35
- version: '0.15'
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.14.0
42
+ version: 0.16.0
43
43
  - - "<"
44
44
  - !ruby/object:Gem::Version
45
- version: '0.15'
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