From e72b531f73680052fac59b6e4b141e2d9ad631aa 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 --- cli/flags.rs | 354 ++++++++++--------- cli/installer.rs | 4 + cli/main.rs | 25 +- cli/ops/runtime.rs | 1 + cli/ops/worker_host.rs | 17 +- cli/rt/99_main.js | 25 +- cli/rt/README.md | 2 + cli/tests/066_location.ts | 8 + cli/tests/066_location.ts.out | 22 ++ cli/tests/067_location_unset.ts | 3 + cli/tests/067_location_unset.ts.out | 4 + cli/tests/068_location_relative_fetch.ts | 2 + cli/tests/068_location_relative_fetch.ts.out | 2 + cli/tests/complex_permissions_test.ts | 4 +- cli/tests/fetch/hello.txt | 1 + cli/tests/integration_tests.rs | 23 ++ 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/fetch/26_fetch.js | 14 +- op_crates/fetch/lib.rs | 16 +- op_crates/web/12_location.js | 224 ++++++++++++ op_crates/web/lib.deno_web.d.ts | 72 +++- op_crates/web/lib.rs | 1 + 27 files changed, 748 insertions(+), 203 deletions(-) create mode 100644 cli/tests/066_location.ts create mode 100644 cli/tests/066_location.ts.out create mode 100644 cli/tests/067_location_unset.ts create mode 100644 cli/tests/067_location_unset.ts.out create mode 100644 cli/tests/068_location_relative_fetch.ts create mode 100644 cli/tests/068_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/12_location.js diff --git a/cli/flags.rs b/cli/flags.rs index 63d4c15e000c52..13ddcda0f36ad8 100644 --- a/cli/flags.rs +++ b/cli/flags.rs @@ -6,6 +6,7 @@ use clap::Arg; use clap::ArgMatches; use clap::ArgSettings; use clap::SubCommand; +use deno_core::url::Url; use log::Level; use std::net::SocketAddr; use std::path::PathBuf; @@ -103,6 +104,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, @@ -229,18 +231,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(); @@ -259,7 +255,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") { @@ -269,17 +265,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") { @@ -287,7 +283,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) @@ -371,10 +367,14 @@ 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); reload_arg_parse(flags, matches); + location_arg_parse(flags, matches)?; ca_file_arg_parse(flags, matches); no_check_arg_parse(flags, matches); @@ -403,6 +403,7 @@ fn install_parse(flags: &mut Flags, matches: &clap::ArgMatches) { root, force, }; + Ok(()) } fn bundle_parse(flags: &mut Flags, matches: &clap::ArgMatches) { @@ -445,8 +446,11 @@ fn completions_parse(flags: &mut Flags, matches: &clap::ArgMatches) { }; } -fn repl_parse(flags: &mut Flags, matches: &clap::ArgMatches) { - runtime_args_parse(flags, matches, false); +fn repl_parse( + flags: &mut Flags, + matches: &clap::ArgMatches, +) -> clap::Result<()> { + runtime_args_parse(flags, matches, false)?; flags.subcommand = DenoSubcommand::Repl; flags.allow_net = true; flags.allow_env = true; @@ -455,10 +459,14 @@ 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) { - runtime_args_parse(flags, matches, false); +fn eval_parse( + flags: &mut Flags, + matches: &clap::ArgMatches, +) -> clap::Result<()> { + runtime_args_parse(flags, matches, false)?; flags.allow_net = true; flags.allow_env = true; flags.allow_run = true; @@ -473,7 +481,8 @@ fn eval_parse(flags: &mut Flags, matches: &clap::ArgMatches) { print, code, as_typescript, - } + }; + Ok(()) } fn info_parse(flags: &mut Flags, matches: &clap::ArgMatches) { @@ -538,6 +547,7 @@ fn runtime_args<'a, 'b>(app: App<'a, 'b>, include_perms: bool) -> App<'a, 'b> { }; app .arg(cached_only_arg()) + .arg(location_arg()) .arg(v8_flags_arg()) .arg(seed_arg()) } @@ -546,19 +556,24 @@ fn runtime_args_parse( flags: &mut Flags, matches: &clap::ArgMatches, include_perms: bool, -) { +) -> clap::Result<()> { compile_args_parse(flags, matches); cached_only_arg_parse(flags, matches); if include_perms { permission_args_parse(flags, matches); } + location_arg_parse(flags, matches)?; v8_flags_arg_parse(flags, matches); seed_arg_parse(flags, matches); inspect_arg_parse(flags, matches); + Ok(()) } -fn run_parse(flags: &mut Flags, matches: &clap::ArgMatches) { - runtime_args_parse(flags, matches, true); +fn run_parse( + flags: &mut Flags, + matches: &clap::ArgMatches, +) -> clap::Result<()> { + runtime_args_parse(flags, matches, true)?; let mut script: Vec = matches .values_of("script_arg") @@ -574,10 +589,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) { - runtime_args_parse(flags, matches, true); +fn test_parse( + flags: &mut Flags, + matches: &clap::ArgMatches, +) -> clap::Result<()> { + runtime_args_parse(flags, matches, true)?; let failfast = matches.is_present("failfast"); let allow_none = matches.is_present("allow_none"); @@ -609,6 +628,7 @@ fn test_parse(flags: &mut Flags, matches: &clap::ArgMatches) { allow_none, coverage, }; + Ok(()) } fn upgrade_parse(flags: &mut Flags, matches: &clap::ArgMatches) { @@ -753,6 +773,7 @@ fn install_subcommand<'a, 'b>() -> App<'a, 'b> { .takes_value(false)) .arg(no_check_arg()) .arg(reload_arg()) + .arg(location_arg()) .arg(ca_file_arg()) .arg(config_arg()) .about("Install script as an executable") @@ -1251,6 +1272,40 @@ fn ca_file_arg_parse(flags: &mut Flags, matches: &clap::ArgMatches) { flags.ca_file = matches.value_of("cert").map(ToOwned::to_owned); } +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 inspect_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { app .arg( @@ -1518,7 +1573,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 deno_core::url::Url; let mut out: Vec = vec![]; for urlstr in urls.iter() { use std::str::FromStr; @@ -1544,7 +1598,7 @@ mod tests { #[test] fn global_flags() { #[rustfmt::skip] - let r = flags_from_vec_safe(svec!["deno", "--unstable", "--log-level", "debug", "--quiet", "run", "script.ts"]); + let r = flags_from_vec(svec!["deno", "--unstable", "--log-level", "debug", "--quiet", "run", "script.ts"]); let flags = r.unwrap(); assert_eq!( flags, @@ -1558,15 +1612,14 @@ mod tests { } ); #[rustfmt::skip] - let r2 = flags_from_vec_safe(svec!["deno", "run", "--unstable", "--log-level", "debug", "--quiet", "script.ts"]); + let r2 = flags_from_vec(svec!["deno", "run", "--unstable", "--log-level", "debug", "--quiet", "script.ts"]); let flags2 = r2.unwrap(); assert_eq!(flags2, flags); } #[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, @@ -1585,15 +1638,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, @@ -1609,7 +1662,7 @@ mod tests { #[test] fn run_watch() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "run", "--unstable", @@ -1632,13 +1685,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 { @@ -1654,12 +1702,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 { @@ -1671,7 +1715,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", @@ -1691,7 +1735,7 @@ mod tests { #[test] fn script_args() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "run", "--allow-net", @@ -1714,7 +1758,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 { @@ -1735,8 +1779,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 { @@ -1751,8 +1794,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 { @@ -1770,7 +1812,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", @@ -1794,8 +1836,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 { @@ -1808,7 +1849,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 { @@ -1821,7 +1862,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 { @@ -1837,7 +1878,7 @@ mod tests { #[test] fn lint() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "lint", "--unstable", @@ -1858,7 +1899,7 @@ mod tests { } ); - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "lint", "--unstable", @@ -1878,7 +1919,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 { @@ -1893,7 +1934,7 @@ mod tests { } ); - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "lint", "--unstable", @@ -1917,7 +1958,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 { @@ -1929,7 +1970,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 { @@ -1943,7 +1984,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 { @@ -1955,7 +1996,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 { @@ -1968,7 +2009,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 { @@ -1980,7 +2021,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 { @@ -1992,7 +2033,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 { @@ -2007,13 +2048,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 { @@ -2028,8 +2064,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 { @@ -2052,7 +2087,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 { @@ -2075,12 +2110,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 { @@ -2104,7 +2135,7 @@ mod tests { #[test] fn eval_with_flags() { #[rustfmt::skip] - let r = flags_from_vec_safe(svec!["deno", "eval", "--unstable", "--importmap", "import_map.json", "--no-remote", "--config", "tsconfig.json", "--no-check", "--reload", "--lock", "lock.json", "--lock-write", "--cert", "example.crt", "--cached-only", "--v8-flags=--help", "--seed", "1", "--inspect=127.0.0.1:9229", "42"]); + let r = flags_from_vec(svec!["deno", "eval", "--unstable", "--importmap", "import_map.json", "--no-remote", "--config", "tsconfig.json", "--no-check", "--reload", "--lock", "lock.json", "--lock-write", "--cert", "example.crt", "--cached-only", "--location", "https:foo", "--v8-flags=--help", "--seed", "1", "--inspect=127.0.0.1:9229", "42"]); assert_eq!( r.unwrap(), Flags { @@ -2123,6 +2154,7 @@ mod tests { lock_write: true, ca_file: Some("example.crt".to_string()), cached_only: true, + location: Some(Url::parse("https://foo/").unwrap()), v8_flags: Some(svec!["--help", "--random-seed=1"]), seed: Some(1), inspect: Some("127.0.0.1:9229".parse().unwrap()), @@ -2140,7 +2172,7 @@ mod tests { #[test] fn repl() { - let r = flags_from_vec_safe(svec!["deno"]); + let r = flags_from_vec(svec!["deno"]); assert_eq!( r.unwrap(), Flags { @@ -2160,7 +2192,7 @@ mod tests { #[test] fn repl_with_flags() { #[rustfmt::skip] - let r = flags_from_vec_safe(svec!["deno", "repl", "--unstable", "--importmap", "import_map.json", "--no-remote", "--config", "tsconfig.json", "--no-check", "--reload", "--lock", "lock.json", "--lock-write", "--cert", "example.crt", "--cached-only", "--v8-flags=--help", "--seed", "1", "--inspect=127.0.0.1:9229"]); + let r = flags_from_vec(svec!["deno", "repl", "--unstable", "--importmap", "import_map.json", "--no-remote", "--config", "tsconfig.json", "--no-check", "--reload", "--lock", "lock.json", "--lock-write", "--cert", "example.crt", "--cached-only", "--location", "https:foo", "--v8-flags=--help", "--seed", "1", "--inspect=127.0.0.1:9229"]); assert_eq!( r.unwrap(), Flags { @@ -2175,6 +2207,7 @@ mod tests { lock_write: true, ca_file: Some("example.crt".to_string()), cached_only: true, + location: Some(Url::parse("https://foo/").unwrap()), v8_flags: Some(svec!["--help", "--random-seed=1"]), seed: Some(1), inspect: Some("127.0.0.1:9229".parse().unwrap()), @@ -2195,7 +2228,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-read=.,{}", temp_dir.to_str().unwrap()), @@ -2219,7 +2252,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 +2273,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 +2294,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,7 +2309,7 @@ mod tests { #[test] fn bundle_with_config() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "bundle", "--no-remote", @@ -2302,8 +2335,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 { @@ -2319,7 +2351,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", @@ -2342,8 +2374,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 { @@ -2359,7 +2390,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", @@ -2379,7 +2410,7 @@ mod tests { #[test] fn info_importmap() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "info", "--importmap=importmap.json", @@ -2400,7 +2431,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", @@ -2421,7 +2452,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 { @@ -2435,8 +2466,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 { @@ -2452,7 +2482,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", @@ -2475,7 +2505,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" @@ -2497,10 +2527,12 @@ mod tests { #[test] fn install_with_args() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "install", "--reload", + "--location", + "https:foo", "--allow-net", "--allow-read", "-n", @@ -2518,6 +2550,7 @@ mod tests { force: false, }, reload: true, + location: Some(Url::parse("https://foo/").unwrap()), allow_net: true, allow_read: true, ..Flags::default() @@ -2527,7 +2560,7 @@ mod tests { #[test] fn install_with_config() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "install", "--config", @@ -2553,7 +2586,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", @@ -2586,12 +2619,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 { @@ -2606,7 +2635,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 { @@ -2621,7 +2650,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()), @@ -2631,7 +2660,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", @@ -2648,9 +2677,11 @@ mod tests { ..Flags::default() } ); - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "run", + "--location", + "https:foo", "--allow-read", "script.ts", "--allow-net", @@ -2665,14 +2696,14 @@ mod tests { subcommand: DenoSubcommand::Run { script: "script.ts".to_string(), }, + location: Some(Url::parse("https://foo/").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 { @@ -2683,7 +2714,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 { @@ -2696,7 +2727,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 { @@ -2711,8 +2742,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 { @@ -2727,8 +2757,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 { @@ -2743,8 +2772,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 { @@ -2759,7 +2787,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", @@ -2787,7 +2815,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", @@ -2818,7 +2846,7 @@ mod tests { #[test] fn lock_write() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "run", "--lock-write", @@ -2839,10 +2867,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:foo", "--allow-net", "--allow-none", "dir1/", @@ -2859,6 +2889,7 @@ mod tests { include: Some(svec!["dir1/", "dir2/"]), coverage: false, }, + location: Some(Url::parse("https://foo/").unwrap()), allow_net: true, ..Flags::default() } @@ -2867,7 +2898,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 { @@ -2886,8 +2917,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 { @@ -2906,13 +2936,8 @@ mod tests { #[test] fn test_coverage() { - let r = flags_from_vec_safe(svec![ - "deno", - "test", - "--unstable", - "--coverage", - "dir1" - ]); + let r = + flags_from_vec(svec!["deno", "test", "--unstable", "--coverage", "dir1"]); assert_eq!( r.unwrap(), Flags { @@ -2933,7 +2958,7 @@ mod tests { #[test] fn run_with_cafile() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "run", "--cert", @@ -2954,7 +2979,7 @@ mod tests { #[test] fn bundle_with_cafile() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "bundle", "--cert", @@ -2976,8 +3001,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 { @@ -2996,7 +3020,7 @@ mod tests { #[test] fn cache_with_cafile() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "cache", "--cert", @@ -3018,7 +3042,7 @@ mod tests { #[test] fn info_with_cafile() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "info", "--cert", @@ -3040,7 +3064,7 @@ mod tests { #[test] fn install_with_cafile() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "install", "--cert", @@ -3067,8 +3091,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 { @@ -3082,7 +3105,7 @@ mod tests { } ); - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "doc", "path/to/module.ts", @@ -3101,7 +3124,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 { @@ -3115,8 +3138,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 { @@ -3130,12 +3152,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 { @@ -3152,7 +3170,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 e017ad16bfa882..b30e73431cbc4a 100644 --- a/cli/installer.rs +++ b/cli/installer.rs @@ -191,6 +191,10 @@ pub 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 { executable_args.push("--cert".to_string()); executable_args.push(ca_file) diff --git a/cli/main.rs b/cli/main.rs index e2b61d33aa6585..98a2c1325e18f2 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -619,12 +619,28 @@ async fn test_command( Ok(()) } +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(AnyError::from)); if let Some(ref v8_flags) = flags.v8_flags { let v8_flags_includes_help = v8_flags @@ -764,10 +780,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/runtime.rs b/cli/ops/runtime.rs index d059301f16cb19..9f6e7895b1acf1 100644 --- a/cli/ops/runtime.rs +++ b/cli/ops/runtime.rs @@ -33,6 +33,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 058ffaa31528e6..dcc7d1794bf555 100644 --- a/cli/ops/worker_host.rs +++ b/cli/ops/worker_host.rs @@ -50,7 +50,7 @@ fn create_web_worker( let mut worker = WebWorker::new( name.clone(), permissions, - specifier, + specifier.clone(), global_state.clone(), has_deno_namespace, ); @@ -73,8 +73,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, specifier, worker.has_deno_namespace, worker_id ); worker.execute(&script)?; @@ -184,7 +184,7 @@ fn op_create_worker( args: Value, _data: &mut [ZeroCopyBuf], ) -> Result { - let cli_state = super::global_state(state); + let global_state = super::global_state(state); let args: CreateWorkerArgs = serde_json::from_value(args)?; let specifier = args.specifier.clone(); @@ -196,19 +196,22 @@ fn op_create_worker( let args_name = args.name; let use_deno_namespace = args.use_deno_namespace; if use_deno_namespace { - cli_state.check_unstable("Worker.deno"); + global_state.check_unstable("Worker.deno"); } let permissions = state.borrow::().clone(); let worker_id = state.take::(); state.put::(worker_id + 1); - let module_specifier = ModuleSpecifier::resolve_url(&specifier)?; + let module_specifier = match global_state.flags.location.as_ref() { + None => ModuleSpecifier::resolve_url(&specifier)?, + Some(url) => ModuleSpecifier::resolve_import(&specifier, url.as_str())?, + }; let worker_name = args_name.unwrap_or_else(|| "".to_string()); let (join_handle, worker_handle) = run_worker_thread( worker_id, worker_name, - &cli_state, + &global_state, permissions, module_specifier, use_deno_namespace, diff --git a/cli/rt/99_main.js b/cli/rt/99_main.js index 26e8fd6da096a6..cb1025650e902a 100644 --- a/cli/rt/99_main.js +++ b/cli/rt/99_main.js @@ -8,6 +8,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 dispatchMinimal = window.__bootstrap.dispatchMinimal; const build = window.__bootstrap.build; const version = window.__bootstrap.version; @@ -193,6 +194,8 @@ delete Object.prototype.__proto__; // https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope const windowOrWorkerGlobalScope = { + Location: location.locationConstructorDescriptor, + location: location.locationDescriptor, Blob: util.nonEnumerable(fetch.Blob), ByteLengthQueuingStrategy: util.nonEnumerable( streams.ByteLengthQueuingStrategy, @@ -294,9 +297,21 @@ 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); + fetch.setBaseUrl(locationHref); + } registerErrors(); const finalDenoNs = { @@ -335,7 +350,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"); } @@ -352,6 +367,8 @@ delete Object.prototype.__proto__; internalName ?? name, ); + location.setLocationHref(url); + fetch.setBaseUrl(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/066_location.ts b/cli/tests/066_location.ts new file mode 100644 index 00000000000000..27795402c00ac1 --- /dev/null +++ b/cli/tests/066_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/066_location.ts.out b/cli/tests/066_location.ts.out new file mode 100644 index 00000000000000..566a478503bb5f --- /dev/null +++ b/cli/tests/066_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/067_location_unset.ts b/cli/tests/067_location_unset.ts new file mode 100644 index 00000000000000..bb60df8c45d009 --- /dev/null +++ b/cli/tests/067_location_unset.ts @@ -0,0 +1,3 @@ +console.log(Location); +console.log(Location.prototype); +console.log(location); diff --git a/cli/tests/067_location_unset.ts.out b/cli/tests/067_location_unset.ts.out new file mode 100644 index 00000000000000..d9e5cef5b4d2a2 --- /dev/null +++ b/cli/tests/067_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/068_location_relative_fetch.ts b/cli/tests/068_location_relative_fetch.ts new file mode 100644 index 00000000000000..d4764bf7f1f358 --- /dev/null +++ b/cli/tests/068_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/068_location_relative_fetch.ts.out b/cli/tests/068_location_relative_fetch.ts.out new file mode 100644 index 00000000000000..8151f6f889503f --- /dev/null +++ b/cli/tests/068_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 4ec816c4f0916a..bafbc56e601a06 100644 --- a/cli/tests/integration_tests.rs +++ b/cli/tests/integration_tests.rs @@ -1546,6 +1546,12 @@ fn workers() { .current_dir(util::tests_path()) .arg("test") .arg("--reload") + .arg("--location") + .arg( + url::Url::from_directory_path(util::tests_path()) + .unwrap() + .as_str(), + ) .arg("--allow-net") .arg("--allow-read") .arg("--unstable") @@ -1777,6 +1783,23 @@ itest!(_065_import_map_info { output: "065_import_map_info.out", }); +itest!(_066_location { + args: "run --location https://foo/bar?baz#bat 066_location.ts", + output: "066_location.ts.out", +}); + +itest!(_067_location_unset { + args: "run 067_location_unset.ts", + output: "067_location_unset.ts.out", + exit_code: 1, +}); + +itest!(_068_location_relative_fetch { + args: "run --location http://127.0.0.1:4545/cli/tests/ --allow-net 068_location_relative_fetch.ts", + output: "068_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 587bc055d78d0d..ca33e6133c3014 100644 --- a/cli/web_worker.rs +++ b/cli/web_worker.rs @@ -282,7 +282,7 @@ mod tests { 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 875fc6166b0bed..70426e598e1273 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/fetch/26_fetch.js b/op_crates/fetch/26_fetch.js index 9916a4f82c0481..1eb02cd8bdc79f 100644 --- a/op_crates/fetch/26_fetch.js +++ b/op_crates/fetch/26_fetch.js @@ -1173,20 +1173,25 @@ } } + let baseUrl = null; + + function setBaseUrl(href) { + baseUrl = href; + } + function sendFetchReq(url, method, headers, body, clientRid) { let headerArray = []; if (headers) { headerArray = Array.from(headers.entries()); } - const args = { + return opFetch({ method, url, + baseUrl, headers: headerArray, clientRid, - }; - - return opFetch(args, body); + }, body); } async function fetch(input, init) { @@ -1381,6 +1386,7 @@ Blob, DomFile, FormData, + setBaseUrl, fetch, Request, Response, diff --git a/op_crates/fetch/lib.rs b/op_crates/fetch/lib.rs index 5a314f75294278..951373b0f0b312 100644 --- a/op_crates/fetch/lib.rs +++ b/op_crates/fetch/lib.rs @@ -80,12 +80,12 @@ where struct FetchArgs { method: Option, url: String, + base_url: Option, headers: Vec<(String, String)>, client_rid: Option, } let args: FetchArgs = serde_json::from_value(args)?; - let url = args.url; let client = if let Some(rid) = args.client_rid { let state_ = state.borrow(); @@ -105,10 +105,16 @@ where None => Method::GET, }; - let url_ = url::Url::parse(&url)?; + let base_url = match args.base_url { + Some(base_url) => Some(url::Url::parse(&base_url)?), + _ => None, + }; + let url = url::Url::options() + .base_url(base_url.as_ref()) + .parse(&args.url)?; // Check scheme before asking for net permission - let scheme = url_.scheme(); + let scheme = url.scheme(); if scheme != "http" && scheme != "https" { return Err(type_error(format!("scheme '{}' not supported", scheme))); } @@ -116,10 +122,10 @@ where { let state_ = state.borrow(); let permissions = state_.borrow::(); - permissions.check_net_url(&url_)?; + permissions.check_net_url(&url)?; } - let mut request = client.request(method, url_); + let mut request = client.request(method, url); match data.len() { 0 => {} diff --git a/op_crates/web/12_location.js b/op_crates/web/12_location.js new file mode 100644 index 00000000000000..ab2570bf58c5b8 --- /dev/null +++ b/op_crates/web/12_location.js @@ -0,0 +1,224 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. + +((window) => { + const { URL } = window.__bootstrap.url; + const locationConstructorKey = Symbol("locationConstuctorKey"); + + class Location { + constructor(href, key) { + if (key != locationConstructorKey) { + throw new TypeError("Illegal constructor."); + } + const url = new URL(href); + Object.defineProperties(this, { + hash: { + get() { + return url.hash; + }, + set() { + throw new DOMException( + `Cannot set "location.hash".`, + "NotSupportedError", + ); + }, + enumerable: true, + }, + host: { + get() { + return url.host; + }, + set() { + throw new DOMException( + `Cannot set "location.host".`, + "NotSupportedError", + ); + }, + enumerable: true, + }, + hostname: { + get() { + return url.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 url.origin; + }, + enumerable: true, + }, + password: { + get() { + return url.password; + }, + set() { + throw new DOMException( + `Cannot set "location.password".`, + "NotSupportedError", + ); + }, + enumerable: true, + }, + pathname: { + get() { + return url.pathname; + }, + set() { + throw new DOMException( + `Cannot set "location.pathname".`, + "NotSupportedError", + ); + }, + enumerable: true, + }, + port: { + get() { + return url.port; + }, + set() { + throw new DOMException( + `Cannot set "location.port".`, + "NotSupportedError", + ); + }, + enumerable: true, + }, + protocol: { + get() { + return url.protocol; + }, + set() { + throw new DOMException( + `Cannot set "location.protocol".`, + "NotSupportedError", + ); + }, + enumerable: true, + }, + search: { + get() { + return url.search; + }, + set() { + throw new DOMException( + `Cannot set "location.search".`, + "NotSupportedError", + ); + }, + enumerable: true, + }, + username: { + get() { + return url.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/lib.deno_web.d.ts b/op_crates/web/lib.deno_web.d.ts index 102fbf7187fdf7..9587f3089dfd6b 100644 --- a/op_crates/web/lib.deno_web.d.ts +++ b/op_crates/web/lib.deno_web.d.ts @@ -231,7 +231,7 @@ interface AbortSignal extends EventTarget { ): void; } -declare const AbortSignal: { +declare var AbortSignal: { prototype: AbortSignal; new (): AbortSignal; }; @@ -295,3 +295,73 @@ declare var FileReader: { readonly EMPTY: number; readonly LOADING: number; }; + +/** 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 1078a7917ef467..36ddfd8cefb6b7 100644 --- a/op_crates/web/lib.rs +++ b/op_crates/web/lib.rs @@ -45,6 +45,7 @@ pub fn init(isolate: &mut JsRuntime) { manifest_dir.join("02_abort_signal.js"), manifest_dir.join("08_text_encoding.js"), manifest_dir.join("11_url.js"), + manifest_dir.join("12_location.js"), manifest_dir.join("21_filereader.js"), ]; // TODO(nayeemrmn): https://github.com/rust-lang/cargo/issues/3946 to get the