Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions test-app/app/src/main/assets/app/tests/testWorkerEsmEntry.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,20 @@ describe("worker ES module entries", function () {
worker.postMessage("ping");
});

// An http(s) worker specifier bypasses the filesystem check entirely: the
// entry is fetched, compiled and registered under its canonical URL key on
// the worker's own thread, which is also the key the settle gate probes.
it("runs a worker whose entry is an http URL", function (done) {
var origin = "http://127.0.0.1:" + com.tns.tests.ModuleTestServer.ensureStarted();
var worker = new Worker(origin + "/esm/worker-entry.mjs");
worker.onmessage = function (msg) {
expect(msg.data).toBe("http-worker-entry:ping");
worker.terminate();
done();
};
worker.postMessage("ping");
});

// Extension resolution tries `.js` before `.mjs`, and no `.js` sibling
// exists, so the ES module entry is what answers. Its top-level await also
// parks past the yield window, so the message posted here proves the
Expand Down
17 changes: 17 additions & 0 deletions test-app/app/src/main/java/com/tns/tests/ModuleTestServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,23 @@ private static void route(Socket socket, String path, String query) throws IOExc
return;
}

if ("/esm/worker-entry.mjs".equals(path)) {
// A worker entry served over HTTP, importing one relative dependency
// so the entry exercises the graph walk and not just the root fetch.
String body = "import { WORKER_TAG } from \"./worker-entry-dep.mjs\";\n"
+ "globalThis.onmessage = function (msg) {\n"
+ " postMessage(WORKER_TAG + \":\" + msg.data);\n"
+ "};\n";
respond(socket, "200 OK", JS_MIME, body.getBytes(UTF8));
return;
}

if ("/esm/worker-entry-dep.mjs".equals(path)) {
String body = "export const WORKER_TAG = \"http-worker-entry\";\n";
respond(socket, "200 OK", JS_MIME, body.getBytes(UTF8));
return;
}

if ("/esm/syntax-error.mjs".equals(path)) {
// Deliberately unparseable: pins that the loader surfaces V8's real
// compile error instead of a generic failure.
Expand Down
52 changes: 35 additions & 17 deletions test-app/runtime/src/main/cpp/CallbackHandlers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
#include <fstream>
#include <cstdio>
#include <chrono>
#include "HttpLoader.h"
#include "MethodCache.h"
#include "ModuleInternal.h"
#include "SimpleProfiler.h"
#include "Runtime.h"
#include "WorkerMessage.h"
Expand Down Expand Up @@ -1215,11 +1217,17 @@ void CallbackHandlers::NewThreadCallback(const v8::FunctionCallbackInfo<v8::Valu

int priority = GetWorkerThreadPriority(isolate, context, args);

// TODO: Validate worker path and call worker.onerror if the script does not exist
// An http(s) entry has no filesystem form to validate or to resolve
// against the caller's directory: it is already absolute, and the
// module loader's HTTP branch fetches it on the worker's own thread
// under the same security gate every other remote load passes. The
// URL is what the worker registers its entry under, so it is also what
// the settle gate probes — it must reach the wrapper unrewritten.
const bool isHttpEntry = ModuleInternal::IsHttpModulePath(workerPath);

// Resolve tilde paths before creating the worker
std::string resolvedPath = workerPath;
if (!workerPath.empty() && workerPath[0] == '~') {
if (!isHttpEntry && !workerPath.empty() && workerPath[0] == '~') {
// Convert ~/path to ApplicationPath/path
std::string tail = workerPath.size() >= 2 && workerPath[1] == '/' ? workerPath.substr(2) : workerPath.substr(1);
resolvedPath = Constants::APP_ROOT_FOLDER_PATH + tail;
Expand All @@ -1232,7 +1240,9 @@ void CallbackHandlers::NewThreadCallback(const v8::FunctionCallbackInfo<v8::Valu
* app-root-relative resolution, mirroring the iOS runtime.
*/
std::string currentDir = Constants::APP_ROOT_FOLDER_PATH;
auto stack = StackTrace::CurrentStackTrace(isolate, 1, StackTrace::kScriptName);
auto stack = isHttpEntry
? Local<StackTrace>()
: StackTrace::CurrentStackTrace(isolate, 1, StackTrace::kScriptName);
if (!stack.IsEmpty() && stack->GetFrameCount() > 0) {
auto currentExecutingScriptName = stack->GetFrame(isolate, 0)->GetScriptName();
auto currentExecutingScriptNameStr = ArgConverter::ConvertToString(
Expand All @@ -1248,22 +1258,30 @@ void CallbackHandlers::NewThreadCallback(const v8::FunctionCallbackInfo<v8::Valu
}
}

// Will throw if the path is invalid or the file doesn't exist. The
// worker runs on its own thread, with its own working directory and
// module registry, so it gets the canonical path resolved here rather
// than the spec: nothing on the other side can redo this resolution,
// and the entry's registry key must be the file that was validated.
// The worker runs on its own thread, with its own working directory
// and module registry, so it gets the entry resolved here rather than
// the spec: nothing on the other side can redo this resolution, and
// the entry's registry key must be what was resolved here.
std::string entryPath;
try {
entryPath = ModuleInternal::CheckFileExists(isolate, resolvedPath, currentDir);
} catch (NativeScriptException& e) {
if (currentDir == Constants::APP_ROOT_FOLDER_PATH) {
throw;
if (isHttpEntry) {
// Repaired, not canonicalized: the canonical key depends on the
// worker's own canonicalization vocabulary, which is installed on
// its isolate, and both the loader and the settle gate derive it
// there from this URL.
entryPath = NormalizeHttpModuleUrl(resolvedPath);
} else {
// Throws if the path is invalid or the file doesn't exist.
try {
entryPath = ModuleInternal::CheckFileExists(isolate, resolvedPath, currentDir);
} catch (NativeScriptException& e) {
if (currentDir == Constants::APP_ROOT_FOLDER_PATH) {
throw;
}
// not found next to the caller - retry against the app root
entryPath = ModuleInternal::CheckFileExists(isolate, resolvedPath,
Constants::APP_ROOT_FOLDER_PATH);
currentDir = Constants::APP_ROOT_FOLDER_PATH;
}
// not found next to the caller - retry against the app root
entryPath = ModuleInternal::CheckFileExists(isolate, resolvedPath,
Constants::APP_ROOT_FOLDER_PATH);
currentDir = Constants::APP_ROOT_FOLDER_PATH;
}

auto workerId = WorkerWrapper::NextWorkerId();
Expand Down
Loading