From 42408febe8cdf9e30ff8d1a3bb13f4994906c53b Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Tue, 12 Feb 2019 21:14:02 -0500 Subject: [PATCH] Add window.location --- BUILD.gn | 1 + js/dom_types.ts | 70 +++++++++++++++++++++++++++++ js/globals.ts | 2 + js/lib.web_assembly.d.ts | 5 +++ js/location.ts | 52 +++++++++++++++++++++ js/location_test.ts | 8 ++++ js/main.ts | 12 +++-- js/unit_tests.ts | 1 + src/deno_dir.rs | 61 +++++++++++++++---------- src/isolate.rs | 16 +++++++ src/main.rs | 11 +++-- src/msg.fbs | 1 + src/ops.rs | 3 ++ tests/error_010_nonexistent_arg.out | 2 +- tests/if_main.test | 2 + tests/if_main.ts | 7 +++ tests/if_main.ts.out | 1 + tests/import_meta.test | 2 + tests/import_meta.ts | 3 ++ tests/import_meta.ts.out | 2 + tests/import_meta2.ts | 1 + tests/imports_meta.js | 3 -- tests/imports_meta.js.out | 2 - tests/imports_meta.test | 2 - tests/imports_meta2.js | 1 - website/manual.md | 11 +++++ 26 files changed, 241 insertions(+), 41 deletions(-) create mode 100644 js/location.ts create mode 100644 js/location_test.ts create mode 100644 tests/if_main.test create mode 100644 tests/if_main.ts create mode 100644 tests/if_main.ts.out create mode 100644 tests/import_meta.test create mode 100644 tests/import_meta.ts create mode 100644 tests/import_meta.ts.out create mode 100644 tests/import_meta2.ts delete mode 100644 tests/imports_meta.js delete mode 100644 tests/imports_meta.js.out delete mode 100644 tests/imports_meta.test delete mode 100644 tests/imports_meta2.js diff --git a/BUILD.gn b/BUILD.gn index 456da61feb..3593751524 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -80,6 +80,7 @@ ts_sources = [ "js/io.ts", "js/libdeno.ts", "js/lib.web_assembly.d.ts", + "js/location.ts", "js/main.ts", "js/make_temp_dir.ts", "js/metrics.ts", diff --git a/js/dom_types.ts b/js/dom_types.ts index 0c6814dae6..651eece9af 100644 --- a/js/dom_types.ts +++ b/js/dom_types.ts @@ -536,3 +536,73 @@ export interface Response extends Body { /** Creates a clone of a `Response` object. */ clone(): Response; } + +export 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. + */ + readonly ancestorOrigins: string[]; + /** + * Returns the Location object's URL's fragment (includes leading "#" if + * non-empty). + * Can be set, to navigate to the same URL with a changed fragment (ignores + * leading "#"). + */ + hash: string; + /** + * Returns the Location object's URL's host and port (if different from the + * default port for the scheme). Can be set, to navigate to the same URL with + * a changed host and port. + */ + host: string; + /** + * Returns the Location object's URL's host. Can be set, to navigate to the + * same URL with a changed host. + */ + hostname: string; + /** + * Returns the Location object's URL. Can be set, to navigate to the given + * URL. + */ + href: string; + /** Returns the Location object's URL's origin. */ + readonly origin: string; + /** + * Returns the Location object's URL's path. + * Can be set, to navigate to the same URL with a changed path. + */ + pathname: string; + /** + * Returns the Location object's URL's port. + * Can be set, to navigate to the same URL with a changed port. + */ + port: string; + /** + * Returns the Location object's URL's scheme. + * Can be set, to navigate to the same URL with a changed scheme. + */ + protocol: string; + /** + * Returns the Location object's URL's query (includes leading "?" if + * non-empty). Can be set, to navigate to the same URL with a changed query + * (ignores leading "?"). + */ + search: string; + /** + * Navigates to the given URL. + */ + assign(url: string): void; + /** + * Reloads the current page. + */ + reload(): void; + /** @deprecated */ + reload(forcedReload: boolean): void; + /** + * Removes the current page from the session history and navigates to the + * given URL. + */ + replace(url: string): void; +} diff --git a/js/globals.ts b/js/globals.ts index e0fa6ef122..935890be4e 100644 --- a/js/globals.ts +++ b/js/globals.ts @@ -58,6 +58,8 @@ window.clearInterval = timers.clearTimer; window.console = new consoleTypes.Console(libdeno.print); window.setTimeout = timers.setTimeout; window.setInterval = timers.setInterval; +// tslint:disable-next-line:no-any +window.location = (undefined as unknown) as domTypes.Location; // When creating the runtime type library, we use modifications to `window` to // determine what is in the global namespace. When we put a class in the diff --git a/js/lib.web_assembly.d.ts b/js/lib.web_assembly.d.ts index a5747b30e6..1ec6a7943a 100644 --- a/js/lib.web_assembly.d.ts +++ b/js/lib.web_assembly.d.ts @@ -161,3 +161,8 @@ declare namespace WebAssembly { constructor(message: string, fileName?: string, lineNumber?: string); } } + +// TODO Move ImportMeta intos its own lib.import_meta.d.ts file? +interface ImportMeta { + url: string; +} diff --git a/js/location.ts b/js/location.ts new file mode 100644 index 0000000000..5e7cb07b45 --- /dev/null +++ b/js/location.ts @@ -0,0 +1,52 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +import { URL } from "./url"; +import { notImplemented } from "./util"; +import { Location } from "./dom_types"; +import { window } from "./globals"; + +export function setLocation(url: string): void { + window.location = new LocationImpl(url); + Object.freeze(window.location); +} + +export class LocationImpl implements Location { + constructor(url: string) { + const u = new URL(url); + this.url = u; + this.hash = u.hash; + this.host = u.host; + this.href = u.href; + this.hostname = u.hostname; + this.origin = u.protocol + "//" + u.host; + this.pathname = u.pathname; + this.protocol = u.protocol; + this.port = u.port; + this.search = u.search; + } + + private url: URL; + + toString(): string { + return this.url.toString(); + } + + readonly ancestorOrigins: string[] = []; + hash: string; + host: string; + hostname: string; + href: string; + readonly origin: string; + pathname: string; + port: string; + protocol: string; + search: string; + assign(url: string): void { + throw notImplemented(); + } + reload(): void { + throw notImplemented(); + } + replace(url: string): void { + throw notImplemented(); + } +} diff --git a/js/location_test.ts b/js/location_test.ts new file mode 100644 index 0000000000..2302c32ed1 --- /dev/null +++ b/js/location_test.ts @@ -0,0 +1,8 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +import { test, assert } from "./test_util.ts"; + +test(function locationBasic() { + // location example: file:///Users/rld/src/deno/js/unit_tests.ts + console.log("location", window.location.toString()); + assert(window.location.toString().endsWith("unit_tests.ts")); +}); diff --git a/js/main.ts b/js/main.ts index a5aae51a1d..b67f188ac5 100644 --- a/js/main.ts +++ b/js/main.ts @@ -4,12 +4,13 @@ import "./globals"; -import { log } from "./util"; +import { assert, log } from "./util"; import * as os from "./os"; import { libdeno } from "./libdeno"; import { args } from "./deno"; import { replLoop } from "./repl"; import { setVersions } from "./version"; +import { setLocation } from "./location"; // builtin modules import * as deno from "./deno"; @@ -42,6 +43,12 @@ export default function denoMain() { os.exit(0); } + const mainModule = startResMsg.mainModule(); + if (mainModule) { + assert(mainModule.length > 0); + setLocation(mainModule); + } + const cwd = startResMsg.cwd(); log("cwd", cwd); @@ -51,8 +58,7 @@ export default function denoMain() { log("args", args); Object.freeze(args); - const inputFn = args[0]; - if (!inputFn) { + if (!mainModule) { replLoop(); } } diff --git a/js/unit_tests.ts b/js/unit_tests.ts index c8479145b9..91c1745b6b 100644 --- a/js/unit_tests.ts +++ b/js/unit_tests.ts @@ -22,6 +22,7 @@ import "./files_test.ts"; import "./form_data_test.ts"; import "./globals_test.ts"; import "./headers_test.ts"; +import "./location_test.ts"; import "./make_temp_dir_test.ts"; import "./metrics_test.ts"; import "./mixins/dom_iterable_test.ts"; diff --git a/src/deno_dir.rs b/src/deno_dir.rs index 9682d93258..1d101dd8a2 100644 --- a/src/deno_dir.rs +++ b/src/deno_dir.rs @@ -400,14 +400,11 @@ impl DenoDir { } /// Returns (module name, local filename) - pub fn resolve_module( + pub fn resolve_module_url( self: &Self, specifier: &str, referrer: &str, - ) -> Result<(String, String), url::ParseError> { - let module_name; - let filename; - + ) -> Result { let specifier = self.src_file_to_url(specifier); let mut referrer = self.src_file_to_url(referrer); @@ -422,8 +419,7 @@ impl DenoDir { referrer = referrer_path.to_str().unwrap().to_string() + "/"; } - let j: Url = if is_remote(&specifier) || Path::new(&specifier).is_absolute() - { + let j = if is_remote(&specifier) || Path::new(&specifier).is_absolute() { parse_local_or_remote(&specifier)? } else if referrer.ends_with('/') { let r = Url::from_directory_path(&referrer); @@ -437,21 +433,29 @@ impl DenoDir { let base = parse_local_or_remote(&referrer)?; base.join(specifier.as_ref())? }; + Ok(j) + } + /// Returns (module name, local filename) + pub fn resolve_module( + self: &Self, + specifier: &str, + referrer: &str, + ) -> Result<(String, String), url::ParseError> { + let j = self.resolve_module_url(specifier, referrer)?; + + let module_name = j.to_string(); + let filename; match j.scheme() { "file" => { - let p = deno_fs::normalize_path(j.to_file_path().unwrap().as_ref()); - module_name = p.clone(); - filename = p; + filename = deno_fs::normalize_path(j.to_file_path().unwrap().as_ref()); } "https" => { - module_name = j.to_string(); filename = deno_fs::normalize_path( get_cache_filename(self.deps_https.as_path(), &j).as_ref(), ) } "http" => { - module_name = j.to_string(); filename = deno_fs::normalize_path( get_cache_filename(self.deps_http.as_path(), &j).as_ref(), ) @@ -517,7 +521,7 @@ fn is_remote(module_name: &str) -> bool { } fn parse_local_or_remote(p: &str) -> Result { - if is_remote(p) { + if is_remote(p) || p.starts_with("file:") { Url::parse(p) } else { Url::from_file_path(p).map_err(|_err| url::ParseError::IdnaError) @@ -605,6 +609,16 @@ mod tests { }; } + macro_rules! file_url { + ($path:expr) => { + if cfg!(target_os = "windows") { + concat!("file:///C:", $path) + } else { + concat!("file://", $path) + } + }; + } + #[test] fn test_get_cache_filename() { let url = Url::parse("http://example.com:1234/path/to/file.ts").unwrap(); @@ -1019,25 +1033,25 @@ mod tests { ( "./subdir/print_hello.ts", add_root!("/Users/rld/go/src/github.com/denoland/deno/testdata/006_url_imports.ts"), - add_root!("/Users/rld/go/src/github.com/denoland/deno/testdata/subdir/print_hello.ts"), + file_url!("/Users/rld/go/src/github.com/denoland/deno/testdata/subdir/print_hello.ts"), add_root!("/Users/rld/go/src/github.com/denoland/deno/testdata/subdir/print_hello.ts"), ), ( "testdata/001_hello.js", add_root!("/Users/rld/go/src/github.com/denoland/deno/"), - add_root!("/Users/rld/go/src/github.com/denoland/deno/testdata/001_hello.js"), + file_url!("/Users/rld/go/src/github.com/denoland/deno/testdata/001_hello.js"), add_root!("/Users/rld/go/src/github.com/denoland/deno/testdata/001_hello.js"), ), ( add_root!("/Users/rld/src/deno/hello.js"), ".", - add_root!("/Users/rld/src/deno/hello.js"), + file_url!("/Users/rld/src/deno/hello.js"), add_root!("/Users/rld/src/deno/hello.js"), ), ( add_root!("/this/module/got/imported.js"), add_root!("/that/module/did/it.js"), - add_root!("/this/module/got/imported.js"), + file_url!("/this/module/got/imported.js"), add_root!("/this/module/got/imported.js"), ), ]; @@ -1169,8 +1183,7 @@ mod tests { let specifier = "http_test.ts"; let referrer = add_root!("/Users/rld/src/deno_net/"); - let expected_module_name = - add_root!("/Users/rld/src/deno_net/http_test.ts"); + let expected_module_name = file_url!("/Users/rld/src/deno_net/http_test.ts"); let expected_filename = add_root!("/Users/rld/src/deno_net/http_test.ts"); let (module_name, filename) = @@ -1187,8 +1200,9 @@ mod tests { let cwd = std::env::current_dir().unwrap(); let expected_path = cwd.join(specifier); - let expected_module_name = deno_fs::normalize_path(&expected_path); - let expected_filename = expected_module_name.clone(); + let expected_module_name = + Url::from_file_path(&expected_path).unwrap().to_string(); + let expected_filename = deno_fs::normalize_path(&expected_path); let (module_name, filename) = deno_dir.resolve_module(specifier, ".").unwrap(); @@ -1209,8 +1223,9 @@ mod tests { let cwd = std::env::current_dir().unwrap(); let expected_path = cwd.join("..").join(specifier); - let expected_module_name = deno_fs::normalize_path(&expected_path); - let expected_filename = expected_module_name.clone(); + let expected_module_name = + Url::from_file_path(&expected_path).unwrap().to_string(); + let expected_filename = deno_fs::normalize_path(&expected_path); let (module_name, filename) = deno_dir.resolve_module(specifier, "..").unwrap(); diff --git a/src/isolate.rs b/src/isolate.rs index 8775c6f4af..661e49edd8 100644 --- a/src/isolate.rs +++ b/src/isolate.rs @@ -100,6 +100,22 @@ impl IsolateState { } } + pub fn main_module(&self) -> Option { + if self.argv.len() <= 1 { + None + } else { + let specifier = self.argv[1].clone(); + let referrer = "."; + match self.dir.resolve_module_url(&specifier, referrer) { + Ok(url) => Some(url.to_string()), + Err(e) => { + debug!("Potentially swallowed error {}", e); + None + } + } + } + } + #[cfg(test)] pub fn mock() -> Arc { let argv = vec![String::from("./deno"), String::from("hello.js")]; diff --git a/src/main.rs b/src/main.rs index d3ec4b721b..10ae15065a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -105,19 +105,18 @@ fn main() { .map_err(errors::RustOrJsError::from) .unwrap_or_else(print_err_and_exit); - // Execute input file. - if isolate.state.argv.len() > 1 { - let input_filename = isolate.state.argv[1].clone(); + // Execute main module. + if let Some(main_module) = isolate.state.main_module() { + debug!("main_module {}", main_module); isolate - .execute_mod(&input_filename, should_prefetch) + .execute_mod(&main_module, should_prefetch) .unwrap_or_else(print_err_and_exit); - if should_display_info { // Display file info and exit. Do not run file modules::print_file_info( &isolate.modules.borrow(), &isolate.state.dir, - input_filename, + main_module, ); std::process::exit(0); } diff --git a/src/msg.fbs b/src/msg.fbs index 9776bb893a..da03e00a4e 100644 --- a/src/msg.fbs +++ b/src/msg.fbs @@ -158,6 +158,7 @@ table StartRes { pid: uint32; argv: [string]; exec_path: string; + main_module: string; // Absolute URL. debug_flag: bool; deps_flag: bool; types_flag: bool; diff --git a/src/ops.rs b/src/ops.rs index c8a0056012..8f32ebc031 100644 --- a/src/ops.rs +++ b/src/ops.rs @@ -261,12 +261,15 @@ fn op_start( let deno_version = version::DENO; let deno_version_off = builder.create_string(deno_version); + let main_module = state.main_module().map(|m| builder.create_string(&m)); + let inner = msg::StartRes::create( &mut builder, &msg::StartResArgs { cwd: Some(cwd_off), pid: std::process::id(), argv: Some(argv_off), + main_module, debug_flag: state.flags.log_debug, types_flag: state.flags.types, version_flag: state.flags.version, diff --git a/tests/error_010_nonexistent_arg.out b/tests/error_010_nonexistent_arg.out index 248cbc3296..ef4f7b0412 100644 --- a/tests/error_010_nonexistent_arg.out +++ b/tests/error_010_nonexistent_arg.out @@ -1 +1 @@ -[WILDCARD]Cannot resolve module "not-a-valid-filename.ts" from "." +[WILDCARD]Cannot resolve module "file:[WILDCARD]not-a-valid-filename.ts" from "." diff --git a/tests/if_main.test b/tests/if_main.test new file mode 100644 index 0000000000..5830d00f84 --- /dev/null +++ b/tests/if_main.test @@ -0,0 +1,2 @@ +args: tests/if_main.ts --reload +output: tests/if_main.ts.out diff --git a/tests/if_main.ts b/tests/if_main.ts new file mode 100644 index 0000000000..b47066b2d5 --- /dev/null +++ b/tests/if_main.ts @@ -0,0 +1,7 @@ +if (window.location.toString() == import.meta.url) { + console.log("main"); +} else { + console.log("import.meta.url", import.meta.url); + console.log("window.location", window.location.toString()); + throw Error("not main"); +} diff --git a/tests/if_main.ts.out b/tests/if_main.ts.out new file mode 100644 index 0000000000..ba2906d066 --- /dev/null +++ b/tests/if_main.ts.out @@ -0,0 +1 @@ +main diff --git a/tests/import_meta.test b/tests/import_meta.test new file mode 100644 index 0000000000..6767cfbb2c --- /dev/null +++ b/tests/import_meta.test @@ -0,0 +1,2 @@ +args: tests/import_meta.ts --reload +output: tests/import_meta.ts.out diff --git a/tests/import_meta.ts b/tests/import_meta.ts new file mode 100644 index 0000000000..8f27120680 --- /dev/null +++ b/tests/import_meta.ts @@ -0,0 +1,3 @@ +console.log("import_meta", import.meta.url); + +import "import_meta2.ts"; diff --git a/tests/import_meta.ts.out b/tests/import_meta.ts.out new file mode 100644 index 0000000000..c43cea6fd6 --- /dev/null +++ b/tests/import_meta.ts.out @@ -0,0 +1,2 @@ +import_meta2 [WILDCARD]import_meta2.ts +import_meta [WILDCARD]import_meta.ts diff --git a/tests/import_meta2.ts b/tests/import_meta2.ts new file mode 100644 index 0000000000..b64265b5be --- /dev/null +++ b/tests/import_meta2.ts @@ -0,0 +1 @@ +console.log("import_meta2", import.meta.url); diff --git a/tests/imports_meta.js b/tests/imports_meta.js deleted file mode 100644 index 3361d12377..0000000000 --- a/tests/imports_meta.js +++ /dev/null @@ -1,3 +0,0 @@ -console.log("imports_meta", import.meta.url); - -import "imports_meta2.js"; diff --git a/tests/imports_meta.js.out b/tests/imports_meta.js.out deleted file mode 100644 index ec6e7eaecf..0000000000 --- a/tests/imports_meta.js.out +++ /dev/null @@ -1,2 +0,0 @@ -imports_meta2 [WILDCARD]imports_meta2.js -imports_meta [WILDCARD]imports_meta.js diff --git a/tests/imports_meta.test b/tests/imports_meta.test deleted file mode 100644 index 17591ea33e..0000000000 --- a/tests/imports_meta.test +++ /dev/null @@ -1,2 +0,0 @@ -args: tests/imports_meta.js --reload -output: tests/imports_meta.js.out diff --git a/tests/imports_meta2.js b/tests/imports_meta2.js deleted file mode 100644 index 583861e126..0000000000 --- a/tests/imports_meta2.js +++ /dev/null @@ -1 +0,0 @@ -console.log("imports_meta2", import.meta.url); diff --git a/website/manual.md b/website/manual.md index 5a9add63ee..524f1d4499 100644 --- a/website/manual.md +++ b/website/manual.md @@ -466,6 +466,17 @@ import { test, assertEqual } from "./package.ts"; This design circumvents a plethora of complexity spawned by package management software, centralized code repositories, and superfluous file formats. +### Testing if current file is the main program + +By using `window.location` and `import.meta.url` one can test if the current +script has been executed as the main input to the program. + +```ts +if (window.location.toString() == import.meta.url) { + console.log("main"); +} +``` + ## Command line interface ### Flags