|
1 /* -*- Mode: c++; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- |
|
2 * This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 #include "NativeJSContainer.h" |
|
7 #include "AndroidBridge.h" |
|
8 #include "mozilla/Vector.h" |
|
9 #include "prthread.h" |
|
10 |
|
11 using namespace mozilla; |
|
12 using namespace mozilla::widget; |
|
13 |
|
14 namespace mozilla { |
|
15 namespace widget { |
|
16 |
|
17 class NativeJSContainer |
|
18 { |
|
19 public: |
|
20 static void InitJNI(JNIEnv* env) { |
|
21 if (jNativeJSContainer) { |
|
22 return; |
|
23 } |
|
24 jNativeJSContainer = AndroidBridge::GetClassGlobalRef( |
|
25 env, "org/mozilla/gecko/util/NativeJSContainer"); |
|
26 MOZ_ASSERT(jNativeJSContainer); |
|
27 jContainerNativeObject = AndroidBridge::GetFieldID( |
|
28 env, jNativeJSContainer, "mNativeObject", "J"); |
|
29 MOZ_ASSERT(jContainerNativeObject); |
|
30 jContainerConstructor = AndroidBridge::GetMethodID( |
|
31 env, jNativeJSContainer, "<init>", "(J)V"); |
|
32 MOZ_ASSERT(jContainerConstructor); |
|
33 |
|
34 jNativeJSObject = AndroidBridge::GetClassGlobalRef( |
|
35 env, "org/mozilla/gecko/util/NativeJSObject"); |
|
36 MOZ_ASSERT(jNativeJSObject); |
|
37 jObjectContainer = AndroidBridge::GetFieldID( |
|
38 env, jNativeJSObject, "mContainer", |
|
39 "Lorg/mozilla/gecko/util/NativeJSContainer;"); |
|
40 MOZ_ASSERT(jObjectContainer); |
|
41 jObjectIndex = AndroidBridge::GetFieldID( |
|
42 env, jNativeJSObject, "mObjectIndex", "I"); |
|
43 MOZ_ASSERT(jObjectIndex); |
|
44 jObjectConstructor = AndroidBridge::GetMethodID( |
|
45 env, jNativeJSObject, "<init>", |
|
46 "(Lorg/mozilla/gecko/util/NativeJSContainer;I)V"); |
|
47 MOZ_ASSERT(jContainerConstructor); |
|
48 } |
|
49 |
|
50 static jobject CreateInstance(JNIEnv* env, JSContext* cx, |
|
51 JS::HandleObject object) { |
|
52 return CreateInstance(env, new NativeJSContainer(cx, object)); |
|
53 } |
|
54 |
|
55 static NativeJSContainer* FromInstance(JNIEnv* env, jobject instance) { |
|
56 MOZ_ASSERT(instance); |
|
57 |
|
58 const jlong fieldValue = |
|
59 env->GetLongField(instance, jContainerNativeObject); |
|
60 NativeJSContainer* const nativeObject = |
|
61 reinterpret_cast<NativeJSContainer* const>( |
|
62 static_cast<uintptr_t>(fieldValue)); |
|
63 if (!nativeObject) { |
|
64 AndroidBridge::ThrowException(env, |
|
65 "java/lang/NullPointerException", |
|
66 "Uninitialized NativeJSContainer"); |
|
67 } |
|
68 return nativeObject; |
|
69 } |
|
70 |
|
71 static void DisposeInstance(JNIEnv* env, jobject instance) { |
|
72 NativeJSContainer* const container = FromInstance(env, instance); |
|
73 if (container) { |
|
74 env->SetLongField(instance, jContainerNativeObject, |
|
75 static_cast<jlong>(reinterpret_cast<uintptr_t>(nullptr))); |
|
76 delete container; |
|
77 } |
|
78 } |
|
79 |
|
80 static jobject CloneInstance(JNIEnv* env, jobject instance) { |
|
81 NativeJSContainer* const container = FromInstance(env, instance); |
|
82 if (!container || !container->EnsureObject(env)) { |
|
83 return nullptr; |
|
84 } |
|
85 JSContext* const cx = container->mThreadContext; |
|
86 JS::RootedObject object(cx, container->mJSObject); |
|
87 MOZ_ASSERT(object); |
|
88 |
|
89 JSAutoStructuredCloneBuffer buffer; |
|
90 if (!buffer.write(cx, JS::RootedValue(cx, JS::ObjectValue(*object)))) { |
|
91 AndroidBridge::ThrowException(env, |
|
92 "java/lang/UnsupportedOperationException", |
|
93 "Cannot serialize object"); |
|
94 return nullptr; |
|
95 } |
|
96 return CreateInstance(env, new NativeJSContainer(cx, Move(buffer))); |
|
97 } |
|
98 |
|
99 static jobject GetInstanceFromObject(JNIEnv* env, jobject object) { |
|
100 MOZ_ASSERT(object); |
|
101 |
|
102 const jobject instance = env->GetObjectField(object, jObjectContainer); |
|
103 MOZ_ASSERT(instance); |
|
104 return instance; |
|
105 } |
|
106 |
|
107 static JSContext* GetContextFromObject(JNIEnv* env, jobject object) { |
|
108 MOZ_ASSERT(object); |
|
109 AutoLocalJNIFrame frame(env, 1); |
|
110 |
|
111 NativeJSContainer* const container = |
|
112 FromInstance(env, GetInstanceFromObject(env, object)); |
|
113 if (!container) { |
|
114 return nullptr; |
|
115 } |
|
116 return container->mThreadContext; |
|
117 } |
|
118 |
|
119 static JSObject* GetObjectFromObject(JNIEnv* env, jobject object) { |
|
120 MOZ_ASSERT(object); |
|
121 AutoLocalJNIFrame frame(env, 1); |
|
122 |
|
123 NativeJSContainer* const container = |
|
124 FromInstance(env, GetInstanceFromObject(env, object)); |
|
125 if (!container || |
|
126 !container->EnsureObject(env)) { // Do thread check |
|
127 return nullptr; |
|
128 } |
|
129 const jint index = env->GetIntField(object, jObjectIndex); |
|
130 if (index < 0) { |
|
131 // -1 for index field means it's the root object of the container |
|
132 return container->mJSObject; |
|
133 } |
|
134 return container->mRootedObjects[index]; |
|
135 } |
|
136 |
|
137 static jobject CreateObjectInstance(JNIEnv* env, jobject object, |
|
138 JS::HandleObject jsObject) { |
|
139 MOZ_ASSERT(object); |
|
140 MOZ_ASSERT(jsObject); |
|
141 AutoLocalJNIFrame frame(env, 2); |
|
142 |
|
143 jobject instance = GetInstanceFromObject(env, object); |
|
144 NativeJSContainer* const container = FromInstance(env, instance); |
|
145 if (!container) { |
|
146 return nullptr; |
|
147 } |
|
148 size_t newIndex = container->mRootedObjects.length(); |
|
149 if (!container->mRootedObjects.append(jsObject)) { |
|
150 AndroidBridge::ThrowException(env, |
|
151 "java/lang/OutOfMemoryError", "Cannot allocate object"); |
|
152 return nullptr; |
|
153 } |
|
154 const jobject newObject = |
|
155 env->NewObject(jNativeJSObject, jObjectConstructor, |
|
156 instance, newIndex); |
|
157 AndroidBridge::HandleUncaughtException(env); |
|
158 MOZ_ASSERT(newObject); |
|
159 return frame.Pop(newObject); |
|
160 } |
|
161 |
|
162 // Make sure we have a JSObject and deserialize if necessary/possible |
|
163 bool EnsureObject(JNIEnv* env) { |
|
164 if (mJSObject) { |
|
165 if (PR_GetCurrentThread() != mThread) { |
|
166 AndroidBridge::ThrowException(env, |
|
167 "java/lang/IllegalThreadStateException", |
|
168 "Using NativeJSObject off its thread"); |
|
169 return false; |
|
170 } |
|
171 return true; |
|
172 } |
|
173 if (!SwitchContextToCurrentThread()) { |
|
174 AndroidBridge::ThrowException(env, |
|
175 "java/lang/IllegalThreadStateException", |
|
176 "Not available for this thread"); |
|
177 return false; |
|
178 } |
|
179 |
|
180 JS::RootedValue value(mThreadContext); |
|
181 MOZ_ASSERT(mBuffer.data()); |
|
182 MOZ_ALWAYS_TRUE(mBuffer.read(mThreadContext, &value)); |
|
183 if (value.isObject()) { |
|
184 mJSObject = &value.toObject(); |
|
185 } |
|
186 if (!mJSObject) { |
|
187 AndroidBridge::ThrowException(env, |
|
188 "java/lang/IllegalStateException", "Cannot deserialize data"); |
|
189 return false; |
|
190 } |
|
191 mBuffer.clear(); |
|
192 return true; |
|
193 } |
|
194 |
|
195 private: |
|
196 static jclass jNativeJSContainer; |
|
197 static jfieldID jContainerNativeObject; |
|
198 static jmethodID jContainerConstructor; |
|
199 static jclass jNativeJSObject; |
|
200 static jfieldID jObjectContainer; |
|
201 static jfieldID jObjectIndex; |
|
202 static jmethodID jObjectConstructor; |
|
203 |
|
204 static jobject CreateInstance(JNIEnv* env, |
|
205 NativeJSContainer* nativeObject) { |
|
206 InitJNI(env); |
|
207 const jobject newObject = |
|
208 env->NewObject(jNativeJSContainer, jContainerConstructor, |
|
209 static_cast<jlong>( |
|
210 reinterpret_cast<uintptr_t>(nativeObject))); |
|
211 AndroidBridge::HandleUncaughtException(env); |
|
212 MOZ_ASSERT(newObject); |
|
213 return newObject; |
|
214 } |
|
215 |
|
216 // Thread that the object is valid on |
|
217 PRThread* mThread; |
|
218 // Context that the object is valid in |
|
219 JSContext* mThreadContext; |
|
220 // Deserialized object, or nullptr if object is in serialized form |
|
221 JS::Heap<JSObject*> mJSObject; |
|
222 // Serialized object, or empty if object is in deserialized form |
|
223 JSAutoStructuredCloneBuffer mBuffer; |
|
224 // Objects derived from mJSObject |
|
225 Vector<JS::Heap<JSObject*>, 4> mRootedObjects; |
|
226 |
|
227 // Create a new container containing the given deserialized object |
|
228 NativeJSContainer(JSContext* cx, JS::HandleObject object) |
|
229 : mThread(PR_GetCurrentThread()) |
|
230 , mThreadContext(cx) |
|
231 , mJSObject(object) |
|
232 { |
|
233 } |
|
234 |
|
235 // Create a new container containing the given serialized object |
|
236 NativeJSContainer(JSContext* cx, JSAutoStructuredCloneBuffer&& buffer) |
|
237 : mThread(PR_GetCurrentThread()) |
|
238 , mThreadContext(cx) |
|
239 , mBuffer(Forward<JSAutoStructuredCloneBuffer>(buffer)) |
|
240 { |
|
241 } |
|
242 |
|
243 bool SwitchContextToCurrentThread() { |
|
244 PRThread* const currentThread = PR_GetCurrentThread(); |
|
245 if (currentThread == mThread) { |
|
246 return true; |
|
247 } |
|
248 return false; |
|
249 } |
|
250 }; |
|
251 |
|
252 jclass NativeJSContainer::jNativeJSContainer = 0; |
|
253 jfieldID NativeJSContainer::jContainerNativeObject = 0; |
|
254 jmethodID NativeJSContainer::jContainerConstructor = 0; |
|
255 jclass NativeJSContainer::jNativeJSObject = 0; |
|
256 jfieldID NativeJSContainer::jObjectContainer = 0; |
|
257 jfieldID NativeJSContainer::jObjectIndex = 0; |
|
258 jmethodID NativeJSContainer::jObjectConstructor = 0; |
|
259 |
|
260 jobject |
|
261 CreateNativeJSContainer(JNIEnv* env, JSContext* cx, JS::HandleObject object) |
|
262 { |
|
263 return NativeJSContainer::CreateInstance(env, cx, object); |
|
264 } |
|
265 |
|
266 } // namespace widget |
|
267 } // namespace mozilla |
|
268 |
|
269 namespace { |
|
270 |
|
271 class JSJNIString |
|
272 { |
|
273 public: |
|
274 JSJNIString(JNIEnv* env, jstring str) |
|
275 : mEnv(env) |
|
276 , mJNIString(str) |
|
277 , mJSString(!str ? nullptr : |
|
278 reinterpret_cast<const jschar*>(env->GetStringChars(str, nullptr))) |
|
279 { |
|
280 } |
|
281 ~JSJNIString() { |
|
282 if (mJNIString) { |
|
283 mEnv->ReleaseStringChars(mJNIString, |
|
284 reinterpret_cast<const jchar*>(mJSString)); |
|
285 } |
|
286 } |
|
287 operator const jschar*() const { |
|
288 return mJSString; |
|
289 } |
|
290 size_t Length() const { |
|
291 return static_cast<size_t>(mEnv->GetStringLength(mJNIString)); |
|
292 } |
|
293 private: |
|
294 JNIEnv* const mEnv; |
|
295 const jstring mJNIString; |
|
296 const jschar* const mJSString; |
|
297 }; |
|
298 |
|
299 bool |
|
300 CheckJSCall(JNIEnv* env, bool result) { |
|
301 if (!result) { |
|
302 AndroidBridge::ThrowException(env, |
|
303 "java/lang/UnsupportedOperationException", "JSAPI call failed"); |
|
304 } |
|
305 return result; |
|
306 } |
|
307 |
|
308 bool |
|
309 CheckJNIArgument(JNIEnv* env, jobject arg) { |
|
310 if (!arg) { |
|
311 AndroidBridge::ThrowException(env, |
|
312 "java/lang/IllegalArgumentException", "Null argument"); |
|
313 return false; |
|
314 } |
|
315 return true; |
|
316 } |
|
317 |
|
318 bool |
|
319 AppendJSON(const jschar* buf, uint32_t len, void* data) |
|
320 { |
|
321 static_cast<nsAutoString*>(data)->Append(buf, len); |
|
322 return true; |
|
323 } |
|
324 |
|
325 template <typename U, typename V, |
|
326 bool (JS::Value::*IsMethod)() const, |
|
327 V (JS::Value::*ToMethod)() const> |
|
328 struct PrimitiveProperty |
|
329 { |
|
330 typedef U Type; // JNI type |
|
331 typedef V NativeType; // JSAPI type |
|
332 |
|
333 static bool InValue(JS::HandleValue val) { |
|
334 return (static_cast<const JS::Value&>(val).*IsMethod)(); |
|
335 } |
|
336 |
|
337 static Type FromValue(JNIEnv* env, jobject instance, |
|
338 JSContext* cx, JS::HandleValue val) { |
|
339 return static_cast<Type>( |
|
340 (static_cast<const JS::Value&>(val).*ToMethod)()); |
|
341 } |
|
342 }; |
|
343 |
|
344 // Statically cast from bool to jboolean (unsigned char); it works |
|
345 // since false and JNI_FALSE have the same value (0), and true and |
|
346 // JNI_TRUE have the same value (1). |
|
347 typedef PrimitiveProperty<jboolean, bool, |
|
348 &JS::Value::isBoolean, &JS::Value::toBoolean> BooleanProperty; |
|
349 |
|
350 typedef PrimitiveProperty<jdouble, double, |
|
351 &JS::Value::isNumber, &JS::Value::toNumber> DoubleProperty; |
|
352 |
|
353 typedef PrimitiveProperty<jint, int32_t, |
|
354 &JS::Value::isInt32, &JS::Value::toInt32> IntProperty; |
|
355 |
|
356 struct StringProperty |
|
357 { |
|
358 typedef jstring Type; |
|
359 |
|
360 static bool InValue(JS::HandleValue val) { |
|
361 return val.isString(); |
|
362 } |
|
363 |
|
364 static Type FromValue(JNIEnv* env, jobject instance, |
|
365 JSContext* cx, JS::HandleValue val) { |
|
366 JS::RootedString str(cx, val.toString()); |
|
367 size_t strLen = 0; |
|
368 const jschar* const strChars = |
|
369 JS_GetStringCharsAndLength(cx, str, &strLen); |
|
370 if (!CheckJSCall(env, !!strChars)) { |
|
371 return nullptr; |
|
372 } |
|
373 jstring ret = env->NewString( |
|
374 reinterpret_cast<const jchar*>(strChars), strLen); |
|
375 AndroidBridge::HandleUncaughtException(env); |
|
376 MOZ_ASSERT(ret); |
|
377 return ret; |
|
378 } |
|
379 }; |
|
380 |
|
381 struct ObjectProperty |
|
382 { |
|
383 typedef jobject Type; |
|
384 |
|
385 static bool InValue(JS::HandleValue val) { |
|
386 return val.isObjectOrNull(); |
|
387 } |
|
388 |
|
389 static Type FromValue(JNIEnv* env, jobject instance, |
|
390 JSContext* cx, JS::HandleValue val) { |
|
391 if (val.isNull()) { |
|
392 return nullptr; |
|
393 } |
|
394 JS::RootedObject object(cx, &val.toObject()); |
|
395 return NativeJSContainer::CreateObjectInstance(env, instance, object); |
|
396 } |
|
397 }; |
|
398 |
|
399 struct HasProperty |
|
400 { |
|
401 typedef jboolean Type; |
|
402 |
|
403 static bool InValue(JS::HandleValue val) { |
|
404 return true; |
|
405 } |
|
406 |
|
407 static Type FromValue(JNIEnv* env, jobject instance, |
|
408 JSContext* cx, JS::HandleValue val) { |
|
409 return JNI_TRUE; |
|
410 } |
|
411 }; |
|
412 |
|
413 MOZ_BEGIN_ENUM_CLASS(FallbackOption) |
|
414 THROW, |
|
415 RETURN, |
|
416 MOZ_END_ENUM_CLASS(FallbackOption) |
|
417 |
|
418 template <class Property> |
|
419 typename Property::Type |
|
420 GetProperty(JNIEnv* env, jobject instance, jstring name, |
|
421 FallbackOption option = FallbackOption::THROW, |
|
422 typename Property::Type fallback = typename Property::Type()) { |
|
423 MOZ_ASSERT(env); |
|
424 MOZ_ASSERT(instance); |
|
425 |
|
426 JSContext* const cx = |
|
427 NativeJSContainer::GetContextFromObject(env, instance); |
|
428 const JS::RootedObject object(cx, |
|
429 NativeJSContainer::GetObjectFromObject(env, instance)); |
|
430 const JSJNIString strName(env, name); |
|
431 JS::RootedValue val(cx); |
|
432 |
|
433 if (!object || |
|
434 !CheckJNIArgument(env, name) || |
|
435 !CheckJSCall(env, |
|
436 JS_GetUCProperty(cx, object, strName, strName.Length(), &val))) { |
|
437 return typename Property::Type(); |
|
438 } |
|
439 if (val.isUndefined()) { |
|
440 if (option == FallbackOption::THROW) { |
|
441 AndroidBridge::ThrowException(env, |
|
442 "java/lang/IllegalArgumentException", |
|
443 "Property does not exist"); |
|
444 } |
|
445 return fallback; |
|
446 } |
|
447 if (!Property::InValue(val)) { |
|
448 AndroidBridge::ThrowException(env, |
|
449 "java/lang/IllegalArgumentException", |
|
450 "Property type mismatch"); |
|
451 return fallback; |
|
452 } |
|
453 return Property::FromValue(env, instance, cx, val); |
|
454 } |
|
455 |
|
456 } // namespace |
|
457 |
|
458 extern "C" { |
|
459 |
|
460 NS_EXPORT void JNICALL |
|
461 Java_org_mozilla_gecko_util_NativeJSContainer_dispose(JNIEnv* env, jobject instance) |
|
462 { |
|
463 MOZ_ASSERT(env); |
|
464 NativeJSContainer::DisposeInstance(env, instance); |
|
465 } |
|
466 |
|
467 NS_EXPORT jobject JNICALL |
|
468 Java_org_mozilla_gecko_util_NativeJSContainer_clone(JNIEnv* env, jobject instance) |
|
469 { |
|
470 MOZ_ASSERT(env); |
|
471 return NativeJSContainer::CloneInstance(env, instance); |
|
472 } |
|
473 |
|
474 NS_EXPORT jboolean JNICALL |
|
475 Java_org_mozilla_gecko_util_NativeJSObject_getBoolean(JNIEnv* env, jobject instance, jstring name) |
|
476 { |
|
477 return GetProperty<BooleanProperty>(env, instance, name); |
|
478 } |
|
479 |
|
480 NS_EXPORT jboolean JNICALL |
|
481 Java_org_mozilla_gecko_util_NativeJSObject_optBoolean(JNIEnv* env, jobject instance, |
|
482 jstring name, jboolean fallback) |
|
483 { |
|
484 return GetProperty<BooleanProperty>(env, instance, name, FallbackOption::RETURN, fallback); |
|
485 } |
|
486 |
|
487 NS_EXPORT jdouble JNICALL |
|
488 Java_org_mozilla_gecko_util_NativeJSObject_getDouble(JNIEnv* env, jobject instance, jstring name) |
|
489 { |
|
490 return GetProperty<DoubleProperty>(env, instance, name); |
|
491 } |
|
492 |
|
493 NS_EXPORT jdouble JNICALL |
|
494 Java_org_mozilla_gecko_util_NativeJSObject_optDouble(JNIEnv* env, jobject instance, |
|
495 jstring name, jdouble fallback) |
|
496 { |
|
497 return GetProperty<DoubleProperty>(env, instance, name, FallbackOption::RETURN, fallback); |
|
498 } |
|
499 |
|
500 NS_EXPORT jint JNICALL |
|
501 Java_org_mozilla_gecko_util_NativeJSObject_getInt(JNIEnv* env, jobject instance, jstring name) |
|
502 { |
|
503 return GetProperty<IntProperty>(env, instance, name); |
|
504 } |
|
505 |
|
506 NS_EXPORT jint JNICALL |
|
507 Java_org_mozilla_gecko_util_NativeJSObject_optInt(JNIEnv* env, jobject instance, |
|
508 jstring name, jint fallback) |
|
509 { |
|
510 return GetProperty<IntProperty>(env, instance, name, FallbackOption::RETURN, fallback); |
|
511 } |
|
512 |
|
513 NS_EXPORT jobject JNICALL |
|
514 Java_org_mozilla_gecko_util_NativeJSObject_getObject(JNIEnv* env, jobject instance, jstring name) |
|
515 { |
|
516 return GetProperty<ObjectProperty>(env, instance, name); |
|
517 } |
|
518 |
|
519 NS_EXPORT jobject JNICALL |
|
520 Java_org_mozilla_gecko_util_NativeJSObject_optObject(JNIEnv* env, jobject instance, |
|
521 jstring name, jobject fallback) |
|
522 { |
|
523 return GetProperty<ObjectProperty>(env, instance, name, FallbackOption::RETURN, fallback); |
|
524 } |
|
525 |
|
526 NS_EXPORT jstring JNICALL |
|
527 Java_org_mozilla_gecko_util_NativeJSObject_getString(JNIEnv* env, jobject instance, jstring name) |
|
528 { |
|
529 return GetProperty<StringProperty>(env, instance, name); |
|
530 } |
|
531 |
|
532 NS_EXPORT jstring JNICALL |
|
533 Java_org_mozilla_gecko_util_NativeJSObject_optString(JNIEnv* env, jobject instance, |
|
534 jstring name, jstring fallback) |
|
535 { |
|
536 return GetProperty<StringProperty>(env, instance, name, FallbackOption::RETURN, fallback); |
|
537 } |
|
538 |
|
539 NS_EXPORT jboolean JNICALL |
|
540 Java_org_mozilla_gecko_util_NativeJSObject_has(JNIEnv* env, jobject instance, jstring name) |
|
541 { |
|
542 return GetProperty<HasProperty>(env, instance, name, FallbackOption::RETURN, JNI_FALSE); |
|
543 } |
|
544 |
|
545 NS_EXPORT jstring JNICALL |
|
546 Java_org_mozilla_gecko_util_NativeJSObject_toString(JNIEnv* env, jobject instance) |
|
547 { |
|
548 MOZ_ASSERT(env); |
|
549 MOZ_ASSERT(instance); |
|
550 |
|
551 JSContext* const cx = NativeJSContainer::GetContextFromObject(env, instance); |
|
552 const JS::RootedObject object(cx, NativeJSContainer::GetObjectFromObject(env, instance)); |
|
553 JS::RootedValue value(cx, JS::ObjectValue(*object)); |
|
554 nsAutoString json; |
|
555 |
|
556 if (!object || |
|
557 !CheckJSCall(env, |
|
558 JS_Stringify(cx, &value, JS::NullPtr(), JS::NullHandleValue, AppendJSON, &json))) { |
|
559 return nullptr; |
|
560 } |
|
561 jstring ret = env->NewString(reinterpret_cast<const jchar*>(json.get()), json.Length()); |
|
562 AndroidBridge::HandleUncaughtException(env); |
|
563 MOZ_ASSERT(ret); |
|
564 return ret; |
|
565 } |
|
566 |
|
567 } // extern "C" |