Tracks the port of substantive commits from the old V8-based runtime
(nativescript/android-runtime, branch master) into this NAPI-based runtime,
starting at 2bab8f5 (fix(URL): allow undefined 2nd args, #1826) through the
old runtime's HEAD (129 commits total).
Plan: ~/.claude/plans/we-want-to-migrate-modular-prism.md
Status: the full range 2bab8f5..HEAD has been triaged. ~60 build(deps)/CI/chore/release
commits are out of scope (fork tooling). Of the substantive commits: 23 ported/partial
(all native/Java build-verified), several already-present, and the rest deferred with
rationale (each deferred row says why + what's needed to complete it). Deferred clusters:
ESM (052cb21 + its dependents 7782720f/288491f/5ceb3d4/92c2654/45ed1f6),
timers (bfd7650), URLSearchParams spec (89893ae), workers→C++ (a84d3c7),
inspector (55da2da/4b5ab0a), NDK r27d (0387a8d). The @CriticalNative pair
(085bc4f+3c956cf) and DexFactory pair (c9d41e6+fce8e29) are now ported (compile-verified;
they carry device/version/release-only runtime risk — validate on real devices). Verification here is
compile/link + config-eval; on-device behavior is noted per-row where relevant.
Disposition: ported · already-present · partial · skipped · deferred
| # | old hash | subject | disposition | new commit | notes |
|---|---|---|---|---|---|
| 1 | 2bab8f5 | fix(URL): allow undefined 2nd args (#1826) | ported | ef20f1d | URL::New (modules/url/URL.cpp): gate base-URL branch on argv[1] not being undefined/null via napi_util::is_of_type; ported expanded testURLImpl.js verbatim (JS is engine-agnostic). Verified: native recompile (V8-13, all ABIs) exit 0. |
| 2 | 94ddb15 | fix: exit(0) causes ANR due to destroyed mutex (#1820) |
ported | b46586b | Old fix was in MetadataNode::BuildMetadata; in napi runtime that error path moved to MetadataBuilder.cpp:55 (identical direct-boot/locked-screen comment). Changed exit(0) → _Exit(0). Verified: native recompile exit 0. |
| — | f290ed2 | perf: optimizations around generating JS classes from Metadata (#1824) | already-present | — | Large metadata-subsystem perf refactor (13 files). Confirmed by user: already implemented in the napi runtime's rewritten metadata code. Skipped. |
| 3 | a983931 | fix: gradle error when compileSdk or targetSdk is provided (#1825) | ported | 6f01665 | test-app/app/build.gradle: add as int cast to provided compileSdk/targetSdk in computeCompileSdkVersion/computeTargetSdkVersion (props arrive as String from -P). Verified: :app:help -PcompileSdk=35 -PtargetSdk=35 config eval exit 0. |
| — | e293636 | fix: inner type not set when companion object defined as function (#1831) | already-present | — | New MetadataNode::SetInnerTypes (metadata/MetadataNode.cpp:1726-1733) already has the napi_has_own_property + if(!hasOwnProperty) guard before defining the inner-type accessor — napi port of the same fix. Skipped. |
| 5 | b31fc5f + 3633aed + 83f611b + 3513ce7 + 45fb275 | Ada v3 + URLPattern (#1830), Ada 3.1.1/3.1.3/3.2.7/3.3.0 (#1832/#1835/#1884) | ported | this commit | Consolidated the whole Ada v3 chain (Ada is a vendored single-file lib, so intermediate bumps collapse to the final): replaced modules/url/ada/ada.{h,cpp} 2.9.0→3.3.0 (verified no API breaks in URL/URLSearchParams). Ported URLPattern V8→napi: new modules/url/URLPattern.{cpp,h} with a napi_regex_provider (RegExp via global ctor + .exec, move-only NapiRegex ref wrapper, thread-local env for create_instance); wired URLPattern::Init into NSRuntimeModules. Registered spec-correct hasRegExpGroups (old C++ had typo hasRegexpGroups). Ported testURLPattern.js + registered in mainpage.js. Skipped the commit's gradle/CMake/Runtime-V8 bits (superseded / engine-specific). Verified: native build all ABIs exit 0. |
| 6 | bec401c | feat: NDK 27 and Support for Java 21 (#1819) | partial | this commit | Ported (Java-21 source compat): NanoWSD.java byte casts (`header |
| — | c9a2a86 | feat: update Gradle 8.14.3 and Android build tools 8.12.1 (#1834) | already-present | — | Fork already on gradle wrapper 8.14.3 and NS_DEFAULT_ANDROID_BUILD_TOOLS_VERSION=8.12.1. Skipped. |
| — | e98367c | feat: performance api (#1838) | already-present | — | runtime/performance/Performance.h registers global.performance/performance.now() (installed at Runtime.cpp:313). Skipped. |
| 7 | 87f7f9c | feat: update libzip to 1.11.4 (#1845) | ported | this commit | Vendored binary dep. Replaced cpp/zip/include/{zip.h,zipconf.h} and the 4 prebuilt libs/common/<abi>/libzip.a with upstream 1.11.4 (fork's zipconf was mislabeled "0.11"). Sole consumer AssetExtractor.cpp uses only stable API (zip_open/fread/stat_index/…). Verified: native compile+link all ABIs exit 0. Caveat: binary swap verified to link only — on-device asset extraction should be smoke-tested. |
| — | 0387a8d | feat: update NDK to latest LTS (r27d) (#1846) | deferred | — | Portable change is defaultNdkVersion 27.1.12297006 → 27.3.13750724 in runtime/build.gradle. Deferred: r27d is NOT installed in this env (only 27.0–27.2), and AGP's ndkVersion defaultNdkVersion would fail the build (blocking verification of later commits). Action for user: sdkmanager "ndk;27.3.13750724", then bump defaultNdkVersion. CI/package.json NDK bumps skipped (fork-specific/superseded). |
| 8 | 78589fd | feat: kotlin 2.2.x support (#1848) | ported | this commit | gradle.properties: ns_default_kotlin_version + ns_default_kotlinx_metadata_jvm_version 2.0.0 → 2.2.20. KotlinClassDescriptor.kt: kotlinx-metadata 2.2.x renamed the API — metaClass.enumEntries → metaClass.kmEnumEntries.map { it.name } (getEnumEntriesAsFields already takes Collection<String>). Verified: metadata-generator compileKotlin under 2.2.20 exit 0 + :app:buildMetadata runs the generator successfully. |
| 9 | f033061 | feat: queueMicrotask support (#1868) | ported | this commit | Registered global queueMicrotask in Runtime::Init (after Performance). The V8 original used isolate->EnqueueMicrotask; napi has no equivalent, so implemented engine-agnostically via Promise.resolve().then(cb) — preserves microtask ordering with promises (['qm1','p','qm2'] case) and runs before timers. Validates the arg is a function (TypeError otherwise). Ported testQueueMicrotask.js + mainpage.js. Verified: native compile exit 0. Caveat: ordering/timing asserts not run on-device here. |
| — | 052cb21 | feat: ES modules (ESM) support (#1836) | deferred | — | Large (26 files). Analyzed in depth. Upstream ESM is 100% V8-native (v8::Module, ScriptCompiler::CompileModule, InstantiateModule, Evaluate, GetModuleNamespace, isolate SetHostInitializeImportMetaObjectCallback / SetHostImportModuleDynamicallyCallback; ~650-line ModuleInternalCallbacks.cpp). No napi ESM API exists, so options are: (a) V8-only native via the napi↔v8 bridge (works only on V8 engine); (b) engine-agnostic load-time ESM→CJS transpilation (recommended if pursued: es-module-lexer + source rewrite + import.meta/dynamic-import polyfills through existing CommonJS path; live-binding snapshot caveat). Portable non-native parts (all engines): Module.java .mjs resolution (.js→.mjs order, index.mjs), AppConfig.logScriptLoading + Runtime.getLogScriptLoadingEnabled, SBG generateUniqueFileIdentifier (MD5, collision-safe for .mjs) + isJsFile .mjs, jsparser sourceType:'module' for .mjs. Deferred by user pending design decision. Related 7782720f (http esm realms + HMR) also depends on this. |
| 10 | 5cb66ee | fix: prevent crash when jweak points to null (#1881) | ported | this commit | Engine-agnostic JNI/cache fix, applied to the same files under new paths. jni/JEnv.h: add isSameObject. jni/LRUCache.h: add optional cacheValidCallback (checked on lookup → evict stale entries via new evictKey) + member. objectmanager/ObjectManager: add ValidateWeakGlobalRefCallback (!isSameObject(obj, NULL)) and pass it to the weak-ref m_cache. Skipped upstream's cosmetic robin_hood/emplace swap (new LRUCache uses std::unordered_map). Verified: native compile all ABIs exit 0. |
| — | 7782720f | feat: http loaded es module realms + HMR DX enrichments (#1883) | deferred | — | Builds directly on the deferred ESM feature (modifies the V8 ModuleInternalCallbacks, adds HMRSupport + DevFlags on top of ESM realms, testESModules.mjs). Deferred together with [[052cb21]] ESM. |
| — | 45fb275 | feat: Ada 3.3.0 (#1884) | already-present | — | Covered by the consolidated Ada v3 upgrade in commit 8109ff9 (fork now on Ada 3.3.0). |
| 11 | c05e283 | fix: improve reThrowToJava exception handling and runtime retrieval (#1886) | ported | this commit | Made ObjectManager::GetClassName (both overloads) + its JAVA_LANG_CLASS/GET_NAME_METHOD_ID JNI ids static (added out-of-class defs), so NativeScriptException can resolve a Java class name during rethrow without Runtime::GetRuntime(env)->GetObjectManager() (avoids a throw-within-rethrow when the env/runtime is unavailable). Updated the 3 class-name-only sites in NativeScriptException; left GetJavaExceptionFromEnv (needs the instance). Skipped upstream's clang-format churn of ObjectManager. Runtime-retrieval robustness (.at→.find) already present in napi GetRuntime. Verified: native compile+link all ABIs exit 0. |
| 12 | 3ecd707 | fix: proguard builds (#1887) | ported | this commit | Comment out buildMetadata.finalizedBy(currentTask) for minify*WithR8 (app/build.gradle:1181); add consumerProguardFiles 'consumer-rules.pro' to runtime defaultConfig; new runtime/consumer-rules.pro (keep com.tns.*, com.tns.gen.**, com.tns.internal.** + RuntimeVisibleAnnotations). Verified: gradle config eval exit 0. Caveat: full effect only observable in a release R8/minify build. |
| — | 288491f | fix: http realm cache key with query params (#1896) | deferred | — | Touches only HMRSupport.cpp + ModuleInternalCallbacks.cpp — both part of the deferred ESM / http-module-realms work ([[052cb21]], [[7782720f]]). Deferred with them. |
| 13 | 3e61cef | fix: URLSearchParams.forEach() crash and spec compliance (#1895) | partial | this commit | New ForEach already had spec arg order (value, key, searchParams), thisArg, and stop-on-throw. Only remaining bug: iterated with get_keys()+get(key) (returns first value for duplicate keys) → switched to get_entries() structured-binding loop (?a=1&a=2 now yields both). Ported the 4 forEach unit tests. Verified: native compile all ABIs exit 0. |
| — | 5ceb3d4 | feat: remote module security (#1899) | deferred | — | Secures the http-loaded module realms; C++ lands in DevFlags.cpp/h + HMRSupport.cpp + ModuleInternalCallbacks.cpp — none of which exist in the fork (they come from the deferred [[7782720f]] / [[052cb21]]). Java AppConfig/Runtime additions only make sense with that feature present. Deferred with ESM. |
| 14 | e924542 | feat: improved error logging for NativeScript exceptions (#1908) | partial | this commit | Ported: the engine-agnostic std::set_terminate(LogAndAbortUncaught) handler in Runtime::Init(JavaVM) — logs an uncaught native exception (message via new NativeScriptException::what()) before _Exit, instead of a bare abort. Skipped: the 726-line V8-based NativeScriptException.cpp/h logging rewrite — the fork's napi NativeScriptException already builds messages/stacks its own way (GetErrorMessage/GetErrorStackTrace/GetFullMessage/PrintErrorMessage) and the old logic is V8-stack-specific; not a clean map. Verified: native compile all ABIs exit 0. |
| 15 | 1fd144f | fix: multithreadedJS should use concurrent java maps (#1920) | ported | this commit | Broadened Runtime.java instance-map fields (strongInstances, weakInstances, strongJavaObjectToID, weakJavaObjectToID, loadedJavaScriptExtends) from concrete types to the Map<> interface (only Map methods are used; NativeScriptHashMap implements Map), and in the Runtime(config, dynamicConfig) ctor swap them to ConcurrentHashMap/Collections.synchronizedMap(...) when getEnableMultithreadedJavascript(). Ported ConcurrentAccessTest.java + testConcurrentAccess.js + mainpage require. Verified: :runtime: + :app:compileDebugJavaWithJavac exit 0. Caveat: concurrency behavior is device-only. |
| 22 | 085bc4f + 3c956cf | feat: @CriticalNative/@FastNative on safe methods (#1921) + optimized native method registration for Android 8-11 (#1942) | ported | this commit | Combined end-state (3c956cf supersedes 085bc4f). Java (Runtime.java): SUPPORTS_OPTIMIZED_NATIVE = SDK_INT>=26; each hot native now has an annotated variant + a *Legacy variant + a dispatcher — @CriticalNative generateNewObjectIdCritical/getCurrentRuntimeIdCritical/getPointerSizeCritical (static, no-arg/primitive) and @FastNative notifyGcFast/setManualInstrumentationModeFast. C++ (com_tns_Runtime.cpp): RegisterOptimizedNatives binds the critical/fast impls via RegisterNatives in JNI_OnLoad (dynamic lookup of annotated methods is broken on API 26-30); *Legacy externs auto-bind for older/ fallback. generateNewObjectId made static-safe via GenerateNewObjectId(nullptr,nullptr) (ignores env/obj); getCurrentRuntimeId uses Runtime::Current() (no JNI). notifyGc keeps the fork's (int,int[]) → "(I[I)Z". Verified: native+Java compile all ABIs exit 0. Caveat: ART calling-convention correctness is device-only — validate on Android 8-11 + 12+ and a release build. |
| 16 | df4e81b | fix: select correct runtime when calling from different threads (#1906) | ported | this commit | Headline fix: in Runtime.callJSMethod(int runtimeId, …), re-select via getObjectRuntime(javaObject) not only when the id misses but also when the found runtime doesn't own the object (runtime.getJavaObjectID(javaObject) == null) — fixes a worker invoking a method on an object created in another runtime. Diagnostic: wrapped GetJavaObjectByID in ObjectManager::GetJavaObjectByJsObject with a clearer error (id + original message via the what() added in [[c05e283]]/#1908 port). Adapted to the fork's runtimeId-based dispatch; skipped upstream's NSE ToString()/GetErrorMessage() (fork already has its own message infra + what()). Verified: native+Java compile exit 0. |
| 17 | 54185ff | [jsparser]: disable sourcemap (#1796) | ported | this commit | jsparser/webpack.config.js: devtool: false. |
| 18 | e963d6c | fix: jsparser report webpack failure (#1797) | ported | this commit | jsparser/js_parser.js: traverseFiles throws when no files found (empty = broken webpack build) instead of silently succeeding. |
| 19 | dd2984b | fix(jsparser): skip non-Identifier keys in .extend({}) (#1950) |
ported | this commit | jsparser/visitors/es5-visitors.js: added _getIdentifierKeyName helper; both .extend property loops now skip spreads/computed/non-Identifier keys instead of crashing (e.g. bundled Zod .extend({ ...other })). Verified: jsparser npm run build (webpack) compiles clean (build/ is gitignored, rebuilt on demand). |
| 20 | 49da71b | fix: circular dependencies when using proguard (#1910) | ported | this commit | Comment out the buildMetadata task's dependsOn compile*ArtProfile and dependsOn optimize*Resources (app/build.gradle) — they created a circular task dependency under proguard/R8. Verified: :app:help config eval exit 0. Caveat: full effect only in a release R8 build. |
| 21 | 2b6cb03 | fix: ensure dex cache directory exists before proxy generation (#1938) | ported | this commit | RuntimeHelper.java: mkdirs() the code_cache/secondary-dexes dir and fall back to appDir/secondary-dexes when it's missing/unwritable. ProxyGenerator.java (runtime-binding-generator): create the parent dir before createNewFile() — prevents ENOENT crashes on newer Android. Verified: app + generators compile exit 0. |
| 23 | c9d41e6 + fce8e29 | fix(DexFactory): inject DEX into parent class loader (#1951) + register with a single class loader (#1968) | ported | this commit | Combined end-state (fce8e29 supersedes c9d41e6). Enables Class.forName() to find runtime-generated proxies (e.g. FragmentFactory). DexFactory: new injectIntoParentClassLoader ctor flag; resolveClass adds the jar into the app's own BaseDexClassLoader via injectDexIntoClassLoader (API 24+ addDexPath; 23/<23 appendDexElements using DexPathList.makePathElements/makeDexElements) so the DexFile has a single owner (avoids ART "registered with multiple class loaders" on release builds), falling back to an isolated DexClassLoader on failure. Runtime.java passes isMainThread (workerId==0). Ported testClassForNameDiscovery.js. Verified: Java compile exit 0 (reflection has a try/catch fallback if hidden-API access is blocked). Caveat: hidden-API reflection is Android-version-sensitive — validate on real devices across versions and a release/non-debuggable build. |
| 24 | bfd7650 | fix(timers): order timers with the Java MessageQueue instead of ALooper fds | ported | this commit | Replaced the fork's ALooper-fd/pipe/watcher-thread/mutex/condvar timer delivery with a per-runtime com.tns.TimerHandler (new) bound to the runtime thread's Looper: each timer enqueues one anonymous due-token via sendMessageAtTime, and native FireTimer() (called from handleMessage) fires the earliest-due entry of sortedTimers_ (now vector<TimerReference> by exact sub-ms dueTime). postTimer posts due-now at (long)now (FIFO tie with postDelayed(0)) and future at ceil(dueTime). Removed threadLoop/PumpTimerLoopCallback/fd/thread/mutex/condvar; added extern "C" Java_com_tns_TimerHandler_nativeFireTimer (symbol-bound). Also fixed the pre-existing callback-napi_ref leak by centralizing ref deletion in TimerTask::Unschedule. Ported testNativeTimers.js. Verified: native+Java compile all ABIs exit 0. Caveat: ordering semantics are device-only — run the regression tests on a device. |
| 25 | 89893ae | fix: URLSearchParams construction and iteration spec compliance (#1970) | ported | this commit | Rewrote the fork's napi URLSearchParams to the WebIDL spec: constructor now accepts string / record / sequence-of-pairs / primitive (via napi_typeof + @@iterator detection, napi_get_all_property_names for records, an ES-iterator driver for sequences with IteratorClose on abrupt completion, napi_coerce_to_string USVString coercion). keys/values/entries now return live spec iterators (a JS {next} object over ada operator[], duplicate-key correct, Symbol.iterator→self, Symbol.toStringTag="URLSearchParams Iterator", freed via napi_add_finalizer); prototype[@@iterator]===entries. Added optional-value delete/has; get returns null (not undefined) on miss; brand checks ("Illegal invocation") + ValueToString coercion on every method. Ported the full 522-line spec test. Verified: native compile all ABIs exit 0. Caveat: spec behavior validated by the ported WPT-style tests on-device. |
| — | 92c2654 | fix: anchor relative dynamic imports at the file:// referrer's directory (#1976) | deferred | — | ESM-only: touches ModuleInternalCallbacks.cpp (deferred ESM file) + .mjs tests. Deferred with [[052cb21]]. |
| — | 45ed1f6 | fix: normalize "." and ".." in resolved module paths to dedupe modules (#1977) | deferred | — | ESM-only: ModuleInternalCallbacks.cpp + .mjs tests. Deferred with [[052cb21]]. |
| 26 | a84d3c7 | feat(workers): move worker threading and messaging to C++ (#1972) | partial | this commit | Ported the engine-agnostic core (Path B): moved worker threading/messaging from Java HandlerThread into C++ — new runtime/workers/{WorkerWrapper,ConcurrentQueue,LooperTasks,WorkerMessage}. Parent→worker via a ConcurrentQueue (eventfd+ALooper) inbox; worker→parent via the parent Runtime's LooperTasks. WorkerWrapper owns the worker std::thread (JVM attach → Java initWorkerRuntime/runWorkerLoop/detachWorkerRuntime → C++ inbox pump), nested-worker cascade, registry. Removed the Java WorkerThread/WorkerThreadHandler/MainThreadHandler worker branches, initWorker, the 6 worker native decls + JNI entrypoints, and MessageType.java/JavaScriptErrorMessage.java. Perf: removes 2 JNI hops + Java Message/Handler alloc + dispatch per postMessage. Scoped out (V8-only, no napi surface): v8::ValueSerializer structured clone and SharedArrayBuffer → payloads stay JSON on all engines (same semantics as before); terminate() is cooperative (no v8::TerminateExecution — a busy-loop worker isn't preempted). napi_env↔WorkerWrapper via a registry (no isolate data slot). Verified: native+Java compile all ABIs exit 0. Caveat: worker threading/lifecycle correctness is device-only — exercise the worker test suite on a device. |
| 27 | 55da2da + 4b5ab0a | feat(inspector): serve source maps to DevTools (#1969) + attach DevTools to Web Worker isolates (#1973) | ported | this commit | Ported both V8-inspector features onto the fork's napi+v8 hybrid inspector (runtime/inspector/JsV8InspectorClient reaches env->isolate). #1969: Network.loadNetworkResource/IO.read/IO.close disk-served source maps, nsruntime:// sourceMapURL rewrite (+disableSourceMapURLRewrite), socket-thread message fast-path, resource-stream cleanup on disconnect. #1973: new WorkerInspectorClient.{h,cpp} (per-worker V8 inspector Target/flat-session CDP) + Target-domain worker-session routing + console history; hooked into the new napi WorkerWrapper — CreateInspector(napi_env)/DestroyInspector() in BackgroundLooper around RunWorker, NotifyTerminating() on cooperative terminate, consoleLogCallback via WorkerWrapper::FromEnv. Vendored third_party/json.hpp. All inspector code gated #if defined(__V8__) && defined(APPLICATION_IN_DEBUG) — verified multi-engine-safe (QuickJS build green). Verified: V8-13 debug + QuickJS native+Java compile exit 0. Caveat: actual DevTools attach / worker breakpoints / source-map fetch / pause-interrupt are device+DevTools-only — run a live debug session before shipping. |
| 4 | 3423e6f | feat: support 16 KB page sizes, gradle 8.5 (#1818) | partial | ca93e66 | Ported: 16 KB -Wl,-z,max-page-size=16384 link option for arm64-v8a/x86_64 in runtime/CMakeLists.txt (verified present in ninja LINK_FLAGS); bumped NS_DEFAULT_COMPILE_SDK_VERSION/NS_DEFAULT_BUILD_TOOLS_VERSION 34→35 (both installed). Skipped: gradle-wrapper 8.4→8.7 and AGP 8.3.2→8.5.0 (new already newer: gradle 8.14.3 / AGP 8.12.1); STL c++_shared→c++_static (new deliberately uses c++_shared for multi-engine libc++ — would break engine .so setup). Verified: native reconfigure+relink exit 0. |
MISSING → implement: f033061 queueMicrotask · Ada v3.x chain (b31fc5f/3633aed/3513ce7/83f611b/45fb275) — new bundles Ada 2.9.0, old 3.3.0.
PARTIAL → port delta only: 052cb21 ESM (deferred) · URLSearchParams spec follow-ups (3e61cef/89893ae/288491f).
ALREADY-PRESENT → skip w/ evidence: e98367c performance api (Performance.h) · e293636 companion-object inner-type (MetadataNode::SetInnerTypes hasOwnProperty guard) · 1fd144f/df4e81b multithreaded runtime selection (Runtime.java ConcurrentHashMap + dual-path getCurrentRuntimeId).
Build deltas (new behind old): compileSdk/targetSdk 34→35, buildTools 34→35, Kotlin 2.0.0→2.2.20, NDK default 27.1→27.3. AGP/Gradle 8.12.1, JDK 17, minSdk 21 already match. Apply only per-commit version/flag deltas; preserve multi-engine gradle/CMake machinery.