expo-modules-core 1.11.3 → 1.11.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -10,12 +10,17 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 1.11.4 — 2023-12-21
14
+
15
+ _This version does not introduce any user-facing changes._
16
+
13
17
  ## 1.11.3 — 2023-12-19
14
18
 
15
19
  ### 🐛 Bug fixes
16
20
 
17
21
  - [iOS] Fixed `SharedObjectRegistry` crash for accessing internal data structures from multi-threads. ([#25997](https://github.com/expo/expo/pull/25997) by [@kudo](https://github.com/kudo))
18
22
  - Fixed splash screen view flickering in dark mode on iOS. ([#26015](https://github.com/expo/expo/pull/26015), [#26029](https://github.com/expo/expo/pull/26029) by [@kudo](https://github.com/kudo))
23
+ - Fixed `SharedObject` leakage on Android. ([#25995](https://github.com/expo/expo/pull/25995) by [@kudo](https://github.com/kudo))
19
24
 
20
25
  ## 1.11.2 — 2023-12-15
21
26
 
@@ -5,7 +5,7 @@ apply plugin: 'kotlin-android'
5
5
  apply plugin: 'maven-publish'
6
6
 
7
7
  group = 'host.exp.exponent'
8
- version = '1.11.3'
8
+ version = '1.11.4'
9
9
 
10
10
  def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
11
11
  if (expoModulesCorePlugin.exists()) {
@@ -143,7 +143,7 @@ android {
143
143
  defaultConfig {
144
144
  consumerProguardFiles 'proguard-rules.pro'
145
145
  versionCode 1
146
- versionName "1.11.3"
146
+ versionName "1.11.4"
147
147
  buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled.toString()
148
148
 
149
149
  testInstrumentationRunner "expo.modules.TestRunner"
@@ -4,6 +4,7 @@
4
4
  #include "JavaScriptValue.h"
5
5
  #include "JavaScriptFunction.h"
6
6
  #include "JavaScriptRuntime.h"
7
+ #include "JavaScriptWeakObject.h"
7
8
  #include "JSITypeConverter.h"
8
9
  #include "ObjectDeallocator.h"
9
10
  #include "JavaReferencesCache.h"
@@ -15,6 +16,7 @@ void JavaScriptObject::registerNatives() {
15
16
  makeNativeMethod("hasProperty", JavaScriptObject::jniHasProperty),
16
17
  makeNativeMethod("getProperty", JavaScriptObject::jniGetProperty),
17
18
  makeNativeMethod("getPropertyNames", JavaScriptObject::jniGetPropertyNames),
19
+ makeNativeMethod("createWeak", JavaScriptObject::createWeak),
18
20
  makeNativeMethod("setBoolProperty", JavaScriptObject::setProperty<bool>),
19
21
  makeNativeMethod("setDoubleProperty", JavaScriptObject::setProperty<double>),
20
22
  makeNativeMethod("setStringProperty",
@@ -109,6 +111,14 @@ jni::local_ref<jni::JArrayClass<jstring>> JavaScriptObject::jniGetPropertyNames(
109
111
  return paredResult;
110
112
  }
111
113
 
114
+ jni::local_ref<jni::HybridClass<JavaScriptWeakObject, Destructible>::javaobject> JavaScriptObject::createWeak() {
115
+ return JavaScriptWeakObject::newInstance(
116
+ runtimeHolder.getModuleRegistry(),
117
+ runtimeHolder,
118
+ get()
119
+ );
120
+ }
121
+
112
122
  jni::local_ref<JavaScriptFunction::javaobject> JavaScriptObject::jniAsFunction() {
113
123
  auto &jsRuntime = runtimeHolder.getJSRuntime();
114
124
  auto jsFuncion = std::make_shared<jsi::Function>(jsObject->asFunction(jsRuntime));
@@ -167,7 +177,7 @@ void JavaScriptObject::defineNativeDeallocator(
167
177
  rt,
168
178
  jsObject,
169
179
  [globalRef = std::move(globalRef)]() mutable {
170
- auto args = jni::Environment::current()->NewObjectArray(
180
+ auto args = jni::Environment::ensureCurrentThreadIsAttached()->NewObjectArray(
171
181
  0,
172
182
  JavaReferencesCache::instance()->getJClass("java/lang/Object").clazz,
173
183
  nullptr
@@ -19,9 +19,11 @@ namespace jni = facebook::jni;
19
19
  namespace jsi = facebook::jsi;
20
20
 
21
21
  namespace expo {
22
- class JavaScriptValue;
23
22
 
24
23
  class JavaScriptFunction;
24
+ class JavaScriptValue;
25
+ class JavaScriptWeakObject;
26
+
25
27
 
26
28
  /**
27
29
  * Represents any JavaScript object. Its purpose is to exposes `jsi::Object` API back to Kotlin.
@@ -91,6 +93,8 @@ private:
91
93
 
92
94
  jni::local_ref<jni::JArrayClass<jstring>> jniGetPropertyNames();
93
95
 
96
+ jni::local_ref<jni::HybridClass<JavaScriptWeakObject, Destructible>::javaobject> createWeak();
97
+
94
98
  jni::local_ref<jni::HybridClass<JavaScriptFunction, Destructible>::javaobject> jniAsFunction();
95
99
 
96
100
  /**
@@ -0,0 +1,49 @@
1
+ // Copyright 2015-present 650 Industries. All rights reserved.
2
+
3
+ #include "JavaScriptWeakObject.h"
4
+ #include "JSIInteropModuleRegistry.h"
5
+
6
+ namespace expo {
7
+
8
+ void JavaScriptWeakObject::registerNatives() {
9
+ registerHybrid({
10
+ makeNativeMethod("lock", JavaScriptWeakObject::lock),
11
+ });
12
+ }
13
+
14
+ jni::local_ref<JavaScriptObject::javaobject> JavaScriptWeakObject::lock() {
15
+ jsi::Runtime &rt = _runtimeHolder.getJSRuntime();
16
+
17
+ jsi::Value value = _weakObject->lock(rt);
18
+ if (value.isUndefined()) {
19
+ return nullptr;
20
+ }
21
+ std::shared_ptr<jsi::Object> objectPtr =
22
+ std::make_shared<jsi::Object>(value.asObject(rt));
23
+ if (!objectPtr) {
24
+ return nullptr;
25
+ }
26
+ return JavaScriptObject::newInstance(_runtimeHolder.getModuleRegistry(),
27
+ _runtimeHolder, objectPtr);
28
+ }
29
+
30
+ jni::local_ref<jni::HybridClass<JavaScriptWeakObject, Destructible>::javaobject>
31
+ JavaScriptWeakObject::newInstance(
32
+ JSIInteropModuleRegistry *jsiInteropModuleRegistry,
33
+ std::weak_ptr<JavaScriptRuntime> runtime,
34
+ std::shared_ptr<jsi::Object> jsObject) {
35
+ auto weakObject = JavaScriptWeakObject::newObjectCxxArgs(std::move(runtime),
36
+ std::move(jsObject));
37
+ jsiInteropModuleRegistry->jniDeallocator->addReference(weakObject);
38
+ return weakObject;
39
+ }
40
+
41
+ JavaScriptWeakObject::JavaScriptWeakObject(
42
+ WeakRuntimeHolder runtime, std::shared_ptr<jsi::Object> jsObject)
43
+ : _runtimeHolder(std::move(runtime)) {
44
+ _runtimeHolder.ensureRuntimeIsValid();
45
+ jsi::Runtime &rt = _runtimeHolder.getJSRuntime();
46
+ _weakObject = std::make_shared<jsi::WeakObject>(rt, *jsObject);
47
+ }
48
+
49
+ } // namespace expo
@@ -0,0 +1,52 @@
1
+ // Copyright 2015-present 650 Industries. All rights reserved.
2
+
3
+ #pragma once
4
+
5
+ #include "JNIDeallocator.h"
6
+ #include "JavaScriptObject.h"
7
+ #include "WeakRuntimeHolder.h"
8
+
9
+ #include <fbjni/fbjni.h>
10
+ #include <jsi/jsi.h>
11
+
12
+ #include <memory>
13
+
14
+ namespace jni = facebook::jni;
15
+ namespace jsi = facebook::jsi;
16
+
17
+ namespace expo {
18
+
19
+ class JavaScriptObject;
20
+
21
+ /**
22
+ * Represents to a jsi::WeakObject.
23
+ */
24
+ class JavaScriptWeakObject
25
+ : public jni::HybridClass<JavaScriptWeakObject, Destructible> {
26
+ public:
27
+ static auto constexpr kJavaDescriptor =
28
+ "Lexpo/modules/kotlin/jni/JavaScriptWeakObject;";
29
+ static auto constexpr TAG = "JavaScriptWeakObject";
30
+
31
+ static void registerNatives();
32
+
33
+ static jni::local_ref<
34
+ jni::HybridClass<JavaScriptWeakObject, Destructible>::javaobject>
35
+ newInstance(JSIInteropModuleRegistry *jsiInteropModuleRegistry,
36
+ std::weak_ptr<JavaScriptRuntime> runtime,
37
+ std::shared_ptr<jsi::Object> jsObject);
38
+
39
+ jni::local_ref<JavaScriptObject::javaobject> lock();
40
+
41
+ private:
42
+ JavaScriptWeakObject(WeakRuntimeHolder runtime,
43
+ std::shared_ptr<jsi::Object> jsObject);
44
+
45
+ private:
46
+ friend HybridBase;
47
+
48
+ WeakRuntimeHolder _runtimeHolder;
49
+ std::shared_ptr<jsi::WeakObject> _weakObject;
50
+ };
51
+
52
+ } // namespace expo
@@ -43,6 +43,8 @@ open class JavaScriptObject @DoNotStrip internal constructor(@DoNotStrip private
43
43
  }
44
44
 
45
45
  external fun getPropertyNames(): Array<String>
46
+ external fun createWeak(): JavaScriptWeakObject
47
+
46
48
  private external fun setBoolProperty(name: String, value: Boolean)
47
49
  private external fun setDoubleProperty(name: String, value: Double)
48
50
  private external fun setStringProperty(name: String, value: String?)
@@ -0,0 +1,22 @@
1
+ package expo.modules.kotlin.jni
2
+
3
+ import com.facebook.jni.HybridData
4
+ import expo.modules.core.interfaces.DoNotStrip
5
+
6
+ /**
7
+ * A Kotlin representation to a jsi::WeakObject
8
+ * Should be used only on the runtime thread.
9
+ */
10
+ @Suppress("KotlinJniMissingFunction")
11
+ @DoNotStrip
12
+ class JavaScriptWeakObject @DoNotStrip internal constructor(@DoNotStrip private val mHybridData: HybridData) : Destructible {
13
+ @Throws(Throwable::class)
14
+ protected fun finalize() {
15
+ deallocate()
16
+ }
17
+
18
+ override fun deallocate() {
19
+ mHybridData.resetNative()
20
+ }
21
+ external fun lock(): JavaScriptObject
22
+ }
@@ -9,4 +9,9 @@ open class SharedObject {
9
9
  * When the object is not linked with any JavaScript object, its value is 0.
10
10
  */
11
11
  internal var sharedObjectId: SharedObjectId = SharedObjectId(0)
12
+
13
+ /**
14
+ * Called when the shared object being deallocated.
15
+ */
16
+ open fun deallocate() {}
12
17
  }
@@ -2,6 +2,7 @@ package expo.modules.kotlin.sharedobjects
2
2
 
3
3
  import expo.modules.kotlin.AppContext
4
4
  import expo.modules.kotlin.jni.JavaScriptObject
5
+ import expo.modules.kotlin.jni.JavaScriptWeakObject
5
6
 
6
7
  @JvmInline
7
8
  value class SharedObjectId(val value: Int) {
@@ -15,8 +16,7 @@ value class SharedObjectId(val value: Int) {
15
16
  }
16
17
  }
17
18
 
18
- // TODO(@lukmccall): use weak ref to hold js object
19
- typealias SharedObjectPair = Pair<SharedObject, JavaScriptObject>
19
+ typealias SharedObjectPair = Pair<SharedObject, JavaScriptWeakObject>
20
20
 
21
21
  const val sharedObjectIdPropertyName = "__expo_shared_object_id__"
22
22
 
@@ -25,7 +25,7 @@ class SharedObjectRegistry {
25
25
 
26
26
  internal var pairs = mutableMapOf<SharedObjectId, SharedObjectPair>()
27
27
 
28
- private fun pullNextId(): SharedObjectId {
28
+ private fun pullNextId(): SharedObjectId = synchronized(this) {
29
29
  val current = currentId
30
30
  currentId = SharedObjectId(current.value + 1)
31
31
  return current
@@ -40,21 +40,27 @@ class SharedObjectRegistry {
40
40
  delete(id)
41
41
  }
42
42
 
43
- pairs[id] = native to js
43
+ val jsWeakObject = js.createWeak()
44
+ synchronized(this) {
45
+ pairs[id] = native to jsWeakObject
46
+ }
44
47
  return id
45
48
  }
46
49
 
47
50
  internal fun delete(id: SharedObjectId) {
48
- pairs.remove(id)?.let { (native, js) ->
51
+ val removedObject: SharedObjectPair? = synchronized(this) {
52
+ return@synchronized pairs.remove(id)
53
+ }
54
+ removedObject?.let { (native, js) ->
49
55
  native.sharedObjectId = SharedObjectId(0)
50
- if (js.isValid()) {
51
- js.defineProperty(sharedObjectIdPropertyName, 0)
52
- }
56
+ native.deallocate()
53
57
  }
54
58
  }
55
59
 
56
60
  internal fun toNativeObject(id: SharedObjectId): SharedObject? {
57
- return pairs[id]?.first
61
+ return synchronized(this) {
62
+ pairs[id]?.first
63
+ }
58
64
  }
59
65
 
60
66
  internal fun toNativeObject(js: JavaScriptObject): SharedObject? {
@@ -67,6 +73,8 @@ class SharedObjectRegistry {
67
73
  }
68
74
 
69
75
  internal fun toJavaScriptObject(native: SharedObject): JavaScriptObject? {
70
- return pairs[native.sharedObjectId]?.second
76
+ return synchronized(this) {
77
+ pairs[native.sharedObjectId]?.second?.lock()
78
+ }
71
79
  }
72
80
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-modules-core",
3
- "version": "1.11.3",
3
+ "version": "1.11.4",
4
4
  "description": "The core of Expo Modules architecture",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -44,5 +44,5 @@
44
44
  "@testing-library/react-hooks": "^7.0.1",
45
45
  "expo-module-scripts": "^3.0.0"
46
46
  },
47
- "gitHead": "43f1b4f8a5a9bca649e4e7ca6e4155482a162431"
47
+ "gitHead": "36402c8b2bc9b63f003c04642d97bf091b35fe16"
48
48
  }