mirror of
https://github.com/denoland/deno.git
synced 2025-01-12 17:09:00 -05:00
feat(web): add beforeunload event (#14830)
This commit adds the 'beforeunload' event. Co-authored-by: Bartek Iwańczuk <biwanczuk@gmail.com>
This commit is contained in:
parent
ab11b45d1d
commit
0f6a5c5fc2
13 changed files with 166 additions and 11 deletions
|
@ -226,6 +226,12 @@ async fn test_specifier(
|
||||||
|
|
||||||
worker.js_runtime.resolve_value(test_result).await?;
|
worker.js_runtime.resolve_value(test_result).await?;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if !worker.dispatch_beforeunload_event(&located_script_name!())? {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
worker.run_event_loop(false).await?;
|
||||||
|
}
|
||||||
worker.dispatch_unload_event(&located_script_name!())?;
|
worker.dispatch_unload_event(&located_script_name!())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
26
cli/main.rs
26
cli/main.rs
|
@ -639,7 +639,13 @@ async fn eval_command(
|
||||||
}
|
}
|
||||||
worker.execute_main_module(&main_module).await?;
|
worker.execute_main_module(&main_module).await?;
|
||||||
worker.dispatch_load_event(&located_script_name!())?;
|
worker.dispatch_load_event(&located_script_name!())?;
|
||||||
|
loop {
|
||||||
worker.run_event_loop(false).await?;
|
worker.run_event_loop(false).await?;
|
||||||
|
|
||||||
|
if !worker.dispatch_beforeunload_event(&located_script_name!())? {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
worker.dispatch_unload_event(&located_script_name!())?;
|
worker.dispatch_unload_event(&located_script_name!())?;
|
||||||
Ok(0)
|
Ok(0)
|
||||||
}
|
}
|
||||||
|
@ -975,7 +981,12 @@ async fn run_from_stdin(flags: Flags) -> Result<i32, AnyError> {
|
||||||
}
|
}
|
||||||
worker.execute_main_module(&main_module).await?;
|
worker.execute_main_module(&main_module).await?;
|
||||||
worker.dispatch_load_event(&located_script_name!())?;
|
worker.dispatch_load_event(&located_script_name!())?;
|
||||||
|
loop {
|
||||||
worker.run_event_loop(false).await?;
|
worker.run_event_loop(false).await?;
|
||||||
|
if !worker.dispatch_beforeunload_event(&located_script_name!())? {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
worker.dispatch_unload_event(&located_script_name!())?;
|
worker.dispatch_unload_event(&located_script_name!())?;
|
||||||
Ok(worker.get_exit_code())
|
Ok(worker.get_exit_code())
|
||||||
}
|
}
|
||||||
|
@ -1014,7 +1025,15 @@ async fn run_with_watch(flags: Flags, script: String) -> Result<i32, AnyError> {
|
||||||
self.worker.dispatch_load_event(&located_script_name!())?;
|
self.worker.dispatch_load_event(&located_script_name!())?;
|
||||||
self.pending_unload = true;
|
self.pending_unload = true;
|
||||||
|
|
||||||
|
let result = loop {
|
||||||
let result = self.worker.run_event_loop(false).await;
|
let result = self.worker.run_event_loop(false).await;
|
||||||
|
if !self
|
||||||
|
.worker
|
||||||
|
.dispatch_beforeunload_event(&located_script_name!())?
|
||||||
|
{
|
||||||
|
break result;
|
||||||
|
}
|
||||||
|
};
|
||||||
self.pending_unload = false;
|
self.pending_unload = false;
|
||||||
|
|
||||||
if let Err(err) = result {
|
if let Err(err) = result {
|
||||||
|
@ -1162,9 +1181,16 @@ async fn run_command(
|
||||||
}
|
}
|
||||||
|
|
||||||
worker.dispatch_load_event(&located_script_name!())?;
|
worker.dispatch_load_event(&located_script_name!())?;
|
||||||
|
|
||||||
|
loop {
|
||||||
worker
|
worker
|
||||||
.run_event_loop(maybe_coverage_collector.is_none())
|
.run_event_loop(maybe_coverage_collector.is_none())
|
||||||
.await?;
|
.await?;
|
||||||
|
if !worker.dispatch_beforeunload_event(&located_script_name!())? {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
worker.dispatch_unload_event(&located_script_name!())?;
|
worker.dispatch_unload_event(&located_script_name!())?;
|
||||||
|
|
||||||
if let Some(coverage_collector) = maybe_coverage_collector.as_mut() {
|
if let Some(coverage_collector) = maybe_coverage_collector.as_mut() {
|
||||||
|
|
|
@ -316,7 +316,14 @@ pub async fn run(
|
||||||
);
|
);
|
||||||
worker.execute_main_module(main_module).await?;
|
worker.execute_main_module(main_module).await?;
|
||||||
worker.dispatch_load_event(&located_script_name!())?;
|
worker.dispatch_load_event(&located_script_name!())?;
|
||||||
worker.run_event_loop(true).await?;
|
|
||||||
|
loop {
|
||||||
|
worker.run_event_loop(false).await?;
|
||||||
|
if !worker.dispatch_beforeunload_event(&located_script_name!())? {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
worker.dispatch_unload_event(&located_script_name!())?;
|
worker.dispatch_unload_event(&located_script_name!())?;
|
||||||
std::process::exit(0);
|
std::process::exit(0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -249,6 +249,12 @@ itest!(webstorage_serialization {
|
||||||
output: "webstorage/serialization.ts.out",
|
output: "webstorage/serialization.ts.out",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// tests the beforeunload event
|
||||||
|
itest!(beforeunload_event {
|
||||||
|
args: "run before_unload.js",
|
||||||
|
output: "before_unload.js.out",
|
||||||
|
});
|
||||||
|
|
||||||
// tests to ensure that when `--location` is set, all code shares the same
|
// tests to ensure that when `--location` is set, all code shares the same
|
||||||
// localStorage cache based on the origin of the location URL.
|
// localStorage cache based on the origin of the location URL.
|
||||||
#[test]
|
#[test]
|
||||||
|
|
21
cli/tests/testdata/before_unload.js
vendored
Normal file
21
cli/tests/testdata/before_unload.js
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
console.log("0");
|
||||||
|
|
||||||
|
globalThis.addEventListener("beforeunload", (e) => {
|
||||||
|
console.log("GOT EVENT");
|
||||||
|
if (count === 0 || count === 1) {
|
||||||
|
e.preventDefault();
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log("3");
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
count++;
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("1");
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log("2");
|
||||||
|
}, 100);
|
8
cli/tests/testdata/before_unload.js.out
vendored
Normal file
8
cli/tests/testdata/before_unload.js.out
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
0
|
||||||
|
1
|
||||||
|
2
|
||||||
|
GOT EVENT
|
||||||
|
3
|
||||||
|
GOT EVENT
|
||||||
|
3
|
||||||
|
GOT EVENT
|
|
@ -404,6 +404,12 @@ async fn bench_specifier(
|
||||||
|
|
||||||
worker.js_runtime.resolve_value(bench_result).await?;
|
worker.js_runtime.resolve_value(bench_result).await?;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if !worker.dispatch_beforeunload_event(&located_script_name!())? {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
worker.run_event_loop(false).await?;
|
||||||
|
}
|
||||||
worker.dispatch_unload_event(&located_script_name!())?;
|
worker.dispatch_unload_event(&located_script_name!())?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -814,6 +814,13 @@ async fn test_specifier(
|
||||||
|
|
||||||
worker.js_runtime.resolve_value(test_result).await?;
|
worker.js_runtime.resolve_value(test_result).await?;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if !worker.dispatch_beforeunload_event(&located_script_name!())? {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
worker.run_event_loop(false).await?;
|
||||||
|
}
|
||||||
|
|
||||||
worker.dispatch_unload_event(&located_script_name!())?;
|
worker.dispatch_unload_event(&located_script_name!())?;
|
||||||
|
|
||||||
if let Some(coverage_collector) = maybe_coverage_collector.as_mut() {
|
if let Some(coverage_collector) = maybe_coverage_collector.as_mut() {
|
||||||
|
|
|
@ -267,6 +267,7 @@
|
||||||
destructureError: opSync.bind(null, "op_destructure_error"),
|
destructureError: opSync.bind(null, "op_destructure_error"),
|
||||||
terminate: opSync.bind(null, "op_terminate"),
|
terminate: opSync.bind(null, "op_terminate"),
|
||||||
opNames: opSync.bind(null, "op_op_names"),
|
opNames: opSync.bind(null, "op_op_names"),
|
||||||
|
eventLoopHasMoreWork: opSync.bind(null, "op_event_loop_has_more_work"),
|
||||||
});
|
});
|
||||||
|
|
||||||
ObjectAssign(globalThis.__bootstrap, { core });
|
ObjectAssign(globalThis.__bootstrap, { core });
|
||||||
|
|
|
@ -47,6 +47,7 @@ pub(crate) fn init_builtins_v8() -> Vec<OpDecl> {
|
||||||
op_op_names::decl(),
|
op_op_names::decl(),
|
||||||
op_apply_source_map::decl(),
|
op_apply_source_map::decl(),
|
||||||
op_set_format_exception_callback::decl(),
|
op_set_format_exception_callback::decl(),
|
||||||
|
op_event_loop_has_more_work::decl(),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -786,3 +787,26 @@ fn op_set_format_exception_callback<'a>(
|
||||||
let old = old.map(|v| v8::Local::new(scope, v));
|
let old = old.map(|v| v8::Local::new(scope, v));
|
||||||
Ok(old.map(|v| from_v8(scope, v.into()).unwrap()))
|
Ok(old.map(|v| from_v8(scope, v.into()).unwrap()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[op(v8)]
|
||||||
|
fn op_event_loop_has_more_work(scope: &mut v8::HandleScope) -> bool {
|
||||||
|
let state_rc = JsRuntime::state(scope);
|
||||||
|
let module_map_rc = JsRuntime::module_map(scope);
|
||||||
|
let state = state_rc.borrow_mut();
|
||||||
|
let module_map = module_map_rc.borrow();
|
||||||
|
|
||||||
|
let has_pending_refed_ops = state.pending_ops.len() > state.unrefed_ops.len();
|
||||||
|
let has_pending_dyn_imports = module_map.has_pending_dynamic_imports();
|
||||||
|
let has_pending_dyn_module_evaluation =
|
||||||
|
!state.pending_dyn_mod_evaluate.is_empty();
|
||||||
|
let has_pending_module_evaluation = state.pending_mod_evaluate.is_some();
|
||||||
|
let has_pending_background_tasks = scope.has_pending_background_tasks();
|
||||||
|
let has_tick_scheduled = state.has_tick_scheduled;
|
||||||
|
|
||||||
|
has_pending_refed_ops
|
||||||
|
|| has_pending_dyn_imports
|
||||||
|
|| has_pending_dyn_module_evaluation
|
||||||
|
|| has_pending_module_evaluation
|
||||||
|
|| has_pending_background_tasks
|
||||||
|
|| has_tick_scheduled
|
||||||
|
}
|
||||||
|
|
|
@ -90,14 +90,14 @@ pub struct JsRuntime {
|
||||||
event_loop_middlewares: Vec<Box<OpEventLoopFn>>,
|
event_loop_middlewares: Vec<Box<OpEventLoopFn>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct DynImportModEvaluate {
|
pub(crate) struct DynImportModEvaluate {
|
||||||
load_id: ModuleLoadId,
|
load_id: ModuleLoadId,
|
||||||
module_id: ModuleId,
|
module_id: ModuleId,
|
||||||
promise: v8::Global<v8::Promise>,
|
promise: v8::Global<v8::Promise>,
|
||||||
module: v8::Global<v8::Module>,
|
module: v8::Global<v8::Module>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ModEvaluate {
|
pub(crate) struct ModEvaluate {
|
||||||
promise: v8::Global<v8::Promise>,
|
promise: v8::Global<v8::Promise>,
|
||||||
sender: oneshot::Sender<Result<(), Error>>,
|
sender: oneshot::Sender<Result<(), Error>>,
|
||||||
}
|
}
|
||||||
|
@ -158,8 +158,8 @@ pub(crate) struct JsRuntimeState {
|
||||||
pub(crate) js_wasm_streaming_cb: Option<v8::Global<v8::Function>>,
|
pub(crate) js_wasm_streaming_cb: Option<v8::Global<v8::Function>>,
|
||||||
pub(crate) pending_promise_exceptions:
|
pub(crate) pending_promise_exceptions:
|
||||||
HashMap<v8::Global<v8::Promise>, v8::Global<v8::Value>>,
|
HashMap<v8::Global<v8::Promise>, v8::Global<v8::Value>>,
|
||||||
pending_dyn_mod_evaluate: Vec<DynImportModEvaluate>,
|
pub(crate) pending_dyn_mod_evaluate: Vec<DynImportModEvaluate>,
|
||||||
pending_mod_evaluate: Option<ModEvaluate>,
|
pub(crate) pending_mod_evaluate: Option<ModEvaluate>,
|
||||||
/// A counter used to delay our dynamic import deadlock detection by one spin
|
/// A counter used to delay our dynamic import deadlock detection by one spin
|
||||||
/// of the event loop.
|
/// of the event loop.
|
||||||
dyn_module_evaluate_idle_counter: u32,
|
dyn_module_evaluate_idle_counter: u32,
|
||||||
|
@ -1021,6 +1021,30 @@ Pending dynamic modules:\n".to_string();
|
||||||
|
|
||||||
Poll::Pending
|
Poll::Pending
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn event_loop_has_work(&mut self) -> bool {
|
||||||
|
let state_rc = Self::state(self.v8_isolate());
|
||||||
|
let module_map_rc = Self::module_map(self.v8_isolate());
|
||||||
|
let state = state_rc.borrow_mut();
|
||||||
|
let module_map = module_map_rc.borrow();
|
||||||
|
|
||||||
|
let has_pending_refed_ops =
|
||||||
|
state.pending_ops.len() > state.unrefed_ops.len();
|
||||||
|
let has_pending_dyn_imports = module_map.has_pending_dynamic_imports();
|
||||||
|
let has_pending_dyn_module_evaluation =
|
||||||
|
!state.pending_dyn_mod_evaluate.is_empty();
|
||||||
|
let has_pending_module_evaluation = state.pending_mod_evaluate.is_some();
|
||||||
|
let has_pending_background_tasks =
|
||||||
|
self.v8_isolate().has_pending_background_tasks();
|
||||||
|
let has_tick_scheduled = state.has_tick_scheduled;
|
||||||
|
|
||||||
|
has_pending_refed_ops
|
||||||
|
|| has_pending_dyn_imports
|
||||||
|
|| has_pending_dyn_module_evaluation
|
||||||
|
|| has_pending_module_evaluation
|
||||||
|
|| has_pending_background_tasks
|
||||||
|
|| has_tick_scheduled
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" fn near_heap_limit_callback<F>(
|
extern "C" fn near_heap_limit_callback<F>(
|
||||||
|
|
|
@ -583,6 +583,7 @@ delete Intl.v8BreakIterator;
|
||||||
|
|
||||||
defineEventHandler(window, "error");
|
defineEventHandler(window, "error");
|
||||||
defineEventHandler(window, "load");
|
defineEventHandler(window, "load");
|
||||||
|
defineEventHandler(window, "beforeunload");
|
||||||
defineEventHandler(window, "unload");
|
defineEventHandler(window, "unload");
|
||||||
const isUnloadDispatched = SymbolFor("isUnloadDispatched");
|
const isUnloadDispatched = SymbolFor("isUnloadDispatched");
|
||||||
// Stores the flag for checking whether unload is dispatched or not.
|
// Stores the flag for checking whether unload is dispatched or not.
|
||||||
|
|
|
@ -371,6 +371,24 @@ impl MainWorker {
|
||||||
"dispatchEvent(new Event('unload'))",
|
"dispatchEvent(new Event('unload'))",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Dispatches "beforeunload" event to the JavaScript runtime. Returns a boolean
|
||||||
|
/// indicating if the event was prevented and thus event loop should continue
|
||||||
|
/// running.
|
||||||
|
pub fn dispatch_beforeunload_event(
|
||||||
|
&mut self,
|
||||||
|
script_name: &str,
|
||||||
|
) -> Result<bool, AnyError> {
|
||||||
|
let value = self.js_runtime.execute_script(
|
||||||
|
script_name,
|
||||||
|
// NOTE(@bartlomieju): not using `globalThis` here, because user might delete
|
||||||
|
// it. Instead we're using global `dispatchEvent` function which will
|
||||||
|
// used a saved reference to global scope.
|
||||||
|
"dispatchEvent(new Event('beforeunload', { cancelable: true }));",
|
||||||
|
)?;
|
||||||
|
let local_value = value.open(&mut self.js_runtime.handle_scope());
|
||||||
|
Ok(local_value.is_false())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
Loading…
Reference in a new issue