1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-21 15:04:11 -05:00

fix(cli): dispatch unload event on watch drop (#11696)

This commit is contained in:
Casper Beyer 2021-08-25 04:34:09 +08:00 committed by GitHub
parent 85a56e7144
commit a3fd4bb998
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 141 additions and 14 deletions

View file

@ -892,29 +892,81 @@ async fn run_with_watch(flags: Flags, script: String) -> Result<(), AnyError> {
})
};
/// The FileWatcherModuleExecutor provides module execution with safe dispatching of life-cycle events by tracking the
/// state of any pending events and emitting accordingly on drop in the case of a future
/// cancellation.
struct FileWatcherModuleExecutor {
worker: MainWorker,
pending_unload: bool,
}
impl FileWatcherModuleExecutor {
pub fn new(worker: MainWorker) -> FileWatcherModuleExecutor {
FileWatcherModuleExecutor {
worker,
pending_unload: false,
}
}
/// Execute the given main module emitting load and unload events before and after execution
/// respectively.
pub async fn execute(
&mut self,
main_module: &ModuleSpecifier,
) -> Result<(), AnyError> {
self.worker.execute_module(main_module).await?;
self.worker.execute_script(
&located_script_name!(),
"window.dispatchEvent(new Event('load'))",
)?;
self.pending_unload = true;
let result = self.worker.run_event_loop(false).await;
self.pending_unload = false;
if let Err(err) = result {
return Err(err);
}
self.worker.execute_script(
&located_script_name!(),
"window.dispatchEvent(new Event('unload'))",
)?;
Ok(())
}
}
impl Drop for FileWatcherModuleExecutor {
fn drop(&mut self) {
if self.pending_unload {
self
.worker
.execute_script(
&located_script_name!(),
"window.dispatchEvent(new Event('unload'))",
)
.unwrap();
}
}
}
let operation =
|(program_state, main_module): (Arc<ProgramState>, ModuleSpecifier)| {
let flags = flags.clone();
let permissions = Permissions::from_options(&flags.into());
async move {
let main_module = main_module.clone();
let mut worker = create_main_worker(
// We make use an module executor guard to ensure that unload is always fired when an
// operation is called.
let mut executor = FileWatcherModuleExecutor::new(create_main_worker(
&program_state,
main_module.clone(),
permissions,
None,
);
debug!("main_module {}", main_module);
worker.execute_module(&main_module).await?;
worker.execute_script(
&located_script_name!(),
"window.dispatchEvent(new Event('load'))",
)?;
worker.run_event_loop(false).await?;
worker.execute_script(
&located_script_name!(),
"window.dispatchEvent(new Event('unload'))",
)?;
));
executor.execute(&main_module).await?;
Ok(())
}
};

View file

@ -322,6 +322,81 @@ fn run_watch() {
drop(t);
}
#[test]
fn run_watch_load_unload_events() {
let t = TempDir::new().expect("tempdir fail");
let file_to_watch = t.path().join("file_to_watch.js");
std::fs::write(
&file_to_watch,
r#"
setInterval(() => {}, 0);
window.addEventListener("load", () => {
console.log("load");
});
window.addEventListener("unload", () => {
console.log("unload");
});
"#,
)
.expect("error writing file");
let mut child = util::deno_cmd()
.current_dir(util::testdata_path())
.arg("run")
.arg("--watch")
.arg("--unstable")
.arg(&file_to_watch)
.env("NO_COLOR", "1")
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.spawn()
.expect("failed to spawn script");
let stdout = child.stdout.as_mut().unwrap();
let mut stdout_lines =
std::io::BufReader::new(stdout).lines().map(|r| r.unwrap());
let stderr = child.stderr.as_mut().unwrap();
let mut stderr_lines =
std::io::BufReader::new(stderr).lines().map(|r| r.unwrap());
// Wait for the first load event to fire
assert!(stdout_lines.next().unwrap().contains("load"));
// Change content of the file, this time without an interval to keep it alive.
std::fs::write(
&file_to_watch,
r#"
window.addEventListener("load", () => {
console.log("load");
});
window.addEventListener("unload", () => {
console.log("unload");
});
"#,
)
.expect("error writing file");
// Events from the file watcher is "debounced", so we need to wait for the next execution to start
std::thread::sleep(std::time::Duration::from_secs(1));
// Wait for the restart
assert!(stderr_lines.next().unwrap().contains("Restarting"));
// Confirm that the unload event was dispatched from the first run
assert!(stdout_lines.next().unwrap().contains("unload"));
// Followed by the load event of the second run
assert!(stdout_lines.next().unwrap().contains("load"));
// Which is then unloaded as there is nothing keeping it alive.
assert!(stdout_lines.next().unwrap().contains("unload"));
child.kill().unwrap();
drop(t);
}
/// Confirm that the watcher continues to work even if module resolution fails at the *first* attempt
#[test]
fn run_watch_not_exit() {