diff --git a/moli-renderer-v8/src/custom_elements/existing_upgrade_invocation.rs b/moli-renderer-v8/src/custom_elements/existing_upgrade_invocation.rs index b95fc6e51d..509c61a134 100644 --- a/moli-renderer-v8/src/custom_elements/existing_upgrade_invocation.rs +++ b/moli-renderer-v8/src/custom_elements/existing_upgrade_invocation.rs @@ -31,7 +31,11 @@ pub(super) fn upgrade_existing_custom_element_with_constructor<'s>( .custom_elements_mut_for_node_handle(handle) .begin_construction(scope, constructor, wrapper, handle); let _dynamic_markup = enter_upgrade_dynamic_markup_insertion(host_ptr, handle); - match invoke_custom_element_constructor(scope, host_ptr, constructor) { + let invocation = invoke_custom_element_constructor(scope, host_ptr, constructor); + let consumed_pending_wrapper = unsafe { &*host_ptr } + .custom_elements_for_node_handle(handle) + .is_some_and(|store| store.pending_construction_is_already_constructed(handle)); + match invocation { CustomElementConstructorInvocation::Created(created) => { let created = v8::Local::new(scope, &created); if !validate_existing_custom_element_upgrade_result(wrapper, created) { @@ -45,7 +49,9 @@ pub(super) fn upgrade_existing_custom_element_with_constructor<'s>( ), failure_prototype, ); - finalize_wrapper_custom_element_prototype(scope, wrapper, constructor); + if consumed_pending_wrapper { + finalize_wrapper_custom_element_prototype(scope, wrapper, constructor); + } return true; } finalize_wrapper_custom_element_prototype(scope, wrapper, constructor); @@ -53,10 +59,13 @@ pub(super) fn upgrade_existing_custom_element_with_constructor<'s>( true } CustomElementConstructorInvocation::Exception(exception) => { - finalize_wrapper_custom_element_prototype(scope, wrapper, constructor); - if let Some(canonical) = unsafe { &mut *host_ptr } - .native_bridge_mut() - .wrap_handle(scope, host_ptr, handle) + if consumed_pending_wrapper { + finalize_wrapper_custom_element_prototype(scope, wrapper, constructor); + } + if consumed_pending_wrapper + && let Some(canonical) = unsafe { &mut *host_ptr } + .native_bridge_mut() + .wrap_handle(scope, host_ptr, handle) { if canonical.strict_equals(wrapper.into()) { synchronize_custom_element_prototype_between_wrappers( @@ -90,7 +99,9 @@ pub(super) fn upgrade_existing_custom_element_with_constructor<'s>( ConstructionFailure::Exception(exception), failure_prototype, ); - finalize_wrapper_custom_element_prototype(scope, wrapper, constructor); + if consumed_pending_wrapper { + finalize_wrapper_custom_element_prototype(scope, wrapper, constructor); + } true } CustomElementConstructorInvocation::Empty => { diff --git a/moli-renderer-v8/src/script_vm/tests/dom_elements/custom_elements.rs b/moli-renderer-v8/src/script_vm/tests/dom_elements/custom_elements.rs index aa0fed6a8d..1884c3a44b 100644 --- a/moli-renderer-v8/src/script_vm/tests/dom_elements/custom_elements.rs +++ b/moli-renderer-v8/src/script_vm/tests/dom_elements/custom_elements.rs @@ -6889,6 +6889,58 @@ fn custom_element_constructor_error_reports_to_definition_window() { ); } +#[test] +fn failed_existing_upgrade_before_super_keeps_original_prototype() { + let mut vm = new_storage_test_vm("https://ce-before-super.test/"); + let result = vm.eval(r#" + (() => { + window.addEventListener("error", event => event.preventDefault()); + const html = document.documentElement || document.appendChild(document.createElement("html")); + const body = document.body || html.appendChild(document.createElement("body")); + const frame = body.appendChild(document.createElement("iframe")); + const results = []; + for (const [realm, w] of [["main", window], ["child", frame.contentWindow]]) { + for (const failure of ["throw", "return-object"]) { + const doc = w.document; + const name = `before-super-${failure}`; + const element = doc.createElement(name); + element.setAttribute("data-value", "before"); + (doc.body || doc.documentElement || doc).appendChild(element); + const original = Object.getPrototypeOf(element); + const log = []; + class FailedBeforeSuper extends w.HTMLElement { + constructor() { + log.push("constructor"); + if (failure === "throw") throw new Error("before super"); + return {}; + } + static get observedAttributes() { return ["data-value"]; } + attributeChangedCallback() { log.push("attribute"); } + connectedCallback() { log.push("connected"); } + } + w.customElements.define(name, FailedBeforeSuper); + results.push({realm, failure, + unchanged: Object.getPrototypeOf(element) === original, + custom: element instanceof FailedBeforeSuper, log}); + } + } + return JSON.stringify(results); + })() + "#).expect("failed upgrades before super should report their original prototypes"); + let results: serde_json::Value = serde_json::from_str(&result).expect("upgrade results"); + let results = results.as_array().expect("four upgrade cases"); + assert_eq!(results.len(), 4); + for result in results { + assert_eq!(result["unchanged"], true, "{result}"); + assert_eq!(result["custom"], false, "{result}"); + assert_eq!( + result["log"], + serde_json::json!(["constructor"]), + "{result}" + ); + } +} + #[test] fn failed_existing_upgrade_preserves_definition_prototype_and_clears_reactions() { let mut vm = new_storage_test_vm("https://ce-failed-upgrade.test/");