From 0b189933c99ec0c8d1908a0b8554e17fb19cd64d Mon Sep 17 00:00:00 2001 From: Nayeem Rahman Date: Thu, 7 Jan 2021 19:45:00 +0000 Subject: [PATCH] fix(op_crates/web): Use WorkerLocation for location in workers --- cli/dts/lib.deno.window.d.ts | 72 +++++++ cli/dts/lib.deno.worker.d.ts | 23 +++ cli/flags.rs | 5 +- cli/tests/070_location.ts | 3 +- cli/tests/070_location.ts.out | 2 - cli/tests/071_location_unset.ts | 3 +- cli/tests/079_location_authentication.ts | 4 + cli/tests/079_location_authentication.ts.out | 3 + cli/tests/integration_tests.rs | 5 + cli/tests/subdir/worker_location.ts | 7 +- cli/tests/workers_test.ts | 5 +- op_crates/web/12_location.js | 188 ++++++++++++++++--- op_crates/web/lib.deno_web.d.ts | 66 ------- runtime/js/99_main.js | 6 +- 14 files changed, 289 insertions(+), 103 deletions(-) create mode 100644 cli/tests/079_location_authentication.ts create mode 100644 cli/tests/079_location_authentication.ts.out diff --git a/cli/dts/lib.deno.window.d.ts b/cli/dts/lib.deno.window.d.ts index da1b25565d2ce2..721c89af229e30 100644 --- a/cli/dts/lib.deno.window.d.ts +++ b/cli/dts/lib.deno.window.d.ts @@ -49,3 +49,75 @@ declare function confirm(message?: string): boolean; * @param defaultValue */ declare function prompt(message?: string, defaultValue?: string): string | null; + +// TODO(nayeemrmn): Move this to `op_crates/web` where its implementation is. +// The types there must first be split into window, worker and global types. +/** 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; +} + +// TODO(nayeemrmn): Move this to `op_crates/web` where its implementation is. +// The types there must first be split into window, worker and global types. +declare var location: Location; diff --git a/cli/dts/lib.deno.worker.d.ts b/cli/dts/lib.deno.worker.d.ts index 74609e8ab0de67..a8f42f3362e9bb 100644 --- a/cli/dts/lib.deno.worker.d.ts +++ b/cli/dts/lib.deno.worker.d.ts @@ -58,3 +58,26 @@ declare var onerror: declare var close: () => void; declare var name: string; declare var postMessage: (message: any) => void; + +// TODO(nayeemrmn): Move this to `op_crates/web` where its implementation is. +// The types there must first be split into window, worker and global types. +/** The absolute location of the script executed by the Worker. Such an object + * is initialized for each worker and is available via the + * WorkerGlobalScope.location property obtained by calling self.location. */ +declare class WorkerLocation { + constructor(); + readonly hash: string; + readonly host: string; + readonly hostname: string; + readonly href: string; + toString(): string; + readonly origin: string; + readonly pathname: string; + readonly port: string; + readonly protocol: string; + readonly search: string; +} + +// TODO(nayeemrmn): Move this to `op_crates/web` where its implementation is. +// The types there must first be split into window, worker and global types. +declare var location: WorkerLocation; diff --git a/cli/flags.rs b/cli/flags.rs index a994b3f0c897e6..9fe25df50da650 100644 --- a/cli/flags.rs +++ b/cli/flags.rs @@ -1437,7 +1437,10 @@ fn location_arg<'a, 'b>() -> Arg<'a, 'b> { if url.is_err() { return Err("Failed to parse URL".to_string()); } - if !["http", "https"].contains(&url.unwrap().scheme()) { + let mut url = url.unwrap(); + url.set_username("").unwrap(); + url.set_password(None).unwrap(); + if !["http", "https"].contains(&url.scheme()) { return Err("Expected protocol \"http\" or \"https\"".to_string()); } Ok(()) diff --git a/cli/tests/070_location.ts b/cli/tests/070_location.ts index 62fd34af2808b7..76d61e84473a48 100644 --- a/cli/tests/070_location.ts +++ b/cli/tests/070_location.ts @@ -1,4 +1,5 @@ -// TODO(nayeemrmn): Add `Location` and `location` to `dlint`'s globals. +// TODO(nayeemrmn): Add `Location`, `WorkerLocation` and `location` to `dlint`'s +// globals. // deno-lint-ignore-file no-undef console.log(Location); console.log(Location.prototype); diff --git a/cli/tests/070_location.ts.out b/cli/tests/070_location.ts.out index 66d470b6f6fd1c..2ba0f259d4d3d4 100644 --- a/cli/tests/070_location.ts.out +++ b/cli/tests/070_location.ts.out @@ -6,12 +6,10 @@ Location { 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], diff --git a/cli/tests/071_location_unset.ts b/cli/tests/071_location_unset.ts index 5c0518940aef19..c68695df4a6144 100644 --- a/cli/tests/071_location_unset.ts +++ b/cli/tests/071_location_unset.ts @@ -1,4 +1,5 @@ -// TODO(nayeemrmn): Add `Location` and `location` to `dlint`'s globals. +// TODO(nayeemrmn): Add `Location`, `WorkerLocation` and `location` to `dlint`'s +// globals. // deno-lint-ignore-file no-undef console.log(Location); console.log(Location.prototype); diff --git a/cli/tests/079_location_authentication.ts b/cli/tests/079_location_authentication.ts new file mode 100644 index 00000000000000..980442b1cd4099 --- /dev/null +++ b/cli/tests/079_location_authentication.ts @@ -0,0 +1,4 @@ +// TODO(nayeemrmn): Add `Location`, `WorkerLocation` and `location` to `dlint`'s +// globals. +// deno-lint-ignore-file no-undef +console.log(location.href); diff --git a/cli/tests/079_location_authentication.ts.out b/cli/tests/079_location_authentication.ts.out new file mode 100644 index 00000000000000..bb245849718581 --- /dev/null +++ b/cli/tests/079_location_authentication.ts.out @@ -0,0 +1,3 @@ +[WILDCARD] +https://baz/qux +[WILDCARD] diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs index 586c405e395977..9c64a9e07de826 100644 --- a/cli/tests/integration_tests.rs +++ b/cli/tests/integration_tests.rs @@ -2651,6 +2651,11 @@ itest!(_078_unload_on_exit { output: "078_unload_on_exit.ts.out", }); +itest!(_079_location_authentication { + args: "run --location https://foo:bar@baz/qux 079_location_authentication.ts", + output: "079_location_authentication.ts.out", +}); + itest!(js_import_detect { args: "run --quiet --reload js_import_detect.ts", output: "js_import_detect.ts.out", diff --git a/cli/tests/subdir/worker_location.ts b/cli/tests/subdir/worker_location.ts index 48003235015475..67824043405339 100644 --- a/cli/tests/subdir/worker_location.ts +++ b/cli/tests/subdir/worker_location.ts @@ -1,4 +1,9 @@ +// TODO(nayeemrmn): Add `Location`, `WorkerLocation` and `location` to `dlint`'s +// globals. +// deno-lint-ignore-file no-undef onmessage = function (): void { - postMessage(self.location.href); + postMessage( + `${location.href}, ${location instanceof WorkerLocation}`, + ); close(); }; diff --git a/cli/tests/workers_test.ts b/cli/tests/workers_test.ts index 045ee3f752ee06..437fcf752c35f7 100644 --- a/cli/tests/workers_test.ts +++ b/cli/tests/workers_test.ts @@ -648,7 +648,7 @@ Deno.test({ 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); + assertEquals(e.data, `${workerModuleHref}, true`); promise.resolve(); }; w.postMessage("Hello, world!"); @@ -660,7 +660,8 @@ Deno.test({ Deno.test({ name: "worker with relative specifier", fn: async function (): Promise { - // TODO(nayeemrmn): Add `Location` and `location` to `dlint`'s globals. + // TODO(nayeemrmn): Add `Location`, `WorkerLocation` 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(); diff --git a/op_crates/web/12_location.js b/op_crates/web/12_location.js index 79a9151f15fa48..d6a132413a6e77 100644 --- a/op_crates/web/12_location.js +++ b/op_crates/web/12_location.js @@ -4,12 +4,19 @@ const { URL } = window.__bootstrap.url; const locationConstructorKey = Symbol("locationConstuctorKey"); + // The differences between the definitions of `Location` and `WorkerLocation` + // are because of the `LegacyUnforgeable` attribute only specified upon + // `Location`'s properties. See: + // - https://html.spec.whatwg.org/multipage/history.html#the-location-interface + // - https://heycam.github.io/webidl/#LegacyUnforgeable class Location { - constructor(href, key) { + constructor(href = null, key = null) { if (key != locationConstructorKey) { throw new TypeError("Illegal constructor."); } const url = new URL(href); + url.username = ""; + url.password = ""; Object.defineProperties(this, { hash: { get() { @@ -49,7 +56,7 @@ }, href: { get() { - return href; + return url.href; }, set() { throw new DOMException( @@ -65,18 +72,6 @@ }, enumerable: true, }, - password: { - get() { - return url.password; - }, - set() { - throw new DOMException( - `Cannot set "location.password".`, - "NotSupportedError", - ); - }, - enumerable: true, - }, pathname: { get() { return url.pathname; @@ -125,18 +120,6 @@ }, 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. @@ -177,7 +160,7 @@ }, toString: { value: function toString() { - return href; + return url.href; }, enumerable: true, }, @@ -192,10 +175,144 @@ }, }); + const workerLocationUrls = new WeakMap(); + + class WorkerLocation { + constructor(href = null, key = null) { + if (key != locationConstructorKey) { + throw new TypeError("Illegal constructor."); + } + const url = new URL(href); + url.username = ""; + url.password = ""; + workerLocationUrls.set(this, url); + } + } + + Object.defineProperties(WorkerLocation.prototype, { + hash: { + get() { + const url = workerLocationUrls.get(this); + if (url == null) { + throw new TypeError("Illegal invocation."); + } + return url.hash; + }, + configurable: true, + enumerable: true, + }, + host: { + get() { + const url = workerLocationUrls.get(this); + if (url == null) { + throw new TypeError("Illegal invocation."); + } + return url.host; + }, + configurable: true, + enumerable: true, + }, + hostname: { + get() { + const url = workerLocationUrls.get(this); + if (url == null) { + throw new TypeError("Illegal invocation."); + } + return url.hostname; + }, + configurable: true, + enumerable: true, + }, + href: { + get() { + const url = workerLocationUrls.get(this); + if (url == null) { + throw new TypeError("Illegal invocation."); + } + return url.href; + }, + configurable: true, + enumerable: true, + }, + origin: { + get() { + const url = workerLocationUrls.get(this); + if (url == null) { + throw new TypeError("Illegal invocation."); + } + return url.origin; + }, + configurable: true, + enumerable: true, + }, + pathname: { + get() { + const url = workerLocationUrls.get(this); + if (url == null) { + throw new TypeError("Illegal invocation."); + } + return url.pathname; + }, + configurable: true, + enumerable: true, + }, + port: { + get() { + const url = workerLocationUrls.get(this); + if (url == null) { + throw new TypeError("Illegal invocation."); + } + return url.port; + }, + configurable: true, + enumerable: true, + }, + protocol: { + get() { + const url = workerLocationUrls.get(this); + if (url == null) { + throw new TypeError("Illegal invocation."); + } + return url.protocol; + }, + configurable: true, + enumerable: true, + }, + search: { + get() { + const url = workerLocationUrls.get(this); + if (url == null) { + throw new TypeError("Illegal invocation."); + } + return url.search; + }, + configurable: true, + enumerable: true, + }, + toString: { + value: function toString() { + const url = workerLocationUrls.get(this); + if (url == null) { + throw new TypeError("Illegal invocation."); + } + return url.href; + }, + configurable: true, + enumerable: true, + writable: true, + }, + [Symbol.toStringTag]: { + value: "WorkerLocation", + configurable: true, + }, + }); + let location = null; + let workerLocation = null; function setLocationHref(href) { location = new Location(href, locationConstructorKey); + workerLocation = new WorkerLocation(href, locationConstructorKey); } window.__bootstrap = (window.__bootstrap || {}); @@ -205,6 +322,11 @@ configurable: true, writable: true, }, + workerLocationConstructorDescriptor: { + value: WorkerLocation, + configurable: true, + writable: true, + }, locationDescriptor: { get() { if (location == null) { @@ -219,6 +341,18 @@ }, enumerable: true, }, + workerLocationDescriptor: { + get() { + if (workerLocation == null) { + throw new Error( + `Assertion: "globalThis.location" must be defined in a worker.`, + ); + } + return workerLocation; + }, + configurable: true, + enumerable: true, + }, setLocationHref, getLocationHref() { return location?.href; diff --git a/op_crates/web/lib.deno_web.d.ts b/op_crates/web/lib.deno_web.d.ts index e9a6bc8eec2e92..79b56f68e28c42 100644 --- a/op_crates/web/lib.deno_web.d.ts +++ b/op_crates/web/lib.deno_web.d.ts @@ -313,69 +313,3 @@ 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/runtime/js/99_main.js b/runtime/js/99_main.js index b41b75d3367a32..66bb5675028a92 100644 --- a/runtime/js/99_main.js +++ b/runtime/js/99_main.js @@ -197,8 +197,6 @@ 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, @@ -251,6 +249,8 @@ delete Object.prototype.__proto__; windowOrWorkerGlobalScope.console.enumerable = false; const mainRuntimeGlobalProperties = { + Location: location.locationConstructorDescriptor, + location: location.locationDescriptor, Window: globalInterfaces.windowConstructorDescriptor, window: util.readOnly(globalThis), self: util.readOnly(globalThis), @@ -266,6 +266,8 @@ delete Object.prototype.__proto__; }; const workerRuntimeGlobalProperties = { + WorkerLocation: location.workerLocationConstructorDescriptor, + location: location.workerLocationDescriptor, WorkerGlobalScope: globalInterfaces.workerGlobalScopeConstructorDescriptor, DedicatedWorkerGlobalScope: globalInterfaces.dedicatedWorkerGlobalScopeConstructorDescriptor,