Skip to content

feat: structuredClone global (HTML structured clone, ArrayBuffer transfer) - #2000

Open
edusperoni wants to merge 3 commits into
mainfrom
feat/structured-clone
Open

feat: structuredClone global (HTML structured clone, ArrayBuffer transfer)#2000
edusperoni wants to merge 3 commits into
mainfrom
feat/structured-clone

Conversation

@edusperoni

@edusperoni edusperoni commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

Android port of NativeScript/ios#431 (now merged on iOS main) — implements the WHATWG structuredClone(value, { transfer }) global, in lockstep with the iOS runtime.

Architecture

Follows the post-context JS builtin pattern from the js-builtins stack (#1989#1992, all now in main):

  • Native stays thin. StructuredClone.{h,cpp} exposes a one-function binding bag (clone(value, transferArray)) and runs BuiltinId::kStructuredClone. The clone is a v8::ValueSerializerv8::ValueDeserializer round-trip inside the one isolate, which is what StructuredDeserialize(StructuredSerializeWithTransfer(...)) reduces to when there is no second agent.
  • All WebIDL coercion is portable JS. js/structured-clone.js owns the argument checks and the sequence<object> conversion for transfer. It is byte-for-byte the same file as the iOS runtime's — nothing platform-specific in it.

ArrayBuffer membership in the transfer list is brand-checked from JS through the captured ArrayBuffer.prototype.byteLength getter (tamper-proof, and correctly excludes SharedArrayBuffer, which is not transferable); the native side re-checks with IsArrayBuffer().

Adds the Error and SymbolIterator primordials.

Serialization consolidation

Mirrors the iOS PR's consolidation so structuredClone and worker postMessage can never diverge:

  • StructuredSerialization.{h,cpp} (tns::serialization) owns the serializer/deserializer delegate pair, DataCloneError construction, transfer-list validation (duplicates, detached/non-detachable), and the register→write→claim→detach ordering. SerializedValue keeps serialize/deserialize as separate halves because a worker message deserializes on a different isolate. WorkerMessage.cpp is deleted (WorkerMessage.h is a one-line alias), and the MallocedBuffer scaffolding it carried goes with it.
  • postMessage gains the ArrayBuffer transfer list on both entry points (Array of ArrayBuffers, TypeError otherwise), using the shared validation. Both callbacks now accept 1 or 2 arguments.
  • Host objects stay intentionally asymmetric, encoded in one place: HostObjectPolicy { kReject, kDegrade }. structuredClone rejects with DataCloneError (spec); postMessage keeps the shipped degrade-to-{} behavior that the cross-runtime worker suite asserts on both runtimes. Unifying on reject is a coordinated follow-up with the iOS runtime; it will be a one-line policy change here.
  • DataCloneError gains its name: worker clone failures previously threw a plain Error with a "DataCloneError: ..." message prefix; both entry points now throw an Error whose name is "DataCloneError", matching iOS and the shared suite's e.name detection.

V8 14.9 notes (same as iOS)

  • ArrayBuffer::Detach() on a non-detachable buffer aborts the process — it does not throw. The IsDetachable() pre-check before detaching is therefore load-bearing, not defensive.
  • The SharedArrayBuffer/shared-value delegate hooks are overridden to keep the DataCloneError name — with a delegate installed, V8's base implementations throw a plain Error directly onto the isolate.
  • Release() must be called and its buffer free()d even after a failed write — the memory is owned by the caller after Release() regardless of whether serialization succeeded.
  • Registering a buffer via TransferArrayBuffer makes the serializer skip its was_detached check, so detaching after a successful WriteValue (rather than before) is the correct ordering.

Deviations from the specification

Same set as iOS, documented in docs/structured-clone.md:

  • DataCloneError is an Error with .name = "DataCloneError", not a DOMException (this runtime has no DOMException).
  • ArrayBuffer is the only transferable type.
  • SharedArrayBuffer shares (a new SAB over the same backing store) and is not transferable.
  • Host objects are never cloneable by structuredClone; postMessage degrades them (see above).

Tests

Shared cross-runtime suite: NativeScript/common-runtime-tests-app#26 (54 specs — clone semantics, graph identity and cycles, transfer, SharedArrayBuffer sharing, worker message transfer, DataCloneError cases), wired up via shared.runStructuredCloneTests() in mainpage.js. An unguarded canary in testRuntimeImplementedAPIs.js asserts the global exists, so the suite's self-gating cannot hide a regression.

Full suite green on a Pixel 3a API 36 emulator (arm64): 701 specs, 0 failures, 5 skipped, 11 disabled.

Merge sequencing

Summary by CodeRabbit

  • New Features

    • Added global structuredClone() support, including cloning, transfer lists, shared buffers, and graph preservation.
    • Added validation and meaningful errors for unsupported values and invalid transfer lists.
    • Extended worker messaging to support optional transferable objects.
  • Documentation

    • Added runtime documentation covering structuredClone() behavior, supported values, transfer semantics, and runtime-specific differences.
  • Tests

    • Added startup and compatibility tests confirming structuredClone() availability and behavior.

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds the global structuredClone API with V8 serialization support, transfer-list handling, and runtime-specific errors. Reuses the serialization core for worker messages, wires the builtin into runtime initialization, adds startup tests, and documents the behavior.

Changes

Structured clone support

Layer / File(s) Summary
Structured serialization core
test-app/runtime/src/main/cpp/StructuredSerialization.h, test-app/runtime/src/main/cpp/StructuredSerialization.cpp
Adds SerializedValue, DataCloneError, host-object policies, transfer validation, buffer ownership, serialization, and deserialization.
structuredClone API and runtime wiring
test-app/runtime/src/main/cpp/js/structured-clone.js, test-app/runtime/src/main/cpp/js/primordials.js, test-app/runtime/src/main/cpp/StructuredClone.*, test-app/runtime/src/main/cpp/Runtime.cpp, test-app/runtime/CMakeLists.txt
Adds the JavaScript API, validates options and transfers, installs the native clone binding, and includes the runtime sources.
Worker message transfer integration
test-app/runtime/src/main/cpp/WorkerMessage.h, test-app/runtime/src/main/cpp/CallbackHandlers.cpp
Replaces the worker serializer with SerializedValue and supports optional transfer lists with host-object degradation.
Runtime validation and documentation
test-app/app/src/main/assets/app/tests/testRuntimeImplementedAPIs.js, test-app/app/src/main/assets/app/mainpage.js, test-app/app/src/main/assets/app/shared, docs/README.md, docs/structured-clone.md
Adds a runtime canary, runs the structured-clone tests at startup, updates the shared test reference, and documents cloning and transfer behavior.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant structuredClone
  participant StructuredClone
  participant SerializedValue
  Caller->>structuredClone: Pass value and options
  structuredClone->>StructuredClone: Pass value and transfer list
  StructuredClone->>SerializedValue: Serialize value
  SerializedValue-->>StructuredClone: Deserialize cloned value
  StructuredClone-->>structuredClone: Return cloned value
  structuredClone-->>Caller: Return result or DataCloneError
Loading

Possibly related PRs

Suggested reviewers: nathanwalker

Poem

A rabbit packed buffers in a neat little row,
Then cloned every cycle without overflow.
“Transfer,” said the hare, “and detach with care!”
Worker messages now share the same pair.
The API hopped in, documented and bright.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 44.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely identifies the main change: adding the structuredClone global with HTML structured clone and ArrayBuffer transfer support.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@edusperoni
edusperoni force-pushed the feat/structured-clone branch from 17badd9 to 429713c Compare August 11, 2026 15:01
Base automatically changed from feat/ns-util to main August 11, 2026 15:44
…sfer)

Android port of NativeScript/ios#431, in lockstep with the iOS runtime.

Implements the WHATWG structuredClone(value, { transfer }) global as a
post-context JS builtin: js/structured-clone.js (shared unchanged with
iOS) owns the WebIDL argument coercion, and a thin native binding runs a
v8::ValueSerializer -> ValueDeserializer round-trip in the one isolate.

The serialization machinery is consolidated into
StructuredSerialization.{h,cpp} (tns::serialization) so structuredClone
and worker postMessage run on one core: delegate pair, DataCloneError
construction (an Error carrying that name -- previously the worker path
threw a plain Error with a message prefix), transfer-list validation,
and the register->write->claim->detach ordering that V8 14.9 requires
(Detach() aborts on non-detachable buffers; Release() must be claimed
even after a failed write). WorkerMessage.cpp is deleted and
WorkerMessage.h reduced to an alias.

postMessage gains the ArrayBuffer transfer list on both entry points.
Host objects stay intentionally asymmetric via HostObjectPolicy:
structuredClone rejects (spec), postMessage keeps the shipped
degrade-to-{} behavior.

Tests: shared cross-runtime suite (common-runtime-tests-app#26, 54
specs) wired via shared.runStructuredCloneTests(), plus an unguarded
canary in testRuntimeImplementedAPIs.js. Documented in
docs/structured-clone.md.
Both tests PRs (#25 Performance, #26 StructuredClone) squash-merged
upstream; the branch commit this previously pinned is now orphaned.
StructuredClone/ content is byte-identical to the tested commit; master
additionally carries the Performance suite, which mainpage.js does not
invoke.
…pecs)

Matches the pin on ios main. Only Performance/index.js changes -- not
invoked by mainpage.js; StructuredClone/ is unchanged.
@edusperoni
edusperoni force-pushed the feat/structured-clone branch from 429713c to 975e724 Compare August 11, 2026 15:46

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (4)
test-app/runtime/src/main/cpp/StructuredClone.cpp (1)

46-65: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Propagate initialization failures instead of only asserting.

In a release build NDEBUG disables every assert here. If Function::New, the Set, or RunBuiltin fails, Init returns normally with structuredClone missing and a pending exception left on the isolate. Runtime::PrepareV8Runtime then continues into Interop::Init and the failure surfaces far from its cause. ErrorEvents::Init and Events::Init throw NativeScriptException for the same failures.

Consider matching that behavior so bootstrap failures are visible in release builds.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test-app/runtime/src/main/cpp/StructuredClone.cpp` around lines 46 - 65,
Update StructuredClone::Init to propagate failures from Function::New,
binding->Set, and BuiltinLoader::RunBuiltin instead of relying solely on assert.
Match the NativeScriptException behavior used by ErrorEvents::Init and
Events::Init so failures throw immediately in release builds and preserve the
underlying isolate exception details.
test-app/runtime/src/main/cpp/StructuredSerialization.cpp (2)

233-237: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Make the single-use contract of Deserialize enforceable.

std::move(transferredBuffers_[i]) empties each shared_ptr but leaves the vector entries in place. A second Deserialize call then passes an empty shared_ptr<BackingStore> to ArrayBuffer::New, which fails a V8 CHECK instead of reporting an error. The header documents the single-use contract, but nothing enforces it. Clear the vector after the loop and assert that the transferred entries are still owned.

♻️ Proposed guard
     for (size_t i = 0; i < transferredBuffers_.size(); i++) {
+        assert(transferredBuffers_[i] != nullptr);
         deserializer.TransferArrayBuffer(
                 static_cast<uint32_t>(i),
                 ArrayBuffer::New(isolate, std::move(transferredBuffers_[i])));
     }
+    transferredBuffers_.clear();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test-app/runtime/src/main/cpp/StructuredSerialization.cpp` around lines 233 -
237, Update Deserialize’s transferred-buffer loop to assert each
transferredBuffers_ entry is still non-null before moving it into
ArrayBuffer::New, then clear transferredBuffers_ after the loop so a second
Deserialize cannot reuse emptied entries.

219-222: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Create the HandleScope before the Context::Scope.

Serialize enters the HandleScope first (Line 169-170), Deserialize reverses the order. Both work, but matching the order keeps scope destruction consistent with the rest of the runtime.

♻️ Proposed reordering
-    Context::Scope contextScope(context);
     EscapableHandleScope handleScope(isolate);
+    Context::Scope contextScope(context);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test-app/runtime/src/main/cpp/StructuredSerialization.cpp` around lines 219 -
222, In SerializedValue::Deserialize, construct the EscapableHandleScope before
entering Context::Scope, matching the established ordering in Serialize. Keep
the existing scopes and behavior unchanged apart from this declaration reorder.
test-app/runtime/src/main/cpp/js/structured-clone.js (1)

93-110: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Install structuredClone with WebIDL property attributes.

A plain assignment creates an enumerable own property on globalThis. WebIDL requires global interface members to be writable, configurable, and non-enumerable. Code that enumerates globalThis now sees structuredClone.

♻️ Proposed change
-g.structuredClone = function structuredClone(value, options = undefined) {
+function structuredClone(value, options = undefined) {
   if (arguments.length < 1) {
     throw new TypeError("structuredClone: 1 argument required, but only 0 present");
   }
@@
   return clone(value, transfer);
-};
+}
+
+ObjectDefineProperty(g, "structuredClone", {
+  value: structuredClone,
+  writable: true,
+  enumerable: false,
+  configurable: true,
+});

ObjectDefineProperty must be added to the destructured primordials list.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test-app/runtime/src/main/cpp/js/structured-clone.js` around lines 93 - 110,
Update the installation of structuredClone around the structuredClone function
to define the global property via ObjectDefineProperty instead of plain
assignment, using writable and configurable true with enumerable false. Add
ObjectDefineProperty to the destructured primordials list and preserve the
existing function behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/structured-clone.md`:
- Around line 37-47: Update the “postMessage” documentation to name both worker
postMessage entry points, including the worker-side API, and state that both
accept an optional transfer list only when it is an array; omitted, undefined,
or null transfer nothing, while other non-array values throw TypeError. Keep the
existing shared serialization and host-object behavior details unchanged.

In `@test-app/runtime/src/main/cpp/CallbackHandlers.cpp`:
- Around line 1323-1328: Update the WorkerGlobalScope.postMessage serialization
failure path around Serialize to call tc.ReThrow() before returning when
Serialize yields Nothing, preserving the pending DataCloneError or TypeError so
JavaScript can catch the synchronous failure.

---

Nitpick comments:
In `@test-app/runtime/src/main/cpp/js/structured-clone.js`:
- Around line 93-110: Update the installation of structuredClone around the
structuredClone function to define the global property via ObjectDefineProperty
instead of plain assignment, using writable and configurable true with
enumerable false. Add ObjectDefineProperty to the destructured primordials list
and preserve the existing function behavior.

In `@test-app/runtime/src/main/cpp/StructuredClone.cpp`:
- Around line 46-65: Update StructuredClone::Init to propagate failures from
Function::New, binding->Set, and BuiltinLoader::RunBuiltin instead of relying
solely on assert. Match the NativeScriptException behavior used by
ErrorEvents::Init and Events::Init so failures throw immediately in release
builds and preserve the underlying isolate exception details.

In `@test-app/runtime/src/main/cpp/StructuredSerialization.cpp`:
- Around line 233-237: Update Deserialize’s transferred-buffer loop to assert
each transferredBuffers_ entry is still non-null before moving it into
ArrayBuffer::New, then clear transferredBuffers_ after the loop so a second
Deserialize cannot reuse emptied entries.
- Around line 219-222: In SerializedValue::Deserialize, construct the
EscapableHandleScope before entering Context::Scope, matching the established
ordering in Serialize. Keep the existing scopes and behavior unchanged apart
from this declaration reorder.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d22843f7-7af5-44bc-91c6-8cbc33f9c4c2

📥 Commits

Reviewing files that changed from the base of the PR and between 7ff8e85 and 975e724.

📒 Files selected for processing (16)
  • docs/README.md
  • docs/structured-clone.md
  • test-app/app/src/main/assets/app/mainpage.js
  • test-app/app/src/main/assets/app/shared
  • test-app/app/src/main/assets/app/tests/testRuntimeImplementedAPIs.js
  • test-app/runtime/CMakeLists.txt
  • test-app/runtime/src/main/cpp/CallbackHandlers.cpp
  • test-app/runtime/src/main/cpp/Runtime.cpp
  • test-app/runtime/src/main/cpp/StructuredClone.cpp
  • test-app/runtime/src/main/cpp/StructuredClone.h
  • test-app/runtime/src/main/cpp/StructuredSerialization.cpp
  • test-app/runtime/src/main/cpp/StructuredSerialization.h
  • test-app/runtime/src/main/cpp/WorkerMessage.cpp
  • test-app/runtime/src/main/cpp/WorkerMessage.h
  • test-app/runtime/src/main/cpp/js/primordials.js
  • test-app/runtime/src/main/cpp/js/structured-clone.js
💤 Files with no reviewable changes (1)
  • test-app/runtime/src/main/cpp/WorkerMessage.cpp

Comment thread docs/structured-clone.md
Comment on lines +37 to +47
`structuredClone` and worker `postMessage` run on the same serialization core, so everything above — which types clone, graph identity, cycles, `SharedArrayBuffer` sharing — holds for messages too. `postMessage` takes the same transfer list as a second argument:

```js
worker.postMessage({ pixels: buffer }, [buffer]); // buffer is detached here,
// its memory now in the worker
```

Two differences are intentional:

- **The transfer list must be an array.** Omitting it, or passing `undefined` or `null`, means "transfer nothing"; every other non-array value is a `TypeError`. The WebIDL iterable-to-sequence conversion that lets `structuredClone` take a `Set` or any iterable lives in the JavaScript wrapper around `structuredClone`; `postMessage` is native all the way down and has no such wrapper.
- **Host objects degrade instead of throwing.** Posting a native/interop object delivers an empty object to the receiver rather than raising a `DataCloneError`. This is long-standing shipped behavior, and app code relies on it; `structuredClone`, being new, follows the spec and rejects. The asymmetry is encoded in exactly one place — the `HostObjectPolicy` enum in `test-app/runtime/src/main/cpp/StructuredSerialization.h` — and unifying the two on rejection is a breaking change that needs the iOS runtime to move at the same time.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Document both postMessage entry points.

The PR adds transfer-list support to both worker postMessage entry points, but this section only names and demonstrates worker.postMessage. State both entry points and their shared array-only transfer-list rule so the worker-side API is also discoverable.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/structured-clone.md` around lines 37 - 47, Update the “postMessage”
documentation to name both worker postMessage entry points, including the
worker-side API, and state that both accept an optional transfer list only when
it is an array; omitted, undefined, or null transfer nothing, while other
non-array values throw TypeError. Keep the existing shared serialization and
host-object behavior details unchanged.

Comment on lines +1323 to 1328
if (message->Serialize(isolate, context, args[0], transferList,
serialization::HostObjectPolicy::kDegrade)
.IsNothing()) {
// The transfer list was rejected or the value could not be cloned;
// the exception is already pending and nothing may be posted.
return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect how worker callback errors are dispatched and whether worker clone
# failures have regression coverage.
rg -n -C 8 '\bWorkerGlobalPostMessageCallback\b|\bCallWorkerScopeOnErrorHandle\b' \
  test-app/runtime/src/main/cpp/CallbackHandlers.cpp

rg -n -C 5 'DataCloneError|structuredClone|postMessage\s*\(' \
  test-app/app/src/main/assets/app

Repository: NativeScript/android

Length of output: 10333


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the complete callback, error-dispatch helper, and Message::Serialize
# implementation without executing repository code.
rg -n -l 'CallWorkerScopeOnErrorHandle|class Message|Message::Serialize|\.Serialize\(' \
  test-app/runtime/src/main/cpp

printf '\n--- callback and error helper ---\n'
sed -n '1288,1535p' test-app/runtime/src/main/cpp/CallbackHandlers.cpp

printf '\n--- serialization declarations and definitions ---\n'
rg -n -C 12 'Serialize\s*\(|class Message|struct Message' \
  test-app/runtime/src/main/cpp test-app/runtime/src/main

Repository: NativeScript/android

Length of output: 40340


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- complete worker error helper ---'
sed -n '1504,1585p' test-app/runtime/src/main/cpp/CallbackHandlers.cpp

printf '%s\n' '--- serialization failure paths ---'
sed -n '1,230p' test-app/runtime/src/main/cpp/StructuredSerialization.cpp
sed -n '230,430p' test-app/runtime/src/main/cpp/StructuredSerialization.cpp

printf '%s\n' '--- related postMessage callback and tests ---'
sed -n '1210,1290p' test-app/runtime/src/main/cpp/CallbackHandlers.cpp
rg -n -C 10 'postMessage|DataCloneError|transfer list|structured clone|clone' \
  test-app/app/src/main/assets/app/tests test-app/runtime/src/main/cpp \
  -g '*.js' -g '*.cpp' -g '*.h'

Repository: NativeScript/android

Length of output: 50376


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- worker execution and exception forwarding ---'
rg -n -C 12 'PassUncaughtExceptionFromWorkerToParent|TryCatch|Execute|RunScript|Script::Run|onerror' \
  test-app/runtime/src/main/cpp \
  -g '*.cpp' -g '*.h' \
  | grep -v 'robin_hood.h' | head -n 500

printf '%s\n' '--- all local TryCatch rethrow patterns ---'
rg -n -C 5 'TryCatch|ReThrow|IsNothing\(\).*return|IsNothing\(\)' \
  test-app/runtime/src/main/cpp \
  -g '*.cpp' -g '*.h' | grep -v 'robin_hood.h' | head -n 500

printf '%s\n' '--- relevant test names and worker error assertions ---'
rg -n -C 8 'worker\.onerror|onerror\s*=|DataCloneError|postMessage.*catch|catch.*postMessage|could not be cloned|transfer list' \
  test-app/app/src/main/assets/app/tests \
  -g '*.js'

Repository: NativeScript/android

Length of output: 44955


🌐 Web query:

V8 TryCatch ReThrow exception caught callback destructor documentation

💡 Result:

In the V8 engine, the v8::TryCatch class is an external exception handler used to catch JavaScript exceptions [1][2]. Its destructor (~TryCatch) is responsible for unregistering and deleting the try/catch block from V8 [3][4]. Regarding the specific behaviors you mentioned: 1. Destructor Functionality: The destructor ~TryCatch performs necessary cleanup by unregistering the block [3][1]. Because it is designed to be stack-allocated, its cleanup happens automatically when the object goes out of scope [3][5]. 2. ReThrow Method: The ReThrow method is used to re-throw an exception that has been caught by the current v8::TryCatch instance [1]. Crucially, it re-throws the exception in a manner that avoids it being caught again by the same TryCatch instance [3][4]. After calling ReThrow, it is illegal to execute any further JavaScript operations; the caller must return immediately to the point where the exception is propagated [1][2]. 3. Destructor Callback: V8 does not provide a specific callback mechanism tied to the destruction of a v8::TryCatch instance itself. If you are looking for callbacks related to object lifecycles (such as cleaning up C++ resources associated with a JavaScript object), V8 provides WeakPersistent handles and finalizers (e.g., v8::Global::SetWeak or v8::FinalizationRegistry) rather than hooks on the TryCatch destructor [6]. In summary, the v8::TryCatch destructor handles internal engine state management to ensure exception handling chains remain consistent [3][1]. It is not a user-extensible callback point [3][2].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- worker message-dispatch control flow ---'
sed -n '130,245p' test-app/runtime/src/main/cpp/WorkerWrapper.cpp
sed -n '365,415p' test-app/runtime/src/main/cpp/WorkerWrapper.cpp

printf '%s\n' '--- focused test inventory ---'
rg -n -C 6 'worker\.onerror|onerror\s*=|DataCloneError|postMessage.*catch|catch.*postMessage|transfer list|could not be cloned' \
  test-app/app/src/main/assets/app/tests \
  -g '*.js' || true

printf '%s\n' '--- static path check ---'
python3 - <<'PY'
from pathlib import Path

p = Path("test-app/runtime/src/main/cpp/CallbackHandlers.cpp")
text = p.read_text()
start = text.index("CallbackHandlers::WorkerGlobalPostMessageCallback")
end = text.index("void\nCallbackHandlers::WorkerObjectTerminateCallback", start)
fn = text[start:end]

checks = {
    "callback creates TryCatch": "TryCatch tc(isolate);" in fn,
    "serialization can return Nothing": ".IsNothing()" in fn,
    "serialization failure returns directly": "                    return;" in fn[fn.index(".IsNothing()"):],
    "serialization failure calls worker error helper": "CallWorkerScopeOnErrorHandle(isolate, tc);" in fn[fn.index(".IsNothing()"):],
    "serialization failure rethrows": "tc.ReThrow();" in fn[fn.index(".IsNothing()"):],
}
for name, result in checks.items():
    print(f"{name}: {result}")
PY

Repository: NativeScript/android

Length of output: 9333


Re-throw the serialization exception from WorkerGlobalScope.postMessage.

When Serialize returns Nothing, it leaves a DataCloneError or TypeError pending in tc. Call tc.ReThrow() before returning so JavaScript can catch the synchronous failure.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test-app/runtime/src/main/cpp/CallbackHandlers.cpp` around lines 1323 - 1328,
Update the WorkerGlobalScope.postMessage serialization failure path around
Serialize to call tc.ReThrow() before returning when Serialize yields Nothing,
preserving the pending DataCloneError or TypeError so JavaScript can catch the
synchronous failure.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant