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 +4 -4
- data/CHANGELOG.md +41 -0
- data/README.md +29 -7
- data/lib/dommy/js/quickjs/backend.rb +19 -38
- data/lib/dommy/js/quickjs/js/browser_globals.js +70 -13
- data/lib/dommy/js/quickjs/js/timer_globals.js +20 -9
- data/lib/dommy/js/quickjs/runtime.rb +20 -22
- data/lib/dommy/js/quickjs/version.rb +1 -1
- metadata +7 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e78c3781c9d334b0ec8bd70fb68b0fa285685e5de516ff5b72d073aa74800ca1
|
|
4
|
+
data.tar.gz: 9278f706bd5d0929b3c74130e188c90f5bb07e2c8213f84130a21a0b3acce671
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
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
|
-
|
|
15
|
-
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
|
26
|
-
// `this` binds to the 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"
|
|
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 {
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
globalThis.
|
|
37
|
-
|
|
38
|
-
|
|
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 = (
|
|
44
|
-
if (typeof
|
|
45
|
-
Promise.resolve().then(() => {
|
|
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
|
-
|
|
25
|
-
|
|
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
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
32
|
+
version: 0.16.0
|
|
33
33
|
- - "<"
|
|
34
34
|
- !ruby/object:Gem::Version
|
|
35
|
-
version: '0.
|
|
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.
|
|
42
|
+
version: 0.16.0
|
|
43
43
|
- - "<"
|
|
44
44
|
- !ruby/object:Gem::Version
|
|
45
|
-
version: '0.
|
|
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
|