summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorColin Ihrig <cjihrig@gmail.com>2022-06-28 10:49:30 -0400
committerGitHub <noreply@github.com>2022-06-28 10:49:30 -0400
commit0f6a5c5fc24e8dc9125c5c536c8547a86ca87b15 (patch)
tree41538ee1df06dc80d60e49ed50177f99ba8dc297
parentab11b45d1d2678cfea2217ac72fc24317eef777d (diff)
feat(web): add beforeunload event (#14830)
This commit adds the 'beforeunload' event. Co-authored-by: Bartek IwaƄczuk <biwanczuk@gmail.com>
-rw-r--r--cli/lsp/testing/execution.rs6
-rw-r--r--cli/main.rs38
-rw-r--r--cli/standalone.rs9
-rw-r--r--cli/tests/integration/run_tests.rs6
-rw-r--r--cli/tests/testdata/before_unload.js21
-rw-r--r--cli/tests/testdata/before_unload.js.out8
-rw-r--r--cli/tools/bench.rs6
-rw-r--r--cli/tools/test.rs7
-rw-r--r--core/01_core.js1
-rw-r--r--core/ops_builtin_v8.rs24
-rw-r--r--core/runtime.rs32
-rw-r--r--runtime/js/99_main.js1
-rw-r--r--runtime/worker.rs18
13 files changed, 166 insertions, 11 deletions
diff --git a/cli/lsp/testing/execution.rs b/cli/lsp/testing/execution.rs
index 6b4e947a0..83f74e5ed 100644
--- a/cli/lsp/testing/execution.rs
+++ b/cli/lsp/testing/execution.rs
@@ -226,6 +226,12 @@ async fn test_specifier(
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!())?;
}
diff --git a/cli/main.rs b/cli/main.rs
index e74ed8518..de44add17 100644
--- a/cli/main.rs
+++ b/cli/main.rs
@@ -639,7 +639,13 @@ async fn eval_command(
}
worker.execute_main_module(&main_module).await?;
worker.dispatch_load_event(&located_script_name!())?;
- worker.run_event_loop(false).await?;
+ loop {
+ worker.run_event_loop(false).await?;
+
+ if !worker.dispatch_beforeunload_event(&located_script_name!())? {
+ break;
+ }
+ }
worker.dispatch_unload_event(&located_script_name!())?;
Ok(0)
}
@@ -975,7 +981,12 @@ async fn run_from_stdin(flags: Flags) -> Result<i32, AnyError> {
}
worker.execute_main_module(&main_module).await?;
worker.dispatch_load_event(&located_script_name!())?;
- worker.run_event_loop(false).await?;
+ loop {
+ worker.run_event_loop(false).await?;
+ if !worker.dispatch_beforeunload_event(&located_script_name!())? {
+ break;
+ }
+ }
worker.dispatch_unload_event(&located_script_name!())?;
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.pending_unload = true;
- let result = self.worker.run_event_loop(false).await;
+ let result = loop {
+ let result = self.worker.run_event_loop(false).await;
+ if !self
+ .worker
+ .dispatch_beforeunload_event(&located_script_name!())?
+ {
+ break result;
+ }
+ };
self.pending_unload = false;
if let Err(err) = result {
@@ -1162,9 +1181,16 @@ async fn run_command(
}
worker.dispatch_load_event(&located_script_name!())?;
- worker
- .run_event_loop(maybe_coverage_collector.is_none())
- .await?;
+
+ loop {
+ worker
+ .run_event_loop(maybe_coverage_collector.is_none())
+ .await?;
+ if !worker.dispatch_beforeunload_event(&located_script_name!())? {
+ break;
+ }
+ }
+
worker.dispatch_unload_event(&located_script_name!())?;
if let Some(coverage_collector) = maybe_coverage_collector.as_mut() {
diff --git a/cli/standalone.rs b/cli/standalone.rs
index d66eb7694..50baf70ea 100644
--- a/cli/standalone.rs
+++ b/cli/standalone.rs
@@ -316,7 +316,14 @@ pub async fn run(
);
worker.execute_main_module(main_module).await?;
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!())?;
std::process::exit(0);
}
diff --git a/cli/tests/integration/run_tests.rs b/cli/tests/integration/run_tests.rs
index bd27cd8dd..1cd1db0ef 100644
--- a/cli/tests/integration/run_tests.rs
+++ b/cli/tests/integration/run_tests.rs
@@ -249,6 +249,12 @@ itest!(webstorage_serialization {
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
// localStorage cache based on the origin of the location URL.
#[test]
diff --git a/cli/tests/testdata/before_unload.js b/cli/tests/testdata/before_unload.js
new file mode 100644
index 000000000..2572e512b
--- /dev/null
+++ b/cli/tests/testdata/before_unload.js
@@ -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);
diff --git a/cli/tests/testdata/before_unload.js.out b/cli/tests/testdata/before_unload.js.out
new file mode 100644
index 000000000..f1f2ab49a
--- /dev/null
+++ b/cli/tests/testdata/before_unload.js.out
@@ -0,0 +1,8 @@
+0
+1
+2
+GOT EVENT
+3
+GOT EVENT
+3
+GOT EVENT
diff --git a/cli/tools/bench.rs b/cli/tools/bench.rs
index 3c40b4e95..3a40a4e97 100644
--- a/cli/tools/bench.rs
+++ b/cli/tools/bench.rs
@@ -404,6 +404,12 @@ async fn bench_specifier(
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!())?;
Ok(())
diff --git a/cli/tools/test.rs b/cli/tools/test.rs
index ac146fdbf..71374d94e 100644
--- a/cli/tools/test.rs
+++ b/cli/tools/test.rs
@@ -814,6 +814,13 @@ async fn test_specifier(
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!())?;
if let Some(coverage_collector) = maybe_coverage_collector.as_mut() {
diff --git a/core/01_core.js b/core/01_core.js
index 9337c0231..156300327 100644
--- a/core/01_core.js
+++ b/core/01_core.js
@@ -267,6 +267,7 @@
destructureError: opSync.bind(null, "op_destructure_error"),
terminate: opSync.bind(null, "op_terminate"),
opNames: opSync.bind(null, "op_op_names"),
+ eventLoopHasMoreWork: opSync.bind(null, "op_event_loop_has_more_work"),
});
ObjectAssign(globalThis.__bootstrap, { core });
diff --git a/core/ops_builtin_v8.rs b/core/ops_builtin_v8.rs
index 7f0c58212..4bc80faa5 100644
--- a/core/ops_builtin_v8.rs
+++ b/core/ops_builtin_v8.rs
@@ -47,6 +47,7 @@ pub(crate) fn init_builtins_v8() -> Vec<OpDecl> {
op_op_names::decl(),
op_apply_source_map::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));
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
+}
diff --git a/core/runtime.rs b/core/runtime.rs
index 23fe73013..8b150e4e9 100644
--- a/core/runtime.rs
+++ b/core/runtime.rs
@@ -90,14 +90,14 @@ pub struct JsRuntime {
event_loop_middlewares: Vec<Box<OpEventLoopFn>>,
}
-struct DynImportModEvaluate {
+pub(crate) struct DynImportModEvaluate {
load_id: ModuleLoadId,
module_id: ModuleId,
promise: v8::Global<v8::Promise>,
module: v8::Global<v8::Module>,
}
-struct ModEvaluate {
+pub(crate) struct ModEvaluate {
promise: v8::Global<v8::Promise>,
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) pending_promise_exceptions:
HashMap<v8::Global<v8::Promise>, v8::Global<v8::Value>>,
- pending_dyn_mod_evaluate: Vec<DynImportModEvaluate>,
- pending_mod_evaluate: Option<ModEvaluate>,
+ pub(crate) pending_dyn_mod_evaluate: Vec<DynImportModEvaluate>,
+ pub(crate) pending_mod_evaluate: Option<ModEvaluate>,
/// A counter used to delay our dynamic import deadlock detection by one spin
/// of the event loop.
dyn_module_evaluate_idle_counter: u32,
@@ -1021,6 +1021,30 @@ Pending dynamic modules:\n".to_string();
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>(
diff --git a/runtime/js/99_main.js b/runtime/js/99_main.js
index 1d046d161..c13faa936 100644
--- a/runtime/js/99_main.js
+++ b/runtime/js/99_main.js
@@ -583,6 +583,7 @@ delete Intl.v8BreakIterator;
defineEventHandler(window, "error");
defineEventHandler(window, "load");
+ defineEventHandler(window, "beforeunload");
defineEventHandler(window, "unload");
const isUnloadDispatched = SymbolFor("isUnloadDispatched");
// Stores the flag for checking whether unload is dispatched or not.
diff --git a/runtime/worker.rs b/runtime/worker.rs
index acb50dc30..fd937559f 100644
--- a/runtime/worker.rs
+++ b/runtime/worker.rs
@@ -371,6 +371,24 @@ impl MainWorker {
"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)]