Skip to content

Commit 037e878

Browse files
committed
feat(runtime): close the remaining gaps against the merged iOS loader PR
Four features from the merged overhaul had not made it into the port, plus the structural move that belonged with them: - ES modules use the compiled-code cache: CompileFileEsModule consumes and produces cache blobs through the existing scheme under the same config gate, with module caches keyed as .mcache - a classic require() and an import of the same .js file each keep their own blob instead of overwriting each other's on every load. - Concurrent module fetches are capped at 16 process-wide: excess jobs queue and finishing threads drain them, so the cap bounds threads (and JVM attaches), not just sockets, and the caller never blocks. A failed thread spawn delivers a transport-error completion instead of wedging the graph pump, and jobs queued behind it fail rather than wait for a thread that will never exist. - The transport takes canonical keys from its callers - computed on the isolate thread, off-thread canonicalization impossible by construction. This also fixes cache-bust marks for collapsed-scheme URLs: eviction marks under the repaired registry key, and the transport previously canonicalized the raw URL, so the mark could never match or clear. - Workers accept http(s) entries: the constructor classifies the URL up front and skips filesystem resolution, the existing HTTP entry branch and boot options do the work, and the settle gate probes the same canonical key the entry registers under. The worker inspector target passes an http(s) URL through instead of prefixing file://. - The ns:module binding lives beside the loader state it configures; the transport TU no longer depends on the loader headers, and configureLoader parses the import map once and installs the parsed result instead of validating by parse and parsing again.
1 parent bce6698 commit 037e878

11 files changed

Lines changed: 726 additions & 518 deletions

File tree

test-app/app/src/main/assets/app/tests/testWorkerEsmEntry.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,20 @@ describe("worker ES module entries", function () {
6161
worker.postMessage("ping");
6262
});
6363

64+
// An http(s) worker specifier bypasses the filesystem check entirely: the
65+
// entry is fetched, compiled and registered under its canonical URL key on
66+
// the worker's own thread, which is also the key the settle gate probes.
67+
it("runs a worker whose entry is an http URL", function (done) {
68+
var origin = "http://127.0.0.1:" + com.tns.tests.ModuleTestServer.ensureStarted();
69+
var worker = new Worker(origin + "/esm/worker-entry.mjs");
70+
worker.onmessage = function (msg) {
71+
expect(msg.data).toBe("http-worker-entry:ping");
72+
worker.terminate();
73+
done();
74+
};
75+
worker.postMessage("ping");
76+
});
77+
6478
// Extension resolution tries `.js` before `.mjs`, and no `.js` sibling
6579
// exists, so the ES module entry is what answers. Its top-level await also
6680
// parks past the yield window, so the message posted here proves the

test-app/app/src/main/java/com/tns/tests/ModuleTestServer.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,23 @@ private static void route(Socket socket, String path, String query) throws IOExc
196196
return;
197197
}
198198

199+
if ("/esm/worker-entry.mjs".equals(path)) {
200+
// A worker entry served over HTTP, importing one relative dependency
201+
// so the entry exercises the graph walk and not just the root fetch.
202+
String body = "import { WORKER_TAG } from \"./worker-entry-dep.mjs\";\n"
203+
+ "globalThis.onmessage = function (msg) {\n"
204+
+ " postMessage(WORKER_TAG + \":\" + msg.data);\n"
205+
+ "};\n";
206+
respond(socket, "200 OK", JS_MIME, body.getBytes(UTF8));
207+
return;
208+
}
209+
210+
if ("/esm/worker-entry-dep.mjs".equals(path)) {
211+
String body = "export const WORKER_TAG = \"http-worker-entry\";\n";
212+
respond(socket, "200 OK", JS_MIME, body.getBytes(UTF8));
213+
return;
214+
}
215+
199216
if ("/esm/syntax-error.mjs".equals(path)) {
200217
// Deliberately unparseable: pins that the loader surfaces V8's real
201218
// compile error instead of a generic failure.

test-app/runtime/src/main/cpp/CallbackHandlers.cpp

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
#include <fstream>
1616
#include <cstdio>
1717
#include <chrono>
18+
#include "HttpLoader.h"
1819
#include "MethodCache.h"
20+
#include "ModuleInternal.h"
1921
#include "SimpleProfiler.h"
2022
#include "Runtime.h"
2123
#include "WorkerMessage.h"
@@ -1215,11 +1217,17 @@ void CallbackHandlers::NewThreadCallback(const v8::FunctionCallbackInfo<v8::Valu
12151217

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

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

12201228
// Resolve tilde paths before creating the worker
12211229
std::string resolvedPath = workerPath;
1222-
if (!workerPath.empty() && workerPath[0] == '~') {
1230+
if (!isHttpEntry && !workerPath.empty() && workerPath[0] == '~') {
12231231
// Convert ~/path to ApplicationPath/path
12241232
std::string tail = workerPath.size() >= 2 && workerPath[1] == '/' ? workerPath.substr(2) : workerPath.substr(1);
12251233
resolvedPath = Constants::APP_ROOT_FOLDER_PATH + tail;
@@ -1232,7 +1240,9 @@ void CallbackHandlers::NewThreadCallback(const v8::FunctionCallbackInfo<v8::Valu
12321240
* app-root-relative resolution, mirroring the iOS runtime.
12331241
*/
12341242
std::string currentDir = Constants::APP_ROOT_FOLDER_PATH;
1235-
auto stack = StackTrace::CurrentStackTrace(isolate, 1, StackTrace::kScriptName);
1243+
auto stack = isHttpEntry
1244+
? Local<StackTrace>()
1245+
: StackTrace::CurrentStackTrace(isolate, 1, StackTrace::kScriptName);
12361246
if (!stack.IsEmpty() && stack->GetFrameCount() > 0) {
12371247
auto currentExecutingScriptName = stack->GetFrame(isolate, 0)->GetScriptName();
12381248
auto currentExecutingScriptNameStr = ArgConverter::ConvertToString(
@@ -1248,22 +1258,30 @@ void CallbackHandlers::NewThreadCallback(const v8::FunctionCallbackInfo<v8::Valu
12481258
}
12491259
}
12501260

1251-
// Will throw if the path is invalid or the file doesn't exist. The
1252-
// worker runs on its own thread, with its own working directory and
1253-
// module registry, so it gets the canonical path resolved here rather
1254-
// than the spec: nothing on the other side can redo this resolution,
1255-
// and the entry's registry key must be the file that was validated.
1261+
// The worker runs on its own thread, with its own working directory
1262+
// and module registry, so it gets the entry resolved here rather than
1263+
// the spec: nothing on the other side can redo this resolution, and
1264+
// the entry's registry key must be what was resolved here.
12561265
std::string entryPath;
1257-
try {
1258-
entryPath = ModuleInternal::CheckFileExists(isolate, resolvedPath, currentDir);
1259-
} catch (NativeScriptException& e) {
1260-
if (currentDir == Constants::APP_ROOT_FOLDER_PATH) {
1261-
throw;
1266+
if (isHttpEntry) {
1267+
// Repaired, not canonicalized: the canonical key depends on the
1268+
// worker's own canonicalization vocabulary, which is installed on
1269+
// its isolate, and both the loader and the settle gate derive it
1270+
// there from this URL.
1271+
entryPath = NormalizeHttpModuleUrl(resolvedPath);
1272+
} else {
1273+
// Throws if the path is invalid or the file doesn't exist.
1274+
try {
1275+
entryPath = ModuleInternal::CheckFileExists(isolate, resolvedPath, currentDir);
1276+
} catch (NativeScriptException& e) {
1277+
if (currentDir == Constants::APP_ROOT_FOLDER_PATH) {
1278+
throw;
1279+
}
1280+
// not found next to the caller - retry against the app root
1281+
entryPath = ModuleInternal::CheckFileExists(isolate, resolvedPath,
1282+
Constants::APP_ROOT_FOLDER_PATH);
1283+
currentDir = Constants::APP_ROOT_FOLDER_PATH;
12621284
}
1263-
// not found next to the caller - retry against the app root
1264-
entryPath = ModuleInternal::CheckFileExists(isolate, resolvedPath,
1265-
Constants::APP_ROOT_FOLDER_PATH);
1266-
currentDir = Constants::APP_ROOT_FOLDER_PATH;
12671285
}
12681286

12691287
auto workerId = WorkerWrapper::NextWorkerId();

0 commit comments

Comments
 (0)