Opt-in document::eval, gated on both sides of the boundary - #23
Merged
Conversation
`polymorph:dioxus` gains an `eval` interface — the back end of dioxus's `document::eval` — that nothing gets by default. A browser cannot sandbox arbitrary JS well enough for a host running untrusted components, so the capability is granted twice or not at all: - Guest: the renderer references the interface only under its new `eval` Cargo feature. wit-component encodes only the interfaces the core module actually imports, so a component built without the feature does not import `polymorph:dioxus/eval` at all (the `example` recipe asserts the absence; `fixtures` asserts the presence for the eval fixture). Without it `document::eval` still resolves to `NoOpDocument`. - Host: `mountApp` supplies the import only for `MountOptions.eval`. A component that imports it against a host that did not opt in fails at instantiation (`PlanError` naming the import), which is the safe direction. polyvisor sets the flag for its visor and never for an app. Semantics follow dioxus-web/dioxus-desktop: the script is the body of an async function with a `dioxus` object (`send`/`recv`), values cross as JSON text, `undefined` returns as `null`, and the constructor's synchronous prefix is bracketed by the dispatch gate like `dom.set-focus`. Guest side, `Eval` is `Copy` over a generational box whose owner the reference renderers release on JS GC. Here the owner is released when the script completes unobserved (fire-and-forget, the common case) or when `poll_join` hands the result over — scheduled through `spawn_local` because `Eval::join` holds a borrow on the box at that moment. Host side carries one workaround: `recv`/`join` settle no earlier than a macrotask after they would otherwise. An async import called from an export's initial activation whose promise settles in the following microtask checkpoint is never delivered back to the guest under the pinned polyengine (the export's driver exits before the settlement pump arms); `handle-event` handlers awaiting an eval hit this directly. The deferral comes out when polyengine closes the gap.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds
document::evalsupport to the renderer as a capability that is off unless granted on both sides:evalCargo feature onpolyengine-dioxus. Without it the component does not importpolymorph:dioxus/evalat all (wit-component prunes unreferenced world imports;just examplenow asserts this,just fixturesasserts the converse for the neweval-probefixture). With it,WitDocumentis provided as root context anddocument::evalworks.MountOptions.eval?: boolean(defaultfalse). Only then ispolymorph:dioxus/eval@0.6.0supplied. A component that imports eval against a non-opted host fails at instantiation with aPlanErrornaming the import — tested.For polyvisor: the visor mounts with
eval: true; apps never do, and an app that was built with the feature cannot instantiate.WIT (
interface eval):resource evaluation { constructor(js); send(json); recv: async -> result<string, error>; join: async -> result<string, error> },error = invalid-js | communication | finished. Semantics mirror dioxus-web/desktop (async-function body withdioxus.send/dioxus.recv, JSON both ways,undefined->null). Package version unchanged: the mutation schema is untouched.Guest owner lifetime:
EvalisCopy; the reference renderers free the evaluator on JS GC. Here it is freed when the script completes unobserved (fire-and-forget) or whenpoll_joindelivers the result. Details insrc/document.rs's module doc.Workaround, flagged:
host/src/eval.tsdefersrecv/joinsettlement by one macrotask. Under the pinned polyengine, an async host import called from an export's initial activation whose promise settles in the next microtask checkpoint is never delivered back to the guest (driver exitsEXIT-donebefore the settlement pump arms).handle-eventhandlers awaiting an eval hit this every time;use_futureevals dodge it by accident. Repro matrix and citation are in the comment. Upstream issue to follow separately.Tests:
host/tests/eval_test.ts(unit, no component),host/tests/eval_component_test.ts(real fixture: fire-and-forget, recv/send/join round trip, both error paths via click handlers, negative mount). Fulljust check/fixtures/example counter/ssg-example counter/serve-test counter/testpass locally.Automerge armed (merge commit).