diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index 70fb3b9a3..dbef2e1c8 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -17,6 +17,6 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout Repository' - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: 'Dependency Review' - uses: actions/dependency-review-action@56339e523c0409420f6c2c9a2f4292bbb3c07dd3 # v4.8.0 + uses: actions/dependency-review-action@3c4e3dcb1aa7874d2c16be7d79418e9b7efd6261 # v4.8.2 diff --git a/.github/workflows/npm_release.yml b/.github/workflows/npm_release.yml index 00f2c9ee3..40b76f74c 100644 --- a/.github/workflows/npm_release.yml +++ b/.github/workflows/npm_release.yml @@ -25,11 +25,11 @@ jobs: npm_tag: ${{ steps.npm_version_output.outputs.NPM_TAG }} steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 + uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 with: egress-policy: audit - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: fetch-depth: 0 submodules: true @@ -97,11 +97,11 @@ jobs: needs: build steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 + uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 with: egress-policy: audit - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: submodules: true - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 @@ -136,7 +136,7 @@ jobs: - name: SBG tests run: ./gradlew runSbgTests --stacktrace - name: Run unit tests - uses: ReactiveCircus/android-emulator-runner@1dcd0090116d15e7c562f8db72807de5e036a4ed # v2.34.0 + uses: ReactiveCircus/android-emulator-runner@b530d96654c385303d652368551fb075bc2f0b6b # v2.35.0 with: api-level: ${{env.ANDROID_API}} # this is needed on API 30+ @@ -145,6 +145,7 @@ jobs: script: ./gradlew runtestsAndVerifyResults --stacktrace publish: runs-on: ubuntu-latest + environment: npm-publish needs: - build - test @@ -156,7 +157,7 @@ jobs: NPM_TAG: ${{needs.build.outputs.npm_tag}} steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 + uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 with: egress-policy: audit @@ -168,10 +169,27 @@ jobs: with: name: npm-package path: dist - - name: Publish package + - name: Update npm (required for OIDC trusted publishing) run: | - echo "Publishing @nativescript/android@$NPM_VERSION to NPM with tag $NPM_TAG..." - npm publish ./dist/nativescript-android-${{env.NPM_VERSION}}.tgz --tag $NPM_TAG --provenance + npm install -g npm@^11.5.1 + npm --version + - name: Publish package (OIDC trusted publishing) + if: ${{ vars.USE_NPM_TOKEN != 'true' }} + run: | + echo "Publishing @nativescript/android@$NPM_VERSION to NPM with tag $NPM_TAG via OIDC trusted publishing..." + unset NODE_AUTH_TOKEN + if [ -n "${NPM_CONFIG_USERCONFIG:-}" ]; then + rm -f "$NPM_CONFIG_USERCONFIG" + fi + npm publish ./dist/nativescript-android-${{env.NPM_VERSION}}.tgz --tag $NPM_TAG --access public --provenance + env: + NODE_AUTH_TOKEN: "" + + - name: Publish package (granular token) + if: ${{ vars.USE_NPM_TOKEN == 'true' }} + run: | + echo "Publishing @nativescript/android@$NPM_VERSION to NPM with tag $NPM_TAG via granular token..." + npm publish ./dist/nativescript-android-${{env.NPM_VERSION}}.tgz --tag $NPM_TAG --access public --provenance env: NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} github-release: @@ -187,11 +205,11 @@ jobs: NPM_VERSION: ${{needs.build.outputs.npm_version}} steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 + uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 with: egress-policy: audit - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: fetch-depth: 0 - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 00ad240a1..d5b39fa0d 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -21,7 +21,7 @@ jobs: npm_version: ${{ steps.npm_version_output.outputs.NPM_VERSION }} npm_tag: ${{ steps.npm_version_output.outputs.NPM_TAG }} steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: fetch-depth: 0 submodules: true @@ -87,7 +87,7 @@ jobs: runs-on: macos-15-intel needs: build steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: submodules: true - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 @@ -122,7 +122,7 @@ jobs: - name: SBG tests run: ./gradlew runSbgTests --stacktrace - name: Run unit tests - uses: ReactiveCircus/android-emulator-runner@1dcd0090116d15e7c562f8db72807de5e036a4ed # v2.34.0 + uses: ReactiveCircus/android-emulator-runner@b530d96654c385303d652368551fb075bc2f0b6b # v2.35.0 with: api-level: ${{env.ANDROID_API}} # this is needed on API 30+ diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index f9a740d7d..a6ebe4115 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -33,7 +33,7 @@ jobs: steps: - name: "Checkout code" - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: persist-credentials: false @@ -68,6 +68,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@2d92b76c45b91eb80fc44c74ce3fce0ee94e8f9d # v3.30.0 + uses: github/codeql-action/upload-sarif@fe4161a26a8629af62121b670040955b330f9af2 # v4.31.6 with: sarif_file: results.sarif \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index c7bcbc413..5fa3b0a05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +## [9.0.1](https://github.com/NativeScript/android/compare/v9.0.0...v9.0.1) (2025-12-14) + + +### Bug Fixes + +* http realm cache key with query params ([#1896](https://github.com/NativeScript/android/issues/1896)) ([288491f](https://github.com/NativeScript/android/commit/288491fb0f5ce0f296285a878ba6ac34f78f299b)) +* improve reThrowToJava exception handling and runtime retrieval logic ([#1886](https://github.com/NativeScript/android/issues/1886)) ([c05e283](https://github.com/NativeScript/android/commit/c05e283823f1ddab43cefa3c9e828b3e66c97794)) +* proguard builds ([#1887](https://github.com/NativeScript/android/issues/1887)) ([3ecd707](https://github.com/NativeScript/android/commit/3ecd707be4494e26050bacc8d1916d22c5dd6433)) +* URLSearchParams.forEach() crash and spec compliance ([#1895](https://github.com/NativeScript/android/issues/1895)) ([3e61cef](https://github.com/NativeScript/android/commit/3e61cef4e90a7d3b27155ca38d69e6ce59dc0d2e)) + + + # [9.0.0](https://github.com/NativeScript/android/compare/v8.9.2...v9.0.0) (2025-11-17) diff --git a/package.json b/package.json index 157053ec9..f0886323d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@nativescript/android", "description": "NativeScript for Android using v8", - "version": "9.0.0", + "version": "9.0.1", "repository": { "type": "git", "url": "https://github.com/NativeScript/android.git" diff --git a/test-app/app/build.gradle b/test-app/app/build.gradle index ec5aea981..fded0e47a 100644 --- a/test-app/app/build.gradle +++ b/test-app/app/build.gradle @@ -1154,7 +1154,7 @@ tasks.configureEach({ DefaultTask currentTask -> // // ensure buildMetadata is done before R8 to allow custom proguard from metadata if (currentTask =~ /minify.*WithR8/) { - buildMetadata.finalizedBy(currentTask) + // buildMetadata.finalizedBy(currentTask) } if (currentTask =~ /assemble.*Debug/ || currentTask =~ /assemble.*Release/) { currentTask.finalizedBy("validateAppIdMatch") diff --git a/test-app/app/src/main/assets/app/tests/testURLSearchParamsImpl.js b/test-app/app/src/main/assets/app/tests/testURLSearchParamsImpl.js index 5437250d5..3d0ca293f 100644 --- a/test-app/app/src/main/assets/app/tests/testURLSearchParamsImpl.js +++ b/test-app/app/src/main/assets/app/tests/testURLSearchParamsImpl.js @@ -61,4 +61,57 @@ describe("Test URLSearchParams ", function () { expect(url.toString()).toBe(toBe); }); + it("Test URLSearchParams forEach", function(){ + const params = new URLSearchParams(fooBar); + const results = []; + params.forEach((value, key, searchParams) => { + results.push({ key, value }); + expect(searchParams).toBe(params); + }); + expect(results.length).toBe(2); + expect(results[0].key).toBe("foo"); + expect(results[0].value).toBe("1"); + expect(results[1].key).toBe("bar"); + expect(results[1].value).toBe("2"); + }); + + it("Test URLSearchParams forEach with URL", function(){ + const url = new URL('https://example.com?si=abc123&name=test'); + const results = []; + url.searchParams.forEach((value, key) => { + results.push({ key, value }); + }); + expect(results.length).toBe(2); + expect(results[0].key).toBe("si"); + expect(results[0].value).toBe("abc123"); + expect(results[1].key).toBe("name"); + expect(results[1].value).toBe("test"); + }); + + it("Test URLSearchParams forEach with thisArg", function(){ + const params = new URLSearchParams(fooBar); + const context = { results: [] }; + params.forEach(function(value, key) { + this.results.push({ key, value }); + }, context); + expect(context.results.length).toBe(2); + expect(context.results[0].key).toBe("foo"); + expect(context.results[0].value).toBe("1"); + }); + + it("Test URLSearchParams forEach with duplicate keys", function(){ + const params = new URLSearchParams("foo=1&foo=2&bar=3"); + const results = []; + params.forEach((value, key) => { + results.push({ key, value }); + }); + expect(results.length).toBe(3); + expect(results[0].key).toBe("foo"); + expect(results[0].value).toBe("1"); + expect(results[1].key).toBe("foo"); + expect(results[1].value).toBe("2"); + expect(results[2].key).toBe("bar"); + expect(results[2].value).toBe("3"); + }); + }); diff --git a/test-app/runtime/build.gradle b/test-app/runtime/build.gradle index 64697a940..212407497 100644 --- a/test-app/runtime/build.gradle +++ b/test-app/runtime/build.gradle @@ -126,6 +126,8 @@ android { abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a' } } + + consumerProguardFiles 'consumer-rules.pro' } compileOptions { diff --git a/test-app/runtime/consumer-rules.pro b/test-app/runtime/consumer-rules.pro new file mode 100644 index 000000000..329ee7266 --- /dev/null +++ b/test-app/runtime/consumer-rules.pro @@ -0,0 +1,11 @@ +# Keep all runtime classes +-keep class com.tns.* { *; } + +# Keep SBG-generated classes +-keep class com.tns.gen.** { *; } + +# Keep internal support/runtime classes +-keep class com.tns.internal.** { *; } + +# Preserve annotation metadata so reflection sees them +-keepattributes RuntimeVisibleAnnotations \ No newline at end of file diff --git a/test-app/runtime/src/main/cpp/HMRSupport.cpp b/test-app/runtime/src/main/cpp/HMRSupport.cpp index be80eae0d..7665c1d16 100644 --- a/test-app/runtime/src/main/cpp/HMRSupport.cpp +++ b/test-app/runtime/src/main/cpp/HMRSupport.cpp @@ -3,6 +3,7 @@ #include "ArgConverter.h" #include "JEnv.h" #include +#include #include #include #include @@ -148,11 +149,43 @@ std::string CanonicalizeHttpUrlKey(const std::string& url) { size_t hashPos = url.find('#'); std::string noHash = (hashPos == std::string::npos) ? url : url.substr(0, hashPos); - // Strip ?import markers and sort remaining query params for stability + // Split into origin+path and query size_t qPos = noHash.find('?'); - if (qPos == std::string::npos) return noHash; - std::string originAndPath = noHash.substr(0, qPos); - std::string query = noHash.substr(qPos + 1); + std::string originAndPath = (qPos == std::string::npos) ? noHash : noHash.substr(0, qPos); + std::string query = (qPos == std::string::npos) ? std::string() : noHash.substr(qPos + 1); + + // Normalize bridge endpoints to keep a single realm across HMR updates: + // - /ns/rt/ -> /ns/rt + // - /ns/core/ -> /ns/core + size_t schemePos = originAndPath.find("://"); + if (schemePos != std::string::npos) { + size_t pathStart = originAndPath.find('/', schemePos + 3); + if (pathStart != std::string::npos) { + std::string pathOnly = originAndPath.substr(pathStart); + auto normalizeBridge = [&](const char* needle) { + size_t nlen = strlen(needle); + if (pathOnly.size() <= nlen) return false; + if (pathOnly.compare(0, nlen, needle) != 0) return false; + if (pathOnly.size() == nlen) return true; + if (pathOnly[nlen] != '/') return false; + size_t i = nlen + 1; + size_t j = i; + while (j < pathOnly.size() && isdigit((unsigned char)pathOnly[j])) j++; + // Only normalize exact version segment: /ns/*/ (no further segments) + if (j == i) return false; + if (j != pathOnly.size()) return false; + originAndPath = originAndPath.substr(0, pathStart) + std::string(needle); + return true; + }; + if (!normalizeBridge("/ns/rt")) { + normalizeBridge("/ns/core"); + } + } + } + + if (query.empty()) return originAndPath; + + // Strip ?import markers and sort remaining query params for stability std::vector kept; size_t start = 0; while (start <= query.size()) { diff --git a/test-app/runtime/src/main/cpp/ModuleInternalCallbacks.cpp b/test-app/runtime/src/main/cpp/ModuleInternalCallbacks.cpp index e95a2b696..e0e9803b0 100644 --- a/test-app/runtime/src/main/cpp/ModuleInternalCallbacks.cpp +++ b/test-app/runtime/src/main/cpp/ModuleInternalCallbacks.cpp @@ -591,7 +591,8 @@ v8::MaybeLocal ResolveModuleCallback(v8::Local context, "}\n" "\n" "export function pathToFileURL(path) {\n" - " return new URL('file://' + encodeURIComponent(path));\n" + " const encoded = encodeURIComponent(path).replace(/%2F/g, '/');\n" + " return new URL('file://' + encoded);\n" "}\n"; } else if (builtinName == "module") { // Create a polyfill for node:module with createRequire diff --git a/test-app/runtime/src/main/cpp/NativeScriptException.cpp b/test-app/runtime/src/main/cpp/NativeScriptException.cpp index add4c553f..1a2e92303 100644 --- a/test-app/runtime/src/main/cpp/NativeScriptException.cpp +++ b/test-app/runtime/src/main/cpp/NativeScriptException.cpp @@ -75,8 +75,7 @@ void NativeScriptException::ReThrowToJava() { if (!m_javaException.IsNull()) { - auto objectManager = Runtime::GetObjectManager(isolate); - auto excClassName = objectManager->GetClassName((jobject) m_javaException); + auto excClassName = ObjectManager::GetClassName((jobject) m_javaException); if (excClassName == "com/tns/NativeScriptException") { ex = m_javaException; } else { @@ -103,8 +102,7 @@ void NativeScriptException::ReThrowToJava() { if (ex == nullptr) { ex = static_cast(env.NewObject(NATIVESCRIPTEXCEPTION_CLASS, NATIVESCRIPTEXCEPTION_JSVALUE_CTOR_ID, (jstring) msg, (jstring)stackTrace, reinterpret_cast(m_javascriptException))); } else { - auto objectManager = Runtime::GetObjectManager(isolate); - auto excClassName = objectManager->GetClassName(ex); + auto excClassName = ObjectManager::GetClassName(ex); if (excClassName != "com/tns/NativeScriptException") { ex = static_cast(env.NewObject(NATIVESCRIPTEXCEPTION_CLASS, NATIVESCRIPTEXCEPTION_THROWABLE_CTOR_ID, (jstring) msg, (jstring)stackTrace, ex)); } @@ -184,9 +182,8 @@ Local NativeScriptException::WrapJavaToJsException() { JEnv env; auto isolate = Isolate::GetCurrent(); - auto objectManager = Runtime::GetObjectManager(isolate); - string excClassName = objectManager->GetClassName((jobject) m_javaException); + string excClassName = ObjectManager::GetClassName((jobject) m_javaException); if (excClassName == "com/tns/NativeScriptException") { jfieldID fieldID = env.GetFieldID(env.GetObjectClass(m_javaException), "jsValueAddress", "J"); jlong addr = env.GetLongField(m_javaException, fieldID); diff --git a/test-app/runtime/src/main/cpp/ObjectManager.cpp b/test-app/runtime/src/main/cpp/ObjectManager.cpp index ba01fb8b6..18454156e 100644 --- a/test-app/runtime/src/main/cpp/ObjectManager.cpp +++ b/test-app/runtime/src/main/cpp/ObjectManager.cpp @@ -1,559 +1,615 @@ #include "ObjectManager.h" -#include "NativeScriptAssert.h" -#include "MetadataNode.h" + +#include +#include + #include "ArgConverter.h" +#include "ManualInstrumentation.h" +#include "MetadataNode.h" +#include "NativeScriptAssert.h" +#include "NativeScriptException.h" +#include "Runtime.h" #include "Util.h" #include "V8GlobalHelpers.h" #include "V8StringConstants.h" -#include "NativeScriptException.h" -#include "Runtime.h" #include "include/v8.h" -#include -#include -#include "ManualInstrumentation.h" using namespace v8; using namespace std; using namespace tns; -ObjectManager::ObjectManager(jobject javaRuntimeObject) : - m_javaRuntimeObject(javaRuntimeObject), - m_numberOfGC(0), - m_currentObjectId(0), - m_cache(NewWeakGlobalRefCallback, DeleteWeakGlobalRefCallback, ValidateWeakGlobalRefCallback, 1000, this) { - - JEnv env; - auto runtimeClass = env.FindClass("com/tns/Runtime"); - assert(runtimeClass != nullptr); - - GET_JAVAOBJECT_BY_ID_METHOD_ID = env.GetMethodID(runtimeClass, "getJavaObjectByID", - "(I)Ljava/lang/Object;"); - assert(GET_JAVAOBJECT_BY_ID_METHOD_ID != nullptr); - - GET_OR_CREATE_JAVA_OBJECT_ID_METHOD_ID = env.GetMethodID(runtimeClass, - "getOrCreateJavaObjectID", - "(Ljava/lang/Object;)I"); - assert(GET_OR_CREATE_JAVA_OBJECT_ID_METHOD_ID != nullptr); - - MAKE_INSTANCE_WEAK_BATCH_METHOD_ID = env.GetMethodID(runtimeClass, "makeInstanceWeak", - "(Ljava/nio/ByteBuffer;IZ)V"); - assert(MAKE_INSTANCE_WEAK_BATCH_METHOD_ID != nullptr); - - MAKE_INSTANCE_WEAK_AND_CHECK_IF_ALIVE_METHOD_ID = env.GetMethodID(runtimeClass, - "makeInstanceWeakAndCheckIfAlive", - "(I)Z"); - assert(MAKE_INSTANCE_WEAK_AND_CHECK_IF_ALIVE_METHOD_ID != nullptr); - - RELEASE_NATIVE_INSTANCE_METHOD_ID = env.GetMethodID(runtimeClass, "releaseNativeCounterpart", - "(I)V"); - assert(RELEASE_NATIVE_INSTANCE_METHOD_ID != nullptr); - - CHECK_WEAK_OBJECTS_ARE_ALIVE_METHOD_ID = env.GetMethodID(runtimeClass, - "checkWeakObjectAreAlive", - "(Ljava/nio/ByteBuffer;Ljava/nio/ByteBuffer;I)V"); - assert(CHECK_WEAK_OBJECTS_ARE_ALIVE_METHOD_ID != nullptr); - - JAVA_LANG_CLASS = env.FindClass("java/lang/Class"); - assert(JAVA_LANG_CLASS != nullptr); - - GET_NAME_METHOD_ID = env.GetMethodID(JAVA_LANG_CLASS, "getName", "()Ljava/lang/String;"); - assert(GET_NAME_METHOD_ID != nullptr); - - auto useGlobalRefsMethodID = env.GetStaticMethodID(runtimeClass, "useGlobalRefs", "()Z"); - assert(useGlobalRefsMethodID != nullptr); - - auto useGlobalRefs = env.CallStaticBooleanMethod(runtimeClass, useGlobalRefsMethodID); - m_useGlobalRefs = useGlobalRefs == JNI_TRUE; - - auto getMarkingModeOrdinalMethodID = env.GetMethodID(runtimeClass, "getMarkingModeOrdinal", - "()I"); - jint markingMode = env.CallIntMethod(m_javaRuntimeObject, getMarkingModeOrdinalMethodID); - m_markingMode = static_cast(markingMode); -} - -void ObjectManager::SetInstanceIsolate(Isolate *isolate) { - m_isolate = isolate; -} - -void ObjectManager::Init(Isolate *isolate) { - auto jsWrapperFuncTemplate = FunctionTemplate::New(isolate, JSWrapperConstructorCallback); - jsWrapperFuncTemplate->InstanceTemplate()->SetInternalFieldCount( - static_cast(MetadataNodeKeys::END)); - auto context = isolate->GetCurrentContext(); - auto jsWrapperFunc = jsWrapperFuncTemplate->GetFunction(context).ToLocalChecked(); - m_poJsWrapperFunc = new Persistent(isolate, jsWrapperFunc); -} - - -JniLocalRef ObjectManager::GetJavaObjectByJsObject(const Local &object) { - JSInstanceInfo *jsInstanceInfo = GetJSInstanceInfo(object); - - if (jsInstanceInfo != nullptr) { - if (m_useGlobalRefs) { - JniLocalRef javaObject(GetJavaObjectByID(jsInstanceInfo->JavaObjectID), true); - return javaObject; - } else { - JEnv env; - JniLocalRef javaObject( - env.NewLocalRef(GetJavaObjectByID(jsInstanceInfo->JavaObjectID))); - return javaObject; - } +ObjectManager::ObjectManager(jobject javaRuntimeObject) + : m_javaRuntimeObject(javaRuntimeObject), + m_numberOfGC(0), + m_currentObjectId(0), + m_cache(NewWeakGlobalRefCallback, DeleteWeakGlobalRefCallback, + ValidateWeakGlobalRefCallback, 1000, this) { + JEnv env; + InitializeJNI(); + + auto runtimeClass = env.FindClass("com/tns/Runtime"); + assert(runtimeClass != nullptr); + + auto useGlobalRefsMethodID = + env.GetStaticMethodID(runtimeClass, "useGlobalRefs", "()Z"); + assert(useGlobalRefsMethodID != nullptr); + + auto useGlobalRefs = + env.CallStaticBooleanMethod(runtimeClass, useGlobalRefsMethodID); + m_useGlobalRefs = useGlobalRefs == JNI_TRUE; + + auto getMarkingModeOrdinalMethodID = + env.GetMethodID(runtimeClass, "getMarkingModeOrdinal", "()I"); + jint markingMode = + env.CallIntMethod(m_javaRuntimeObject, getMarkingModeOrdinalMethodID); + m_markingMode = static_cast(markingMode); +} + +void ObjectManager::InitializeJNI() { + if (GET_JAVAOBJECT_BY_ID_METHOD_ID != nullptr) { + return; + } + JEnv env; + auto runtimeClass = env.FindClass("com/tns/Runtime"); + assert(runtimeClass != nullptr); + GET_JAVAOBJECT_BY_ID_METHOD_ID = env.GetMethodID( + runtimeClass, "getJavaObjectByID", "(I)Ljava/lang/Object;"); + assert(GET_JAVAOBJECT_BY_ID_METHOD_ID != nullptr); + + GET_OR_CREATE_JAVA_OBJECT_ID_METHOD_ID = env.GetMethodID( + runtimeClass, "getOrCreateJavaObjectID", "(Ljava/lang/Object;)I"); + assert(GET_OR_CREATE_JAVA_OBJECT_ID_METHOD_ID != nullptr); + + MAKE_INSTANCE_WEAK_BATCH_METHOD_ID = env.GetMethodID( + runtimeClass, "makeInstanceWeak", "(Ljava/nio/ByteBuffer;IZ)V"); + assert(MAKE_INSTANCE_WEAK_BATCH_METHOD_ID != nullptr); + + MAKE_INSTANCE_WEAK_AND_CHECK_IF_ALIVE_METHOD_ID = + env.GetMethodID(runtimeClass, "makeInstanceWeakAndCheckIfAlive", "(I)Z"); + assert(MAKE_INSTANCE_WEAK_AND_CHECK_IF_ALIVE_METHOD_ID != nullptr); + + RELEASE_NATIVE_INSTANCE_METHOD_ID = + env.GetMethodID(runtimeClass, "releaseNativeCounterpart", "(I)V"); + assert(RELEASE_NATIVE_INSTANCE_METHOD_ID != nullptr); + + CHECK_WEAK_OBJECTS_ARE_ALIVE_METHOD_ID = + env.GetMethodID(runtimeClass, "checkWeakObjectAreAlive", + "(Ljava/nio/ByteBuffer;Ljava/nio/ByteBuffer;I)V"); + assert(CHECK_WEAK_OBJECTS_ARE_ALIVE_METHOD_ID != nullptr); + + JAVA_LANG_CLASS = env.FindClass("java/lang/Class"); + assert(JAVA_LANG_CLASS != nullptr); + + GET_NAME_METHOD_ID = + env.GetMethodID(JAVA_LANG_CLASS, "getName", "()Ljava/lang/String;"); + assert(GET_NAME_METHOD_ID != nullptr); +} + +void ObjectManager::SetInstanceIsolate(Isolate* isolate) { + m_isolate = isolate; +} + +void ObjectManager::Init(Isolate* isolate) { + auto jsWrapperFuncTemplate = + FunctionTemplate::New(isolate, JSWrapperConstructorCallback); + jsWrapperFuncTemplate->InstanceTemplate()->SetInternalFieldCount( + static_cast(MetadataNodeKeys::END)); + auto context = isolate->GetCurrentContext(); + auto jsWrapperFunc = + jsWrapperFuncTemplate->GetFunction(context).ToLocalChecked(); + m_poJsWrapperFunc = new Persistent(isolate, jsWrapperFunc); +} + +JniLocalRef ObjectManager::GetJavaObjectByJsObject( + const Local& object) { + JSInstanceInfo* jsInstanceInfo = GetJSInstanceInfo(object); + + if (jsInstanceInfo != nullptr) { + if (m_useGlobalRefs) { + JniLocalRef javaObject(GetJavaObjectByID(jsInstanceInfo->JavaObjectID), + true); + return javaObject; + } else { + JEnv env; + JniLocalRef javaObject( + env.NewLocalRef(GetJavaObjectByID(jsInstanceInfo->JavaObjectID))); + return javaObject; } + } - return JniLocalRef(); + return JniLocalRef(); } -ObjectManager::JSInstanceInfo *ObjectManager::GetJSInstanceInfo(const Local &object) { - JSInstanceInfo *jsInstanceInfo = nullptr; - if (IsJsRuntimeObject(object)) { - return GetJSInstanceInfoFromRuntimeObject(object); - } - return nullptr; +ObjectManager::JSInstanceInfo* ObjectManager::GetJSInstanceInfo( + const Local& object) { + JSInstanceInfo* jsInstanceInfo = nullptr; + if (IsJsRuntimeObject(object)) { + return GetJSInstanceInfoFromRuntimeObject(object); + } + return nullptr; } -ObjectManager::JSInstanceInfo * -ObjectManager::GetJSInstanceInfoFromRuntimeObject(const Local &object) { - HandleScope handleScope(m_isolate); +ObjectManager::JSInstanceInfo* +ObjectManager::GetJSInstanceInfoFromRuntimeObject(const Local& object) { + HandleScope handleScope(m_isolate); - const int jsInfoIdx = static_cast(MetadataNodeKeys::JsInfo); - auto jsInfo = object->GetInternalField(jsInfoIdx); - if (jsInfo->IsUndefined()) { - //Typescript object layout has an object instance as child of the actual registered instance. checking for that - auto prototypeObject = object->GetPrototype().As(); + const int jsInfoIdx = static_cast(MetadataNodeKeys::JsInfo); + auto jsInfo = object->GetInternalField(jsInfoIdx); + if (jsInfo->IsUndefined()) { + // Typescript object layout has an object instance as child of the actual + // registered instance. checking for that + auto prototypeObject = object->GetPrototype().As(); - if (!prototypeObject.IsEmpty() && prototypeObject->IsObject()) { - DEBUG_WRITE("GetJSInstanceInfo: need to check prototype :%d", - prototypeObject->GetIdentityHash()); - if (IsJsRuntimeObject(prototypeObject)) { - jsInfo = prototypeObject->GetInternalField(jsInfoIdx); - } - } + if (!prototypeObject.IsEmpty() && prototypeObject->IsObject()) { + DEBUG_WRITE("GetJSInstanceInfo: need to check prototype :%d", + prototypeObject->GetIdentityHash()); + if (IsJsRuntimeObject(prototypeObject)) { + jsInfo = prototypeObject->GetInternalField(jsInfoIdx); + } } + } - if (!jsInfo.IsEmpty() && jsInfo->IsExternal()) { - auto external = jsInfo.As(); - return static_cast(external->Value()); - } + if (!jsInfo.IsEmpty() && jsInfo->IsExternal()) { + auto external = jsInfo.As(); + return static_cast(external->Value()); + } - return nullptr; + return nullptr; } -bool ObjectManager::IsJsRuntimeObject(const v8::Local &object) { - int internalFieldCount = object->InternalFieldCount(); - const int count = static_cast(MetadataNodeKeys::END); - return internalFieldCount == count; +bool ObjectManager::IsJsRuntimeObject(const v8::Local& object) { + int internalFieldCount = object->InternalFieldCount(); + const int count = static_cast(MetadataNodeKeys::END); + return internalFieldCount == count; } jweak ObjectManager::GetJavaObjectByID(uint32_t javaObjectID) { - jweak obj = m_cache(javaObjectID); + jweak obj = m_cache(javaObjectID); - return obj; + return obj; } jobject ObjectManager::GetJavaObjectByIDImpl(uint32_t javaObjectID) { - JEnv env; - jobject object = env.CallObjectMethod(m_javaRuntimeObject, GET_JAVAOBJECT_BY_ID_METHOD_ID, - javaObjectID); - return object; + JEnv env; + jobject object = env.CallObjectMethod( + m_javaRuntimeObject, GET_JAVAOBJECT_BY_ID_METHOD_ID, javaObjectID); + return object; } void ObjectManager::UpdateCache(int objectID, jobject obj) { - m_cache.update(objectID, obj); + m_cache.update(objectID, obj); } -jclass ObjectManager::GetJavaClass(const Local &instance) { - DEBUG_WRITE("GetClass called"); +jclass ObjectManager::GetJavaClass(const Local& instance) { + DEBUG_WRITE("GetClass called"); - JSInstanceInfo *jsInfo = GetJSInstanceInfo(instance); - jclass clazz = jsInfo->ObjectClazz; + JSInstanceInfo* jsInfo = GetJSInstanceInfo(instance); + jclass clazz = jsInfo->ObjectClazz; - return clazz; + return clazz; } -void ObjectManager::SetJavaClass(const Local &instance, jclass clazz) { - JSInstanceInfo *jsInfo = GetJSInstanceInfo(instance); - jsInfo->ObjectClazz = clazz; +void ObjectManager::SetJavaClass(const Local& instance, jclass clazz) { + JSInstanceInfo* jsInfo = GetJSInstanceInfo(instance); + jsInfo->ObjectClazz = clazz; } int ObjectManager::GetOrCreateObjectId(jobject object) { - JEnv env; - jint javaObjectID = env.CallIntMethod(m_javaRuntimeObject, - GET_OR_CREATE_JAVA_OBJECT_ID_METHOD_ID, object); + JEnv env; + jint javaObjectID = env.CallIntMethod( + m_javaRuntimeObject, GET_OR_CREATE_JAVA_OBJECT_ID_METHOD_ID, object); - return javaObjectID; + return javaObjectID; } Local ObjectManager::GetJsObjectByJavaObject(int javaObjectID) { - auto isolate = m_isolate; - EscapableHandleScope handleScope(isolate); + auto isolate = m_isolate; + EscapableHandleScope handleScope(isolate); - auto it = m_idToObject.find(javaObjectID); - if (it == m_idToObject.end()) { - return handleScope.Escape(Local()); - } + auto it = m_idToObject.find(javaObjectID); + if (it == m_idToObject.end()) { + return handleScope.Escape(Local()); + } - Persistent *jsObject = it->second; + Persistent* jsObject = it->second; - auto localObject = Local::New(isolate, *jsObject); - return handleScope.Escape(localObject); + auto localObject = Local::New(isolate, *jsObject); + return handleScope.Escape(localObject); } - -Local ObjectManager::CreateJSWrapper(jint javaObjectID, const string &typeName) { - return CreateJSWrapperHelper(javaObjectID, typeName, nullptr); +Local ObjectManager::CreateJSWrapper(jint javaObjectID, + const string& typeName) { + return CreateJSWrapperHelper(javaObjectID, typeName, nullptr); } -Local -ObjectManager::CreateJSWrapper(jint javaObjectID, const string &typeName, jobject instance) { - JEnv env; - JniLocalRef clazz(env.GetObjectClass(instance)); +Local ObjectManager::CreateJSWrapper(jint javaObjectID, + const string& typeName, + jobject instance) { + JEnv env; + JniLocalRef clazz(env.GetObjectClass(instance)); - return CreateJSWrapperHelper(javaObjectID, typeName, clazz); + return CreateJSWrapperHelper(javaObjectID, typeName, clazz); } -Local -ObjectManager::CreateJSWrapperHelper(jint javaObjectID, const string &typeName, jclass clazz) { - auto isolate = m_isolate; +Local ObjectManager::CreateJSWrapperHelper(jint javaObjectID, + const string& typeName, + jclass clazz) { + auto isolate = m_isolate; - auto className = (clazz != nullptr) ? GetClassName(clazz) : typeName; + auto className = (clazz != nullptr) ? GetClassName(clazz) : typeName; - auto node = MetadataNode::GetOrCreate(className); + auto node = MetadataNode::GetOrCreate(className); - auto jsWrapper = node->CreateJSWrapper(isolate, this); + auto jsWrapper = node->CreateJSWrapper(isolate, this); - if (!jsWrapper.IsEmpty()) { - JEnv env; - auto claz = env.FindClass(className); - Link(jsWrapper, javaObjectID, claz); - } - return jsWrapper; + if (!jsWrapper.IsEmpty()) { + JEnv env; + auto claz = env.FindClass(className); + Link(jsWrapper, javaObjectID, claz); + } + return jsWrapper; } - /* * * Link the JavaScript object and it's java counterpart with an ID */ -void ObjectManager::Link(const Local &object, uint32_t javaObjectID, jclass clazz) { - if (!IsJsRuntimeObject(object)) { - string errMsg("Trying to link invalid 'this' to a Java object"); - throw NativeScriptException(errMsg); - } +void ObjectManager::Link(const Local& object, uint32_t javaObjectID, + jclass clazz) { + if (!IsJsRuntimeObject(object)) { + string errMsg("Trying to link invalid 'this' to a Java object"); + throw NativeScriptException(errMsg); + } - auto isolate = m_isolate; + auto isolate = m_isolate; - DEBUG_WRITE("Linking js object: %d and java instance id: %d", object->GetIdentityHash(), - javaObjectID); + DEBUG_WRITE("Linking js object: %d and java instance id: %d", + object->GetIdentityHash(), javaObjectID); - auto jsInstanceInfo = new JSInstanceInfo(false/*isJavaObjWeak*/, javaObjectID, clazz); + auto jsInstanceInfo = + new JSInstanceInfo(false /*isJavaObjWeak*/, javaObjectID, clazz); - auto objectHandle = new Persistent(isolate, object); - auto state = new ObjectWeakCallbackState(this, jsInstanceInfo, objectHandle); + auto objectHandle = new Persistent(isolate, object); + auto state = new ObjectWeakCallbackState(this, jsInstanceInfo, objectHandle); - // subscribe for JS GC event - if (m_markingMode == JavaScriptMarkingMode::None) { - objectHandle->SetWeak(state, JSObjectFinalizerStatic, WeakCallbackType::kFinalizer); - } else { - objectHandle->SetWeak(state, JSObjectWeakCallbackStatic, WeakCallbackType::kFinalizer); - } + // subscribe for JS GC event + if (m_markingMode == JavaScriptMarkingMode::None) { + objectHandle->SetWeak(state, JSObjectFinalizerStatic, + WeakCallbackType::kFinalizer); + } else { + objectHandle->SetWeak(state, JSObjectWeakCallbackStatic, + WeakCallbackType::kFinalizer); + } - auto jsInfoIdx = static_cast(MetadataNodeKeys::JsInfo); + auto jsInfoIdx = static_cast(MetadataNodeKeys::JsInfo); - auto jsInfo = External::New(isolate, jsInstanceInfo); + auto jsInfo = External::New(isolate, jsInstanceInfo); - //link - object->SetInternalField(jsInfoIdx, jsInfo); + // link + object->SetInternalField(jsInfoIdx, jsInfo); - m_idToObject.emplace(javaObjectID, objectHandle); + m_idToObject.emplace(javaObjectID, objectHandle); } -bool ObjectManager::CloneLink(const Local &src, const Local &dest) { - auto jsInfo = GetJSInstanceInfo(src); +bool ObjectManager::CloneLink(const Local& src, + const Local& dest) { + auto jsInfo = GetJSInstanceInfo(src); - auto success = jsInfo != nullptr; + auto success = jsInfo != nullptr; - if (success) { - auto jsInfoIdx = static_cast(MetadataNodeKeys::JsInfo); - auto jsInfo = src->GetInternalField(jsInfoIdx); - dest->SetInternalField(jsInfoIdx, jsInfo); - } + if (success) { + auto jsInfoIdx = static_cast(MetadataNodeKeys::JsInfo); + auto jsInfo = src->GetInternalField(jsInfoIdx); + dest->SetInternalField(jsInfoIdx, jsInfo); + } - return success; + return success; } string ObjectManager::GetClassName(jobject javaObject) { - JEnv env; - JniLocalRef objectClass(env.GetObjectClass(javaObject)); + InitializeJNI(); + JEnv env; + JniLocalRef objectClass(env.GetObjectClass(javaObject)); - return GetClassName((jclass) objectClass); + return GetClassName((jclass)objectClass); } string ObjectManager::GetClassName(jclass clazz) { - JEnv env; - JniLocalRef javaCanonicalName(env.CallObjectMethod(clazz, GET_NAME_METHOD_ID)); + InitializeJNI(); + JEnv env; + JniLocalRef javaCanonicalName( + env.CallObjectMethod(clazz, GET_NAME_METHOD_ID)); - string className = ArgConverter::jstringToString(javaCanonicalName); + string className = ArgConverter::jstringToString(javaCanonicalName); - std::replace(className.begin(), className.end(), '.', '/'); + std::replace(className.begin(), className.end(), '.', '/'); - return className; + return className; } -void -ObjectManager::JSObjectWeakCallbackStatic(const WeakCallbackInfo &data) { - ObjectWeakCallbackState *callbackState = data.GetParameter(); +void ObjectManager::JSObjectWeakCallbackStatic( + const WeakCallbackInfo& data) { + ObjectWeakCallbackState* callbackState = data.GetParameter(); - ObjectManager *thisPtr = callbackState->thisPtr; + ObjectManager* thisPtr = callbackState->thisPtr; - auto isolate = data.GetIsolate(); + auto isolate = data.GetIsolate(); - thisPtr->JSObjectWeakCallback(isolate, callbackState); + thisPtr->JSObjectWeakCallback(isolate, callbackState); } -void ObjectManager::JSObjectFinalizerStatic(const WeakCallbackInfo &data) { - ObjectWeakCallbackState *callbackState = data.GetParameter(); +void ObjectManager::JSObjectFinalizerStatic( + const WeakCallbackInfo& data) { + ObjectWeakCallbackState* callbackState = data.GetParameter(); - ObjectManager *thisPtr = callbackState->thisPtr; + ObjectManager* thisPtr = callbackState->thisPtr; - auto isolate = data.GetIsolate(); + auto isolate = data.GetIsolate(); - thisPtr->JSObjectFinalizer(isolate, callbackState); + thisPtr->JSObjectFinalizer(isolate, callbackState); } -void ObjectManager::JSObjectFinalizer(Isolate *isolate, ObjectWeakCallbackState *callbackState) { - HandleScope handleScope(m_isolate); - Persistent *po = callbackState->target; - auto jsInstanceInfo = GetJSInstanceInfoFromRuntimeObject(po->Get(m_isolate)); - - if (jsInstanceInfo == nullptr) { - po->Reset(); - delete po; - delete callbackState; - return; - } +void ObjectManager::JSObjectFinalizer(Isolate* isolate, + ObjectWeakCallbackState* callbackState) { + HandleScope handleScope(m_isolate); + Persistent* po = callbackState->target; + auto jsInstanceInfo = GetJSInstanceInfoFromRuntimeObject(po->Get(m_isolate)); - auto javaObjectID = jsInstanceInfo->JavaObjectID; - JEnv env; - jboolean isJavaInstanceAlive = env.CallBooleanMethod(m_javaRuntimeObject, - MAKE_INSTANCE_WEAK_AND_CHECK_IF_ALIVE_METHOD_ID, - javaObjectID); - if (isJavaInstanceAlive) { - // If the Java instance is alive, keep the JavaScript instance alive. - po->SetWeak(callbackState, JSObjectFinalizerStatic, WeakCallbackType::kFinalizer); - } else { - // If the Java instance is dead, this JavaScript instance can be let die. - delete jsInstanceInfo; - auto jsInfoIdx = static_cast(MetadataNodeKeys::JsInfo); - po->Get(m_isolate)->SetInternalField(jsInfoIdx, Undefined(m_isolate)); - po->Reset(); - m_idToObject.erase(javaObjectID); - delete po; - delete callbackState; - } + if (jsInstanceInfo == nullptr) { + po->Reset(); + delete po; + delete callbackState; + return; + } + + auto javaObjectID = jsInstanceInfo->JavaObjectID; + JEnv env; + jboolean isJavaInstanceAlive = env.CallBooleanMethod( + m_javaRuntimeObject, MAKE_INSTANCE_WEAK_AND_CHECK_IF_ALIVE_METHOD_ID, + javaObjectID); + if (isJavaInstanceAlive) { + // If the Java instance is alive, keep the JavaScript instance alive. + po->SetWeak(callbackState, JSObjectFinalizerStatic, + WeakCallbackType::kFinalizer); + } else { + // If the Java instance is dead, this JavaScript instance can be let die. + delete jsInstanceInfo; + auto jsInfoIdx = static_cast(MetadataNodeKeys::JsInfo); + po->Get(m_isolate)->SetInternalField(jsInfoIdx, Undefined(m_isolate)); + po->Reset(); + m_idToObject.erase(javaObjectID); + delete po; + delete callbackState; + } } - /* - * When JS GC happens change state of the java counterpart to mirror state of JS object and REVIVE the JS object unconditionally - * "Regular" js objects are pushed into the "regular objects" array - * "Callback" js objects (ones that have implementation object) are pushed into two "special objects" array: - * -ones called for the first time which are originally strong in java - * -ones called for the second or next time which are already weak in java too - * These objects are categorized by "regular" and "callback" and saved in different arrays for performance optimizations during GC + * When JS GC happens change state of the java counterpart to mirror state of JS + * object and REVIVE the JS object unconditionally "Regular" js objects are + * pushed into the "regular objects" array "Callback" js objects (ones that have + * implementation object) are pushed into two "special objects" array: -ones + * called for the first time which are originally strong in java -ones called + * for the second or next time which are already weak in java too These objects + * are categorized by "regular" and "callback" and saved in different arrays for + * performance optimizations during GC * */ -void ObjectManager::JSObjectWeakCallback(Isolate *isolate, ObjectWeakCallbackState *callbackState) { - HandleScope handleScope(isolate); - - Persistent *po = callbackState->target; - - auto itFound = m_visitedPOs.find(po); - - if (itFound == m_visitedPOs.end()) { - m_visitedPOs.insert(po); - - auto obj = Local::New(isolate, *po); - JSInstanceInfo *jsInstanceInfo = GetJSInstanceInfo(obj); - - if(jsInstanceInfo != nullptr){ - int javaObjectID = jsInstanceInfo->JavaObjectID; - - bool hasImplObj = HasImplObject(isolate, obj); - - DEBUG_WRITE("JSObjectWeakCallback objectId: %d, hasImplObj=%d", javaObjectID, hasImplObj); - - if (hasImplObj) { - if (jsInstanceInfo->IsJavaObjectWeak) { - m_implObjWeak.emplace_back(po, javaObjectID); - } else { - m_implObjStrong.emplace(javaObjectID, po); - jsInstanceInfo->IsJavaObjectWeak = true; - } - } else { - if(m_markedForGC.empty()){ - // Emulates the behavior in the OnGcStarted callback. Тhis is necessary as the hooking - // to the V8 GC is done only on the markSweepCompact phase. The JSObjectWeakCallback - // however is still triggered in other V8 GC phases (scavenger for example). This - // creates a problem that there is no 'top' on the m_markedForGC stack. - GarbageCollectionInfo gcInfo(++m_numberOfGC); - gcInfo.markedForGC.push_back(po); - m_markedForGC.push(gcInfo); - } else { - auto &topGCInfo = m_markedForGC.top(); - topGCInfo.markedForGC.push_back(po); - } - } +void ObjectManager::JSObjectWeakCallback( + Isolate* isolate, ObjectWeakCallbackState* callbackState) { + HandleScope handleScope(isolate); + + Persistent* po = callbackState->target; + + auto itFound = m_visitedPOs.find(po); + + if (itFound == m_visitedPOs.end()) { + m_visitedPOs.insert(po); + + auto obj = Local::New(isolate, *po); + JSInstanceInfo* jsInstanceInfo = GetJSInstanceInfo(obj); + + if (jsInstanceInfo != nullptr) { + int javaObjectID = jsInstanceInfo->JavaObjectID; + + bool hasImplObj = HasImplObject(isolate, obj); + + DEBUG_WRITE("JSObjectWeakCallback objectId: %d, hasImplObj=%d", + javaObjectID, hasImplObj); + + if (hasImplObj) { + if (jsInstanceInfo->IsJavaObjectWeak) { + m_implObjWeak.emplace_back(po, javaObjectID); + } else { + m_implObjStrong.emplace(javaObjectID, po); + jsInstanceInfo->IsJavaObjectWeak = true; } + } else { + if (m_markedForGC.empty()) { + // Emulates the behavior in the OnGcStarted callback. Тhis is + // necessary as the hooking to the V8 GC is done only on the + // markSweepCompact phase. The JSObjectWeakCallback however is still + // triggered in other V8 GC phases (scavenger for example). This + // creates a problem that there is no 'top' on the m_markedForGC + // stack. + GarbageCollectionInfo gcInfo(++m_numberOfGC); + gcInfo.markedForGC.push_back(po); + m_markedForGC.push(gcInfo); + } else { + auto& topGCInfo = m_markedForGC.top(); + topGCInfo.markedForGC.push_back(po); + } + } } + } - po->SetWeak(callbackState, JSObjectWeakCallbackStatic, WeakCallbackType::kFinalizer); + po->SetWeak(callbackState, JSObjectWeakCallbackStatic, + WeakCallbackType::kFinalizer); } int ObjectManager::GenerateNewObjectID() { - const int one = 1; - int oldValue = __sync_fetch_and_add(&m_currentObjectId, one); + const int one = 1; + int oldValue = __sync_fetch_and_add(&m_currentObjectId, one); - return oldValue; + return oldValue; } -void ObjectManager::ReleaseJSInstance(Persistent *po, JSInstanceInfo *jsInstanceInfo) { - int javaObjectID = jsInstanceInfo->JavaObjectID; +void ObjectManager::ReleaseJSInstance(Persistent* po, + JSInstanceInfo* jsInstanceInfo) { + int javaObjectID = jsInstanceInfo->JavaObjectID; - auto it = m_idToObject.find(javaObjectID); + auto it = m_idToObject.find(javaObjectID); - if (it == m_idToObject.end()) { - stringstream ss; - ss << "(InternalError): Js object with id: " << javaObjectID << " not found"; - throw NativeScriptException(ss.str()); - } + if (it == m_idToObject.end()) { + stringstream ss; + ss << "(InternalError): Js object with id: " << javaObjectID + << " not found"; + throw NativeScriptException(ss.str()); + } - assert(po == it->second); + assert(po == it->second); - m_idToObject.erase(it); - m_released.insert(po, javaObjectID); - po->Reset(); + m_idToObject.erase(it); + m_released.insert(po, javaObjectID); + po->Reset(); - delete po; - delete jsInstanceInfo; + delete po; + delete jsInstanceInfo; - DEBUG_WRITE("ReleaseJSObject instance disposed. id:%d", javaObjectID); + DEBUG_WRITE("ReleaseJSObject instance disposed. id:%d", javaObjectID); } /* - * The "regular" JS objects added on ObjectManager::JSObjectWeakCallback are dealt with(released) here. + * The "regular" JS objects added on ObjectManager::JSObjectWeakCallback are + * dealt with(released) here. * */ void ObjectManager::ReleaseRegularObjects() { - TNSPERF(); + TNSPERF(); - HandleScope handleScope(m_isolate); + HandleScope handleScope(m_isolate); - auto propName = String::NewFromUtf8(m_isolate, "t::gcNum", - NewStringType::kNormal).ToLocalChecked(); + auto propName = + String::NewFromUtf8(m_isolate, "t::gcNum", NewStringType::kNormal) + .ToLocalChecked(); - auto &topGCInfo = m_markedForGC.top(); - auto &marked = topGCInfo.markedForGC; - int numberOfGC = topGCInfo.numberOfGC; + auto& topGCInfo = m_markedForGC.top(); + auto& marked = topGCInfo.markedForGC; + int numberOfGC = topGCInfo.numberOfGC; - for (auto po : marked) { - if (m_released.contains(po)) { - continue; - } + for (auto po : marked) { + if (m_released.contains(po)) { + continue; + } - auto obj = Local::New(m_isolate, *po); + auto obj = Local::New(m_isolate, *po); - assert(!obj.IsEmpty()); + assert(!obj.IsEmpty()); - Local gcNum; - V8GetPrivateValue(m_isolate, obj, propName, gcNum); + Local gcNum; + V8GetPrivateValue(m_isolate, obj, propName, gcNum); - bool isReachableFromImplementationObject = false; + bool isReachableFromImplementationObject = false; - if (!gcNum.IsEmpty() && gcNum->IsNumber()) { - double objGcNum = gcNum.As()->Value(); + if (!gcNum.IsEmpty() && gcNum->IsNumber()) { + double objGcNum = gcNum.As()->Value(); - // done so we can release only java objects from this GC stack and pass all objects that will be released in parent GC stacks - isReachableFromImplementationObject = objGcNum >= numberOfGC; - } + // done so we can release only java objects from this GC stack and pass + // all objects that will be released in parent GC stacks + isReachableFromImplementationObject = objGcNum >= numberOfGC; + } - JSInstanceInfo *jsInstanceInfo = GetJSInstanceInfo(obj); + JSInstanceInfo* jsInstanceInfo = GetJSInstanceInfo(obj); - if (!isReachableFromImplementationObject) { - if (!jsInstanceInfo->IsJavaObjectWeak) { - jsInstanceInfo->IsJavaObjectWeak = true; + if (!isReachableFromImplementationObject) { + if (!jsInstanceInfo->IsJavaObjectWeak) { + jsInstanceInfo->IsJavaObjectWeak = true; - ReleaseJSInstance(po, jsInstanceInfo); - } - } + ReleaseJSInstance(po, jsInstanceInfo); + } } + } - marked.clear(); + marked.clear(); } -bool ObjectManager::HasImplObject(Isolate *isolate, const Local &obj) { - auto implObject = MetadataNode::GetImplementationObject(isolate, obj); +bool ObjectManager::HasImplObject(Isolate* isolate, const Local& obj) { + auto implObject = MetadataNode::GetImplementationObject(isolate, obj); - bool hasImplObj = !implObject.IsEmpty(); + bool hasImplObj = !implObject.IsEmpty(); - return hasImplObj; + return hasImplObj; } -jweak ObjectManager::NewWeakGlobalRefCallback(const int &javaObjectID, void *state) { - auto objManager = reinterpret_cast(state); +jweak ObjectManager::NewWeakGlobalRefCallback(const int& javaObjectID, + void* state) { + auto objManager = reinterpret_cast(state); - JniLocalRef obj(objManager->GetJavaObjectByIDImpl(javaObjectID)); + JniLocalRef obj(objManager->GetJavaObjectByIDImpl(javaObjectID)); - JEnv env; - jweak weakRef = env.NewWeakGlobalRef(obj); + JEnv env; + jweak weakRef = env.NewWeakGlobalRef(obj); - return weakRef; + return weakRef; } -void ObjectManager::DeleteWeakGlobalRefCallback(const jweak &object, void *state) { - JEnv env; - env.DeleteWeakGlobalRef(object); +void ObjectManager::DeleteWeakGlobalRefCallback(const jweak& object, + void* state) { + JEnv env; + env.DeleteWeakGlobalRef(object); } -bool ObjectManager::ValidateWeakGlobalRefCallback(const int &javaObjectID, const jweak &object, void *state) { - JEnv env; - return !env.isSameObject(object, NULL); +bool ObjectManager::ValidateWeakGlobalRefCallback(const int& javaObjectID, + const jweak& object, + void* state) { + JEnv env; + return !env.isSameObject(object, NULL); } -Local ObjectManager::GetEmptyObject(Isolate *isolate) { - auto emptyObjCtorFunc = Local::New(isolate, *m_poJsWrapperFunc); - auto context = Runtime::GetRuntime(isolate)->GetContext(); - auto val = emptyObjCtorFunc->CallAsConstructor(context, 0, nullptr); - if (val.IsEmpty()) { - return Local(); - } - auto localVal = val.ToLocalChecked(); - assert(localVal->IsObject()); - auto obj = localVal.As(); - return obj; +Local ObjectManager::GetEmptyObject(Isolate* isolate) { + auto emptyObjCtorFunc = Local::New(isolate, *m_poJsWrapperFunc); + auto context = Runtime::GetRuntime(isolate)->GetContext(); + auto val = emptyObjCtorFunc->CallAsConstructor(context, 0, nullptr); + if (val.IsEmpty()) { + return Local(); + } + auto localVal = val.ToLocalChecked(); + assert(localVal->IsObject()); + auto obj = localVal.As(); + return obj; } -void ObjectManager::JSWrapperConstructorCallback(const v8::FunctionCallbackInfo &info) { - assert(info.IsConstructCall()); +void ObjectManager::JSWrapperConstructorCallback( + const v8::FunctionCallbackInfo& info) { + assert(info.IsConstructCall()); } -void ObjectManager::ReleaseNativeCounterpart(v8::Local &object) { +void ObjectManager::ReleaseNativeCounterpart(v8::Local& object) { + if (!object->IsObject()) { + throw NativeScriptException("Argument is not an object!"); + } - if(!object->IsObject()){ - throw NativeScriptException("Argument is not an object!"); - } - - JSInstanceInfo *jsInstanceInfo = GetJSInstanceInfo(object); + JSInstanceInfo* jsInstanceInfo = GetJSInstanceInfo(object); - if(jsInstanceInfo == nullptr){ - throw NativeScriptException("Trying to release a non native object!"); - } + if (jsInstanceInfo == nullptr) { + throw NativeScriptException("Trying to release a non native object!"); + } - JEnv env; - env.CallVoidMethod(m_javaRuntimeObject, RELEASE_NATIVE_INSTANCE_METHOD_ID, - jsInstanceInfo->JavaObjectID); + JEnv env; + env.CallVoidMethod(m_javaRuntimeObject, RELEASE_NATIVE_INSTANCE_METHOD_ID, + jsInstanceInfo->JavaObjectID); - delete jsInstanceInfo; - auto jsInfoIdx = static_cast(MetadataNodeKeys::JsInfo); - object->SetInternalField(jsInfoIdx, Undefined(m_isolate)); + delete jsInstanceInfo; + auto jsInfoIdx = static_cast(MetadataNodeKeys::JsInfo); + object->SetInternalField(jsInfoIdx, Undefined(m_isolate)); } ObjectManager::JavaScriptMarkingMode ObjectManager::GetMarkingMode() { - return this->m_markingMode; -} \ No newline at end of file + return this->m_markingMode; +} + +jclass ObjectManager::JAVA_LANG_CLASS = nullptr; +jmethodID ObjectManager::GET_NAME_METHOD_ID = nullptr; +jmethodID ObjectManager::GET_JAVAOBJECT_BY_ID_METHOD_ID = nullptr; +jmethodID ObjectManager::GET_OR_CREATE_JAVA_OBJECT_ID_METHOD_ID = nullptr; +jmethodID ObjectManager::MAKE_INSTANCE_WEAK_BATCH_METHOD_ID = nullptr; +jmethodID ObjectManager::MAKE_INSTANCE_WEAK_AND_CHECK_IF_ALIVE_METHOD_ID = + nullptr; +jmethodID ObjectManager::RELEASE_NATIVE_INSTANCE_METHOD_ID = nullptr; +jmethodID ObjectManager::CHECK_WEAK_OBJECTS_ARE_ALIVE_METHOD_ID = nullptr; diff --git a/test-app/runtime/src/main/cpp/ObjectManager.h b/test-app/runtime/src/main/cpp/ObjectManager.h index a0ae80703..483307697 100644 --- a/test-app/runtime/src/main/cpp/ObjectManager.h +++ b/test-app/runtime/src/main/cpp/ObjectManager.h @@ -1,236 +1,246 @@ #ifndef OBJECTMANAGER_H_ #define OBJECTMANAGER_H_ -#include "v8.h" -#include "JEnv.h" -#include "JniLocalRef.h" -#include "ArgsWrapper.h" -#include "DirectBuffer.h" -#include "LRUCache.h" #include #include #include -#include #include +#include + +#include "ArgsWrapper.h" +#include "DirectBuffer.h" +#include "JEnv.h" +#include "JniLocalRef.h" +#include "LRUCache.h" +#include "v8.h" namespace tns { class ObjectManager { - public: - ObjectManager(jobject javaRuntimeObject); - - void Init(v8::Isolate* isolate); - - JniLocalRef GetJavaObjectByJsObject(const v8::Local& object); - - void UpdateCache(int objectID, jobject obj); + public: + ObjectManager(jobject javaRuntimeObject); - jclass GetJavaClass(const v8::Local& instance); + void Init(v8::Isolate* isolate); - void SetJavaClass(const v8::Local& instance, jclass clazz); - int GetOrCreateObjectId(jobject object); + JniLocalRef GetJavaObjectByJsObject(const v8::Local& object); - v8::Local GetJsObjectByJavaObject(int javaObjectID); + void UpdateCache(int objectID, jobject obj); - v8::Local CreateJSWrapper(jint javaObjectID, const std::string& typeName); + jclass GetJavaClass(const v8::Local& instance); - v8::Local CreateJSWrapper(jint javaObjectID, const std::string& typeName, jobject instance); + void SetJavaClass(const v8::Local& instance, jclass clazz); + int GetOrCreateObjectId(jobject object); - void Link(const v8::Local& object, uint32_t javaObjectID, jclass clazz); + v8::Local GetJsObjectByJavaObject(int javaObjectID); - void ReleaseNativeCounterpart(v8::Local& object); + v8::Local CreateJSWrapper(jint javaObjectID, + const std::string& typeName); - bool CloneLink(const v8::Local& src, const v8::Local& dest); + v8::Local CreateJSWrapper(jint javaObjectID, + const std::string& typeName, + jobject instance); - bool IsJsRuntimeObject(const v8::Local& object); + void Link(const v8::Local& object, uint32_t javaObjectID, + jclass clazz); - std::string GetClassName(jobject javaObject); + void ReleaseNativeCounterpart(v8::Local& object); - std::string GetClassName(jclass clazz); + bool CloneLink(const v8::Local& src, + const v8::Local& dest); - int GenerateNewObjectID(); + bool IsJsRuntimeObject(const v8::Local& object); - void SetInstanceIsolate(v8::Isolate* isolate); + static std::string GetClassName(jobject javaObject); - v8::Local GetEmptyObject(v8::Isolate* isolate); + static std::string GetClassName(jclass clazz); - enum class MetadataNodeKeys { - JsInfo, - CallSuper, - END - }; + int GenerateNewObjectID(); - /** - * Memory management modes. Keep the members in sync with the java/com/tns/MarkingMode. - */ - enum JavaScriptMarkingMode { - /** - * For JavaScript instances with implementation objects that were marked for collection, - * MarkReachableObjects will scan the whole graph of reachable objects and keep strong reference to - * the Java instances of implementation objects. - */ - Full, - /** - * Fully suppress the MarkReachableObjects. - */ - None - }; + void SetInstanceIsolate(v8::Isolate* isolate); - JavaScriptMarkingMode GetMarkingMode(); + v8::Local GetEmptyObject(v8::Isolate* isolate); - private: + enum class MetadataNodeKeys { JsInfo, CallSuper, END }; - struct JSInstanceInfo { - public: - JSInstanceInfo(bool isJavaObjectWeak, uint32_t javaObjectID, jclass claz) - :IsJavaObjectWeak(isJavaObjectWeak), JavaObjectID(javaObjectID), ObjectClazz(claz) { - } + /** + * Memory management modes. Keep the members in sync with the + * java/com/tns/MarkingMode. + */ + enum JavaScriptMarkingMode { + /** + * For JavaScript instances with implementation objects that were marked for + * collection, MarkReachableObjects will scan the whole graph of reachable + * objects and keep strong reference to the Java instances of implementation + * objects. + */ + Full, + /** + * Fully suppress the MarkReachableObjects. + */ + None + }; - bool IsJavaObjectWeak; - uint32_t JavaObjectID; - jclass ObjectClazz; - }; + JavaScriptMarkingMode GetMarkingMode(); - struct ObjectWeakCallbackState { - ObjectWeakCallbackState(ObjectManager* _thisPtr, JSInstanceInfo* _jsInfo, v8::Persistent* _target) - : - thisPtr(_thisPtr), jsInfo(_jsInfo), target(_target) { - } + private: + static void InitializeJNI(); + struct JSInstanceInfo { + public: + JSInstanceInfo(bool isJavaObjectWeak, uint32_t javaObjectID, jclass claz) + : IsJavaObjectWeak(isJavaObjectWeak), + JavaObjectID(javaObjectID), + ObjectClazz(claz) {} - ObjectManager* thisPtr; - JSInstanceInfo* jsInfo; - v8::Persistent* target; - }; + bool IsJavaObjectWeak; + uint32_t JavaObjectID; + jclass ObjectClazz; + }; - struct GarbageCollectionInfo { - GarbageCollectionInfo(int _numberOfGC) - : - numberOfGC(_numberOfGC) { - } - std::vector*> markedForGC; - int numberOfGC; - }; + struct ObjectWeakCallbackState { + ObjectWeakCallbackState(ObjectManager* _thisPtr, JSInstanceInfo* _jsInfo, + v8::Persistent* _target) + : thisPtr(_thisPtr), jsInfo(_jsInfo), target(_target) {} - class PersistentObjectIdSet { - public: - PersistentObjectIdSet() { - /* TODO: use functors */ - } + ObjectManager* thisPtr; + JSInstanceInfo* jsInfo; + v8::Persistent* target; + }; - void clear() { - m_POs.clear(); - m_IDs.clear(); - } + struct GarbageCollectionInfo { + GarbageCollectionInfo(int _numberOfGC) : numberOfGC(_numberOfGC) {} + std::vector*> markedForGC; + int numberOfGC; + }; - void insert(v8::Persistent* po, int javaObjectId) { - m_POs.insert(po); - m_IDs.insert(javaObjectId); - } + class PersistentObjectIdSet { + public: + PersistentObjectIdSet() { /* TODO: use functors */ } - bool contains(v8::Persistent* po) { - return m_POs.find(po) != m_POs.end(); - } + void clear() { + m_POs.clear(); + m_IDs.clear(); + } - std::set*> m_POs; - std::set m_IDs; - }; + void insert(v8::Persistent* po, int javaObjectId) { + m_POs.insert(po); + m_IDs.insert(javaObjectId); + } - struct PersistentObjectIdPair { - PersistentObjectIdPair(v8::Persistent* _po, int _javaObjectId) - : - po(_po), javaObjectId(_javaObjectId) { - } - v8::Persistent* po; - int javaObjectId; - }; + bool contains(v8::Persistent* po) { + return m_POs.find(po) != m_POs.end(); + } + std::set*> m_POs; + std::set m_IDs; + }; + struct PersistentObjectIdPair { + PersistentObjectIdPair(v8::Persistent* _po, int _javaObjectId) + : po(_po), javaObjectId(_javaObjectId) {} + v8::Persistent* po; + int javaObjectId; + }; - JSInstanceInfo* GetJSInstanceInfo(const v8::Local& object); + JSInstanceInfo* GetJSInstanceInfo(const v8::Local& object); - JSInstanceInfo* GetJSInstanceInfoFromRuntimeObject(const v8::Local& object); + JSInstanceInfo* GetJSInstanceInfoFromRuntimeObject( + const v8::Local& object); - void ReleaseJSInstance(v8::Persistent* po, JSInstanceInfo* jsInstanceInfo); + void ReleaseJSInstance(v8::Persistent* po, + JSInstanceInfo* jsInstanceInfo); - void ReleaseRegularObjects(); + void ReleaseRegularObjects(); - void MakeRegularObjectsWeak(const std::set& instances, DirectBuffer& inputBuff); + void MakeRegularObjectsWeak(const std::set& instances, + DirectBuffer& inputBuff); - void MakeImplObjectsWeak(const std::unordered_map*>& instances, DirectBuffer& inputBuff); + void MakeImplObjectsWeak( + const std::unordered_map*>& instances, + DirectBuffer& inputBuff); - void CheckWeakObjectsAreAlive(const std::vector& instances, DirectBuffer& inputBuff, DirectBuffer& outputBuff); + void CheckWeakObjectsAreAlive( + const std::vector& instances, + DirectBuffer& inputBuff, DirectBuffer& outputBuff); - v8::Local CreateJSWrapperHelper(jint javaObjectID, const std::string& typeName, jclass clazz); + v8::Local CreateJSWrapperHelper(jint javaObjectID, + const std::string& typeName, + jclass clazz); - static void JSObjectWeakCallbackStatic(const v8::WeakCallbackInfo& data); + static void JSObjectWeakCallbackStatic( + const v8::WeakCallbackInfo& data); - static void JSObjectFinalizerStatic(const v8::WeakCallbackInfo& data); + static void JSObjectFinalizerStatic( + const v8::WeakCallbackInfo& data); - void JSObjectWeakCallback(v8::Isolate* isolate, ObjectWeakCallbackState* callbackState); + void JSObjectWeakCallback(v8::Isolate* isolate, + ObjectWeakCallbackState* callbackState); - void JSObjectFinalizer(v8::Isolate* isolate, ObjectWeakCallbackState* callbackState); + void JSObjectFinalizer(v8::Isolate* isolate, + ObjectWeakCallbackState* callbackState); - bool HasImplObject(v8::Isolate* isolate, const v8::Local& obj); + bool HasImplObject(v8::Isolate* isolate, const v8::Local& obj); - jweak GetJavaObjectByID(uint32_t javaObjectID); + jweak GetJavaObjectByID(uint32_t javaObjectID); - jobject GetJavaObjectByIDImpl(uint32_t javaObjectID); + jobject GetJavaObjectByIDImpl(uint32_t javaObjectID); - static jweak NewWeakGlobalRefCallback(const int& javaObjectID, void* state); + static jweak NewWeakGlobalRefCallback(const int& javaObjectID, void* state); - static void DeleteWeakGlobalRefCallback(const jweak& object, void* state); + static void DeleteWeakGlobalRefCallback(const jweak& object, void* state); - static bool ValidateWeakGlobalRefCallback(const int &javaObjectID, const jweak &object, void *state); + static bool ValidateWeakGlobalRefCallback(const int& javaObjectID, + const jweak& object, void* state); - static void JSWrapperConstructorCallback(const v8::FunctionCallbackInfo& info); + static void JSWrapperConstructorCallback( + const v8::FunctionCallbackInfo& info); - jobject m_javaRuntimeObject; + jobject m_javaRuntimeObject; - int m_numberOfGC; + int m_numberOfGC; - v8::Isolate* m_isolate; + v8::Isolate* m_isolate; - std::stack m_markedForGC; + std::stack m_markedForGC; - std::unordered_map*> m_idToObject; + std::unordered_map*> m_idToObject; - PersistentObjectIdSet m_released; + PersistentObjectIdSet m_released; - std::set m_visited; + std::set m_visited; - LRUCache m_cache; + LRUCache m_cache; - std::set*> m_visitedPOs; - std::vector m_implObjWeak; - std::unordered_map*> m_implObjStrong; + std::set*> m_visitedPOs; + std::vector m_implObjWeak; + std::unordered_map*> m_implObjStrong; - volatile int m_currentObjectId; + volatile int m_currentObjectId; - DirectBuffer m_buff; + DirectBuffer m_buff; - DirectBuffer m_outBuff; + DirectBuffer m_outBuff; - bool m_useGlobalRefs; + bool m_useGlobalRefs; - JavaScriptMarkingMode m_markingMode; + JavaScriptMarkingMode m_markingMode; - jclass JAVA_LANG_CLASS; + static jclass JAVA_LANG_CLASS; - jmethodID GET_NAME_METHOD_ID; + static jmethodID GET_NAME_METHOD_ID; - jmethodID GET_JAVAOBJECT_BY_ID_METHOD_ID; + static jmethodID GET_JAVAOBJECT_BY_ID_METHOD_ID; - jmethodID GET_OR_CREATE_JAVA_OBJECT_ID_METHOD_ID; + static jmethodID GET_OR_CREATE_JAVA_OBJECT_ID_METHOD_ID; - jmethodID MAKE_INSTANCE_WEAK_BATCH_METHOD_ID; + static jmethodID MAKE_INSTANCE_WEAK_BATCH_METHOD_ID; - jmethodID MAKE_INSTANCE_WEAK_AND_CHECK_IF_ALIVE_METHOD_ID; + static jmethodID MAKE_INSTANCE_WEAK_AND_CHECK_IF_ALIVE_METHOD_ID; - jmethodID RELEASE_NATIVE_INSTANCE_METHOD_ID; + static jmethodID RELEASE_NATIVE_INSTANCE_METHOD_ID; - jmethodID CHECK_WEAK_OBJECTS_ARE_ALIVE_METHOD_ID; + static jmethodID CHECK_WEAK_OBJECTS_ARE_ALIVE_METHOD_ID; - v8::Persistent* m_poJsWrapperFunc; + v8::Persistent* m_poJsWrapperFunc; }; -} +} // namespace tns #endif /* OBJECTMANAGER_H_ */ diff --git a/test-app/runtime/src/main/cpp/Runtime.cpp b/test-app/runtime/src/main/cpp/Runtime.cpp index d972ba5d6..aa5142912 100644 --- a/test-app/runtime/src/main/cpp/Runtime.cpp +++ b/test-app/runtime/src/main/cpp/Runtime.cpp @@ -132,15 +132,15 @@ Runtime* Runtime::GetRuntime(int runtimeId) { } Runtime* Runtime::GetRuntime(v8::Isolate* isolate) { - auto runtime = s_isolate2RuntimesCache.at(isolate); + auto it = s_isolate2RuntimesCache.find(isolate); - if (runtime == nullptr) { + if (it == s_isolate2RuntimesCache.end()) { stringstream ss; ss << "Cannot find runtime for isolate: " << isolate; throw NativeScriptException(ss.str()); } - return runtime; + return it->second; } Runtime* Runtime::GetRuntimeFromIsolateData(v8::Isolate* isolate) { diff --git a/test-app/runtime/src/main/cpp/URLSearchParamsImpl.cpp b/test-app/runtime/src/main/cpp/URLSearchParamsImpl.cpp index f8663e526..710079bab 100644 --- a/test-app/runtime/src/main/cpp/URLSearchParamsImpl.cpp +++ b/test-app/runtime/src/main/cpp/URLSearchParamsImpl.cpp @@ -160,19 +160,27 @@ namespace tns { return; } auto callback = args[0].As(); - auto keys = ptr->GetURLSearchParams()->get_keys(); - while (keys.has_next()) { - auto key = keys.next(); - if (key) { - auto keyValue = key.value(); - auto value = ptr->GetURLSearchParams()->get(keyValue).value(); - v8::Local values[] = { - ArgConverter::ConvertToV8String(isolate, keyValue.data()), + auto searchParams = args.This(); + // Use thisArg if provided, otherwise undefined + auto thisArg = args.Length() > 1 ? args[1] : v8::Undefined(isolate).As(); + // Use get_entries() to correctly handle duplicate keys + auto entries = ptr->GetURLSearchParams()->get_entries(); + while (entries.has_next()) { + auto entry = entries.next(); + if (entry) { + auto& [key, value] = entry.value(); + // Per spec, forEach callback receives (value, key, searchParams) + v8::Local callbackArgs[] = { ArgConverter::ConvertToV8String(isolate, value.data()), + ArgConverter::ConvertToV8String(isolate, key.data()), + searchParams, }; - callback->Call(context, v8::Local(), 2, values); + v8::Local result; + if (!callback->Call(context, thisArg, 3, callbackArgs).ToLocal(&result)) { + // If the callback throws an exception, stop iteration + return; + } } - } }