diff --git a/experiments/WebScene.NativeEngine.Probe/native/webscene_v8_runtime_lifecycle.inc b/experiments/WebScene.NativeEngine.Probe/native/webscene_v8_runtime_lifecycle.inc index 35cfb1741..64857c030 100644 --- a/experiments/WebScene.NativeEngine.Probe/native/webscene_v8_runtime_lifecycle.inc +++ b/experiments/WebScene.NativeEngine.Probe/native/webscene_v8_runtime_lifecycle.inc @@ -146,6 +146,12 @@ pending_programmatic_scroll_events.clear(); pending_interop_promises.clear(); pending_callback_promises.clear(); + // Native host and file completions can still be pending when a window + // closes. Their persistent context/resolver handles must be released + // while the isolate is alive; member destruction happens after this + // body and therefore after isolate disposal below. + host_promise_targets.clear(); + file_targets.clear(); pending_fetches.clear(); interop_handles.clear(); pending_promise_rejections.clear(); diff --git a/experiments/WebScene.NativeEngine.Probe/tests/native_v8_runtime_browser_dom_tests.inc b/experiments/WebScene.NativeEngine.Probe/tests/native_v8_runtime_browser_dom_tests.inc index 6f19c4d79..1aeded1cb 100644 --- a/experiments/WebScene.NativeEngine.Probe/tests/native_v8_runtime_browser_dom_tests.inc +++ b/experiments/WebScene.NativeEngine.Probe/tests/native_v8_runtime_browser_dom_tests.inc @@ -2956,6 +2956,49 @@ void test_native_legacy_clipboard_completion_stress(webscene_engine*) webscene_engine_destroy(engine); } +void test_native_pending_legacy_clipboard_shutdown(webscene_engine*) +{ + auto* engine = webscene_engine_create(0); + require(engine != nullptr, + "pending legacy clipboard shutdown engine creation failed"); + execute_and_wait(engine, R"JS( + document.body.innerHTML = ''; + const editor = document.getElementById('pending-copy'); + editor.addEventListener('keydown', event => { + if (event.metaKey && event.key.toLowerCase() === 'c') { + event.preventDefault(); + document.execCommand('copy'); + } + }); + editor.addEventListener('copy', event => { + event.clipboardData.setData('text/plain', 'pending close'); + event.preventDefault(); + }); + editor.focus(); + )JS", "native-pending-legacy-clipboard-shutdown-setup.js"); + + keyboard_input( + engine, WEBSCENE_INPUT_KEY_DOWN, 'C', 31001U, + WEBSCENE_INPUT_MODIFIER_META); + webscene_engine_metrics metrics{}; + webscene_engine_get_metrics(engine, &metrics); + wait_for_consumed_inputs( + engine, metrics.enqueued_inputs, + "pending legacy clipboard shutdown input was not consumed"); + const auto request = take_typed_host_request(engine); + require( + request.kind == WEBSCENE_HOST_REQUEST_CLIPBOARD_WRITE_V1 + && request.content_type == "text/plain" + && std::string(request.bytes.begin(), request.bytes.end()) + == "pending close", + "pending legacy clipboard shutdown did not retain its host promise"); + + // A native window may close before the operating-system host completes a + // request. Destruction must release the pending persistent resolver while + // its V8 isolate is still alive. + webscene_engine_destroy(engine); +} + void test_clipboard_maximum_payload_gate(webscene_engine* engine) { constexpr size_t maximum_bytes = 16U * 1024U * 1024U; diff --git a/experiments/WebScene.NativeEngine.Probe/tests/native_v8_runtime_tests.cpp b/experiments/WebScene.NativeEngine.Probe/tests/native_v8_runtime_tests.cpp index 7ab017a87..ac29d87ca 100644 --- a/experiments/WebScene.NativeEngine.Probe/tests/native_v8_runtime_tests.cpp +++ b/experiments/WebScene.NativeEngine.Probe/tests/native_v8_runtime_tests.cpp @@ -833,6 +833,7 @@ int main() test_clipboard_read_host_completion(engine); test_native_clipboard_shortcut_events(engine); test_native_legacy_clipboard_completion_stress(engine); + test_native_pending_legacy_clipboard_shutdown(engine); test_clipboard_maximum_payload_gate(engine); test_clipboard_small_round_trip_performance(engine); test_fullscreen_host_completion(engine);