RDEV-10097 - Bail out of unbound native object proxy calls - #219
RDEV-10097 - Bail out of unbound native object proxy calls#219helderjgoncalves wants to merge 5 commits into
Conversation
Behind BailOutOnUnboundNativeObjectCalls, view property proxies no-op when the native object is missing or CefGlue rejects with "was not found", so teardown/remount races no longer surface as uncaught errors. Bump to 5.120.7. Co-authored-by: Cursor <cursoragent@cursor.com>
Restores the previous behaviour exactly when BailOutOnUnboundNativeObjectCalls is off: the proxy calls nativeObject[key].apply again, so a call to an object that is no longer bound still fails naming the method it was trying to reach, which is what identifies the caller. With the flag on, a released view is now the primary signal. The proxy checks view.isReleased before binding anything, and the rejection text is left as the backstop for the object that is still on the window while the host has already unregistered it. "Failed to create native object" is no longer swallowed: it is raised when registration cannot enter the v8 context, which is a failure rather than a teardown race. Dropped calls are logged, since an object that was never registered looks exactly like one that went away with its view, and only one of those is a bug. Covered by Tests.ReactView/UnboundNativeObjectCallsTests.cs, for both values of the flag. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Review round addressed in 5ed8d66. 1. The flag-off path is byte-identical to the previous code again. 2. 3. 4. Dropped calls are logged. 5. Tests added in Caveat worth knowing about The new tests could not be run locally. The whole Version stays at 5.120.7 - same unpublished slot. |
console.debug lands on the Verbose level in the devtools console, which is hidden by default, so the log for a dropped call was there but invisible and the bail out looked silent. It is now a warning: the call was ignored, and an object that was never registered still needs to be noticed. Co-authored-by: Cursor <cursoragent@cursor.com>
A call that returns a value cannot be dropped: the proxy resolves it with undefined, the still mounted component writes that into its state, and the render that follows fails somewhere else, away from the teardown race that caused it. Rapidly switching views in ODC Studio turned dropped getToolbarItems, getTopPaneInfo and getGridLayout calls into "Cannot read properties of undefined" crashes. Only methods known to return nothing are dropped now, which is the class the bail out was written for: the mouseEnterNode / mouseLeaveTree notifications from the original report. The host reflects over the native object it registered and hands the loader the names of its void members. A method that returns a value, or one that could not be matched and about which nothing is known, keeps failing exactly as it did before this branch, naming the method it was trying to reach. A method returning Task counts as void: the promise the caller awaits carries no value either way. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Narrowed the bail out to calls that return nothing (a74a8ec). WhyStress testing this in ODC Studio, by switching views as fast as possible with Verbose logging on, showed the dropped calls split into two very different classes. Void notifications — dropping these is correct, and it is what the original report was about: Value returning queries — dropping these resolves the promise with Each of those came back as a crash further away from its cause, in the consuming app: What changedThe host now reflects over the native object it registered, matches its methods against the module's events, and sends the loader the names of the ones that return nothing, as one more A method returning plain
|
The void / value axis this bail out was narrowed to is the wrong one. Dropping a void call on a view that is still alive discards a real user interaction: a doubleClickNode or a mouseEnterNode that vanishes leaves a tree that looks alive and does nothing, and nothing anywhere reports it. A render that crashes is loud and recoverable; view to presenter communication that quietly stops is neither. What separates the two cases is whether the view was released, not what the call returns. A destroyed view has no native object left to reach and nobody left to receive a result, so its calls are dropped whatever their return type, as the getGridInfo call on an already destroyed UIEditorView harmlessly was. Everything else is left alone: a live view that cannot reach its native object is a broken channel to the presenter, so getToolbarItems, getLeftPaneState and the flood of mouseEnterNode calls seen rejecting with "the object is no longer bound" go back to surfacing exactly as they did before this branch. Since isReleased is checked before the call is dispatched, a released view never reaches native, and every rejection the catch used to swallow necessarily came from a live view. The catch, the unbound object check that followed the bind, and the reflected void member metadata that fed both of them are gone with it. BailOutOnUnboundNativeObjectCalls stays, now gating only the dropped call into a destroyed view. The host lifecycle bug that strands the native objects of live views is being diagnosed separately. A bounded wait for a rebind, so that an in flight call survives a legitimate re-registration, is a follow up once that lands. Co-authored-by: Cursor <cursoragent@cursor.com>
Re-scoped: the axis is released vs live, not void vs valueRuntime evidence and a product decision moved this branch off the void / value distinction that Why the void / value axis was wrongThe priority is that a render crash is acceptable but silently breaking view-to-presenter communication is not. Measured against that, return type is the wrong thing to key on: dropping a void call on a view that is still alive discards a real user interaction. A What actually separates the two casesView genuinely released ( View live but its native object unbound — a broken channel to the presenter, never silently swallowed, whatever its return type. These go back to surfacing exactly as they did before the branch existed:
What that removedBecause The native invocation in Net effect on the diff against Not in scope, and what comes nextThe host-lifecycle bug that strands the native objects of live views is the real defect behind the Once that diagnosis lands, the planned follow-up is a bounded wait for a rebind, so that an in-flight call survives a legitimate re-registration instead of failing on a race, without ever silently dropping a call from a live view. Verification
|
Summary
BailOutOnUnboundNativeObjectCalls(default true), threaded throughReactView→ loader → window flag.ViewPropertiesProxyno-ops when the native object/method is missing, and swallows CefGlue "was not found" / bind failures from teardown/remount races; other errors still throw. Flag off restores previous behaviour.Test plan
$editorCanvas$MultipleEditors was not foundorproxy.<computed>TypeErrors formouseEnterNode/mouseLeaveTreeMade with Cursor