From 9ad90598adf08498151ba79d8dc5cf4605acdc75 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 | 334 ++++++++++--------- cli/main.rs | 24 +- cli/ops/runtime.rs | 1 + cli/ops/worker_host.rs | 15 +- cli/rt/99_main.js | 22 +- cli/rt/README.md | 2 + cli/tests/070_location.ts | 10 + cli/tests/070_location.ts.out | 22 ++ cli/tests/071_location_unset.ts | 5 + cli/tests/071_location_unset.ts.out | 4 + cli/tests/072_location_relative_fetch.ts | 2 + cli/tests/072_location_relative_fetch.ts.out | 2 + cli/tests/complex_permissions_test.ts | 4 +- cli/tests/fetch/hello.txt | 1 + cli/tests/integration_tests.rs | 29 +- cli/tests/subdir/worker_location.ts | 4 + cli/tests/unit/fetch_test.ts | 8 - cli/tests/unit/request_test.ts | 8 +- cli/tests/unit/unit_test_runner.ts | 1 + cli/tests/workers_test.ts | 38 +++ cli/tools/installer.rs | 4 + cli/worker.rs | 2 +- docs/runtime/location_api.md | 76 +++++ docs/runtime/workers.md | 8 +- docs/toc.json | 1 + op_crates/fetch/26_fetch.js | 21 +- op_crates/fetch/lib.rs | 16 +- op_crates/web/12_location.js | 227 +++++++++++++ op_crates/web/lib.deno_web.d.ts | 70 ++++ op_crates/web/lib.rs | 4 + 30 files changed, 761 insertions(+), 204 deletions(-) create mode 100644 cli/tests/070_location.ts create mode 100644 cli/tests/070_location.ts.out create mode 100644 cli/tests/071_location_unset.ts create mode 100644 cli/tests/071_location_unset.ts.out create mode 100644 cli/tests/072_location_relative_fetch.ts create mode 100644 cli/tests/072_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 76176362700b9c..2c56ee00639605 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, @@ -234,18 +236,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(); @@ -264,7 +260,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") { @@ -274,17 +270,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") { @@ -292,7 +288,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) @@ -377,8 +373,11 @@ fn fmt_parse(flags: &mut Flags, matches: &clap::ArgMatches) { } } -fn install_parse(flags: &mut Flags, matches: &clap::ArgMatches) { - runtime_args_parse(flags, matches, true); +fn install_parse( + flags: &mut Flags, + matches: &clap::ArgMatches, +) -> clap::Result<()> { + runtime_args_parse(flags, matches, true)?; let root = if matches.is_present("root") { let install_root = matches.value_of("root").unwrap(); @@ -405,6 +404,7 @@ fn install_parse(flags: &mut Flags, matches: &clap::ArgMatches) { root, force, }; + Ok(()) } fn bundle_parse(flags: &mut Flags, matches: &clap::ArgMatches) { @@ -441,8 +441,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.repl = true; flags.subcommand = DenoSubcommand::Repl; flags.allow_net = true; @@ -452,10 +455,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; @@ -470,7 +477,8 @@ fn eval_parse(flags: &mut Flags, matches: &clap::ArgMatches) { print, code, as_typescript, - } + }; + Ok(()) } fn info_parse(flags: &mut Flags, matches: &clap::ArgMatches) { @@ -535,6 +543,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()) } @@ -543,19 +552,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") @@ -571,10 +585,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 no_run = matches.is_present("no-run"); let fail_fast = matches.is_present("fail-fast"); @@ -618,6 +636,7 @@ fn test_parse(flags: &mut Flags, matches: &clap::ArgMatches) { filter, allow_none, }; + Ok(()) } fn upgrade_parse(flags: &mut Flags, matches: &clap::ArgMatches) { @@ -1266,6 +1285,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( @@ -1535,7 +1588,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() { if let Ok(mut url) = Url::from_str(urlstr) { @@ -1559,7 +1611,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, @@ -1573,15 +1625,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, @@ -1600,15 +1651,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, @@ -1624,7 +1675,7 @@ mod tests { #[test] fn run_watch() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "run", "--unstable", @@ -1647,13 +1698,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 { @@ -1669,7 +1715,7 @@ mod tests { #[test] fn run_v8_flags() { - let r = flags_from_vec_safe(svec!["deno", "run", "--v8-flags=--help"]); + let r = flags_from_vec(svec!["deno", "run", "--v8-flags=--help"]); assert_eq!( r.unwrap(), Flags { @@ -1681,7 +1727,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", @@ -1701,7 +1747,7 @@ mod tests { #[test] fn script_args() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "run", "--allow-net", @@ -1724,7 +1770,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 { @@ -1745,8 +1791,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 { @@ -1761,8 +1806,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 { @@ -1780,7 +1824,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", @@ -1804,8 +1848,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 { @@ -1821,7 +1864,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 { @@ -1834,7 +1877,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 { @@ -1847,7 +1890,7 @@ mod tests { } ); - let r = flags_from_vec_safe(svec!["deno", "fmt", "--watch", "--unstable"]); + let r = flags_from_vec(svec!["deno", "fmt", "--watch", "--unstable"]); assert_eq!( r.unwrap(), Flags { @@ -1862,7 +1905,7 @@ mod tests { } ); - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "fmt", "--check", @@ -1888,7 +1931,7 @@ mod tests { #[test] fn lint() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "lint", "--unstable", @@ -1912,7 +1955,7 @@ mod tests { } ); - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "lint", "--unstable", @@ -1935,7 +1978,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 { @@ -1950,7 +1993,7 @@ mod tests { } ); - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "lint", "--unstable", @@ -1974,7 +2017,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 { @@ -1986,7 +2029,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 { @@ -2000,7 +2043,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 { @@ -2012,7 +2055,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 { @@ -2025,7 +2068,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 { @@ -2037,7 +2080,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 { @@ -2049,7 +2092,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 { @@ -2064,13 +2107,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 { @@ -2085,8 +2123,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 { @@ -2109,7 +2146,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 { @@ -2132,12 +2169,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 { @@ -2161,7 +2194,7 @@ mod tests { #[test] fn eval_with_flags() { #[rustfmt::skip] - let r = flags_from_vec_safe(svec!["deno", "eval", "--unstable", "--import-map", "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", "--import-map", "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 { @@ -2180,6 +2213,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()), @@ -2197,7 +2231,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 { @@ -2218,7 +2252,7 @@ mod tests { #[test] fn repl_with_flags() { #[rustfmt::skip] - let r = flags_from_vec_safe(svec!["deno", "repl", "--unstable", "--import-map", "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", "--import-map", "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 { @@ -2234,6 +2268,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()), @@ -2254,7 +2289,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()), @@ -2278,7 +2313,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()), @@ -2299,7 +2334,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", @@ -2320,7 +2355,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 { @@ -2335,7 +2370,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", @@ -2361,8 +2396,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 { @@ -2378,7 +2412,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", @@ -2401,8 +2435,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 { @@ -2418,9 +2451,8 @@ mod tests { #[test] fn bundle_nocheck() { - let r = - flags_from_vec_safe(svec!["deno", "bundle", "--no-check", "script.ts"]) - .unwrap(); + let r = flags_from_vec(svec!["deno", "bundle", "--no-check", "script.ts"]) + .unwrap(); assert_eq!( r, Flags { @@ -2436,7 +2468,7 @@ mod tests { #[test] fn bundle_watch() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "bundle", "--watch", @@ -2459,7 +2491,7 @@ mod tests { #[test] fn run_import_map() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "run", "--unstable", @@ -2481,7 +2513,7 @@ mod tests { #[test] fn info_import_map() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "info", "--unstable", @@ -2504,7 +2536,7 @@ mod tests { #[test] fn cache_import_map() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "cache", "--unstable", @@ -2526,7 +2558,7 @@ mod tests { #[test] fn doc_import_map() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "doc", "--unstable", @@ -2552,7 +2584,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 { @@ -2566,8 +2598,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 { @@ -2583,7 +2614,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", @@ -2606,7 +2637,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" @@ -2629,7 +2660,7 @@ mod tests { #[test] fn install_with_flags() { #[rustfmt::skip] - let r = flags_from_vec_safe(svec!["deno", "install", "--unstable", "--import-map", "import_map.json", "--no-remote", "--config", "tsconfig.json", "--no-check", "--reload", "--lock", "lock.json", "--lock-write", "--cert", "example.crt", "--cached-only", "--allow-read", "--allow-net", "--v8-flags=--help", "--seed", "1", "--inspect=127.0.0.1:9229", "--name", "file_server", "--root", "/foo", "--force", "https://deno.land/std/http/file_server.ts", "foo", "bar"]); + let r = flags_from_vec(svec!["deno", "install", "--unstable", "--import-map", "import_map.json", "--no-remote", "--config", "tsconfig.json", "--no-check", "--reload", "--lock", "lock.json", "--lock-write", "--cert", "example.crt", "--cached-only", "--allow-read", "--allow-net", "--v8-flags=--help", "--seed", "1", "--inspect=127.0.0.1:9229", "--name", "file_server", "--root", "/foo", "--force", "https://deno.land/std/http/file_server.ts", "foo", "bar"]); assert_eq!( r.unwrap(), Flags { @@ -2662,12 +2693,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 { @@ -2682,7 +2709,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 { @@ -2697,7 +2724,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()), @@ -2707,7 +2734,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", @@ -2724,9 +2751,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", @@ -2741,14 +2770,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 { @@ -2759,7 +2788,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 { @@ -2772,7 +2801,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 { @@ -2787,8 +2816,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 { @@ -2803,8 +2831,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 { @@ -2819,8 +2846,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 { @@ -2835,7 +2861,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", @@ -2863,7 +2889,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", @@ -2894,7 +2920,7 @@ mod tests { #[test] fn lock_write() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "run", "--lock-write", @@ -2917,7 +2943,7 @@ mod tests { #[test] fn test_with_flags() { #[rustfmt::skip] - let r = flags_from_vec_safe(svec!["deno", "test", "--unstable", "--no-run", "--filter", "- foo", "--coverage", "--allow-net", "--allow-none", "dir1/", "dir2/", "--", "arg1", "arg2"]); + let r = flags_from_vec(svec!["deno", "test", "--unstable", "--no-run", "--filter", "- foo", "--coverage", "--location", "https:foo", "--allow-net", "--allow-none", "dir1/", "dir2/", "--", "arg1", "arg2"]); assert_eq!( r.unwrap(), Flags { @@ -2931,6 +2957,7 @@ mod tests { }, unstable: true, coverage: true, + location: Some(Url::parse("https://foo/").unwrap()), allow_net: true, argv: svec!["arg1", "arg2"], ..Flags::default() @@ -2940,7 +2967,7 @@ mod tests { #[test] fn run_with_cafile() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "run", "--cert", @@ -2961,7 +2988,7 @@ mod tests { #[test] fn bundle_with_cafile() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "bundle", "--cert", @@ -2983,8 +3010,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 { @@ -3003,7 +3029,7 @@ mod tests { #[test] fn cache_with_cafile() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "cache", "--cert", @@ -3025,7 +3051,7 @@ mod tests { #[test] fn info_with_cafile() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "info", "--cert", @@ -3047,8 +3073,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 { @@ -3062,7 +3087,7 @@ mod tests { } ); - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "doc", "path/to/module.ts", @@ -3081,7 +3106,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 { @@ -3095,8 +3120,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 { @@ -3110,12 +3134,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 { @@ -3132,7 +3152,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/main.rs b/cli/main.rs index 47dd4087dcee53..f74f9ecbe7c9f0 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -792,12 +792,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 @@ -936,9 +952,5 @@ pub fn main() { } }; - let result = tokio_util::run_basic(fut); - if let Err(err) = result { - eprintln!("{}: {}", colors::red_bold("error"), err.to_string()); - 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 e29cf9f85bbdd9..9d665b1fda3e71 100644 --- a/cli/ops/runtime.rs +++ b/cli/ops/runtime.rs @@ -35,6 +35,7 @@ fn op_start( "args": gs.flags.argv.clone(), "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 f2e936ef25b469..ac6bc5aa76c94a 100644 --- a/cli/ops/worker_host.rs +++ b/cli/ops/worker_host.rs @@ -82,7 +82,7 @@ fn create_web_worker( let mut worker = WebWorker::new( name.clone(), permissions, - specifier, + specifier.clone(), program_state.clone(), has_deno_namespace, ); @@ -105,8 +105,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)?; @@ -230,6 +230,7 @@ fn op_create_worker( args: Value, _data: &mut [ZeroCopyBuf], ) -> Result { + let program_state = super::program_state(state); let args: CreateWorkerArgs = serde_json::from_value(args)?; let specifier = args.specifier.clone(); @@ -247,14 +248,16 @@ fn op_create_worker( let worker_id = state.take::(); state.put::(worker_id + 1); - let module_specifier = ModuleSpecifier::resolve_url(&specifier)?; + let module_specifier = match program_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 cli_state = super::program_state(state); let (join_handle, worker_handle) = run_worker_thread( worker_id, worker_name, - &cli_state, + &program_state, permissions, module_specifier, use_deno_namespace, diff --git a/cli/rt/99_main.js b/cli/rt/99_main.js index 68572f79a4329e..b4f105c5e29079 100644 --- a/cli/rt/99_main.js +++ b/cli/rt/99_main.js @@ -9,6 +9,7 @@ delete Object.prototype.__proto__; const util = window.__bootstrap.util; const eventTarget = window.__bootstrap.eventTarget; const globalInterfaces = window.__bootstrap.globalInterfaces; + const location = window.__bootstrap.location; const dispatchMinimal = window.__bootstrap.dispatchMinimal; const build = window.__bootstrap.build; const version = window.__bootstrap.version; @@ -194,6 +195,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, @@ -298,8 +301,19 @@ delete Object.prototype.__proto__; } }); - const { args, noColor, pid, ppid, unstableFlag } = runtimeStart(); - + const { + args, + location: locationHref, + noColor, + pid, + ppid, + unstableFlag, + } = runtimeStart(); + + if (locationHref != null) { + location.setLocationHref(locationHref); + fetch.setBaseUrl(locationHref); + } registerErrors(); const finalDenoNs = { @@ -333,7 +347,7 @@ delete Object.prototype.__proto__; util.log("args", args); } - function bootstrapWorkerRuntime(name, useDenoNamespace, internalName) { + function bootstrapWorkerRuntime(name, url, useDenoNamespace, internalName) { if (hasBootstrapped) { throw new Error("Worker runtime already bootstrapped"); } @@ -351,6 +365,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 b17fa22e56a5fd..7e3fe4345eb2d4 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/070_location.ts b/cli/tests/070_location.ts new file mode 100644 index 00000000000000..62fd34af2808b7 --- /dev/null +++ b/cli/tests/070_location.ts @@ -0,0 +1,10 @@ +// TODO(nayeemrmn): Add `Location` and `location` to `dlint`'s globals. +// deno-lint-ignore-file no-undef +console.log(Location); +console.log(Location.prototype); +console.log(location); +try { + location.hostname = "bar"; +} catch (error) { + console.log(error); +} diff --git a/cli/tests/070_location.ts.out b/cli/tests/070_location.ts.out new file mode 100644 index 00000000000000..66d470b6f6fd1c --- /dev/null +++ b/cli/tests/070_location.ts.out @@ -0,0 +1,22 @@ +[WILDCARD][Function: Location] +Location { [Symbol(Symbol.toStringTag)]: "Location" } +Location { + hash: [Getter/Setter], + host: [Getter/Setter], + hostname: [Getter/Setter], + href: [Getter/Setter], + origin: [Getter], + password: [Getter/Setter], + pathname: [Getter/Setter], + port: [Getter/Setter], + protocol: [Getter/Setter], + search: [Getter/Setter], + username: [Getter/Setter], + ancestorOrigins: [Getter], + assign: [Function: assign], + reload: [Function: reload], + replace: [Function: replace], + toString: [Function: toString] +} +NotSupportedError: Cannot set "location.hostname". +[WILDCARD] diff --git a/cli/tests/071_location_unset.ts b/cli/tests/071_location_unset.ts new file mode 100644 index 00000000000000..5c0518940aef19 --- /dev/null +++ b/cli/tests/071_location_unset.ts @@ -0,0 +1,5 @@ +// TODO(nayeemrmn): Add `Location` and `location` to `dlint`'s globals. +// deno-lint-ignore-file no-undef +console.log(Location); +console.log(Location.prototype); +console.log(location); diff --git a/cli/tests/071_location_unset.ts.out b/cli/tests/071_location_unset.ts.out new file mode 100644 index 00000000000000..2c030a7733958c --- /dev/null +++ b/cli/tests/071_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/072_location_relative_fetch.ts b/cli/tests/072_location_relative_fetch.ts new file mode 100644 index 00000000000000..d4764bf7f1f358 --- /dev/null +++ b/cli/tests/072_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/072_location_relative_fetch.ts.out b/cli/tests/072_location_relative_fetch.ts.out new file mode 100644 index 00000000000000..8151f6f889503f --- /dev/null +++ b/cli/tests/072_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 fba761a2c5fb58..002d3354045c55 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 43b9e091bf31af..022df676ff2f37 100644 --- a/cli/tests/integration_tests.rs +++ b/cli/tests/integration_tests.rs @@ -1154,7 +1154,7 @@ fn bundle_import_map_no_check() { .current_dir(util::root_path()) .arg("bundle") .arg("--no-check") - .arg("--importmap") + .arg("--import-map") .arg(import_map_path) .arg("--unstable") .arg(import) @@ -1521,7 +1521,7 @@ fn repl_test_pty_unpaired_braces() { } #[test] -fn run_watch_with_importmap_and_relative_paths() { +fn run_watch_with_import_map_and_relative_paths() { fn create_relative_tmp_file( directory: &TempDir, filename: &'static str, @@ -1554,7 +1554,7 @@ fn run_watch_with_importmap_and_relative_paths() { .arg("run") .arg("--unstable") .arg("--watch") - .arg("--importmap") + .arg("--import-map") .arg(&import_map_path) .arg(&file_to_watch) .env("NO_COLOR", "1") @@ -2165,6 +2165,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") @@ -2406,6 +2412,23 @@ itest!(_067_test_no_run_type_error { exit_code: 1, }); +itest!(_070_location { + args: "run --location https://foo/bar?baz#bat 070_location.ts", + output: "070_location.ts.out", +}); + +itest!(_071_location_unset { + args: "run 071_location_unset.ts", + output: "071_location_unset.ts.out", + exit_code: 1, +}); + +itest!(_072_location_relative_fetch { + args: "run --location http://127.0.0.1:4545/cli/tests/ --allow-net 072_location_relative_fetch.ts", + output: "072_location_relative_fetch.ts.out", + http_server: true, +}); + itest!(_073_worker_error { args: "run -A 073_worker_error.ts", output: "073_worker_error.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/unit/fetch_test.ts b/cli/tests/unit/fetch_test.ts index 681eefad41caf4..4fdbbe291da842 100644 --- a/cli/tests/unit/fetch_test.ts +++ b/cli/tests/unit/fetch_test.ts @@ -196,14 +196,6 @@ unitTest({ perms: { net: true } }, async function responseClone(): Promise< } }); -unitTest({ perms: { net: true } }, async function fetchEmptyInvalid(): Promise< - void -> { - await assertThrowsAsync(async () => { - await fetch(""); - }, URIError); -}); - unitTest( { perms: { net: true } }, async function fetchMultipartFormDataSuccess(): Promise { diff --git a/cli/tests/unit/request_test.ts b/cli/tests/unit/request_test.ts index 8132e0184632e2..da6a0e15bb6289 100644 --- a/cli/tests/unit/request_test.ts +++ b/cli/tests/unit/request_test.ts @@ -1,5 +1,5 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. -import { assert, assertEquals, assertThrows, unitTest } from "./test_util.ts"; +import { assert, assertEquals, unitTest } from "./test_util.ts"; unitTest(function fromInit(): void { const req = new Request("http://foo/", { @@ -46,8 +46,10 @@ unitTest(function methodNonString(): void { }); unitTest(function requestRelativeUrl(): void { - // TODO(nayeemrmn): Base from `--location` when implemented and set. - assertThrows(() => new Request("relative-url"), TypeError, "Invalid URL."); + assertEquals( + new Request("relative-url").url, + "http://js-unit-tests/foo/relative-url", + ); }); unitTest(async function cloneRequestBodyStream(): Promise { diff --git a/cli/tests/unit/unit_test_runner.ts b/cli/tests/unit/unit_test_runner.ts index 35a691ebde1b76..1f347ec9a340ba 100755 --- a/cli/tests/unit/unit_test_runner.ts +++ b/cli/tests/unit/unit_test_runner.ts @@ -97,6 +97,7 @@ function spawnWorkerRunner( Deno.execPath(), "run", "--unstable", // TODO(ry) be able to test stable vs unstable + "--location=http://js-unit-tests/foo/bar", "-A", "cli/tests/unit/unit_test_runner.ts", "--worker", diff --git a/cli/tests/workers_test.ts b/cli/tests/workers_test.ts index 7cff6848bbc2e4..98dde55aa4783d 100644 --- a/cli/tests/workers_test.ts +++ b/cli/tests/workers_test.ts @@ -358,3 +358,41 @@ 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 { + // TODO(nayeemrmn): Add `Location` and `location` to `dlint`'s globals. + // deno-lint-ignore no-undef + 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/tools/installer.rs b/cli/tools/installer.rs index e0a99873a12d08..891ed3e067b999 100644 --- a/cli/tools/installer.rs +++ b/cli/tools/installer.rs @@ -187,6 +187,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/worker.rs b/cli/worker.rs index 74bee1d037b562..c6d5d48b48ffcf 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -700,7 +700,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..ac040af6e92c55 --- /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/path main.ts + +console.log(location.href); +// "https://example.com/path" +``` + +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/path 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 f255b3fa19bd00..bc8ce739e3af5e 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 d0bc6d979cde74..cede1951685cba 100644 --- a/op_crates/fetch/26_fetch.js +++ b/op_crates/fetch/26_fetch.js @@ -5,6 +5,7 @@ // provided by "deno_web" const { URLSearchParams } = window.__bootstrap.url; + const { getLocationHref } = window.__bootstrap.location; const { requiredArguments } = window.__bootstrap.fetchUtil; const { ReadableStream, isReadableStreamDisturbed } = @@ -987,8 +988,10 @@ this.credentials = input.credentials; this._stream = input._stream; } else { - // TODO(nayeemrmn): Base from `--location` when implemented and set. - this.url = new URL(String(input)).href; + const baseUrl = getLocationHref(); + this.url = baseUrl != null + ? new URL(String(input), baseUrl).href + : new URL(String(input)).href; } if (init && "method" in init && init.method) { @@ -1175,20 +1178,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) { @@ -1385,6 +1393,7 @@ Blob, DomFile, FormData, + setBaseUrl, fetch, Request, Response, diff --git a/op_crates/fetch/lib.rs b/op_crates/fetch/lib.rs index 49d951d8f411a7..6641c2dad8d510 100644 --- a/op_crates/fetch/lib.rs +++ b/op_crates/fetch/lib.rs @@ -96,12 +96,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(); @@ -121,10 +121,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))); } @@ -132,10 +138,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..d56ccc1e4d4d52 --- /dev/null +++ b/op_crates/web/12_location.js @@ -0,0 +1,227 @@ +// 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, + getLocationHref() { + return location?.href; + }, + }; +})(this); diff --git a/op_crates/web/lib.deno_web.d.ts b/op_crates/web/lib.deno_web.d.ts index 2175a5192b970d..8cb79213baf387 100644 --- a/op_crates/web/lib.deno_web.d.ts +++ b/op_crates/web/lib.deno_web.d.ts @@ -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 958d1117727780..33bb0a33f4ed49 100644 --- a/op_crates/web/lib.rs +++ b/op_crates/web/lib.rs @@ -61,6 +61,10 @@ pub fn init(isolate: &mut JsRuntime) { include_str!("08_text_encoding.js"), ), ("deno:op_crates/web/11_url.js", include_str!("11_url.js")), + ( + "deno:op_crates/web/12_location.js", + include_str!("12_location.js"), + ), ( "deno:op_crates/web/21_filereader.js", include_str!("21_filereader.js"),