diff --git a/moli-renderer-v8/src/native_bridge/node.rs b/moli-renderer-v8/src/native_bridge/node.rs index e03b8c6057..9d7011be70 100644 --- a/moli-renderer-v8/src/native_bridge/node.rs +++ b/moli-renderer-v8/src/native_bridge/node.rs @@ -10,7 +10,7 @@ use moli_webapi_declare::WebApiFunctionTemplate; use super::super::{ document_runtime::DomHandle, util::{ - call_global_bridge_method, context_host_ptr_from_global_bridge, get_private_value, + call_global_bridge_method, context_host_ptr_from_context_slot, get_private_value, throw_type_error, v8_string, v8str, }, }; @@ -813,8 +813,13 @@ pub(crate) fn node_runtime_and_handle_from_object_or_detached<'s>( if let Ok(node) = node_runtime_and_handle_from_object(scope, object) { return Ok(node); } - let runtime_ptr = context_host_ptr_from_global_bridge(scope) - .context("current context has no JsContextHost")?; + // Detached node indices are host-local too. Borrowed bindings and + // structuredClone can run in another Page, so resolve the producer's + // lifecycle-checked context instead of looking in the caller's arena. + let runtime_ptr = object + .get_creation_context(scope) + .and_then(context_host_ptr_from_context_slot) + .context("detached node's context has no JsContextHost")?; let handle = super::document::detached_native_handle_for_runtime(scope, runtime_ptr, object) .context("object is neither a Node wrapper nor a detached node")?; Ok((runtime_ptr, handle)) diff --git a/moli-renderer-v8/src/script_vm/tests/rectangle_receivers.rs b/moli-renderer-v8/src/script_vm/tests/rectangle_receivers.rs index 9ad388e48b..c9732a32c7 100644 --- a/moli-renderer-v8/src/script_vm/tests/rectangle_receivers.rs +++ b/moli-renderer-v8/src/script_vm/tests/rectangle_receivers.rs @@ -10,7 +10,7 @@ fn svg_rect_uses_shared_geometry_in_live_and_windowless_realms() { "[]" ); assert_eq!( - vm.eval("__uiEventResults.complete && __uiEventResults.total === 3522 && __uiEventResults.passed === 3522") + vm.eval("__uiEventResults.complete && __uiEventResults.total === 4122 && __uiEventResults.passed === 4122") .unwrap(), "true" ); diff --git a/moli-renderer-v8/src/script_vm/tests/svg_rect_geometry.js b/moli-renderer-v8/src/script_vm/tests/svg_rect_geometry.js index c67aabb548..c48f824df4 100644 --- a/moli-renderer-v8/src/script_vm/tests/svg_rect_geometry.js +++ b/moli-renderer-v8/src/script_vm/tests/svg_rect_geometry.js @@ -60,7 +60,19 @@ if (kind === 'DOMRect') clone.width = .1; return owner.getAttribute('viewBox') === previous && (kind !== 'DOMRect' || clone.width === .1); }); - check(`${prefix}/${kind}/cross realm JSON`, () => Object.getPrototypeOf(DOMRectReadOnly.prototype.toJSON.call(rect)) === Object.prototype); + check(`${prefix}/${kind}/cross realm JSON`, () => { + owner.setAttribute('viewBox','10 20 30 40'); + const json = DOMRectReadOnly.prototype.toJSON.call(rect); + return Object.getPrototypeOf(json) === Object.prototype && fields.every((name,i) => json[name] === [10,20,30,40][i]) && json.right === 40 && json.bottom === 60; + }); + check(`${prefix}/${kind}/borrowed coordinate getters`, () => { + owner.setAttribute('viewBox','11 22 33 44'); + return fields.every((name,i) => Object.getOwnPropertyDescriptor(DOMRectReadOnly.prototype,name).get.call(rect) === [11,22,33,44][i]); + }); + check(`${prefix}/${kind}/borrowed side getters`, () => { + owner.setAttribute('viewBox','-5 -7 3 4'); + return ['left','top','right','bottom'].every((name,i) => Object.getOwnPropertyDescriptor(DOMRectReadOnly.prototype,name).get.call(rect) === [-5,-7,-2,-3][i]); + }); check(`${prefix}/${kind}/fromRect snapshot`, () => { const copy = w.DOMRect.fromRect(rect); const x = copy.x; owner.setAttribute('viewBox','5 6 7 8'); @@ -82,6 +94,13 @@ const expected = [10,20,30,40]; expected[fields.indexOf(name)] = .1; return conversions === 1 && fields.every((key,i) => base[key] === expected[i] && anim[key] === expected[i]) && owner.getAttribute('viewBox') === expected.join(' '); }); + check(`${prefix}/${name}/borrowed setter reentrant mutation`, () => { + let conversions = 0; + const borrowed = Object.getOwnPropertyDescriptor(DOMRect.prototype,name).set; + borrowed.call(base,{valueOf(){conversions++;owner.setAttribute('viewBox','50 60 70 80');return .2;}}); + const expected = [50,60,70,80]; expected[fields.indexOf(name)] = .2; + return conversions === 1 && fields.every((key,i) => base[key] === expected[i] && anim[key] === expected[i]) && owner.getAttribute('viewBox') === expected.join(' '); + }); check(`${prefix}/${name}/throwing conversion preserved`, () => { const sentinel = {}, previous = owner.getAttribute('viewBox'); return thrown(() => setter.call(base,{valueOf(){throw sentinel;}})) === sentinel && owner.getAttribute('viewBox') === previous;