diff --git a/cli/flags.rs b/cli/flags.rs index da0f8ad0e4..6b4e5aa5f6 100644 --- a/cli/flags.rs +++ b/cli/flags.rs @@ -11,6 +11,7 @@ use deno_core::serde::Deserialize; use deno_core::serde::Deserializer; use deno_core::serde::Serialize; use deno_core::serde::Serializer; +use deno_core::url::Url; use deno_runtime::permissions::PermissionsOptions; use log::Level; use std::fmt; @@ -172,6 +173,7 @@ pub struct Flags { pub allow_read: Option>, pub allow_run: bool, pub allow_write: Option>, + pub location: Option, pub cache_blocklist: Vec, pub ca_file: Option, pub cached_only: bool, @@ -324,19 +326,13 @@ 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 version = crate::version::deno(); let app = clap_root(&*version); - 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(); @@ -597,7 +593,7 @@ fn eval_parse(flags: &mut Flags, matches: &clap::ArgMatches) { print, code, as_typescript, - } + }; } fn info_parse(flags: &mut Flags, matches: &clap::ArgMatches) { @@ -671,6 +667,7 @@ fn runtime_args<'a, 'b>( }; app .arg(cached_only_arg()) + .arg(location_arg()) .arg(v8_flags_arg()) .arg(seed_arg()) } @@ -689,8 +686,10 @@ fn runtime_args_parse( if include_inspector { inspect_arg_parse(flags, matches); } + location_arg_parse(flags, matches); v8_flags_arg_parse(flags, matches); seed_arg_parse(flags, matches); + inspect_arg_parse(flags, matches); } fn run_parse(flags: &mut Flags, matches: &clap::ArgMatches) { @@ -1487,6 +1486,30 @@ 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) { + flags.location = matches + .value_of("location") + .map(|href| Url::parse(href).unwrap()); +} + +fn location_arg<'a, 'b>() -> Arg<'a, 'b> { + Arg::with_name("location") + .long("location") + .takes_value(true) + .value_name("HREF") + .validator(|href| { + let url = Url::parse(&href); + if url.is_err() { + return Err("Failed to parse URL".to_string()); + } + if !["http", "https"].contains(&url.unwrap().scheme()) { + return Err("Expected protocol \"http\" or \"https\"".to_string()); + } + Ok(()) + }) + .help("Value of 'globalThis.location' used by some web APIs") +} + fn inspect_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { app .arg( @@ -1729,7 +1752,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) { @@ -1758,7 +1780,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, @@ -1772,15 +1794,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, @@ -1800,15 +1821,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, @@ -1824,7 +1845,7 @@ mod tests { #[test] fn run_watch() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "run", "--unstable", @@ -1847,13 +1868,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 { @@ -1869,7 +1885,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 { @@ -1881,7 +1897,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", @@ -1901,7 +1917,7 @@ mod tests { #[test] fn script_args() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "run", "--allow-net", @@ -1924,7 +1940,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 { @@ -1945,8 +1961,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 { @@ -1961,8 +1976,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 { @@ -1980,7 +1994,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", @@ -2004,8 +2018,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 { @@ -2021,7 +2034,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 { @@ -2034,7 +2047,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 { @@ -2047,7 +2060,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 { @@ -2062,7 +2075,7 @@ mod tests { } ); - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "fmt", "--check", @@ -2088,7 +2101,7 @@ mod tests { #[test] fn language_server() { - let r = flags_from_vec_safe(svec!["deno", "lsp"]); + let r = flags_from_vec(svec!["deno", "lsp"]); assert_eq!( r.unwrap(), Flags { @@ -2100,7 +2113,7 @@ mod tests { #[test] fn lint() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "lint", "--unstable", @@ -2124,7 +2137,7 @@ mod tests { } ); - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "lint", "--unstable", @@ -2147,7 +2160,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 { @@ -2162,7 +2175,7 @@ mod tests { } ); - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "lint", "--unstable", @@ -2186,7 +2199,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 { @@ -2198,7 +2211,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 { @@ -2212,7 +2225,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 { @@ -2224,7 +2237,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 { @@ -2237,7 +2250,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 { @@ -2249,7 +2262,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 { @@ -2261,7 +2274,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 { @@ -2276,13 +2289,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 { @@ -2297,8 +2305,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 { @@ -2321,7 +2328,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 { @@ -2344,12 +2351,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 { @@ -2373,7 +2376,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 { @@ -2392,6 +2395,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: svec!["--help", "--random-seed=1"], seed: Some(1), inspect: Some("127.0.0.1:9229".parse().unwrap()), @@ -2409,7 +2413,7 @@ mod tests { #[test] fn eval_args() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "eval", "console.log(Deno.args)", @@ -2439,7 +2443,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 { @@ -2460,7 +2464,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 { @@ -2476,6 +2480,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: svec!["--help", "--random-seed=1"], seed: Some(1), inspect: Some("127.0.0.1:9229".parse().unwrap()), @@ -2496,7 +2501,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()), @@ -2519,7 +2524,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()), @@ -2539,7 +2544,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", @@ -2559,7 +2564,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 { @@ -2574,7 +2579,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", @@ -2600,8 +2605,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 { @@ -2617,7 +2621,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", @@ -2640,8 +2644,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 { @@ -2657,9 +2660,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 { @@ -2675,7 +2677,7 @@ mod tests { #[test] fn bundle_watch() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "bundle", "--watch", @@ -2698,7 +2700,7 @@ mod tests { #[test] fn run_import_map() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "run", "--unstable", @@ -2720,7 +2722,7 @@ mod tests { #[test] fn info_import_map() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "info", "--unstable", @@ -2743,7 +2745,7 @@ mod tests { #[test] fn cache_import_map() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "cache", "--unstable", @@ -2765,7 +2767,7 @@ mod tests { #[test] fn doc_import_map() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "doc", "--unstable", @@ -2791,7 +2793,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 { @@ -2805,8 +2807,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 { @@ -2822,7 +2823,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", @@ -2845,7 +2846,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" @@ -2868,7 +2869,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 { @@ -2901,12 +2902,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 { @@ -2921,7 +2918,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 { @@ -2936,7 +2933,7 @@ mod tests { #[test] fn completions() { - let r = flags_from_vec_safe(svec!["deno", "completions", "zsh"]).unwrap(); + let r = flags_from_vec(svec!["deno", "completions", "zsh"]).unwrap(); match r.subcommand { DenoSubcommand::Completions { buf } => assert!(!buf.is_empty()), @@ -2946,7 +2943,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", @@ -2963,9 +2960,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", @@ -2980,14 +2979,14 @@ mod tests { subcommand: DenoSubcommand::Run { script: "script.ts".to_string(), }, + location: Some(Url::parse("https://foo/").unwrap()), allow_read: Some(vec![]), 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 { @@ -2998,7 +2997,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 { @@ -3011,7 +3010,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 { @@ -3026,8 +3025,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 { @@ -3042,8 +3040,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 { @@ -3058,8 +3055,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 { @@ -3074,7 +3070,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", @@ -3102,7 +3098,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", @@ -3133,7 +3129,7 @@ mod tests { #[test] fn lock_write() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "run", "--lock-write", @@ -3156,7 +3152,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=cov", "--allow-net", "--allow-none", "dir1/", "dir2/", "--", "arg1", "arg2"]); + let r = flags_from_vec(svec!["deno", "test", "--unstable", "--no-run", "--filter", "- foo", "--coverage=cov", "--location", "https:foo", "--allow-net", "--allow-none", "dir1/", "dir2/", "--", "arg1", "arg2"]); assert_eq!( r.unwrap(), Flags { @@ -3170,6 +3166,7 @@ mod tests { }, unstable: true, coverage_dir: Some("cov".to_string()), + location: Some(Url::parse("https://foo/").unwrap()), allow_net: Some(vec![]), argv: svec!["arg1", "arg2"], ..Flags::default() @@ -3179,7 +3176,7 @@ mod tests { #[test] fn run_with_cafile() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "run", "--cert", @@ -3200,7 +3197,7 @@ mod tests { #[test] fn bundle_with_cafile() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "bundle", "--cert", @@ -3222,8 +3219,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 { @@ -3243,7 +3239,7 @@ mod tests { #[test] fn cache_with_cafile() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "cache", "--cert", @@ -3265,7 +3261,7 @@ mod tests { #[test] fn info_with_cafile() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "info", "--cert", @@ -3287,8 +3283,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 { @@ -3302,7 +3297,7 @@ mod tests { } ); - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "doc", "path/to/module.ts", @@ -3321,7 +3316,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 { @@ -3335,8 +3330,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 { @@ -3350,12 +3344,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 { @@ -3372,7 +3362,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 { @@ -3387,7 +3377,7 @@ mod tests { #[test] fn compile() { - let r = flags_from_vec_safe(svec![ + let r = flags_from_vec(svec![ "deno", "compile", "https://deno.land/std/examples/colors.ts" @@ -3408,7 +3398,7 @@ mod tests { #[test] fn compile_with_flags() { #[rustfmt::skip] - let r = flags_from_vec_safe(svec!["deno", "compile", "--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", "--output", "colors", "https://deno.land/std/examples/colors.ts", "foo", "bar"]); + let r = flags_from_vec(svec!["deno", "compile", "--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", "--allow-read", "--allow-net", "--v8-flags=--help", "--seed", "1", "--output", "colors", "https://deno.land/std/examples/colors.ts", "foo", "bar"]); assert_eq!( r.unwrap(), Flags { @@ -3427,6 +3417,7 @@ mod tests { lock_write: true, ca_file: Some("example.crt".to_string()), cached_only: true, + location: Some(Url::parse("https://foo/").unwrap()), allow_read: Some(vec![]), allow_net: Some(vec![]), v8_flags: svec!["--help", "--random-seed=1"], diff --git a/cli/main.rs b/cli/main.rs index 8210eb5c71..0669916248 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -204,6 +204,7 @@ pub fn create_main_worker( ts_version: version::TYPESCRIPT.to_string(), no_color: !colors::use_color(), get_error_class_fn: Some(&crate::errors::get_error_class_name), + location: program_state.flags.location.clone(), }; let mut worker = MainWorker::from_options(main_module, permissions, &options); @@ -1223,6 +1224,21 @@ fn get_subcommand( } } +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 @@ -1233,16 +1249,12 @@ pub fn main() { std::process::exit(1); } - let flags = flags::flags_from_vec(args); + let flags = + unwrap_or_exit(flags::flags_from_vec(args).map_err(AnyError::from)); if !flags.v8_flags.is_empty() { init_v8_flags(&*flags.v8_flags); } init_logger(flags.log_level); - let subcommand_future = get_subcommand(flags); - let result = tokio_util::run_basic(subcommand_future); - if let Err(err) = result { - eprintln!("{}: {}", colors::red_bold("error"), err.to_string()); - std::process::exit(1); - } + unwrap_or_exit(tokio_util::run_basic(get_subcommand(flags))); } diff --git a/cli/standalone.rs b/cli/standalone.rs index 8379358a02..8d95d30f2c 100644 --- a/cli/standalone.rs +++ b/cli/standalone.rs @@ -167,7 +167,7 @@ async fn run(source_code: String, metadata: Metadata) -> Result<(), AnyError> { // TODO(nayeemrmn): Unify this Flags -> WorkerOptions mapping with `deno run`. let options = WorkerOptions { apply_source_maps: false, - args: flags.argv.clone(), + args: flags.argv, debug_flag: flags.log_level.map_or(false, |l| l == log::Level::Debug), user_agent: crate::http_util::get_user_agent(), unstable: flags.unstable, @@ -183,6 +183,7 @@ async fn run(source_code: String, metadata: Metadata) -> Result<(), AnyError> { ts_version: version::TYPESCRIPT.to_string(), no_color: !colors::use_color(), get_error_class_fn: Some(&crate::errors::get_error_class_name), + location: flags.location, }; let mut worker = MainWorker::from_options(main_module.clone(), permissions, &options); @@ -297,6 +298,7 @@ pub fn compile_to_runtime_flags( import_map_path: None, inspect: None, inspect_brk: None, + location: flags.location, lock: None, lock_write: false, log_level: flags.log_level, diff --git a/cli/tests/070_location.ts b/cli/tests/070_location.ts new file mode 100644 index 0000000000..62fd34af28 --- /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 0000000000..66d470b6f6 --- /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 0000000000..5c0518940a --- /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 0000000000..2c030a7733 --- /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 0000000000..d4764bf7f1 --- /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 0000000000..8151f6f889 --- /dev/null +++ b/cli/tests/072_location_relative_fetch.ts.out @@ -0,0 +1,2 @@ +[WILDCARD]Hello, world! + diff --git a/cli/tests/077_fetch_empty.ts b/cli/tests/077_fetch_empty.ts new file mode 100644 index 0000000000..b10a9094eb --- /dev/null +++ b/cli/tests/077_fetch_empty.ts @@ -0,0 +1 @@ +await fetch(""); diff --git a/cli/tests/077_fetch_empty.ts.out b/cli/tests/077_fetch_empty.ts.out new file mode 100644 index 0000000000..9dfccf10c0 --- /dev/null +++ b/cli/tests/077_fetch_empty.ts.out @@ -0,0 +1,2 @@ +[WILDCARD]error: Uncaught (in promise) URIError: relative URL without a base +[WILDCARD] diff --git a/cli/tests/complex_permissions_test.ts b/cli/tests/complex_permissions_test.ts index fba761a2c5..002d335404 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 0000000000..af5626b4a1 --- /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 68cfe0b507..aabeb1f77b 100644 --- a/cli/tests/integration_tests.rs +++ b/cli/tests/integration_tests.rs @@ -1246,7 +1246,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) @@ -1689,7 +1689,7 @@ fn repl_test_pty_bad_input() { #[test] #[ignore] -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, @@ -1722,7 +1722,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") @@ -2307,6 +2307,8 @@ fn workers() { .current_dir(util::tests_path()) .arg("test") .arg("--reload") + .arg("--location") + .arg("http://127.0.0.1:4545/cli/tests/") .arg("--allow-net") .arg("--allow-read") .arg("--unstable") @@ -2548,6 +2550,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", @@ -2570,6 +2589,12 @@ itest!(_076_info_json_deps_order { output: "076_info_json_deps_order.out", }); +itest!(_077_fetch_empty { + args: "run -A 077_fetch_empty.ts", + output: "077_fetch_empty.ts.out", + exit_code: 1, +}); + itest!(js_import_detect { args: "run --quiet --reload js_import_detect.ts", output: "js_import_detect.ts.out", @@ -5200,16 +5225,14 @@ fn web_platform_tests() { .tempfile() .unwrap(); - let bundle = concat_bundle( - files, - file.path(), - format!("window.location = {{search: \"{}\"}};\n", variant), - ); + let bundle = concat_bundle(files, file.path(), "".to_string()); file.write_all(bundle.as_bytes()).unwrap(); let child = util::deno_cmd() .current_dir(test_file_path.parent().unwrap()) .arg("run") + .arg("--location") + .arg(&format!("http://web-platform-tests/?{}", variant)) .arg("-A") .arg(file.path()) .arg(deno_core::serde_json::to_string(&expect_fail).unwrap()) diff --git a/cli/tests/subdir/worker_location.ts b/cli/tests/subdir/worker_location.ts new file mode 100644 index 0000000000..4800323501 --- /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 e5389d0240..6945b2e2a0 100644 --- a/cli/tests/unit/fetch_test.ts +++ b/cli/tests/unit/fetch_test.ts @@ -221,17 +221,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 8132e01846..da6a0e15bb 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 35a691ebde..1f347ec9a3 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 c35ea72f5f..9cbc864bd6 100644 --- a/cli/tests/workers_test.ts +++ b/cli/tests/workers_test.ts @@ -614,3 +614,41 @@ Deno.test("Worker with disabled permissions", async function () { await promise; worker.terminate(); }); + +Deno.test({ + name: "worker location", + fn: async function (): Promise { + const promise = deferred(); + 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, "http://127.0.0.1:4545/cli/tests/"); + const promise = deferred(); + const w = new Worker( + "./workers/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 f6eb331120..e047631722 100644 --- a/cli/tools/installer.rs +++ b/cli/tools/installer.rs @@ -202,6 +202,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/docs/runtime/location_api.md b/docs/runtime/location_api.md new file mode 100644 index 0000000000..6e73418285 --- /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` or `https` 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 14e028705e..82e1e5b2db 100644 --- a/docs/runtime/workers.md +++ b/docs/runtime/workers.md @@ -9,10 +9,11 @@ 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` constructor and `import.meta.url` to easily create a -specifier for some nearby script. +Use of relative module specifiers in the main worker 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. Dedicated workers, however, have a location +and this capability by default. ```ts // Good diff --git a/docs/toc.json b/docs/toc.json index df2c004c5d..6d1896999c 100644 --- a/docs/toc.json +++ b/docs/toc.json @@ -23,6 +23,7 @@ "permission_apis": "Permission APIs", "compiler_apis": "Compiler APIs", "web_platform_apis": "Web Platform APIs", + "location_api": "Location API", "workers": "Workers" } }, diff --git a/op_crates/fetch/26_fetch.js b/op_crates/fetch/26_fetch.js index 379c88e2fc..0d405d4ec6 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 91e44f75c2..c2e458d89b 100644 --- a/op_crates/fetch/lib.rs +++ b/op_crates/fetch/lib.rs @@ -100,12 +100,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(); @@ -125,10 +125,16 @@ where None => Method::GET, }; - let url_ = Url::parse(&url)?; + let base_url = match args.base_url { + Some(base_url) => Some(Url::parse(&base_url)?), + _ => None, + }; + let 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))); } @@ -136,10 +142,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 0000000000..d56ccc1e4d --- /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 33d8677530..491a0ee842 100644 --- a/op_crates/web/lib.deno_web.d.ts +++ b/op_crates/web/lib.deno_web.d.ts @@ -312,3 +312,69 @@ 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`. */ +declare class Location { + constructor(); + /** 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: Location; diff --git a/op_crates/web/lib.rs b/op_crates/web/lib.rs index 958d111772..33bb0a33f4 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"), diff --git a/runtime/examples/hello_runtime.rs b/runtime/examples/hello_runtime.rs index c93bbc90b1..829b2d36ca 100644 --- a/runtime/examples/hello_runtime.rs +++ b/runtime/examples/hello_runtime.rs @@ -39,6 +39,7 @@ async fn main() -> Result<(), AnyError> { ts_version: "x".to_string(), no_color: false, get_error_class_fn: Some(&get_error_class_name), + location: None, }; let js_path = diff --git a/runtime/js/11_workers.js b/runtime/js/11_workers.js index 4989cd4b50..57f4207289 100644 --- a/runtime/js/11_workers.js +++ b/runtime/js/11_workers.js @@ -3,6 +3,7 @@ ((window) => { const core = window.Deno.core; const { Window } = window.__bootstrap.globalInterfaces; + const { getLocationHref } = window.__bootstrap.location; const { log, pathFromURL } = window.__bootstrap.util; const { defineEventHandler } = window.__bootstrap.webUtil; const build = window.__bootstrap.build.build; @@ -127,6 +128,7 @@ constructor(specifier, options = {}) { super(); + specifier = String(specifier); const { deno = {}, name = "unknown", @@ -177,6 +179,16 @@ const hasSourceCode = false; const sourceCode = decoder.decode(new Uint8Array()); + if ( + specifier.startsWith("./") || specifier.startsWith("../") || + specifier.startsWith("/") || type == "classic" + ) { + const baseUrl = getLocationHref(); + if (baseUrl != null) { + specifier = new URL(specifier, baseUrl).href; + } + } + const { id } = createWorker( specifier, hasSourceCode, diff --git a/runtime/js/99_main.js b/runtime/js/99_main.js index f38d51936a..284bef48c8 100644 --- a/runtime/js/99_main.js +++ b/runtime/js/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; @@ -196,6 +197,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, @@ -290,7 +293,19 @@ delete Object.prototype.__proto__; defineEventHandler(window, "unload", null); runtimeStart(runtimeOptions); - const { args, noColor, pid, ppid, unstableFlag } = runtimeOptions; + const { + args, + location: locationHref, + noColor, + pid, + ppid, + unstableFlag, + } = runtimeOptions; + + if (locationHref != null) { + location.setLocationHref(locationHref); + fetch.setBaseUrl(locationHref); + } registerErrors(); @@ -349,8 +364,11 @@ delete Object.prototype.__proto__; runtimeOptions, internalName ?? name, ); - const { unstableFlag, pid, noColor, args } = runtimeOptions; + const { unstableFlag, pid, noColor, args, location: locationHref } = + runtimeOptions; + location.setLocationHref(locationHref); + fetch.setBaseUrl(locationHref); registerErrors(); const finalDenoNs = { diff --git a/runtime/js/README.md b/runtime/js/README.md index b17fa22e56..7e3fe4345e 100644 --- a/runtime/js/README.md +++ b/runtime/js/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/runtime/web_worker.rs b/runtime/web_worker.rs index 313c711773..3a1f5316c1 100644 --- a/runtime/web_worker.rs +++ b/runtime/web_worker.rs @@ -130,6 +130,7 @@ pub struct WebWorker { terminate_rx: mpsc::Receiver<()>, handle: WebWorkerHandle, pub use_deno_namespace: bool, + pub main_module: ModuleSpecifier, } pub struct WebWorkerOptions { @@ -197,6 +198,7 @@ impl WebWorker { terminate_rx, handle, use_deno_namespace: options.use_deno_namespace, + main_module: main_module.clone(), }; { @@ -286,6 +288,7 @@ impl WebWorker { "tsVersion": options.ts_version, "unstableFlag": options.unstable, "v8Version": deno_core::v8_version(), + "location": self.main_module, }); let runtime_options_str = diff --git a/runtime/worker.rs b/runtime/worker.rs index 9326d632e2..6ebb2fb36f 100644 --- a/runtime/worker.rs +++ b/runtime/worker.rs @@ -63,6 +63,7 @@ pub struct WorkerOptions { /// Sets `Deno.noColor` in JS runtime. pub no_color: bool, pub get_error_class_fn: Option, + pub location: Option, } impl MainWorker { @@ -179,6 +180,7 @@ impl MainWorker { "tsVersion": options.ts_version, "unstableFlag": options.unstable, "v8Version": deno_core::v8_version(), + "location": options.location, }); let script = format!( @@ -282,6 +284,7 @@ mod tests { ts_version: "x".to_string(), no_color: true, get_error_class_fn: None, + location: None, }; MainWorker::from_options(main_module, permissions, &options)