mini_racer-csim 0.21.1.2 → 0.21.1.4
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 +10 -0
- data/README.md +3 -2
- data/ext/mini_racer_csim_extension/mini_racer_v8.cc +136 -93
- data/lib/mini_racer_csim/version.rb +1 -1
- metadata +8 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4a64c6a3cd59b38b736212c4ef9685cbc7c6bc9e56d0aee12734e957e749fb7c
|
|
4
|
+
data.tar.gz: 50e7e8a671b90f3bf2e6563092f31ed00edf7d2c8093eb52eccbe502635773ca
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 53cb234c4dca6643756f5f0558080dade542569bf64aef713e0f44477e05d2c7e14bc305f63671519d67131a63f35dcee65829893dd493011b5f2764552677bd
|
|
7
|
+
data.tar.gz: ee5e61767e56179bf2aec29c54ca81d0f3c6b3ad7d1788498bb45b84ee5f7d3b6dcb613cc5f297dbdcb4eae32f4005b594a6a7a562d51c9d1904b646537976cb
|
data/CHANGELOG
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
- 0.21.1.4 - 08-06-2026
|
|
2
|
+
- Memoize the safe-context reply filter (the recursive deep-copy fallback used when a reply value is not cloneable by V8's ValueSerializer — e.g. it contains a function or a host object). A visited-set (original → filtered copy) makes shared subgraphs clone once instead of being re-walked per reference path (O(N) instead of super-linear), terminates cycles (previously a cyclic value containing a non-cloneable member recursed forever), and preserves object identity — matching the ValueSerializer fast path it backs
|
|
3
|
+
|
|
4
|
+
- 0.21.1.3 - 08-06-2026
|
|
5
|
+
- **Breaking (realm JS API):** move the per-frame-realm JS helpers off bare `globalThis` globals onto the opt-in host namespace, so globalThis pollution is decided once (by `host_namespace:`) rather than per feature
|
|
6
|
+
- `__mr_realmGlobal(id)` → `<host_namespace>.realmGlobal(id)`
|
|
7
|
+
- `__mr_realmOf(fn)` → `<host_namespace>.realmOf(fn)`
|
|
8
|
+
- the embedder-defined `globalThis.__mr_emitUnhandledRejection` hook → a `<host_namespace>.onUnhandledRejection(fn)` registration method (the engine stores the handler per realm and calls it)
|
|
9
|
+
- **using these JS helpers now requires `Context.new(host_namespace:)`.** Realms themselves do not: `Context#create_realm` + `Realm#eval`/`call`/`attach` work without it (isolated realms driven from Ruby) — only cross-realm wiring *in JS* needs the namespace, which is why the helpers live there
|
|
10
|
+
|
|
1
11
|
- 0.21.1.2 - 07-06-2026
|
|
2
12
|
- **Breaking:** move off the `mini_racer` require path / `MiniRacer` namespace to a fork-specific identity so the gem never collides with — and loads deterministically alongside — upstream `mini_racer` in the same bundle
|
|
3
13
|
- require path: `require 'mini_racer_csim'` (was `require 'mini_racer'`); the `require 'mini_racer-csim'` autorequire shim now points here
|
data/README.md
CHANGED
|
@@ -12,7 +12,7 @@ MiniRacer has an adapter for [execjs](https://github.com/rails/execjs) so it can
|
|
|
12
12
|
|
|
13
13
|
## This repository is `mini_racer-csim` (a fork)
|
|
14
14
|
|
|
15
|
-
This is **`mini_racer-csim`**, a private fork of [`mini_racer`](https://github.com/rubyjs/mini_racer) maintained for [capybara-simulated](https://github.com/ursm/capybara-simulated). It adds browser-fidelity extensions (ES modules, realm reset, …) that capybara-simulated needs but most users do not — **if you are not using capybara-simulated, use upstream `mini_racer`.**
|
|
15
|
+
This is **`mini_racer-csim`**, a private fork of [`mini_racer`](https://github.com/rubyjs/mini_racer) maintained for [capybara-simulated](https://github.com/ursm/capybara-simulated). It adds browser-fidelity extensions (ES modules, per-frame realms, realm reset, …) that capybara-simulated needs but most users do not — **if you are not using capybara-simulated, use upstream `mini_racer`.**
|
|
16
16
|
|
|
17
17
|
It has its **own require path and namespace** so it never collides with — and loads deterministically alongside — upstream `mini_racer` in the same bundle: load it with `require 'mini_racer_csim'` and use the `MiniRacerCsim` module (e.g. `MiniRacerCsim::Context`). The native extensions are `mini_racer_csim_extension` / `mini_racer_csim_loader`. (The JS-side host-namespace brand, `globalThis.MiniRacer` by default, is embedder-chosen and unrelated to the Ruby namespace.)
|
|
18
18
|
|
|
@@ -24,10 +24,11 @@ It has its **own require path and namespace** so it never collides with — and
|
|
|
24
24
|
| ES Module API | `Context#compile_module` → `MiniRacerCsim::Module` (`#instantiate` / `#evaluate` / `#namespace` / `#status` / `#cached_data` / `#dispose`); `Context#dynamic_import_resolver=` | V8's ES module pipeline, `import.meta.url`, dynamic `import()` |
|
|
25
25
|
| Batched module-graph loader | `Context#load_module_graph(resolve:, …)` | Loads an ESM graph in one batched, native (C++) pass; one `Module` per URL shared across every load path |
|
|
26
26
|
| Realm reset | `Context#reset_realm` | Discards the user realm (`globalThis`) while keeping the warm isolate (browser per-navigation model); re-binds attached host functions and the host namespace |
|
|
27
|
+
| Per-frame realms | `Context#create_realm` → `MiniRacerCsim::Realm` (`#eval` / `#call` / `#attach` / `#dispose` / `#disposed?`) | Multiple V8 realms (Contexts) in one isolate with browser-iframe semantics: realms share one security token for cross-realm access. JS-side helpers live on the [host namespace](#host-namespace) (so using them needs `host_namespace:`): `<ns>.realmGlobal(id)` exposes a realm's live `globalThis`, `<ns>.realmOf(fn)` reports a callback's `[[Realm]]`, and `<ns>.onUnhandledRejection(fn)` registers a per-realm unhandled-rejection handler. Realms themselves work without the namespace (driven from Ruby). |
|
|
27
28
|
| Host namespace | `Context.new(host_namespace: "MiniRacer")` → `globalThis.MiniRacer.drainMicrotasks()` | Opt-in JS namespace exposing an inline, rendezvous-free microtask checkpoint |
|
|
28
29
|
| GVL release on boot | (automatic) | Releases the Ruby GVL while the V8 thread boots the isolate |
|
|
29
30
|
|
|
30
|
-
|
|
31
|
+
This is a hard fork: it no longer tracks upstream `mini_racer`, and follows only `libv8-node` for V8 version bumps.
|
|
31
32
|
|
|
32
33
|
## Supported Ruby Versions & Troubleshooting
|
|
33
34
|
|
|
@@ -23,44 +23,61 @@ static const char safe_context_script_source[] = R"js(
|
|
|
23
23
|
;(function($globalThis) {
|
|
24
24
|
const {Map: $Map, Set: $Set} = $globalThis
|
|
25
25
|
const sentinel = {}
|
|
26
|
-
return function filter(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
26
|
+
return function filter(root) {
|
|
27
|
+
// Memoize original -> filtered copy. Registered BEFORE recursing into a
|
|
28
|
+
// value's children so that a value reachable by many paths is cloned
|
|
29
|
+
// once (linear, not super-linear: e.g. a DOM node's ownerDocument is
|
|
30
|
+
// reachable from every node), cycles terminate (the in-progress copy is
|
|
31
|
+
// returned), and object identity is preserved. This matches V8's
|
|
32
|
+
// ValueSerializer (the fast path this is the fallback for), whose ref
|
|
33
|
+
// table also dedupes and handles cycles; without it the slow path both
|
|
34
|
+
// diverges (duplicates shared objects) and can recurse forever on a
|
|
35
|
+
// cyclic value that happens to contain a non-cloneable member.
|
|
36
|
+
const seen = new Map()
|
|
37
|
+
return (function rec(v) {
|
|
38
|
+
if (typeof v === "function")
|
|
39
|
+
return sentinel
|
|
40
|
+
if (typeof v !== "object" || v === null)
|
|
41
|
+
return v
|
|
42
|
+
if (seen.has(v))
|
|
43
|
+
return seen.get(v)
|
|
44
|
+
if (v instanceof $Map) {
|
|
45
|
+
const m = new Map()
|
|
46
|
+
seen.set(v, m)
|
|
47
|
+
for (let [k, t] of Map.prototype.entries.call(v)) {
|
|
48
|
+
t = rec(t)
|
|
49
|
+
if (t !== sentinel)
|
|
50
|
+
m.set(k, t)
|
|
51
|
+
}
|
|
52
|
+
return m
|
|
53
|
+
} else if (v instanceof $Set) {
|
|
54
|
+
const s = new Set()
|
|
55
|
+
seen.set(v, s)
|
|
56
|
+
for (let t of Set.prototype.values.call(v)) {
|
|
57
|
+
t = rec(t)
|
|
58
|
+
if (t !== sentinel)
|
|
59
|
+
s.add(t)
|
|
60
|
+
}
|
|
61
|
+
return s
|
|
62
|
+
} else {
|
|
63
|
+
const o = Array.isArray(v) ? [] : {}
|
|
64
|
+
seen.set(v, o)
|
|
65
|
+
const pds = Object.getOwnPropertyDescriptors(v)
|
|
66
|
+
for (const [k, d] of Object.entries(pds)) {
|
|
67
|
+
if (!d.enumerable)
|
|
68
|
+
continue
|
|
69
|
+
let t = d.value
|
|
70
|
+
if (d.get) {
|
|
71
|
+
// *not* d.get.call(...), may have been tampered with
|
|
72
|
+
t = Function.prototype.call.call(d.get, v, k)
|
|
73
|
+
}
|
|
74
|
+
t = rec(t)
|
|
75
|
+
if (t !== sentinel)
|
|
76
|
+
Object.defineProperty(o, k, {value: t, enumerable: true})
|
|
57
77
|
}
|
|
58
|
-
|
|
59
|
-
if (t !== sentinel)
|
|
60
|
-
Object.defineProperty(o, k, {value: t, enumerable: true})
|
|
78
|
+
return o
|
|
61
79
|
}
|
|
62
|
-
|
|
63
|
-
}
|
|
80
|
+
})(root)
|
|
64
81
|
}
|
|
65
82
|
})
|
|
66
83
|
)js";
|
|
@@ -134,6 +151,11 @@ struct Realm
|
|
|
134
151
|
// graph_resolve_callback can resolve imports from the pre-walked graph
|
|
135
152
|
// with zero Ruby round-trips. Null otherwise.
|
|
136
153
|
struct GraphLoad *active_graph = nullptr;
|
|
154
|
+
// Embedder-registered handler for promises that reject with no handler in
|
|
155
|
+
// this realm, set via <host_namespace>.onUnhandledRejection(fn). Called by
|
|
156
|
+
// notify_unhandled_rejections with (reason, promise). Global so its dtor
|
|
157
|
+
// releases the function when the realm is disposed; reset on reset_realm.
|
|
158
|
+
v8::Global<v8::Function> unhandled_rejection_handler;
|
|
137
159
|
};
|
|
138
160
|
|
|
139
161
|
struct State
|
|
@@ -768,13 +790,14 @@ void v8_drain_microtasks_callback(const v8::FunctionCallbackInfo<v8::Value>& inf
|
|
|
768
790
|
info.GetReturnValue().SetUndefined();
|
|
769
791
|
}
|
|
770
792
|
|
|
771
|
-
//
|
|
772
|
-
// object in the calling realm (same isolate, not a copy), or undefined
|
|
773
|
-
// unknown realm.
|
|
774
|
-
//
|
|
775
|
-
//
|
|
776
|
-
//
|
|
777
|
-
//
|
|
793
|
+
// <host_namespace>.realmGlobal(id): returns the globalThis of realm `id` as a
|
|
794
|
+
// LIVE V8 object in the calling realm (same isolate, not a copy), or undefined
|
|
795
|
+
// for an unknown realm. Hung off the host namespace (opt-in via
|
|
796
|
+
// Context.new(host_namespace:)) rather than a bare global, so globalThis stays
|
|
797
|
+
// unpolluted. Because all realms share one security token (see install_realm),
|
|
798
|
+
// the caller can read/write the returned global's properties — this is how csim
|
|
799
|
+
// wires frames[i] / iframe.contentWindow to the right realm. Cross-realm object
|
|
800
|
+
// identity holds: mutating a property here is visible in the target realm.
|
|
778
801
|
void v8_realm_global_callback(const v8::FunctionCallbackInfo<v8::Value>& info)
|
|
779
802
|
{
|
|
780
803
|
auto isolate = info.GetIsolate();
|
|
@@ -815,13 +838,13 @@ static int32_t realm_id_of_context(v8::Local<v8::Context> ctx)
|
|
|
815
838
|
return v.As<v8::Int32>()->Value();
|
|
816
839
|
}
|
|
817
840
|
|
|
818
|
-
//
|
|
819
|
-
// created — its [[Realm]] / creation context — or
|
|
820
|
-
// the realm WebIDL's "invoke a callback function"
|
|
821
|
-
// a setTimeout callback's uncaught throw is
|
|
822
|
-
// the callback, not the scheduling realm
|
|
823
|
-
// uses it to dispatch an ErrorEvent on
|
|
824
|
-
// its own per-callback try/catch.
|
|
841
|
+
// <host_namespace>.realmOf(fn): returns the realm id where `fn` (any
|
|
842
|
+
// object/function) was created — its [[Realm]] / creation context — or
|
|
843
|
+
// undefined if unknown. This is the realm WebIDL's "invoke a callback function"
|
|
844
|
+
// reports errors against (e.g. a setTimeout callback's uncaught throw is
|
|
845
|
+
// reported on the realm that *created* the callback, not the scheduling realm
|
|
846
|
+
// nor the thrown Error's realm). csim uses it to dispatch an ErrorEvent on
|
|
847
|
+
// <ns>.realmGlobal(<ns>.realmOf(cb)) from its own per-callback try/catch.
|
|
825
848
|
void v8_realm_of_callback(const v8::FunctionCallbackInfo<v8::Value>& info)
|
|
826
849
|
{
|
|
827
850
|
auto isolate = info.GetIsolate();
|
|
@@ -842,6 +865,27 @@ void v8_realm_of_callback(const v8::FunctionCallbackInfo<v8::Value>& info)
|
|
|
842
865
|
info.GetReturnValue().Set(v8::Integer::New(isolate, rid));
|
|
843
866
|
}
|
|
844
867
|
|
|
868
|
+
// <host_namespace>.onUnhandledRejection(fn): register fn as the calling realm's
|
|
869
|
+
// handler for promises that reject with no handler. notify_unhandled_rejections
|
|
870
|
+
// calls it with (reason, promise) at the next microtask checkpoint, entered in
|
|
871
|
+
// the rejecting promise's realm (HTML notify-rejected-promises). The handler is
|
|
872
|
+
// stored per realm (keyed by the calling context's realm id) instead of as a
|
|
873
|
+
// bare globalThis property, so globalThis stays unpolluted. Passing a non-
|
|
874
|
+
// function clears the handler.
|
|
875
|
+
void v8_set_unhandled_rejection_handler(const v8::FunctionCallbackInfo<v8::Value>& info)
|
|
876
|
+
{
|
|
877
|
+
auto isolate = info.GetIsolate();
|
|
878
|
+
State& st = *static_cast<State*>(isolate->GetData(0));
|
|
879
|
+
int32_t rid = realm_id_of_context(isolate->GetCurrentContext());
|
|
880
|
+
auto it = st.realms.find(rid);
|
|
881
|
+
if (it == st.realms.end())
|
|
882
|
+
return; // unknown realm: no-op
|
|
883
|
+
if (info.Length() >= 1 && info[0]->IsFunction())
|
|
884
|
+
it->second->unhandled_rejection_handler.Reset(isolate, info[0].As<v8::Function>());
|
|
885
|
+
else
|
|
886
|
+
it->second->unhandled_rejection_handler.Reset();
|
|
887
|
+
}
|
|
888
|
+
|
|
845
889
|
// V8 calls this when a promise's rejection state changes. We implement the
|
|
846
890
|
// HTML "notify rejected promises" bookkeeping: queue promises that reject with
|
|
847
891
|
// no handler (tagged with the realm they were created in), and drop them again
|
|
@@ -1041,12 +1085,13 @@ static void clear_realm_locals(State& st)
|
|
|
1041
1085
|
}
|
|
1042
1086
|
|
|
1043
1087
|
// HTML notify-rejected-promises: for each promise that rejected with no handler
|
|
1044
|
-
// since the last checkpoint, enter its realm and call
|
|
1045
|
-
//
|
|
1046
|
-
// turns that into a PromiseRejectionEvent so
|
|
1047
|
-
// fires natively in the right realm). The
|
|
1048
|
-
// so rejections triggered by a handler
|
|
1049
|
-
// looping. A realm disposed in the
|
|
1088
|
+
// since the last checkpoint, enter its realm and call that realm's handler
|
|
1089
|
+
// (registered via <host_namespace>.onUnhandledRejection) with (reason, promise)
|
|
1090
|
+
// if one was set (csim turns that into a PromiseRejectionEvent so
|
|
1091
|
+
// addEventListener('unhandledrejection') fires natively in the right realm). The
|
|
1092
|
+
// list is snapshotted and cleared first so rejections triggered by a handler
|
|
1093
|
+
// queue for the next checkpoint instead of looping. A realm disposed in the
|
|
1094
|
+
// meantime is skipped.
|
|
1050
1095
|
static void notify_unhandled_rejections(State& st)
|
|
1051
1096
|
{
|
|
1052
1097
|
if (st.pending_rejections.empty())
|
|
@@ -1067,18 +1112,17 @@ static void notify_unhandled_rejections(State& st)
|
|
|
1067
1112
|
auto context = v8::Local<v8::Context>::New(st.isolate, it->second->persistent_context);
|
|
1068
1113
|
if (context.IsEmpty())
|
|
1069
1114
|
continue;
|
|
1115
|
+
if (it->second->unhandled_rejection_handler.IsEmpty())
|
|
1116
|
+
continue; // realm registered no onUnhandledRejection handler
|
|
1070
1117
|
st.active_realm_id = pr.second;
|
|
1071
1118
|
restore_realm_locals(st);
|
|
1072
1119
|
v8::Context::Scope context_scope(context);
|
|
1073
1120
|
auto global = context->Global();
|
|
1074
|
-
auto
|
|
1075
|
-
v8::Local<v8::Value> hook;
|
|
1076
|
-
if (!global->Get(context, name).ToLocal(&hook) || !hook->IsFunction())
|
|
1077
|
-
continue;
|
|
1121
|
+
auto hook = v8::Local<v8::Function>::New(st.isolate, it->second->unhandled_rejection_handler);
|
|
1078
1122
|
auto promise = v8::Local<v8::Promise>::New(st.isolate, pr.first);
|
|
1079
1123
|
v8::TryCatch try_catch(st.isolate); // swallow errors from the handler itself
|
|
1080
1124
|
v8::Local<v8::Value> args[2] = { promise->Result(), promise };
|
|
1081
|
-
(void)hook
|
|
1125
|
+
(void)hook->Call(context, global, 2, args);
|
|
1082
1126
|
}
|
|
1083
1127
|
st.active_realm_id = prev;
|
|
1084
1128
|
st.context = saved_context;
|
|
@@ -1133,47 +1177,41 @@ static bool install_realm(State& st)
|
|
|
1133
1177
|
v8::Context::Scope context_scope(context);
|
|
1134
1178
|
// If the embedder opted in via Context.new(host_namespace:), install a
|
|
1135
1179
|
// single host-namespace object (in the spirit of Deno's `Deno` / Bun's
|
|
1136
|
-
// `Bun`) under that global name and hang native
|
|
1137
|
-
//
|
|
1138
|
-
//
|
|
1139
|
-
//
|
|
1140
|
-
//
|
|
1141
|
-
//
|
|
1180
|
+
// `Bun`) under that global name and hang EVERY native JS helper off it:
|
|
1181
|
+
// drainMicrotasks(), realmGlobal(id), realmOf(fn), onUnhandledRejection(fn).
|
|
1182
|
+
// Keeping them on one opt-in object (rather than bare __mr_* globals) means
|
|
1183
|
+
// globalThis pollution is decided once, by the opt-in — not relitigated per
|
|
1184
|
+
// feature. The object closes over native code pointers so it cannot live in
|
|
1185
|
+
// the (de)serialized snapshot; it is installed here on every fresh realm.
|
|
1186
|
+
// The namespace is non-enumerable on globalThis (out of Object.keys/for-in);
|
|
1187
|
+
// its methods are ordinary properties so they remain discoverable.
|
|
1188
|
+
//
|
|
1189
|
+
// Consequence: the JS-side realm-reflection helpers (realmGlobal/realmOf)
|
|
1190
|
+
// and per-realm unhandled-rejection delivery require host_namespace. Realms
|
|
1191
|
+
// themselves do NOT — Context#create_realm + Realm#eval/call/attach work
|
|
1192
|
+
// without it (isolated realms driven from Ruby); only cross-realm wiring *in
|
|
1193
|
+
// JS* needs these helpers, which is why they live on the namespace.
|
|
1142
1194
|
if (!st.host_namespace.empty()) {
|
|
1143
1195
|
v8::Local<v8::String> ns_name;
|
|
1144
1196
|
if (!v8::String::NewFromUtf8(st.isolate, st.host_namespace.c_str()).ToLocal(&ns_name))
|
|
1145
1197
|
return false;
|
|
1146
1198
|
auto ns = v8::Object::New(st.isolate);
|
|
1147
1199
|
auto data = v8::External::New(st.isolate, pst);
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
if (!
|
|
1200
|
+
v8::Local<v8::Function> drain, rg, ro, onrej;
|
|
1201
|
+
// drainMicrotasks + realmGlobal read State via info.Data() (the External);
|
|
1202
|
+
// realmOf + onUnhandledRejection take none (onUnhandledRejection uses
|
|
1203
|
+
// isolate->GetData(0)), so they are created without data.
|
|
1204
|
+
if (!v8::Function::New(context, v8_drain_microtasks_callback, data).ToLocal(&drain)) return false;
|
|
1205
|
+
if (!v8::Function::New(context, v8_realm_global_callback, data).ToLocal(&rg)) return false;
|
|
1206
|
+
if (!v8::Function::New(context, v8_realm_of_callback).ToLocal(&ro)) return false;
|
|
1207
|
+
if (!v8::Function::New(context, v8_set_unhandled_rejection_handler).ToLocal(&onrej)) return false;
|
|
1208
|
+
if (!ns->Set(context, v8::String::NewFromUtf8Literal(st.isolate, "drainMicrotasks"), drain).FromMaybe(false)) return false;
|
|
1209
|
+
if (!ns->Set(context, v8::String::NewFromUtf8Literal(st.isolate, "realmGlobal"), rg).FromMaybe(false)) return false;
|
|
1210
|
+
if (!ns->Set(context, v8::String::NewFromUtf8Literal(st.isolate, "realmOf"), ro).FromMaybe(false)) return false;
|
|
1211
|
+
if (!ns->Set(context, v8::String::NewFromUtf8Literal(st.isolate, "onUnhandledRejection"), onrej).FromMaybe(false)) return false;
|
|
1153
1212
|
if (!context->Global()->DefineOwnProperty(context, ns_name, ns, v8::DontEnum).FromMaybe(false))
|
|
1154
1213
|
return false;
|
|
1155
1214
|
}
|
|
1156
|
-
// Install __mr_realmGlobal(id) on every realm (not gated by host_namespace):
|
|
1157
|
-
// it returns realm `id`'s live globalThis so realms can reach each other
|
|
1158
|
-
// (per-frame realms / iframes). Non-enumerable so it stays out of
|
|
1159
|
-
// Object.keys(globalThis).
|
|
1160
|
-
{
|
|
1161
|
-
auto rg_name = v8::String::NewFromUtf8Literal(st.isolate, "__mr_realmGlobal");
|
|
1162
|
-
auto rg_data = v8::External::New(st.isolate, pst);
|
|
1163
|
-
v8::Local<v8::Function> rg;
|
|
1164
|
-
if (!v8::Function::New(context, v8_realm_global_callback, rg_data).ToLocal(&rg))
|
|
1165
|
-
return false;
|
|
1166
|
-
if (!context->Global()->DefineOwnProperty(context, rg_name, rg, v8::DontEnum).FromMaybe(false))
|
|
1167
|
-
return false;
|
|
1168
|
-
// __mr_realmOf(fn) -> realm id where fn was created (for per-realm error
|
|
1169
|
-
// attribution; the embedder dispatches error events on that realm).
|
|
1170
|
-
auto ro_name = v8::String::NewFromUtf8Literal(st.isolate, "__mr_realmOf");
|
|
1171
|
-
v8::Local<v8::Function> ro;
|
|
1172
|
-
if (!v8::Function::New(context, v8_realm_of_callback).ToLocal(&ro))
|
|
1173
|
-
return false;
|
|
1174
|
-
if (!context->Global()->DefineOwnProperty(context, ro_name, ro, v8::DontEnum).FromMaybe(false))
|
|
1175
|
-
return false;
|
|
1176
|
-
}
|
|
1177
1215
|
// Re-attach host functions onto the fresh global. Empty at boot; populated
|
|
1178
1216
|
// when install_realm runs from v8_reset_realm. bind_callback reads st.context,
|
|
1179
1217
|
// so point the members at the new realm for the duration of the loop, and
|
|
@@ -2999,6 +3037,10 @@ extern "C" void v8_reset_realm(State *pst)
|
|
|
2999
3037
|
cur(st).scripts.clear();
|
|
3000
3038
|
cur(st).modules.clear();
|
|
3001
3039
|
cur(st).module_id_by_url.clear();
|
|
3040
|
+
// The realm-0 struct is reused across reset, so its onUnhandledRejection
|
|
3041
|
+
// handler points at a function in the now-discarded old realm. Drop it; the
|
|
3042
|
+
// embedder re-registers on the fresh realm (the namespace is reinstalled).
|
|
3043
|
+
cur(st).unhandled_rejection_handler.Reset();
|
|
3002
3044
|
// Same rationale as the scripts/modules above: a not-yet-delivered rejection
|
|
3003
3045
|
// recorded against the old realm would, after the swap, fire against the
|
|
3004
3046
|
// fresh realm's globalThis (reset reuses the realm id). Drop them.
|
|
@@ -3067,6 +3109,7 @@ State::~State()
|
|
|
3067
3109
|
Realm& r = *kv.second;
|
|
3068
3110
|
r.modules.clear();
|
|
3069
3111
|
r.scripts.clear();
|
|
3112
|
+
r.unhandled_rejection_handler.Reset();
|
|
3070
3113
|
r.persistent_safe_context_function.Reset();
|
|
3071
3114
|
r.persistent_safe_context.Reset();
|
|
3072
3115
|
r.persistent_context.Reset();
|
|
@@ -4,6 +4,6 @@ module MiniRacerCsim
|
|
|
4
4
|
# mini_racer-csim fork: upstream version + a fork revision segment.
|
|
5
5
|
# 0.21.1.0 = first fork release on upstream 0.21.1; bump the 4th segment for
|
|
6
6
|
# fork-only changes, reset it when rebasing onto a new upstream version.
|
|
7
|
-
VERSION = "0.21.1.
|
|
7
|
+
VERSION = "0.21.1.4"
|
|
8
8
|
LIBV8_NODE_VERSION = "~> 24.12.0.1"
|
|
9
9
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mini_racer-csim
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.21.1.
|
|
4
|
+
version: 0.21.1.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Keita Urashima
|
|
@@ -9,7 +9,7 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date: 2026-06-
|
|
12
|
+
date: 2026-06-08 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: bundler
|
|
@@ -110,11 +110,12 @@ dependencies:
|
|
|
110
110
|
- !ruby/object:Gem::Version
|
|
111
111
|
version: 24.12.0.1
|
|
112
112
|
description: 'A private fork of mini_racer (minimal embedded V8 for Ruby) adding browser-level
|
|
113
|
-
behavior used by capybara-simulated: the V8 ES Module API,
|
|
114
|
-
caching, an opt-in host namespace,
|
|
115
|
-
with a URL module registry. These are niche browser-fidelity features; general
|
|
116
|
-
should use upstream mini_racer.
|
|
117
|
-
|
|
113
|
+
behavior used by capybara-simulated: the V8 ES Module API, per-frame realms, realm
|
|
114
|
+
reset, cross-process bytecode caching, an opt-in host namespace, and a batched module-graph
|
|
115
|
+
loader with a URL module registry. These are niche browser-fidelity features; general
|
|
116
|
+
users should use upstream mini_racer. It loads under its own `mini_racer_csim` require
|
|
117
|
+
path and `MiniRacerCsim` namespace, so it never collides with upstream mini_racer
|
|
118
|
+
in the same bundle.'
|
|
118
119
|
email:
|
|
119
120
|
- ursm@ursm.jp
|
|
120
121
|
executables: []
|