From 7df835a791d8cbc79de693d6f3499e5c7f347025 Mon Sep 17 00:00:00 2001 From: Nayeem Rahman Date: Sun, 6 Sep 2020 12:39:08 +0100 Subject: [PATCH] feat: Add --location and globalThis.location --- Cargo.lock | 2 +- cli/Cargo.toml | 2 +- cli/flags.rs | 417 +++++++++++-------- cli/installer.rs | 4 + cli/main.rs | 24 +- cli/ops/fetch.rs | 7 +- cli/ops/runtime.rs | 1 + cli/ops/worker_host.rs | 9 +- cli/rt/99_main.js | 23 +- cli/rt/README.md | 2 + cli/tests/065_location.ts | 8 + cli/tests/065_location.ts.out | 22 + cli/tests/066_location_unset.ts | 3 + cli/tests/066_location_unset.ts.out | 4 + cli/tests/067_location_relative_fetch.ts | 2 + cli/tests/067_location_relative_fetch.ts.out | 2 + cli/tests/complex_permissions_test.ts | 4 +- cli/tests/fetch/hello.txt | 1 + cli/tests/integration_tests.rs | 24 ++ cli/tests/subdir/worker_location.ts | 4 + cli/tests/workers_test.ts | 36 ++ cli/web_worker.rs | 2 +- docs/runtime/location_api.md | 76 ++++ docs/runtime/workers.md | 8 +- docs/toc.json | 1 + op_crates/web/09_location.js | 223 ++++++++++ op_crates/web/Cargo.toml | 2 +- op_crates/web/lib.deno_web.d.ts | 74 +++- op_crates/web/lib.rs | 1 + 29 files changed, 787 insertions(+), 201 deletions(-) create mode 100644 cli/tests/065_location.ts create mode 100644 cli/tests/065_location.ts.out create mode 100644 cli/tests/066_location_unset.ts create mode 100644 cli/tests/066_location_unset.ts.out create mode 100644 cli/tests/067_location_relative_fetch.ts create mode 100644 cli/tests/067_location_relative_fetch.ts.out create mode 100644 cli/tests/fetch/hello.txt create mode 100644 cli/tests/subdir/worker_location.ts create mode 100644 docs/runtime/location_api.md create mode 100644 op_crates/web/09_location.js diff --git a/Cargo.lock b/Cargo.lock index 427dc592184b26..d5a8630c0589ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -498,7 +498,7 @@ dependencies = [ [[package]] name = "deno_web" -version = "0.7.1" +version = "0.8.0" dependencies = [ "deno_core", "futures", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 5a969b785fefb6..cd45fd24606511 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -21,7 +21,7 @@ path = "./bench/main.rs" [build-dependencies] deno_core = { path = "../core", version = "0.56.0" } -deno_web = { path = "../op_crates/web", version = "0.7.1" } +deno_web = { path = "../op_crates/web", version = "0.8.0" } [target.'cfg(windows)'.build-dependencies] winres = "0.1.11" diff --git a/cli/flags.rs b/cli/flags.rs index 18fd3b29e50e7f..2e8002bdbc0930 100644 --- a/cli/flags.rs +++ b/cli/flags.rs @@ -9,6 +9,7 @@ use clap::SubCommand; use log::Level; use std::net::SocketAddr; use std::path::PathBuf; +use url::Url; /// Creates vector of strings, Vec macro_rules! svec { @@ -102,6 +103,7 @@ pub struct Flags { pub allow_read: bool, pub allow_run: bool, pub allow_write: bool, + pub location: Option, pub cache_blocklist: Vec, pub ca_file: Option, pub cached_only: bool, @@ -228,18 +230,12 @@ lazy_static! { } /// Main entry point for parsing deno's command line flags. -/// Exits the process on error. -pub fn flags_from_vec(args: Vec) -> Flags { - match flags_from_vec_safe(args) { - Ok(flags) => flags, - Err(err) => err.exit(), - } -} - -/// Same as flags_from_vec but does not exit on error. -pub fn flags_from_vec_safe(args: Vec) -> clap::Result { +pub fn flags_from_vec(args: Vec) -> clap::Result { let app = clap_root(); - let matches = app.get_matches_from_safe(args)?; + let matches = app.get_matches_from_safe(args).map_err(|e| clap::Error { + message: e.message.trim_start_matches("error: ").to_string(), + ..e + })?; let mut flags = Flags::default(); @@ -255,7 +251,7 @@ pub fn flags_from_vec_safe(args: Vec) -> clap::Result { } if let Some(m) = matches.subcommand_matches("run") { - run_parse(&mut flags, m); + run_parse(&mut flags, m)?; } else if let Some(m) = matches.subcommand_matches("fmt") { fmt_parse(&mut flags, m); } else if let Some(m) = matches.subcommand_matches("types") { @@ -265,17 +261,17 @@ pub fn flags_from_vec_safe(args: Vec) -> clap::Result { } else if let Some(m) = matches.subcommand_matches("info") { info_parse(&mut flags, m); } else if let Some(m) = matches.subcommand_matches("eval") { - eval_parse(&mut flags, m); + eval_parse(&mut flags, m)?; } else if let Some(m) = matches.subcommand_matches("repl") { - repl_parse(&mut flags, m); + repl_parse(&mut flags, m)?; } else if let Some(m) = matches.subcommand_matches("bundle") { bundle_parse(&mut flags, m); } else if let Some(m) = matches.subcommand_matches("install") { - install_parse(&mut flags, m); + install_parse(&mut flags, m)?; } else if let Some(m) = matches.subcommand_matches("completions") { completions_parse(&mut flags, m); } else if let Some(m) = matches.subcommand_matches("test") { - test_parse(&mut flags, m); + test_parse(&mut flags, m)?; } else if let Some(m) = matches.subcommand_matches("upgrade") { upgrade_parse(&mut flags, m); } else if let Some(m) = matches.subcommand_matches("doc") { @@ -283,7 +279,7 @@ pub fn flags_from_vec_safe(args: Vec) -> clap::Result { } else if let Some(m) = matches.subcommand_matches("lint") { lint_parse(&mut flags, m); } else { - repl_parse(&mut flags, &matches); + repl_parse(&mut flags, &matches)?; } Ok(flags) @@ -364,9 +360,13 @@ fn fmt_parse(flags: &mut Flags, matches: &clap::ArgMatches) { } } -fn install_parse(flags: &mut Flags, matches: &clap::ArgMatches) { +fn install_parse( + flags: &mut Flags, + matches: &clap::ArgMatches, +) -> clap::Result<()> { permission_args_parse(flags, matches); config_arg_parse(flags, matches); + location_arg_parse(flags, matches)?; ca_file_arg_parse(flags, matches); no_check_arg_parse(flags, matches); unstable_arg_parse(flags, matches); @@ -396,6 +396,7 @@ fn install_parse(flags: &mut Flags, matches: &clap::ArgMatches) { root, force, }; + Ok(()) } fn bundle_parse(flags: &mut Flags, matches: &clap::ArgMatches) { @@ -436,8 +437,12 @@ fn completions_parse(flags: &mut Flags, matches: &clap::ArgMatches) { }; } -fn repl_parse(flags: &mut Flags, matches: &clap::ArgMatches) { +fn repl_parse( + flags: &mut Flags, + matches: &clap::ArgMatches, +) -> clap::Result<()> { v8_flags_arg_parse(flags, matches); + location_arg_parse(flags, matches)?; ca_file_arg_parse(flags, matches); inspect_arg_parse(flags, matches); unstable_arg_parse(flags, matches); @@ -449,10 +454,15 @@ fn repl_parse(flags: &mut Flags, matches: &clap::ArgMatches) { flags.allow_write = true; flags.allow_plugin = true; flags.allow_hrtime = true; + Ok(()) } -fn eval_parse(flags: &mut Flags, matches: &clap::ArgMatches) { +fn eval_parse( + flags: &mut Flags, + matches: &clap::ArgMatches, +) -> clap::Result<()> { v8_flags_arg_parse(flags, matches); + location_arg_parse(flags, matches)?; ca_file_arg_parse(flags, matches); inspect_arg_parse(flags, matches); unstable_arg_parse(flags, matches); @@ -470,7 +480,8 @@ fn eval_parse(flags: &mut Flags, matches: &clap::ArgMatches) { print, code, as_typescript, - } + }; + Ok(()) } fn info_parse(flags: &mut Flags, matches: &clap::ArgMatches) { @@ -513,7 +524,10 @@ fn lock_args_parse(flags: &mut Flags, matches: &clap::ArgMatches) { } // Shared between the run and test subcommands. They both take similar options. -fn run_test_args_parse(flags: &mut Flags, matches: &clap::ArgMatches) { +fn run_test_args_parse( + flags: &mut Flags, + matches: &clap::ArgMatches, +) -> clap::Result<()> { reload_arg_parse(flags, matches); lock_args_parse(flags, matches); importmap_arg_parse(flags, matches); @@ -522,6 +536,7 @@ fn run_test_args_parse(flags: &mut Flags, matches: &clap::ArgMatches) { no_check_arg_parse(flags, matches); no_remote_arg_parse(flags, matches); permission_args_parse(flags, matches); + location_arg_parse(flags, matches)?; ca_file_arg_parse(flags, matches); inspect_arg_parse(flags, matches); unstable_arg_parse(flags, matches); @@ -546,10 +561,14 @@ fn run_test_args_parse(flags: &mut Flags, matches: &clap::ArgMatches) { } } } + Ok(()) } -fn run_parse(flags: &mut Flags, matches: &clap::ArgMatches) { - run_test_args_parse(flags, matches); +fn run_parse( + flags: &mut Flags, + matches: &clap::ArgMatches, +) -> clap::Result<()> { + run_test_args_parse(flags, matches)?; let mut script: Vec = matches .values_of("script_arg") @@ -565,10 +584,14 @@ fn run_parse(flags: &mut Flags, matches: &clap::ArgMatches) { flags.watch = matches.is_present("watch"); flags.subcommand = DenoSubcommand::Run { script }; + Ok(()) } -fn test_parse(flags: &mut Flags, matches: &clap::ArgMatches) { - run_test_args_parse(flags, matches); +fn test_parse( + flags: &mut Flags, + matches: &clap::ArgMatches, +) -> clap::Result<()> { + run_test_args_parse(flags, matches)?; let failfast = matches.is_present("failfast"); let allow_none = matches.is_present("allow_none"); @@ -592,6 +615,7 @@ fn test_parse(flags: &mut Flags, matches: &clap::ArgMatches) { filter, allow_none, }; + Ok(()) } fn upgrade_parse(flags: &mut Flags, matches: &clap::ArgMatches) { @@ -710,6 +734,7 @@ fn repl_subcommand<'a, 'b>() -> App<'a, 'b> { inspect_args(SubCommand::with_name("repl")) .about("Read Eval Print Loop") .arg(v8_flags_arg()) + .arg(location_arg()) .arg(ca_file_arg()) .arg(unstable_arg()) } @@ -742,6 +767,7 @@ fn install_subcommand<'a, 'b>() -> App<'a, 'b> { .help("Forcefully overwrite existing installation") .takes_value(false)) .arg(no_check_arg()) + .arg(location_arg()) .arg(ca_file_arg()) .arg(unstable_arg()) .arg(config_arg()) @@ -816,6 +842,7 @@ fn completions_subcommand<'a, 'b>() -> App<'a, 'b> { fn eval_subcommand<'a, 'b>() -> App<'a, 'b> { inspect_args(SubCommand::with_name("eval")) + .arg(location_arg()) .arg(ca_file_arg()) .arg(unstable_arg()) .about("Eval script") @@ -1138,6 +1165,7 @@ fn run_test_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { .arg(no_check_arg()) .arg(no_remote_arg()) .arg(v8_flags_arg()) + .arg(location_arg()) .arg(ca_file_arg()) .arg( Arg::with_name("cached-only") @@ -1279,6 +1307,40 @@ fn unstable_arg<'a, 'b>() -> Arg<'a, 'b> { .help("Enable unstable APIs") } +fn location_arg_parse( + flags: &mut Flags, + matches: &clap::ArgMatches, +) -> clap::Result<()> { + flags.location = match matches.value_of("location") { + None => None, + Some(href) => Some( + Url::parse(href) + .map_err(|e| e.to_string()) + .and_then(|url| { + if !["http", "https", "file"].contains(&url.scheme()) { + Err("Expected protocol \"http\", \"https\" or \"file\"".to_string()) + } else { + Ok(url) + } + }) + .map_err(|s| clap::Error { + kind: clap::ErrorKind::InvalidValue, + message: format!("Invalid location href \"{}\": {}", href, s), + info: None, + })?, + ), + }; + Ok(()) +} + +fn location_arg<'a, 'b>() -> Arg<'a, 'b> { + Arg::with_name("location") + .long("location") + .takes_value(true) + .value_name("HREF") + .help("Value of 'globalThis.location' used by certain web APIs") +} + fn unstable_arg_parse(flags: &mut Flags, matches: &clap::ArgMatches) { if matches.is_present("unstable") { flags.unstable = true; @@ -1507,7 +1569,6 @@ fn permission_args_parse(flags: &mut Flags, matches: &clap::ArgMatches) { // TODO(ry) move this to utility module and add test. /// Strips fragment part of URL. Panics on bad URL. pub fn resolve_urls(urls: Vec) -> Vec { - use url::Url; let mut out: Vec = vec![]; for urlstr in urls.iter() { use std::str::FromStr; @@ -1532,8 +1593,7 @@ mod tests { #[test] fn upgrade() { - let r = - flags_from_vec_safe(svec!["deno", "upgrade", "--dry-run", "--force"]); + let r = flags_from_vec(svec!["deno", "upgrade", "--dry-run", "--force"]); let flags = r.unwrap(); assert_eq!( flags, @@ -1552,15 +1612,15 @@ mod tests { #[test] fn version() { - let r = flags_from_vec_safe(svec!["deno", "--version"]); + let r = flags_from_vec(svec!["deno", "--version"]); assert_eq!(r.unwrap_err().kind, clap::ErrorKind::VersionDisplayed); - let r = flags_from_vec_safe(svec!["deno", "-V"]); + let r = flags_from_vec(svec!["deno", "-V"]); assert_eq!(r.unwrap_err().kind, clap::ErrorKind::VersionDisplayed); } #[test] fn run_reload() { - let r = flags_from_vec_safe(svec!["deno", "run", "-r", "script.ts"]); + let r = flags_from_vec(svec!["deno", "run", "-r", "script.ts"]); let flags = r.unwrap(); assert_eq!( flags, @@ -1576,7 +1636,7 @@ mod tests { #[test] fn run_watch() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "run", "--unstable", @@ -1599,13 +1659,8 @@ mod tests { #[test] fn run_reload_allow_write() { - let r = flags_from_vec_safe(svec![ - "deno", - "run", - "-r", - "--allow-write", - "script.ts" - ]); + let r = + flags_from_vec(svec!["deno", "run", "-r", "--allow-write", "script.ts"]); assert_eq!( r.unwrap(), Flags { @@ -1621,12 +1676,8 @@ mod tests { #[test] fn run_v8_flags() { - let r = flags_from_vec_safe(svec![ - "deno", - "run", - "--v8-flags=--help", - "script.ts" - ]); + let r = + flags_from_vec(svec!["deno", "run", "--v8-flags=--help", "script.ts"]); assert_eq!( r.unwrap(), Flags { @@ -1638,7 +1689,7 @@ mod tests { } ); - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "run", "--v8-flags=--expose-gc,--gc-stats=1", @@ -1658,7 +1709,7 @@ mod tests { #[test] fn script_args() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "run", "--allow-net", @@ -1681,7 +1732,7 @@ mod tests { #[test] fn allow_all() { - let r = flags_from_vec_safe(svec!["deno", "run", "--allow-all", "gist.ts"]); + let r = flags_from_vec(svec!["deno", "run", "--allow-all", "gist.ts"]); assert_eq!( r.unwrap(), Flags { @@ -1702,8 +1753,7 @@ mod tests { #[test] fn allow_read() { - let r = - flags_from_vec_safe(svec!["deno", "run", "--allow-read", "gist.ts"]); + let r = flags_from_vec(svec!["deno", "run", "--allow-read", "gist.ts"]); assert_eq!( r.unwrap(), Flags { @@ -1718,8 +1768,7 @@ mod tests { #[test] fn allow_hrtime() { - let r = - flags_from_vec_safe(svec!["deno", "run", "--allow-hrtime", "gist.ts"]); + let r = flags_from_vec(svec!["deno", "run", "--allow-hrtime", "gist.ts"]); assert_eq!( r.unwrap(), Flags { @@ -1737,7 +1786,7 @@ mod tests { // notice that flags passed after double dash will not // be parsed to Flags but instead forwarded to // script args as Deno.args - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "run", "--allow-write", @@ -1761,8 +1810,7 @@ mod tests { #[test] fn fmt() { - let r = - flags_from_vec_safe(svec!["deno", "fmt", "script_1.ts", "script_2.ts"]); + let r = flags_from_vec(svec!["deno", "fmt", "script_1.ts", "script_2.ts"]); assert_eq!( r.unwrap(), Flags { @@ -1775,7 +1823,7 @@ mod tests { } ); - let r = flags_from_vec_safe(svec!["deno", "fmt", "--check"]); + let r = flags_from_vec(svec!["deno", "fmt", "--check"]); assert_eq!( r.unwrap(), Flags { @@ -1788,7 +1836,7 @@ mod tests { } ); - let r = flags_from_vec_safe(svec!["deno", "fmt"]); + let r = flags_from_vec(svec!["deno", "fmt"]); assert_eq!( r.unwrap(), Flags { @@ -1804,7 +1852,7 @@ mod tests { #[test] fn lint() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "lint", "--unstable", @@ -1825,7 +1873,7 @@ mod tests { } ); - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "lint", "--unstable", @@ -1845,7 +1893,7 @@ mod tests { } ); - let r = flags_from_vec_safe(svec!["deno", "lint", "--unstable", "--rules"]); + let r = flags_from_vec(svec!["deno", "lint", "--unstable", "--rules"]); assert_eq!( r.unwrap(), Flags { @@ -1860,7 +1908,7 @@ mod tests { } ); - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "lint", "--unstable", @@ -1884,7 +1932,7 @@ mod tests { #[test] fn types() { - let r = flags_from_vec_safe(svec!["deno", "types"]); + let r = flags_from_vec(svec!["deno", "types"]); assert_eq!( r.unwrap(), Flags { @@ -1896,7 +1944,7 @@ mod tests { #[test] fn types_unstable() { - let r = flags_from_vec_safe(svec!["deno", "types", "--unstable"]); + let r = flags_from_vec(svec!["deno", "types", "--unstable"]); assert_eq!( r.unwrap(), Flags { @@ -1909,7 +1957,7 @@ mod tests { #[test] fn cache() { - let r = flags_from_vec_safe(svec!["deno", "cache", "script.ts"]); + let r = flags_from_vec(svec!["deno", "cache", "script.ts"]); assert_eq!( r.unwrap(), Flags { @@ -1923,8 +1971,7 @@ mod tests { #[test] fn cache_unstable() { - let r = - flags_from_vec_safe(svec!["deno", "cache", "--unstable", "script.ts"]); + let r = flags_from_vec(svec!["deno", "cache", "--unstable", "script.ts"]); assert_eq!( r.unwrap(), Flags { @@ -1939,7 +1986,7 @@ mod tests { #[test] fn info() { - let r = flags_from_vec_safe(svec!["deno", "info", "script.ts"]); + let r = flags_from_vec(svec!["deno", "info", "script.ts"]); assert_eq!( r.unwrap(), Flags { @@ -1951,7 +1998,7 @@ mod tests { } ); - let r = flags_from_vec_safe(svec!["deno", "info", "--reload", "script.ts"]); + let r = flags_from_vec(svec!["deno", "info", "--reload", "script.ts"]); assert_eq!( r.unwrap(), Flags { @@ -1964,7 +2011,7 @@ mod tests { } ); - let r = flags_from_vec_safe(svec!["deno", "info", "--json", "script.ts"]); + let r = flags_from_vec(svec!["deno", "info", "--json", "script.ts"]); assert_eq!( r.unwrap(), Flags { @@ -1976,7 +2023,7 @@ mod tests { } ); - let r = flags_from_vec_safe(svec!["deno", "info"]); + let r = flags_from_vec(svec!["deno", "info"]); assert_eq!( r.unwrap(), Flags { @@ -1988,7 +2035,7 @@ mod tests { } ); - let r = flags_from_vec_safe(svec!["deno", "info", "--json"]); + let r = flags_from_vec(svec!["deno", "info", "--json"]); assert_eq!( r.unwrap(), Flags { @@ -2003,13 +2050,8 @@ mod tests { #[test] fn tsconfig() { - let r = flags_from_vec_safe(svec![ - "deno", - "run", - "-c", - "tsconfig.json", - "script.ts" - ]); + let r = + flags_from_vec(svec!["deno", "run", "-c", "tsconfig.json", "script.ts"]); assert_eq!( r.unwrap(), Flags { @@ -2024,8 +2066,7 @@ mod tests { #[test] fn eval() { - let r = - flags_from_vec_safe(svec!["deno", "eval", "'console.log(\"hello\")'"]); + let r = flags_from_vec(svec!["deno", "eval", "'console.log(\"hello\")'"]); assert_eq!( r.unwrap(), Flags { @@ -2048,7 +2089,7 @@ mod tests { #[test] fn eval_p() { - let r = flags_from_vec_safe(svec!["deno", "eval", "-p", "1+2"]); + let r = flags_from_vec(svec!["deno", "eval", "-p", "1+2"]); assert_eq!( r.unwrap(), Flags { @@ -2071,7 +2112,7 @@ mod tests { #[test] fn eval_unstable() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "eval", "--unstable", @@ -2100,12 +2141,8 @@ mod tests { #[test] fn eval_typescript() { - let r = flags_from_vec_safe(svec![ - "deno", - "eval", - "-T", - "'console.log(\"hello\")'" - ]); + let r = + flags_from_vec(svec!["deno", "eval", "-T", "'console.log(\"hello\")'"]); assert_eq!( r.unwrap(), Flags { @@ -2128,8 +2165,7 @@ mod tests { #[test] fn eval_with_v8_flags() { - let r = - flags_from_vec_safe(svec!["deno", "eval", "--v8-flags=--help", "42"]); + let r = flags_from_vec(svec!["deno", "eval", "--v8-flags=--help", "42"]); assert_eq!( r.unwrap(), Flags { @@ -2151,9 +2187,39 @@ mod tests { ); } + #[test] + fn eval_with_location() { + let r = flags_from_vec(svec![ + "deno", + "eval", + "--location", + "https:a", + "'console.log(\"hello\")'" + ]); + assert_eq!( + r.unwrap(), + Flags { + subcommand: DenoSubcommand::Eval { + print: false, + code: "'console.log(\"hello\")'".to_string(), + as_typescript: false, + }, + location: Some(Url::parse("https://a/").unwrap()), + allow_net: true, + allow_env: true, + allow_run: true, + allow_read: true, + allow_write: true, + allow_plugin: true, + allow_hrtime: true, + ..Flags::default() + } + ); + } + #[test] fn repl() { - let r = flags_from_vec_safe(svec!["deno"]); + let r = flags_from_vec(svec!["deno"]); assert_eq!( r.unwrap(), Flags { @@ -2172,7 +2238,7 @@ mod tests { #[test] fn repl_unstable() { - let r = flags_from_vec_safe(svec!["deno", "repl", "--unstable"]); + let r = flags_from_vec(svec!["deno", "repl", "--unstable"]); assert_eq!( r.unwrap(), Flags { @@ -2190,12 +2256,32 @@ mod tests { ); } + #[test] + fn repl_with_location() { + let r = flags_from_vec(svec!["deno", "repl", "--location", "https:a"]); + assert_eq!( + r.unwrap(), + Flags { + location: Some(Url::parse("https://a/").unwrap()), + subcommand: DenoSubcommand::Repl, + allow_net: true, + allow_env: true, + allow_run: true, + allow_read: true, + allow_write: true, + allow_plugin: true, + allow_hrtime: true, + ..Flags::default() + } + ); + } + #[test] fn allow_read_allowlist() { use tempfile::TempDir; let temp_dir = TempDir::new().expect("tempdir fail").path().to_path_buf(); - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "run", format!("--allow-read=.,{}", temp_dir.to_str().unwrap()), @@ -2219,7 +2305,7 @@ mod tests { use tempfile::TempDir; let temp_dir = TempDir::new().expect("tempdir fail").path().to_path_buf(); - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "run", format!("--allow-write=.,{}", temp_dir.to_str().unwrap()), @@ -2240,7 +2326,7 @@ mod tests { #[test] fn allow_net_allowlist() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "run", "--allow-net=127.0.0.1", @@ -2261,7 +2347,7 @@ mod tests { #[test] fn bundle() { - let r = flags_from_vec_safe(svec!["deno", "bundle", "source.ts"]); + let r = flags_from_vec(svec!["deno", "bundle", "source.ts"]); assert_eq!( r.unwrap(), Flags { @@ -2276,8 +2362,7 @@ mod tests { #[test] fn bundle_unstable() { - let r = - flags_from_vec_safe(svec!["deno", "bundle", "--unstable", "source.ts"]); + let r = flags_from_vec(svec!["deno", "bundle", "--unstable", "source.ts"]); assert_eq!( r.unwrap(), Flags { @@ -2293,7 +2378,7 @@ mod tests { #[test] fn bundle_with_config() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "bundle", "--config", @@ -2317,8 +2402,7 @@ mod tests { #[test] fn bundle_with_output() { - let r = - flags_from_vec_safe(svec!["deno", "bundle", "source.ts", "bundle.js"]); + let r = flags_from_vec(svec!["deno", "bundle", "source.ts", "bundle.js"]); assert_eq!( r.unwrap(), Flags { @@ -2334,7 +2418,7 @@ mod tests { #[test] fn bundle_with_lock() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "bundle", "--lock-write", @@ -2357,8 +2441,7 @@ mod tests { #[test] fn bundle_with_reload() { - let r = - flags_from_vec_safe(svec!["deno", "bundle", "--reload", "source.ts"]); + let r = flags_from_vec(svec!["deno", "bundle", "--reload", "source.ts"]); assert_eq!( r.unwrap(), Flags { @@ -2374,7 +2457,7 @@ mod tests { #[test] fn run_importmap() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "run", "--importmap=importmap.json", @@ -2394,7 +2477,7 @@ mod tests { #[test] fn cache_importmap() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "cache", "--importmap=importmap.json", @@ -2415,7 +2498,7 @@ mod tests { #[test] fn cache_multiple() { let r = - flags_from_vec_safe(svec!["deno", "cache", "script.ts", "script_two.ts"]); + flags_from_vec(svec!["deno", "cache", "script.ts", "script_two.ts"]); assert_eq!( r.unwrap(), Flags { @@ -2429,8 +2512,7 @@ mod tests { #[test] fn run_seed() { - let r = - flags_from_vec_safe(svec!["deno", "run", "--seed", "250", "script.ts"]); + let r = flags_from_vec(svec!["deno", "run", "--seed", "250", "script.ts"]); assert_eq!( r.unwrap(), Flags { @@ -2446,7 +2528,7 @@ mod tests { #[test] fn run_seed_with_v8_flags() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "run", "--seed", @@ -2469,7 +2551,7 @@ mod tests { #[test] fn install() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "install", "https://deno.land/std/examples/colors.ts" @@ -2491,7 +2573,7 @@ mod tests { #[test] fn install_unstable() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "install", "--unstable", @@ -2515,9 +2597,11 @@ mod tests { #[test] fn install_with_args() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "install", + "--location", + "https:a", "--allow-net", "--allow-read", "-n", @@ -2534,6 +2618,7 @@ mod tests { root: None, force: false, }, + location: Some(Url::parse("https://a/").unwrap()), allow_net: true, allow_read: true, ..Flags::default() @@ -2543,7 +2628,7 @@ mod tests { #[test] fn install_with_config() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "install", "--config", @@ -2569,7 +2654,7 @@ mod tests { #[test] fn install_with_args_and_dir_and_force() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "install", "--root", @@ -2602,12 +2687,8 @@ mod tests { #[test] fn log_level() { - let r = flags_from_vec_safe(svec![ - "deno", - "run", - "--log-level=debug", - "script.ts" - ]); + let r = + flags_from_vec(svec!["deno", "run", "--log-level=debug", "script.ts"]); assert_eq!( r.unwrap(), Flags { @@ -2622,7 +2703,7 @@ mod tests { #[test] fn quiet() { - let r = flags_from_vec_safe(svec!["deno", "run", "-q", "script.ts"]); + let r = flags_from_vec(svec!["deno", "run", "-q", "script.ts"]); assert_eq!( r.unwrap(), Flags { @@ -2637,7 +2718,7 @@ mod tests { #[test] fn completions() { - let r = flags_from_vec_safe(svec!["deno", "completions", "bash"]).unwrap(); + let r = flags_from_vec(svec!["deno", "completions", "bash"]).unwrap(); match r.subcommand { DenoSubcommand::Completions { buf } => assert!(!buf.is_empty()), @@ -2647,7 +2728,7 @@ mod tests { #[test] fn run_with_args() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "run", "script.ts", @@ -2664,9 +2745,11 @@ mod tests { ..Flags::default() } ); - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "run", + "--location", + "https:a", "--allow-read", "script.ts", "--allow-net", @@ -2681,14 +2764,14 @@ mod tests { subcommand: DenoSubcommand::Run { script: "script.ts".to_string(), }, + location: Some(Url::parse("https://a/").unwrap()), allow_read: true, argv: svec!["--allow-net", "-r", "--help", "--foo", "bar"], ..Flags::default() } ); - let r = - flags_from_vec_safe(svec!["deno", "run", "script.ts", "foo", "bar"]); + let r = flags_from_vec(svec!["deno", "run", "script.ts", "foo", "bar"]); assert_eq!( r.unwrap(), Flags { @@ -2699,7 +2782,7 @@ mod tests { ..Flags::default() } ); - let r = flags_from_vec_safe(svec!["deno", "run", "script.ts", "-"]); + let r = flags_from_vec(svec!["deno", "run", "script.ts", "-"]); assert_eq!( r.unwrap(), Flags { @@ -2712,7 +2795,7 @@ mod tests { ); let r = - flags_from_vec_safe(svec!["deno", "run", "script.ts", "-", "foo", "bar"]); + flags_from_vec(svec!["deno", "run", "script.ts", "-", "foo", "bar"]); assert_eq!( r.unwrap(), Flags { @@ -2727,8 +2810,7 @@ mod tests { #[test] fn no_check() { - let r = - flags_from_vec_safe(svec!["deno", "run", "--no-check", "script.ts"]); + let r = flags_from_vec(svec!["deno", "run", "--no-check", "script.ts"]); assert_eq!( r.unwrap(), Flags { @@ -2743,8 +2825,7 @@ mod tests { #[test] fn no_remote() { - let r = - flags_from_vec_safe(svec!["deno", "run", "--no-remote", "script.ts"]); + let r = flags_from_vec(svec!["deno", "run", "--no-remote", "script.ts"]); assert_eq!( r.unwrap(), Flags { @@ -2759,8 +2840,7 @@ mod tests { #[test] fn cached_only() { - let r = - flags_from_vec_safe(svec!["deno", "run", "--cached-only", "script.ts"]); + let r = flags_from_vec(svec!["deno", "run", "--cached-only", "script.ts"]); assert_eq!( r.unwrap(), Flags { @@ -2775,7 +2855,7 @@ mod tests { #[test] fn allow_net_allowlist_with_ports() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "run", "--allow-net=deno.land,:8000,:4545", @@ -2803,7 +2883,7 @@ mod tests { #[test] fn allow_net_allowlist_with_ipv6_address() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "run", "--allow-net=deno.land,deno.land:80,::,127.0.0.1,[::1],1.2.3.4:5678,:5678,[::1]:8080", @@ -2834,7 +2914,7 @@ mod tests { #[test] fn lock_write() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "run", "--lock-write", @@ -2855,10 +2935,12 @@ mod tests { } #[test] - fn test_with_allow_net() { - let r = flags_from_vec_safe(svec![ + fn test_with_args() { + let r = flags_from_vec(svec![ "deno", "test", + "--location", + "https:a", "--allow-net", "--allow-none", "dir1/", @@ -2874,6 +2956,7 @@ mod tests { quiet: false, include: Some(svec!["dir1/", "dir2/"]), }, + location: Some(Url::parse("https://a/").unwrap()), allow_net: true, ..Flags::default() } @@ -2882,7 +2965,7 @@ mod tests { #[test] fn test_filter() { - let r = flags_from_vec_safe(svec!["deno", "test", "--filter=foo", "dir1"]); + let r = flags_from_vec(svec!["deno", "test", "--filter=foo", "dir1"]); assert_eq!( r.unwrap(), Flags { @@ -2900,8 +2983,7 @@ mod tests { #[test] fn test_filter_leading_hyphen() { - let r = - flags_from_vec_safe(svec!["deno", "test", "--filter", "- foo", "dir1"]); + let r = flags_from_vec(svec!["deno", "test", "--filter", "- foo", "dir1"]); assert_eq!( r.unwrap(), Flags { @@ -2919,7 +3001,7 @@ mod tests { #[test] fn run_with_cafile() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "run", "--cert", @@ -2940,7 +3022,7 @@ mod tests { #[test] fn bundle_with_cafile() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "bundle", "--cert", @@ -2962,7 +3044,7 @@ mod tests { #[test] fn eval_with_cafile() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "eval", "--cert", @@ -2992,8 +3074,7 @@ mod tests { #[test] fn upgrade_with_ca_file() { - let r = - flags_from_vec_safe(svec!["deno", "upgrade", "--cert", "example.crt"]); + let r = flags_from_vec(svec!["deno", "upgrade", "--cert", "example.crt"]); assert_eq!( r.unwrap(), Flags { @@ -3012,12 +3093,8 @@ mod tests { #[test] fn eval_with_inspect() { - let r = flags_from_vec_safe(svec![ - "deno", - "eval", - "--inspect", - "const foo = 'bar'" - ]); + let r = + flags_from_vec(svec!["deno", "eval", "--inspect", "const foo = 'bar'"]); assert_eq!( r.unwrap(), Flags { @@ -3041,7 +3118,7 @@ mod tests { #[test] fn cache_with_cafile() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "cache", "--cert", @@ -3063,7 +3140,7 @@ mod tests { #[test] fn info_with_cafile() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "info", "--cert", @@ -3085,7 +3162,7 @@ mod tests { #[test] fn install_with_cafile() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "install", "--cert", @@ -3112,7 +3189,7 @@ mod tests { #[test] fn repl_with_cafile() { - let r = flags_from_vec_safe(svec!["deno", "repl", "--cert", "example.crt"]); + let r = flags_from_vec(svec!["deno", "repl", "--cert", "example.crt"]); assert_eq!( r.unwrap(), Flags { @@ -3132,7 +3209,7 @@ mod tests { #[test] fn repl_with_inspect() { - let r = flags_from_vec_safe(svec!["deno", "repl", "--inspect"]); + let r = flags_from_vec(svec!["deno", "repl", "--inspect"]); assert_eq!( r.unwrap(), Flags { @@ -3152,8 +3229,7 @@ mod tests { #[test] fn doc() { - let r = - flags_from_vec_safe(svec!["deno", "doc", "--json", "path/to/module.ts"]); + let r = flags_from_vec(svec!["deno", "doc", "--json", "path/to/module.ts"]); assert_eq!( r.unwrap(), Flags { @@ -3167,7 +3243,7 @@ mod tests { } ); - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "doc", "path/to/module.ts", @@ -3186,7 +3262,7 @@ mod tests { } ); - let r = flags_from_vec_safe(svec!["deno", "doc"]); + let r = flags_from_vec(svec!["deno", "doc"]); assert_eq!( r.unwrap(), Flags { @@ -3200,8 +3276,7 @@ mod tests { } ); - let r = - flags_from_vec_safe(svec!["deno", "doc", "--builtin", "Deno.Listener"]); + let r = flags_from_vec(svec!["deno", "doc", "--builtin", "Deno.Listener"]); assert_eq!( r.unwrap(), Flags { @@ -3215,12 +3290,8 @@ mod tests { } ); - let r = flags_from_vec_safe(svec![ - "deno", - "doc", - "--private", - "path/to/module.js" - ]); + let r = + flags_from_vec(svec!["deno", "doc", "--private", "path/to/module.js"]); assert_eq!( r.unwrap(), Flags { @@ -3237,7 +3308,7 @@ mod tests { #[test] fn inspect_default_host() { - let r = flags_from_vec_safe(svec!["deno", "run", "--inspect", "foo.js"]); + let r = flags_from_vec(svec!["deno", "run", "--inspect", "foo.js"]); assert_eq!( r.unwrap(), Flags { diff --git a/cli/installer.rs b/cli/installer.rs index ac51d75f7ed913..a7dae2047816de 100644 --- a/cli/installer.rs +++ b/cli/installer.rs @@ -193,6 +193,10 @@ pub async fn install( let mut executable_args = vec!["run".to_string()]; executable_args.extend_from_slice(&flags.to_permission_args()); + if let Some(url) = flags.location.as_ref() { + executable_args.push("--location".to_string()); + executable_args.push(url.to_string()); + } if let Some(ca_file) = flags.ca_file.clone() { executable_args.push("--cert".to_string()); executable_args.push(ca_file) diff --git a/cli/main.rs b/cli/main.rs index 0d55ea1dfa4410..034e6ca9a7e8ba 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -585,12 +585,27 @@ async fn test_command( worker.execute("window.dispatchEvent(new Event('unload'))") } +fn unwrap_or_exit(result: Result) -> T { + match result { + Ok(value) => value, + Err(error) => { + let msg = format!( + "{}: {}", + colors::red_bold("error"), + error.to_string().trim() + ); + eprintln!("{}", msg); + std::process::exit(1); + } + } +} + pub fn main() { #[cfg(windows)] colors::enable_ansi(); // For Windows 10 let args: Vec = env::args().collect(); - let flags = flags::flags_from_vec(args); + let flags = unwrap_or_exit(flags::flags_from_vec(args).map_err(ErrBox::from)); if let Some(ref v8_flags) = flags.v8_flags { let v8_flags_includes_help = v8_flags @@ -723,10 +738,5 @@ pub fn main() { _ => unreachable!(), }; - let result = tokio_util::run_basic(fut); - if let Err(err) = result { - let msg = format!("{}: {}", colors::red_bold("error"), err.to_string(),); - eprintln!("{}", msg); - std::process::exit(1); - } + unwrap_or_exit(tokio_util::run_basic(fut)); } diff --git a/cli/ops/fetch.rs b/cli/ops/fetch.rs index 44031e2aca0656..20ce74008a107c 100644 --- a/cli/ops/fetch.rs +++ b/cli/ops/fetch.rs @@ -39,6 +39,8 @@ async fn op_fetch( let args: FetchArgs = serde_json::from_value(args)?; let url = args.url; + let cli_state = super::cli_state2(&state); + let client = if let Some(rid) = args.client_rid { let state = state.borrow(); let r = state @@ -47,7 +49,6 @@ async fn op_fetch( .ok_or_else(ErrBox::bad_resource_id)?; r.client.clone() } else { - let cli_state = super::cli_state2(&state); let client_ref = cli_state.http_client.borrow(); client_ref.clone() }; @@ -57,7 +58,9 @@ async fn op_fetch( None => Method::GET, }; - let url_ = url::Url::parse(&url)?; + let url_ = url::Url::options() + .base_url(cli_state.global_state.flags.location.as_ref()) + .parse(&url)?; // Check scheme before asking for net permission let scheme = url_.scheme(); diff --git a/cli/ops/runtime.rs b/cli/ops/runtime.rs index 396a6951ce8cab..263544cc0d3e85 100644 --- a/cli/ops/runtime.rs +++ b/cli/ops/runtime.rs @@ -29,6 +29,7 @@ fn op_start( "cwd": &env::current_dir().unwrap(), "debugFlag": gs.flags.log_level.map_or(false, |l| l == log::Level::Debug), "denoVersion": version::DENO, + "location": gs.flags.location.as_ref().map(|url| url.as_str()), "noColor": !colors::use_color(), "pid": std::process::id(), "ppid": ppid(), diff --git a/cli/ops/worker_host.rs b/cli/ops/worker_host.rs index 6a100b19860115..1f9c6d1878d27b 100644 --- a/cli/ops/worker_host.rs +++ b/cli/ops/worker_host.rs @@ -67,8 +67,8 @@ fn create_web_worker( // Instead of using name for log we use `worker-${id}` because // WebWorkers can have empty string as name. let script = format!( - "bootstrap.workerRuntime(\"{}\", {}, \"worker-{}\")", - name, worker.has_deno_namespace, worker_id + "bootstrap.workerRuntime(\"{}\", \"{}\", {}, \"worker-{}\")", + name, cli_state.main_module, worker.has_deno_namespace, worker_id ); worker.execute(&script)?; @@ -192,12 +192,15 @@ fn op_create_worker( if use_deno_namespace { cli_state.check_unstable("Worker.deno"); } + let module_specifier = match cli_state.global_state.flags.location.as_ref() { + None => ModuleSpecifier::resolve_url(&specifier)?, + Some(url) => ModuleSpecifier::resolve_import(&specifier, url.as_str())?, + }; let global_state = cli_state.global_state.clone(); let permissions = cli_state.permissions.borrow().clone(); let worker_id = cli_state.next_worker_id.get(); cli_state.next_worker_id.set(worker_id + 1); - let module_specifier = ModuleSpecifier::resolve_url(&specifier)?; let worker_name = args_name.unwrap_or_else(|| "".to_string()); let (join_handle, worker_handle) = run_worker_thread( diff --git a/cli/rt/99_main.js b/cli/rt/99_main.js index ede47980c2de4d..d3863486362447 100644 --- a/cli/rt/99_main.js +++ b/cli/rt/99_main.js @@ -7,6 +7,7 @@ delete Object.prototype.__proto__; const core = Deno.core; const util = window.__bootstrap.util; const eventTarget = window.__bootstrap.eventTarget; + const location = window.__bootstrap.location; const dispatchJson = window.__bootstrap.dispatchJson; const dispatchMinimal = window.__bootstrap.dispatchMinimal; const build = window.__bootstrap.build; @@ -219,6 +220,8 @@ delete Object.prototype.__proto__; // Other properties shared between WindowScope and WorkerGlobalScope const windowOrWorkerGlobalScopeProperties = { + Location: location.locationConstructorDescriptor, + location: location.locationDescriptor, console: util.writable(new Console(core.print)), Blob: util.nonEnumerable(blob.Blob), ByteLengthQueuingStrategy: util.nonEnumerable( @@ -320,9 +323,20 @@ delete Object.prototype.__proto__; } }); - const { args, cwd, noColor, pid, ppid, repl, unstableFlag } = - runtimeStart(); - + const { + args, + cwd, + location: locationHref, + noColor, + pid, + ppid, + repl, + unstableFlag, + } = runtimeStart(); + + if (locationHref != null) { + location.setLocationHref(locationHref); + } registerErrors(); const finalDenoNs = { @@ -359,7 +373,7 @@ delete Object.prototype.__proto__; } } - function bootstrapWorkerRuntime(name, useDenoNamespace, internalName) { + function bootstrapWorkerRuntime(name, url, useDenoNamespace, internalName) { if (hasBootstrapped) { throw new Error("Worker runtime already bootstrapped"); } @@ -378,6 +392,7 @@ delete Object.prototype.__proto__; internalName ?? name, ); + location.setLocationHref(url); registerErrors(); const finalDenoNs = { diff --git a/cli/rt/README.md b/cli/rt/README.md index f73163df21e395..9483afc6758538 100644 --- a/cli/rt/README.md +++ b/cli/rt/README.md @@ -38,6 +38,8 @@ Some Web APIs are using ops under the hood, eg. `console`, `performance`. [Body](https://developer.mozilla.org/en-US/docs/Web/API/Body) and [Headers](https://developer.mozilla.org/en-US/docs/Web/API/Headers): modern Promise-based HTTP Request API +- [location](https://developer.mozilla.org/en-US/docs/Web/API/Window/location) + and [Location](https://developer.mozilla.org/en-US/docs/Web/API/Location) - [FormData](https://developer.mozilla.org/en-US/docs/Web/API/FormData): access to a `multipart/form-data` serialization - [Performance](https://developer.mozilla.org/en-US/docs/Web/API/Performance): diff --git a/cli/tests/065_location.ts b/cli/tests/065_location.ts new file mode 100644 index 00000000000000..27795402c00ac1 --- /dev/null +++ b/cli/tests/065_location.ts @@ -0,0 +1,8 @@ +console.log(Location); +console.log(Location.prototype); +console.log(location); +try { + location.hostname = "bar"; +} catch (error) { + console.log(error); +} diff --git a/cli/tests/065_location.ts.out b/cli/tests/065_location.ts.out new file mode 100644 index 00000000000000..566a478503bb5f --- /dev/null +++ b/cli/tests/065_location.ts.out @@ -0,0 +1,22 @@ +[WILDCARD][Function: Location] +Location { Symbol(Symbol.toStringTag): "Location" } +Location { + hash: "#bat", + host: "foo", + hostname: "foo", + href: "https://foo/bar?baz#bat", + origin: "https://foo", + password: "", + pathname: "/bar", + port: "", + protocol: "https:", + search: "?baz", + username: "", + ancestorOrigins: { length: 0, item: [Function: item], contains: [Function: contains] }, + assign: [Function: assign], + reload: [Function: reload], + replace: [Function: replace], + toString: [Function: toString] +} +NotSupportedError: Cannot set "location.hostname". +[WILDCARD] diff --git a/cli/tests/066_location_unset.ts b/cli/tests/066_location_unset.ts new file mode 100644 index 00000000000000..bb60df8c45d009 --- /dev/null +++ b/cli/tests/066_location_unset.ts @@ -0,0 +1,3 @@ +console.log(Location); +console.log(Location.prototype); +console.log(location); diff --git a/cli/tests/066_location_unset.ts.out b/cli/tests/066_location_unset.ts.out new file mode 100644 index 00000000000000..d9e5cef5b4d2a2 --- /dev/null +++ b/cli/tests/066_location_unset.ts.out @@ -0,0 +1,4 @@ +[WILDCARD][Function: Location] +Location { Symbol(Symbol.toStringTag): "Location" } +error: Uncaught ReferenceError: Access to "location", run again with --location . +[WILDCARD] diff --git a/cli/tests/067_location_relative_fetch.ts b/cli/tests/067_location_relative_fetch.ts new file mode 100644 index 00000000000000..d4764bf7f1f358 --- /dev/null +++ b/cli/tests/067_location_relative_fetch.ts @@ -0,0 +1,2 @@ +const response = await fetch("fetch/hello.txt"); +console.log(await response.text()); diff --git a/cli/tests/067_location_relative_fetch.ts.out b/cli/tests/067_location_relative_fetch.ts.out new file mode 100644 index 00000000000000..8151f6f889503f --- /dev/null +++ b/cli/tests/067_location_relative_fetch.ts.out @@ -0,0 +1,2 @@ +[WILDCARD]Hello, world! + diff --git a/cli/tests/complex_permissions_test.ts b/cli/tests/complex_permissions_test.ts index d7737fa2d360be..981f8d4f92bf9d 100644 --- a/cli/tests/complex_permissions_test.ts +++ b/cli/tests/complex_permissions_test.ts @@ -10,8 +10,8 @@ const test: { [key: string]: (...args: any[]) => void | Promise } = { Deno.writeFileSync(file, new Uint8Array(0), { append: true }) ); }, - netFetch(hosts: string[]): void { - hosts.forEach((host) => fetch(host)); + netFetch(urls: string[]): void { + urls.forEach((url) => fetch(url)); }, netListen(endpoints: string[]): void { endpoints.forEach((endpoint) => { diff --git a/cli/tests/fetch/hello.txt b/cli/tests/fetch/hello.txt new file mode 100644 index 00000000000000..af5626b4a114ab --- /dev/null +++ b/cli/tests/fetch/hello.txt @@ -0,0 +1 @@ +Hello, world! diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs index ec13f73805b092..cc97924c736313 100644 --- a/cli/tests/integration_tests.rs +++ b/cli/tests/integration_tests.rs @@ -9,6 +9,7 @@ use futures::prelude::*; use std::io::{BufRead, Write}; use std::process::Command; use tempfile::TempDir; +use url::Url; #[test] fn std_tests() { @@ -1467,6 +1468,12 @@ fn workers() { .current_dir(util::tests_path()) .arg("test") .arg("--reload") + .arg("--location") + .arg( + Url::from_directory_path(util::tests_path()) + .unwrap() + .as_str(), + ) .arg("--allow-net") .arg("--allow-read") .arg("--unstable") @@ -1692,6 +1699,23 @@ itest!(_064_permissions_revoke_global { output: "064_permissions_revoke_global.ts.out", }); +itest!(_065_location { + args: "run --location https://foo/bar?baz#bat 065_location.ts", + output: "065_location.ts.out", +}); + +itest!(_066_location_unset { + args: "run 066_location_unset.ts", + output: "066_location_unset.ts.out", + exit_code: 1, +}); + +itest!(_067_location_relative_fetch { + args: "run --location http://127.0.0.1:4545/cli/tests/ --allow-net 067_location_relative_fetch.ts", + output: "067_location_relative_fetch.ts.out", + http_server: true, +}); + itest!(js_import_detect { args: "run --quiet --reload js_import_detect.ts", output: "js_import_detect.ts.out", diff --git a/cli/tests/subdir/worker_location.ts b/cli/tests/subdir/worker_location.ts new file mode 100644 index 00000000000000..48003235015475 --- /dev/null +++ b/cli/tests/subdir/worker_location.ts @@ -0,0 +1,4 @@ +onmessage = function (): void { + postMessage(self.location.href); + close(); +}; diff --git a/cli/tests/workers_test.ts b/cli/tests/workers_test.ts index 395b1da244e028..0a1f888fe38f51 100644 --- a/cli/tests/workers_test.ts +++ b/cli/tests/workers_test.ts @@ -316,3 +316,39 @@ Deno.test({ w.terminate(); }, }); + +Deno.test({ + name: "worker location", + fn: async function (): Promise { + const promise = createResolvable(); + const workerModuleHref = + new URL("subdir/worker_location.ts", import.meta.url).href; + const w = new Worker(workerModuleHref, { type: "module" }); + w.onmessage = (e): void => { + assertEquals(e.data, workerModuleHref); + promise.resolve(); + }; + w.postMessage("Hello, world!"); + await promise; + w.terminate(); + }, +}); + +Deno.test({ + name: "worker with relative specifier", + fn: async function (): Promise { + assertEquals(location.href, new URL(".", import.meta.url).href); + const promise = createResolvable(); + const w = new Worker( + "./subdir/test_worker.ts", + { type: "module", name: "tsWorker" }, + ); + w.onmessage = (e): void => { + assertEquals(e.data, "Hello, world!"); + promise.resolve(); + }; + w.postMessage("Hello, world!"); + await promise; + w.terminate(); + }, +}); diff --git a/cli/web_worker.rs b/cli/web_worker.rs index b0990d31374f64..8bca6ee198e623 100644 --- a/cli/web_worker.rs +++ b/cli/web_worker.rs @@ -247,7 +247,7 @@ mod tests { let state = State::mock("./hello.js"); let mut worker = WebWorker::new("TEST".to_string(), &state, false); worker - .execute("bootstrap.workerRuntime(\"TEST\", false)") + .execute("bootstrap.workerRuntime(\"TEST\", \"https://foo\", false)") .unwrap(); worker } diff --git a/docs/runtime/location_api.md b/docs/runtime/location_api.md new file mode 100644 index 00000000000000..7c60854269b3ec --- /dev/null +++ b/docs/runtime/location_api.md @@ -0,0 +1,76 @@ +## Location API + +Deno supports the +[`location`](https://developer.mozilla.org/en-US/docs/Web/API/Window/location) +global from the web. Please read on. + +### Location flag + +There is no "web page" whose URL we can use for a location in a Deno process. We +instead allow users to emulate a document location by specifying one on the CLI +using the `--location` flag. It can be a `http`, `https` or `file` URL. + +```ts +// deno run --location https://example.com/ main.ts + +console.log(location.href); +// "https://example.com/" +``` + +You must pass `--location ` for this to work. If you don't, any access to +the `location` global will throw an error. + +```ts +// deno run main.ts + +console.log(location.href); +// error: Uncaught ReferenceError: Access to "location", run again with --location . +``` + +Setting `location` or any of its fields will normally cause navigation in +browsers. This is not applicable in Deno, so it will throw in this situation. + +```ts +// deno run --location https://example.com/ main.ts + +location.pathname = "./foo"; +// error: Uncaught NotSupportedError: Cannot set "location.pathname". +``` + +### Extended usage + +On the web, resource resolution (excluding modules) typically uses the value of +`location.href` as the root on which to base any relative URLs. This affects +some web APIs adopted by Deno. + +#### Fetch API + +```ts +// deno run --location https://api.github.com/ --allow-net main.ts + +const response = await fetch("./orgs/denoland"); +// Fetches "https://api.github.com/orgs/denoland". +``` + +The `fetch()` call above would throw if the `--location` flag was not passed, +since there is no web-analogous location to base it onto. + +#### Worker modules + +```ts +// deno run --location https://example.com/index.html --allow-net main.ts + +const worker = new Worker("./workers/hello.ts", { type: "module" }); +// Fetches worker module at "https://example.com/workers/hello.ts". +``` + +### Only use if necessary + +For the above use cases, it is preferable to pass URLs in full rather than +relying on `--location`. You can manually base a relative URL using the `URL` +constructor if needed. + +The `--location` flag is intended for those who have some specific purpose in +mind for emulating a document location and are aware that this will only work at +application-level. However, you may also use it to silence errors from a +dependency which is frivolously accessing the `location` global. diff --git a/docs/runtime/workers.md b/docs/runtime/workers.md index 019df720121a71..64c1fd7ccf4d6e 100644 --- a/docs/runtime/workers.md +++ b/docs/runtime/workers.md @@ -9,10 +9,10 @@ is run on a separate thread, dedicated only to that worker. Currently Deno supports only `module` type workers; thus it's essential to pass the `type: "module"` option when creating a new worker. -Relative module specifiers are -[not supported](https://github.com/denoland/deno/issues/5216) at the moment. You -can instead use the `URL` contructor and `import.meta.url` to easily create a -specifier for some nearby script. +Relative module specifiers are only supported with `--location ` passed on +the CLI. This is not recommended for portability. You can instead use the `URL` +contructor and `import.meta.url` to easily create a specifier for some nearby +script. ```ts // Good diff --git a/docs/toc.json b/docs/toc.json index 331629fcfeae45..3a0461765b7798 100644 --- a/docs/toc.json +++ b/docs/toc.json @@ -22,6 +22,7 @@ "program_lifecycle": "Program lifecycle", "permission_apis": "Permission APIs", "compiler_apis": "Compiler APIs", + "location_api": "Location API", "workers": "Workers" } }, diff --git a/op_crates/web/09_location.js b/op_crates/web/09_location.js new file mode 100644 index 00000000000000..c1fab0de7ad6e9 --- /dev/null +++ b/op_crates/web/09_location.js @@ -0,0 +1,223 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. + +((window) => { + const locationConstructorKey = Symbol("locationConstuctorKey"); + + class Location { + constructor(href, key) { + if (key != locationConstructorKey) { + throw new TypeError("Illegal constructor."); + } + + Object.defineProperties(this, { + hash: { + get() { + return new URL(href).hash; + }, + set() { + throw new DOMException( + `Cannot set "location.hash".`, + "NotSupportedError", + ); + }, + enumerable: true, + }, + host: { + get() { + return new URL(href).host; + }, + set() { + throw new DOMException( + `Cannot set "location.host".`, + "NotSupportedError", + ); + }, + enumerable: true, + }, + hostname: { + get() { + return new URL(href).hostname; + }, + set() { + throw new DOMException( + `Cannot set "location.hostname".`, + "NotSupportedError", + ); + }, + enumerable: true, + }, + href: { + get() { + return href; + }, + set() { + throw new DOMException( + `Cannot set "location.href".`, + "NotSupportedError", + ); + }, + enumerable: true, + }, + origin: { + get() { + return new URL(href).origin; + }, + enumerable: true, + }, + password: { + get() { + return new URL(href).password; + }, + set() { + throw new DOMException( + `Cannot set "location.password".`, + "NotSupportedError", + ); + }, + enumerable: true, + }, + pathname: { + get() { + return new URL(href).pathname; + }, + set() { + throw new DOMException( + `Cannot set "location.pathname".`, + "NotSupportedError", + ); + }, + enumerable: true, + }, + port: { + get() { + return new URL(href).port; + }, + set() { + throw new DOMException( + `Cannot set "location.port".`, + "NotSupportedError", + ); + }, + enumerable: true, + }, + protocol: { + get() { + return new URL(href).protocol; + }, + set() { + throw new DOMException( + `Cannot set "location.protocol".`, + "NotSupportedError", + ); + }, + enumerable: true, + }, + search: { + get() { + return new URL(href).search; + }, + set() { + throw new DOMException( + `Cannot set "location.search".`, + "NotSupportedError", + ); + }, + enumerable: true, + }, + username: { + get() { + return new URL(href).username; + }, + set() { + throw new DOMException( + `Cannot set "location.username".`, + "NotSupportedError", + ); + }, + enumerable: true, + }, + ancestorOrigins: { + get() { + // TODO(nayeemrmn): Replace with a `DOMStringList` instance. + return { + length: 0, + item: () => null, + contains: () => false, + }; + }, + enumerable: true, + }, + assign: { + value: function assign() { + throw new DOMException( + `Cannot call "location.assign()".`, + "NotSupportedError", + ); + }, + enumerable: true, + }, + reload: { + value: function reload() { + throw new DOMException( + `Cannot call "location.reload()".`, + "NotSupportedError", + ); + }, + enumerable: true, + }, + replace: { + value: function replace() { + throw new DOMException( + `Cannot call "location.replace()".`, + "NotSupportedError", + ); + }, + enumerable: true, + }, + toString: { + value: function toString() { + return href; + }, + enumerable: true, + }, + }); + } + } + + Object.defineProperties(Location.prototype, { + [Symbol.toStringTag]: { + value: "Location", + configurable: true, + }, + }); + + let location = null; + + function setLocationHref(href) { + location = new Location(href, locationConstructorKey); + } + + window.__bootstrap = (window.__bootstrap || {}); + window.__bootstrap.location = { + locationConstructorDescriptor: { + value: Location, + configurable: true, + writable: true, + }, + locationDescriptor: { + get() { + if (location == null) { + throw new ReferenceError( + `Access to "location", run again with --location .`, + ); + } + return location; + }, + set() { + throw new DOMException(`Cannot set "location".`, "NotSupportedError"); + }, + enumerable: true, + }, + setLocationHref, + }; +})(this); diff --git a/op_crates/web/Cargo.toml b/op_crates/web/Cargo.toml index feb562e58a3f00..f4343ea6ab6b7c 100644 --- a/op_crates/web/Cargo.toml +++ b/op_crates/web/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "deno_web" -version = "0.7.1" +version = "0.8.0" edition = "2018" description = "Collection of Web APIs" authors = ["the Deno authors"] diff --git a/op_crates/web/lib.deno_web.d.ts b/op_crates/web/lib.deno_web.d.ts index d24a3b76f55bc8..225b082342ab33 100644 --- a/op_crates/web/lib.deno_web.d.ts +++ b/op_crates/web/lib.deno_web.d.ts @@ -1,6 +1,6 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. -/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/no-explicit-any, no-var */ /// /// @@ -231,7 +231,77 @@ interface AbortSignal extends EventTarget { ): void; } -declare const AbortSignal: { +declare var AbortSignal: { prototype: AbortSignal; new (): AbortSignal; }; + +/** The location (URL) of the object it is linked to. Changes done on it are + * reflected on the object it relates to. Accessible via + * `globalThis.location`. */ +interface Location { + /** Returns a DOMStringList object listing the origins of the ancestor + * browsing contexts, from the parent browsing context to the top-level + * browsing context. + * + * Always empty in Deno. */ + readonly ancestorOrigins: DOMStringList; + /** Returns the Location object's URL's fragment (includes leading "#" if non-empty). + * + * Cannot be set in Deno. */ + hash: string; + /** Returns the Location object's URL's host and port (if different from the default port for the scheme). + * + * Cannot be set in Deno. */ + host: string; + /** Returns the Location object's URL's host. + * + * Cannot be set in Deno. */ + hostname: string; + /** Returns the Location object's URL. + * + * Cannot be set in Deno. */ + href: string; + toString(): string; + /** Returns the Location object's URL's origin. */ + readonly origin: string; + /** Returns the Location object's URL's path. + * + * Cannot be set in Deno. */ + pathname: string; + /** Returns the Location object's URL's port. + * + * Cannot be set in Deno. */ + port: string; + /** Returns the Location object's URL's scheme. + * + * Cannot be set in Deno. */ + protocol: string; + /** Returns the Location object's URL's query (includes leading "?" if + * non-empty). + * + * Cannot be set in Deno. */ + search: string; + /** Navigates to the given URL. + * + * Cannot be set in Deno. */ + assign(url: string): void; + /** Reloads the current page. + * + * Disabled in Deno. */ + reload(): void; + /** @deprecated */ + reload(forcedReload: boolean): void; + /** Removes the current page from the session history and navigates to the + * given URL. + * + * Disabled in Deno. */ + replace(url: string): void; +} + +declare var Location: { + prototype: Location; + new (): Location; +}; + +declare var location: Location; diff --git a/op_crates/web/lib.rs b/op_crates/web/lib.rs index 96f269a1d50953..4a596da000b486 100644 --- a/op_crates/web/lib.rs +++ b/op_crates/web/lib.rs @@ -11,6 +11,7 @@ pub fn init(isolate: &mut JsRuntime) { manifest_dir.join("01_event.js"), manifest_dir.join("02_abort_signal.js"), manifest_dir.join("08_text_encoding.js"), + manifest_dir.join("09_location.js"), ]; // TODO(nayeemrmn): https://github.com/rust-lang/cargo/issues/3946 to get the // workspace root.