diff --git a/builtins/web/worker-location.cpp b/builtins/web/worker-location.cpp index 812d9d49..654075c2 100644 --- a/builtins/web/worker-location.cpp +++ b/builtins/web/worker-location.cpp @@ -12,6 +12,25 @@ namespace builtins::web::worker_location { JS::PersistentRooted WorkerLocation::url; +namespace { +DEF_ERR(LocationNotSetError, JSEXN_TYPEERR, "{0} can only be used during request handling, " + "or if an initialization-time location was set " + "using `--init-location`", 1) +bool ensure_location_access(JSContext *cx, const char *name) { + auto *engine = api::Engine::get(cx); + + if (engine->state() == api::EngineState::Initialized) { + return true; + } + + if (engine->state() == api::EngineState::ScriptPreInitializing && WorkerLocation::url.get()) { + return true; + } + + return api::throw_error(cx, LocationNotSetError, name); +} +} // namespace + #define WorkerLocation_ACCESSOR_GET(field) \ bool field##_get(JSContext *cx, unsigned argc, JS::Value *vp) { \ auto result = WorkerLocation::MethodHeaderWithName(0, cx, argc, vp, __func__); \ @@ -19,7 +38,9 @@ JS::PersistentRooted WorkerLocation::url; return false; \ } \ auto [args, self] = result.unwrap(); \ - REQUEST_HANDLER_ONLY("location." #field) \ + if (!ensure_location_access(cx, "location." #field)) { \ + return false; \ + } \ return url::URL::field(cx, WorkerLocation::url, args.rval()); \ } @@ -80,7 +101,28 @@ bool WorkerLocation::init_class(JSContext *cx, JS::HandleObject global) { } bool install(api::Engine *engine) { - return WorkerLocation::init_class(engine->cx(), engine->global()); + if (!WorkerLocation::init_class(engine->cx(), engine->global())) { + return false; + } + + const auto &init_location = engine->init_location(); + if (init_location) { + // Set the URL for `globalThis.location` to the configured value. + JSContext *cx = engine->cx(); + JS::RootedObject url_instance( + cx, JS_NewObjectWithGivenProto(cx, &url::URL::class_, url::URL::proto_obj)); + if (!url_instance) { + return false; + } + + auto *uri_bytes = new uint8_t[init_location->size() + 1]; + std::copy(init_location->begin(), init_location->end(), uri_bytes); + jsurl::SpecString spec(uri_bytes, init_location->size(), init_location->size()); + + WorkerLocation::url = url::URL::create(cx, url_instance, spec); + } + + return true; } } // namespace builtins::web::worker_location diff --git a/componentize.sh.in b/componentize.sh.in index b56fd1ac..43f5a0b0 100755 --- a/componentize.sh.in +++ b/componentize.sh.in @@ -17,6 +17,7 @@ usage() { echo " Specifying '--strip-path-prefix' will cause the provided prefix to be stripped from paths in stack traces and the debugger" echo " Specifying '--legacy-script' causes evaluation as a legacy JS script instead of a module" echo " Specifying '--wpt-mode' enables WPT compatibility mode" + echo " Specifying '--init-location url' allows setting the URL to use for 'globalThis.location' during initialization" exit 1 } @@ -55,6 +56,10 @@ do STARLING_ARGS="$STARLING_ARGS $1 $2" shift 2 ;; + --init-location) + STARLING_ARGS="$STARLING_ARGS $1 $2" + shift 2 + ;; -v|--verbose) STARLING_ARGS="$1 $STARLING_ARGS" VERBOSE=1 diff --git a/include/config-parser.h b/include/config-parser.h index 5b5658ec..9a3cb69c 100644 --- a/include/config-parser.h +++ b/include/config-parser.h @@ -99,6 +99,11 @@ class ConfigParser { } } else if (args[i] == "--wpt-mode") { config_->wpt_mode = true; + } else if (args[i] == "--init-location") { + if (i + 1 < args.size()) { + config_->init_location = mozilla::Some(args[i + 1]); + i++; + } } else if (args[i].starts_with("--")) { std::cerr << "Unknown option: " << args[i] << std::endl; exit(1); diff --git a/include/extension-api.h b/include/extension-api.h index 3c9d2541..b2ddc2d3 100644 --- a/include/extension-api.h +++ b/include/extension-api.h @@ -32,6 +32,7 @@ struct EngineConfig { mozilla::Maybe content_script_path = mozilla::Nothing(); mozilla::Maybe content_script = mozilla::Nothing(); mozilla::Maybe path_prefix = mozilla::Nothing(); + mozilla::Maybe init_location = mozilla::Nothing(); bool module_mode = true; /** @@ -84,6 +85,7 @@ class Engine { EngineState state(); bool debugging_enabled(); bool wpt_mode(); + const mozilla::Maybe &init_location() const; void finish_pre_initialization(); diff --git a/runtime/engine.cpp b/runtime/engine.cpp index d097106f..0a1a54ea 100644 --- a/runtime/engine.cpp +++ b/runtime/engine.cpp @@ -520,6 +520,9 @@ bool Engine::debugging_enabled() { return config_->debugging; } bool Engine::wpt_mode() { return config_->wpt_mode; } +const mozilla::Maybe &Engine::init_location() const { + return config_->init_location; +} void Engine::finish_pre_initialization() { MOZ_ASSERT(state_ == EngineState::ScriptPreInitializing); diff --git a/tests/e2e/init-location/expect_serve_stdout.txt b/tests/e2e/init-location/expect_serve_stdout.txt new file mode 100644 index 00000000..86167dc1 --- /dev/null +++ b/tests/e2e/init-location/expect_serve_stdout.txt @@ -0,0 +1 @@ +stdout [0] :: Log: localhost diff --git a/tests/e2e/init-location/expect_wizer_stdout.txt b/tests/e2e/init-location/expect_wizer_stdout.txt new file mode 100644 index 00000000..c6187e1d --- /dev/null +++ b/tests/e2e/init-location/expect_wizer_stdout.txt @@ -0,0 +1,2 @@ +Componentizing e2e/init-location/init-location.js into e2e/init-location/init-location.wasm +Log: http://foo.bar/ diff --git a/tests/e2e/init-location/init-location.js b/tests/e2e/init-location/init-location.js new file mode 100644 index 00000000..6d8ab9df --- /dev/null +++ b/tests/e2e/init-location/init-location.js @@ -0,0 +1,6 @@ +console.log(self.location.href); + +addEventListener("fetch", (event) => { + console.log(self.location.hostname); + event.respondWith(new Response("ok")); +}); diff --git a/tests/e2e/init-location/runtime-args b/tests/e2e/init-location/runtime-args new file mode 100644 index 00000000..270968b7 --- /dev/null +++ b/tests/e2e/init-location/runtime-args @@ -0,0 +1 @@ +--init-location http://foo.bar/ diff --git a/tests/e2e/no-init-location/expect_wizer_fail b/tests/e2e/no-init-location/expect_wizer_fail new file mode 100644 index 00000000..e69de29b diff --git a/tests/e2e/no-init-location/expect_wizer_stderr.txt b/tests/e2e/no-init-location/expect_wizer_stderr.txt new file mode 100644 index 00000000..0f8a5c6f --- /dev/null +++ b/tests/e2e/no-init-location/expect_wizer_stderr.txt @@ -0,0 +1,2 @@ +Exception while evaluating top-level script +e2e/no-init-location/no-init-location.js:1:9 TypeError: location.href can only be used during request handling, or if an initialization-time location was set using `--init-location` diff --git a/tests/e2e/no-init-location/no-init-location.js b/tests/e2e/no-init-location/no-init-location.js new file mode 100644 index 00000000..6e5604d8 --- /dev/null +++ b/tests/e2e/no-init-location/no-init-location.js @@ -0,0 +1 @@ +console.log(self.location); diff --git a/tests/test.sh b/tests/test.sh index 82175be9..84f22d95 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -6,6 +6,7 @@ test_component="${3:-}" test_name="$(basename $test_dir)" test_serve_path="${4:-}" componentize_flags="${COMPONENTIZE_FLAGS:-}" +runtime_args_file="$test_dir/runtime-args" wasmtime="${WASMTIME:-wasmtime}" @@ -42,6 +43,10 @@ if [ -z "$test_component" ]; then runtime_args="--strip-path-prefix $test_top_level $runtime_args" + if [ -f "$runtime_args_file" ]; then + runtime_args="$runtime_args $(cat $runtime_args_file)" + fi + # Run Wizer set +e PREOPEN_DIR="$test_top_level" "$test_runtime/componentize.sh" $componentize_flags $runtime_args "$test_component" 1> "$stdout_log" 2> "$stderr_log" @@ -64,6 +69,10 @@ if [ -z "$test_component" ]; then fi if [ -f "$test_wizer_stdout_expectation" ]; then + # Strip $test_top_level from stdout and stderr logs if present + mv "$stdout_log" "$stdout_log.orig" + cat "$stdout_log.orig" | sed "s|$test_top_level||g" > "$stdout_log" + rm "$stdout_log.orig" cmp -b "$stdout_log" "$test_wizer_stdout_expectation" fi @@ -86,7 +95,7 @@ if [ -z "$test_component" ]; then rm "$test_component" fi exit 0 - fi + fi fi fi diff --git a/tests/tests.cmake b/tests/tests.cmake index f4f11876..59c7a0ef 100644 --- a/tests/tests.cmake +++ b/tests/tests.cmake @@ -56,6 +56,8 @@ test_e2e(stream-forwarding) test_e2e(multi-stream-forwarding) test_e2e(teed-stream-as-outgoing-body) test_e2e(init-script) +test_e2e(no-init-location) +test_e2e(init-location) integration_tests( blob