summaryrefslogtreecommitdiff
path: root/widget/android/NativeJSContainer.cpp
blob: b8c434656c5c8ab5b1b07ddd6335d41cf54ffb2b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
/* -*- Mode: c++; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "NativeJSContainer.h"

#include <jni.h>

#include "Bundle.h"
#include "GeneratedJNINatives.h"
#include "MainThreadUtils.h"
#include "jsapi.h"
#include "nsJSUtils.h"

#include <mozilla/Vector.h>
#include <mozilla/jni/Accessors.h>
#include <mozilla/jni/Refs.h>
#include <mozilla/jni/Utils.h>

/**
 * NativeJSContainer.cpp implements the native methods in both
 * NativeJSContainer and NativeJSObject, using JSAPI to retrieve values from a
 * JSObject and using JNI to return those values to Java code.
 */

namespace mozilla {
namespace widget {

namespace {

bool CheckThread()
{
    if (!NS_IsMainThread()) {
        jni::ThrowException("java/lang/IllegalThreadStateException",
                            "Not on Gecko thread");
        return false;
    }
    return true;
}

template<class C, typename T> bool
CheckJNIArgument(const jni::Ref<C, T>& arg)
{
    if (!arg) {
        jni::ThrowException("java/lang/IllegalArgumentException",
                            "Null argument");
    }
    return !!arg;
}

nsresult
CheckSDKCall(nsresult rv)
{
    if (NS_FAILED(rv)) {
        jni::ThrowException("java/lang/UnsupportedOperationException",
                            "SDK JNI call failed");
    }
    return rv;
}

// Convert a JNI string to a char16_t string that JSAPI expects.
class JSJNIString final
{
    JNIEnv* const mEnv;
    jni::String::Param mJNIString;
    const char16_t* const mJSString;

public:
    JSJNIString(JNIEnv* env, jni::String::Param str)
        : mEnv(env)
        , mJNIString(str)
        , mJSString(!str ? nullptr : reinterpret_cast<const char16_t*>(
                mEnv->GetStringChars(str.Get(), nullptr)))
    {}

    ~JSJNIString() {
        if (mJNIString) {
            mEnv->ReleaseStringChars(mJNIString.Get(),
                reinterpret_cast<const jchar*>(mJSString));
        }
    }

    operator const char16_t*() const {
        return mJSString;
    }

    size_t Length() const {
        return static_cast<size_t>(mEnv->GetStringLength(mJNIString.Get()));
    }
};

} // namepsace

class NativeJSContainerImpl final
    : public NativeJSObject::Natives<NativeJSContainerImpl>
    , public NativeJSContainer::Natives<NativeJSContainerImpl>
{
    typedef NativeJSContainerImpl Self;
    typedef NativeJSContainer::Natives<NativeJSContainerImpl> ContainerBase;
    typedef NativeJSObject::Natives<NativeJSContainerImpl> ObjectBase;

    typedef JS::PersistentRooted<JSObject*> PersistentObject;

    JNIEnv* const mEnv;
    // Context that the object is valid in
    JSContext* const mJSContext;
    // Root JS object
    PersistentObject mJSObject;
    // Children objects
    Vector<NativeJSObject::GlobalRef, 0> mChildren;

    bool CheckObject() const
    {
        if (!mJSObject) {
            jni::ThrowException("java/lang/NullPointerException",
                                "Null JSObject");
        }
        return !!mJSObject;
    }

    bool CheckJSCall(bool result) const
    {
        if (!result) {
            JS_ClearPendingException(mJSContext);
            jni::ThrowException("java/lang/UnsupportedOperationException",
                                "JSAPI call failed");
        }
        return result;
    }

    // Check that a JS Value contains a particular property type as indicaed by
    // the property's InValue method (e.g. StringProperty::InValue).
    bool CheckProperty(bool (Self::*InValue)(JS::HandleValue) const,
                       JS::HandleValue val) const
    {
        if (!(this->*InValue)(val)) {
            // XXX this can happen when converting a double array inside a
            // Bundle, because double arrays can be misidentified as an int
            // array. The workaround is to add a dummy first element to the
            // array that is a floating point value, i.e. [0.5, ...].
            jni::ThrowException(
                "org/mozilla/gecko/util/NativeJSObject$InvalidPropertyException",
                "Property type mismatch");
            return false;
        }
        return true;
    }

    // Primitive properties

    template<bool (JS::Value::*IsType)() const> bool
    PrimitiveInValue(JS::HandleValue val) const
    {
        return (static_cast<const JS::Value&>(val).*IsType)();
    }

    template<typename U, U (JS::Value::*ToType)() const> U
    PrimitiveFromValue(JS::HandleValue val) const
    {
        return (static_cast<const JS::Value&>(val).*ToType)();
    }

    template<class Prop> typename Prop::NativeArray
    PrimitiveNewArray(JS::HandleObject array, size_t length) const
    {
        typedef typename Prop::JNIType JNIType;

        // Fill up a temporary buffer for our array, then use
        // JNIEnv::Set*ArrayRegion to fill out array in one go.

        UniquePtr<JNIType[]> buffer = MakeUnique<JNIType[]>(length);
        for (size_t i = 0; i < length; i++) {
            JS::RootedValue elem(mJSContext);
            if (!CheckJSCall(JS_GetElement(mJSContext, array, i, &elem)) ||
                !CheckProperty(Prop::InValue, elem)) {
                return nullptr;
            }
            buffer[i] = JNIType((this->*Prop::FromValue)(elem));
        }
        auto jarray = Prop::NativeArray::Adopt(
                mEnv, (mEnv->*Prop::NewJNIArray)(length));
        if (!jarray) {
            return nullptr;
        }
        (mEnv->*Prop::SetJNIArrayRegion)(
                jarray.Get(), 0, length, buffer.get());
        if (mEnv->ExceptionCheck()) {
            return nullptr;
        }
        return jarray;
    }

    template<typename U, typename UA, typename V, typename VA,
             bool (JS::Value::*IsType)() const,
             U (JS::Value::*ToType)() const,
             VA (JNIEnv::*NewArray_)(jsize),
             void (JNIEnv::*SetArrayRegion_)(VA, jsize, jsize, const V*)>
    struct PrimitiveProperty
    {
        // C++ type for a primitive property (e.g. bool)
        typedef U NativeType;
        // C++ type for the fallback value used in opt* methods
        typedef U NativeFallback;
        // Type for an array of the primitive type (e.g. BooleanArray::LocalRef)
        typedef typename UA::LocalRef NativeArray;
        // Type for the fallback value used in opt*Array methods
        typedef const typename UA::Ref ArrayFallback;
        // JNI type (e.g. jboolean)
        typedef V JNIType;

        // JNIEnv function to create a new JNI array of the primiive type
        typedef decltype(NewArray_) NewJNIArray_t;
        static constexpr NewJNIArray_t NewJNIArray = NewArray_;

        // JNIEnv function to fill a JNI array of the primiive type
        typedef decltype(SetArrayRegion_) SetJNIArrayRegion_t;
        static constexpr SetJNIArrayRegion_t SetJNIArrayRegion = SetArrayRegion_;

        // Function to determine if a JS Value contains the primitive type
        typedef decltype(&Self::PrimitiveInValue<IsType>) InValue_t;
        static constexpr InValue_t InValue = &Self::PrimitiveInValue<IsType>;

        // Function to convert a JS Value to the primitive type
        typedef decltype(&Self::PrimitiveFromValue<U, ToType>) FromValue_t;
        static constexpr FromValue_t FromValue
                = &Self::PrimitiveFromValue<U, ToType>;

        // Function to convert a JS array to a JNI array
        typedef decltype(&Self::PrimitiveNewArray<PrimitiveProperty>) NewArray_t;
        static constexpr NewArray_t NewArray
                = &Self::PrimitiveNewArray<PrimitiveProperty>;
    };

    // String properties

    bool StringInValue(JS::HandleValue val) const
    {
        return val.isString();
    }

    jni::String::LocalRef
    StringFromValue(const JS::HandleString str) const
    {
        nsAutoJSString autoStr;
        if (!CheckJSCall(autoStr.init(mJSContext, str))) {
            return nullptr;
        }
        // StringParam can automatically convert a nsString to jstring.
        return jni::StringParam(autoStr, mEnv);
    }

    jni::String::LocalRef
    StringFromValue(JS::HandleValue val)
    {
        const JS::RootedString str(mJSContext, val.toString());
        return StringFromValue(str);
    }

    // Bundle properties

    sdk::Bundle::LocalRef
    BundleFromValue(const JS::HandleObject obj)
    {
        JS::Rooted<JS::IdVector> ids(mJSContext, JS::IdVector(mJSContext));
        if (!CheckJSCall(JS_Enumerate(mJSContext, obj, &ids))) {
            return nullptr;
        }

        const size_t length = ids.length();
        sdk::Bundle::LocalRef newBundle(mEnv);
        NS_ENSURE_SUCCESS(CheckSDKCall(
                sdk::Bundle::New(length, &newBundle)), nullptr);

        // Iterate through each property of the JS object. For each property,
        // determine its type from a list of supported types, and convert that
        // proeprty to the supported type.

        for (size_t i = 0; i < ids.length(); i++) {
            const JS::RootedId id(mJSContext, ids[i]);
            JS::RootedValue idVal(mJSContext);
            if (!CheckJSCall(JS_IdToValue(mJSContext, id, &idVal))) {
                return nullptr;
            }

            const JS::RootedString idStr(mJSContext,
                                         JS::ToString(mJSContext, idVal));
            if (!CheckJSCall(!!idStr)) {
                return nullptr;
            }

            jni::String::LocalRef name = StringFromValue(idStr);
            JS::RootedValue val(mJSContext);
            if (!name ||
                !CheckJSCall(JS_GetPropertyById(mJSContext, obj, id, &val))) {
                return nullptr;
            }

#define PUT_IN_BUNDLE_IF_TYPE_IS(TYPE)                                  \
            if ((this->*TYPE##Property::InValue)(val)) {                \
                auto jval = (this->*TYPE##Property::FromValue)(val);    \
                if (mEnv->ExceptionCheck()) {                           \
                    return nullptr;                                     \
                }                                                       \
                NS_ENSURE_SUCCESS(CheckSDKCall(                         \
                        newBundle->Put##TYPE(name, jval)), nullptr);    \
                continue;                                               \
            }                                                           \
            ((void) 0) // Accommodate trailing semicolon.

            // Scalar values are faster to check, so check them first.
            PUT_IN_BUNDLE_IF_TYPE_IS(Boolean);
            // Int can be casted to double, so check int first.
            PUT_IN_BUNDLE_IF_TYPE_IS(Int);
            PUT_IN_BUNDLE_IF_TYPE_IS(Double);
            PUT_IN_BUNDLE_IF_TYPE_IS(String);
            // There's no "putObject", so don't check ObjectProperty

            // Check for array types if scalar checks all failed.
            // XXX empty arrays are treated as boolean arrays. Workaround is
            // to always have a dummy element to create a non-empty array.
            PUT_IN_BUNDLE_IF_TYPE_IS(BooleanArray);
            // XXX because we only check the first element of an array,
            // a double array can potentially be seen as an int array.
            // When that happens, the Bundle conversion will fail.
            PUT_IN_BUNDLE_IF_TYPE_IS(IntArray);
            PUT_IN_BUNDLE_IF_TYPE_IS(DoubleArray);
            PUT_IN_BUNDLE_IF_TYPE_IS(StringArray);
            // There's no "putObjectArray", so don't check ObjectArrayProperty
            // There's no "putBundleArray", so don't check BundleArrayProperty

            // Use Bundle as the default catch-all for objects
            PUT_IN_BUNDLE_IF_TYPE_IS(Bundle);

#undef PUT_IN_BUNDLE_IF_TYPE_IS

            // We tried all supported types; just bail.
            jni::ThrowException("java/lang/UnsupportedOperationException",
                                "Unsupported property type");
            return nullptr;
        }
        return jni::Object::LocalRef::Adopt(newBundle.Env(),
                                            newBundle.Forget());
    }

    sdk::Bundle::LocalRef
    BundleFromValue(JS::HandleValue val)
    {
        if (val.isNull()) {
            return nullptr;
        }
        JS::RootedObject object(mJSContext, &val.toObject());
        return BundleFromValue(object);
    }

    // Object properties

    bool ObjectInValue(JS::HandleValue val) const
    {
        return val.isObjectOrNull();
    }

    NativeJSObject::LocalRef
    ObjectFromValue(JS::HandleValue val)
    {
        if (val.isNull()) {
            return nullptr;
        }
        JS::RootedObject object(mJSContext, &val.toObject());
        return CreateChild(object);
    }

    template<class Prop> typename Prop::NativeArray
    ObjectNewArray(JS::HandleObject array, size_t length)
    {
        auto jarray = Prop::NativeArray::Adopt(mEnv, mEnv->NewObjectArray(
                length, typename Prop::ClassType::Context().ClassRef(),
                nullptr));
        if (!jarray) {
            return nullptr;
        }

        // For object arrays, we have to set each element separately.
        for (size_t i = 0; i < length; i++) {
            JS::RootedValue elem(mJSContext);
            if (!CheckJSCall(JS_GetElement(mJSContext, array, i, &elem)) ||
                !CheckProperty(Prop::InValue, elem)) {
                return nullptr;
            }
            mEnv->SetObjectArrayElement(
                    jarray.Get(), i, (this->*Prop::FromValue)(elem).Get());
            if (mEnv->ExceptionCheck()) {
                return nullptr;
            }
        }
        return jarray;
    }

    template<class Class,
             bool (Self::*InValue_)(JS::HandleValue) const,
             typename Class::LocalRef (Self::*FromValue_)(JS::HandleValue)>
    struct BaseObjectProperty
    {
        // JNI class for the object type (e.g. jni::String)
        typedef Class ClassType;

        // See comments in PrimitiveProperty.
        typedef typename ClassType::LocalRef NativeType;
        typedef const typename ClassType::Ref NativeFallback;
        typedef typename jni::ObjectArray::LocalRef NativeArray;
        typedef const jni::ObjectArray::Ref ArrayFallback;

        typedef decltype(InValue_) InValue_t;
        static constexpr InValue_t InValue = InValue_;

        typedef decltype(FromValue_) FromValue_t;
        static constexpr FromValue_t FromValue = FromValue_;

        typedef decltype(&Self::ObjectNewArray<BaseObjectProperty>) NewArray_t;
        static constexpr NewArray_t NewArray
                = &Self::ObjectNewArray<BaseObjectProperty>;
    };

    // Array properties

    template<class Prop> bool
    ArrayInValue(JS::HandleValue val) const
    {
        if (!val.isObject()) {
            return false;
        }
        JS::RootedObject obj(mJSContext, &val.toObject());
        bool isArray;
        uint32_t length = 0;
        if (!JS_IsArrayObject(mJSContext, obj, &isArray) ||
            !isArray ||
            !JS_GetArrayLength(mJSContext, obj, &length)) {
            JS_ClearPendingException(mJSContext);
            return false;
        }
        if (!length) {
            // Empty arrays are always okay.
            return true;
        }
        // We only check to see the first element is the target type. If the
        // array has mixed types, we'll throw an error during actual conversion.
        JS::RootedValue element(mJSContext);
        if (!JS_GetElement(mJSContext, obj, 0, &element)) {
            JS_ClearPendingException(mJSContext);
            return false;
        }
        return (this->*Prop::InValue)(element);
    }

    template<class Prop> typename Prop::NativeArray
    ArrayFromValue(JS::HandleValue val)
    {
        JS::RootedObject obj(mJSContext, &val.toObject());
        uint32_t length = 0;
        if (!CheckJSCall(JS_GetArrayLength(mJSContext, obj, &length))) {
            return nullptr;
        }
        return (this->*Prop::NewArray)(obj, length);
    }

    template<class Prop>
    struct ArrayProperty
    {
        // See comments in PrimitiveProperty.
        typedef typename Prop::NativeArray NativeType;
        typedef typename Prop::ArrayFallback NativeFallback;

        typedef decltype(&Self::ArrayInValue<Prop>) InValue_t;
        static constexpr InValue_t InValue
                = &Self::ArrayInValue<Prop>;

        typedef decltype(&Self::ArrayFromValue<Prop>) FromValue_t;
        static constexpr FromValue_t FromValue
                = &Self::ArrayFromValue<Prop>;
    };

    // "Has" property is a special property type that is used to implement
    // NativeJSObject.has, by returning true from InValue and FromValue for
    // every existing property, and having false as the fallback value for
    // when a property doesn't exist.

    bool HasValue(JS::HandleValue val) const
    {
        return true;
    }

    struct HasProperty
    {
        // See comments in PrimitiveProperty.
        typedef bool NativeType;
        typedef bool NativeFallback;

        typedef decltype(&Self::HasValue) HasValue_t;
        static constexpr HasValue_t InValue = &Self::HasValue;
        static constexpr HasValue_t FromValue = &Self::HasValue;
    };

    // Statically cast from bool to jboolean (unsigned char); it works
    // since false and JNI_FALSE have the same value (0), and true and
    // JNI_TRUE have the same value (1).
    typedef PrimitiveProperty<
            bool, jni::BooleanArray, jboolean, jbooleanArray,
            &JS::Value::isBoolean, &JS::Value::toBoolean,
            &JNIEnv::NewBooleanArray, &JNIEnv::SetBooleanArrayRegion>
        BooleanProperty;

    typedef PrimitiveProperty<
            double, jni::DoubleArray, jdouble, jdoubleArray,
            &JS::Value::isNumber, &JS::Value::toNumber,
            &JNIEnv::NewDoubleArray, &JNIEnv::SetDoubleArrayRegion>
        DoubleProperty;

    typedef PrimitiveProperty<
            int32_t, jni::IntArray, jint, jintArray,
            &JS::Value::isInt32, &JS::Value::toInt32,
            &JNIEnv::NewIntArray, &JNIEnv::SetIntArrayRegion>
        IntProperty;

    typedef BaseObjectProperty<
            jni::String, &Self::StringInValue, &Self::StringFromValue>
        StringProperty;

    typedef BaseObjectProperty<
            sdk::Bundle, &Self::ObjectInValue, &Self::BundleFromValue>
        BundleProperty;

    typedef BaseObjectProperty<
            NativeJSObject, &Self::ObjectInValue, &Self::ObjectFromValue>
        ObjectProperty;

    typedef ArrayProperty<BooleanProperty> BooleanArrayProperty;
    typedef ArrayProperty<DoubleProperty> DoubleArrayProperty;
    typedef ArrayProperty<IntProperty> IntArrayProperty;
    typedef ArrayProperty<StringProperty> StringArrayProperty;
    typedef ArrayProperty<BundleProperty> BundleArrayProperty;
    typedef ArrayProperty<ObjectProperty> ObjectArrayProperty;

    template<class Prop>
    typename Prop::NativeType
    GetProperty(jni::String::Param name,
                typename Prop::NativeFallback* fallback = nullptr)
    {
        if (!CheckThread() || !CheckObject()) {
            return typename Prop::NativeType();
        }

        const JSJNIString nameStr(mEnv, name);
        JS::RootedValue val(mJSContext);

        if (!CheckJNIArgument(name) ||
            !CheckJSCall(JS_GetUCProperty(
                    mJSContext, mJSObject, nameStr, nameStr.Length(), &val))) {
            return typename Prop::NativeType();
        }

        // Strictly, null is different from undefined in JS. However, in
        // practice, null is often used to indicate a property doesn't exist in
        // the same manner as undefined. Therefore, we treat null in the same
        // way as undefined when checking property existence (bug 1014965).
        if (val.isUndefined() || val.isNull()) {
            if (fallback) {
                return mozilla::Move(*fallback);
            }
            jni::ThrowException(
                "org/mozilla/gecko/util/NativeJSObject$InvalidPropertyException",
                "Property does not exist");
            return typename Prop::NativeType();
        }

        if (!CheckProperty(Prop::InValue, val)) {
            return typename Prop::NativeType();
        }
        return (this->*Prop::FromValue)(val);
    }

    NativeJSObject::LocalRef CreateChild(JS::HandleObject object)
    {
        auto instance = NativeJSObject::New();
        mozilla::UniquePtr<NativeJSContainerImpl> impl(
                new NativeJSContainerImpl(instance.Env(), mJSContext, object));

        ObjectBase::AttachNative(instance, mozilla::Move(impl));
        if (!mChildren.append(NativeJSObject::GlobalRef(instance))) {
            MOZ_CRASH();
        }
        return instance;
    }

    NativeJSContainerImpl(JNIEnv* env, JSContext* cx, JS::HandleObject object)
        : mEnv(env)
        , mJSContext(cx)
        , mJSObject(cx, object)
    {}

public:
    ~NativeJSContainerImpl()
    {
        // Dispose of all children on destruction. The children will in turn
        // dispose any of their children (i.e. our grandchildren) and so on.
        NativeJSObject::LocalRef child(mEnv);
        for (size_t i = 0; i < mChildren.length(); i++) {
            child = mChildren[i];
            ObjectBase::GetNative(child)->ObjectBase::DisposeNative(child);
        }
    }

    static NativeJSContainer::LocalRef
    CreateInstance(JSContext* cx, JS::HandleObject object)
    {
        auto instance = NativeJSContainer::New();
        mozilla::UniquePtr<NativeJSContainerImpl> impl(
                new NativeJSContainerImpl(instance.Env(), cx, object));

        ContainerBase::AttachNative(instance, mozilla::Move(impl));
        return instance;
    }

    // NativeJSContainer methods

    void DisposeNative(const NativeJSContainer::LocalRef& instance)
    {
        if (!CheckThread()) {
            return;
        }
        ContainerBase::DisposeNative(instance);
    }

    NativeJSContainer::LocalRef Clone()
    {
        if (!CheckThread()) {
            return nullptr;
        }
        return CreateInstance(mJSContext, mJSObject);
    }

    // NativeJSObject methods

    bool GetBoolean(jni::String::Param name)
    {
        return GetProperty<BooleanProperty>(name);
    }

    bool OptBoolean(jni::String::Param name, bool fallback)
    {
        return GetProperty<BooleanProperty>(name, &fallback);
    }

    jni::BooleanArray::LocalRef
    GetBooleanArray(jni::String::Param name)
    {
        return GetProperty<BooleanArrayProperty>(name);
    }

    jni::BooleanArray::LocalRef
    OptBooleanArray(jni::String::Param name, jni::BooleanArray::Param fallback)
    {
        return GetProperty<BooleanArrayProperty>(name, &fallback);
    }

    jni::Object::LocalRef
    GetBundle(jni::String::Param name)
    {
        return GetProperty<BundleProperty>(name);
    }

    jni::Object::LocalRef
    OptBundle(jni::String::Param name, jni::Object::Param fallback)
    {
        // Because the GetProperty expects a sdk::Bundle::Param,
        // we have to do conversions here from jni::Object::Param.
        const auto& fb = sdk::Bundle::Ref::From(fallback.Get());
        return GetProperty<BundleProperty>(name, &fb);
    }

    jni::ObjectArray::LocalRef
    GetBundleArray(jni::String::Param name)
    {
        return GetProperty<BundleArrayProperty>(name);
    }

    jni::ObjectArray::LocalRef
    OptBundleArray(jni::String::Param name, jni::ObjectArray::Param fallback)
    {
        return GetProperty<BundleArrayProperty>(name, &fallback);
    }

    double GetDouble(jni::String::Param name)
    {
        return GetProperty<DoubleProperty>(name);
    }

    double OptDouble(jni::String::Param name, double fallback)
    {
        return GetProperty<DoubleProperty>(name, &fallback);
    }

    jni::DoubleArray::LocalRef
    GetDoubleArray(jni::String::Param name)
    {
        return GetProperty<DoubleArrayProperty>(name);
    }

    jni::DoubleArray::LocalRef
    OptDoubleArray(jni::String::Param name, jni::DoubleArray::Param fallback)
    {
        jni::DoubleArray::LocalRef fb(fallback);
        return GetProperty<DoubleArrayProperty>(name, &fb);
    }

    int GetInt(jni::String::Param name)
    {
        return GetProperty<IntProperty>(name);
    }

    int OptInt(jni::String::Param name, int fallback)
    {
        return GetProperty<IntProperty>(name, &fallback);
    }

    jni::IntArray::LocalRef
    GetIntArray(jni::String::Param name)
    {
        return GetProperty<IntArrayProperty>(name);
    }

    jni::IntArray::LocalRef
    OptIntArray(jni::String::Param name, jni::IntArray::Param fallback)
    {
        jni::IntArray::LocalRef fb(fallback);
        return GetProperty<IntArrayProperty>(name, &fb);
    }

    NativeJSObject::LocalRef
    GetObject(jni::String::Param name)
    {
        return GetProperty<ObjectProperty>(name);
    }

    NativeJSObject::LocalRef
    OptObject(jni::String::Param name, NativeJSObject::Param fallback)
    {
        return GetProperty<ObjectProperty>(name, &fallback);
    }

    jni::ObjectArray::LocalRef
    GetObjectArray(jni::String::Param name)
    {
        return GetProperty<ObjectArrayProperty>(name);
    }

    jni::ObjectArray::LocalRef
    OptObjectArray(jni::String::Param name, jni::ObjectArray::Param fallback)
    {
        return GetProperty<ObjectArrayProperty>(name, &fallback);
    }

    jni::String::LocalRef
    GetString(jni::String::Param name)
    {
        return GetProperty<StringProperty>(name);
    }

    jni::String::LocalRef
    OptString(jni::String::Param name, jni::String::Param fallback)
    {
        return GetProperty<StringProperty>(name, &fallback);
    }

    jni::ObjectArray::LocalRef
    GetStringArray(jni::String::Param name)
    {
        return GetProperty<StringArrayProperty>(name);
    }

    jni::ObjectArray::LocalRef
    OptStringArray(jni::String::Param name, jni::ObjectArray::Param fallback)
    {
        return GetProperty<StringArrayProperty>(name, &fallback);
    }

    bool Has(jni::String::Param name)
    {
        bool no = false;
        // Fallback to false indicating no such property.
        return GetProperty<HasProperty>(name, &no);
    }

    jni::Object::LocalRef ToBundle()
    {
        if (!CheckThread() || !CheckObject()) {
            return nullptr;
        }
        return BundleFromValue(mJSObject);
    }

private:
    static bool AppendJSON(const char16_t* buf, uint32_t len, void* data)
    {
        static_cast<nsAutoString*>(data)->Append(buf, len);
        return true;
    }

public:
    jni::String::LocalRef ToString()
    {
        if (!CheckThread() || !CheckObject()) {
            return nullptr;
        }

        JS::RootedValue value(mJSContext, JS::ObjectValue(*mJSObject));
        nsAutoString json;
        if (!CheckJSCall(JS_Stringify(mJSContext, &value, nullptr,
                                      JS::NullHandleValue, AppendJSON, &json))) {
            return nullptr;
        }
        return jni::StringParam(json, mEnv);
    }
};


// Define the "static constexpr" members of our property types (e.g.
// PrimitiveProperty<>::InValue). This is tricky because there are a lot of
// template parameters, so we use macros to make it simpler.

#define DEFINE_PRIMITIVE_PROPERTY_MEMBER(Name) \
    template<typename U, typename UA, typename V, typename VA, \
             bool (JS::Value::*I)() const, \
             U (JS::Value::*T)() const, \
             VA (JNIEnv::*N)(jsize), \
             void (JNIEnv::*S)(VA, jsize, jsize, const V*)> \
    constexpr typename NativeJSContainerImpl \
        ::PrimitiveProperty<U, UA, V, VA, I, T, N, S>::Name##_t \
    NativeJSContainerImpl::PrimitiveProperty<U, UA, V, VA, I, T, N, S>::Name

DEFINE_PRIMITIVE_PROPERTY_MEMBER(NewJNIArray);
DEFINE_PRIMITIVE_PROPERTY_MEMBER(SetJNIArrayRegion);
DEFINE_PRIMITIVE_PROPERTY_MEMBER(InValue);
DEFINE_PRIMITIVE_PROPERTY_MEMBER(FromValue);
DEFINE_PRIMITIVE_PROPERTY_MEMBER(NewArray);

#undef DEFINE_PRIMITIVE_PROPERTY_MEMBER

#define DEFINE_OBJECT_PROPERTY_MEMBER(Name) \
    template<class C, \
             bool (NativeJSContainerImpl::*I)(JS::HandleValue) const, \
             typename C::LocalRef (NativeJSContainerImpl::*F)(JS::HandleValue)> \
    constexpr typename NativeJSContainerImpl \
        ::BaseObjectProperty<C, I, F>::Name##_t \
    NativeJSContainerImpl::BaseObjectProperty<C, I, F>::Name

DEFINE_OBJECT_PROPERTY_MEMBER(InValue);
DEFINE_OBJECT_PROPERTY_MEMBER(FromValue);
DEFINE_OBJECT_PROPERTY_MEMBER(NewArray);

#undef DEFINE_OBJECT_PROPERTY_MEMBER

template<class P> constexpr typename NativeJSContainerImpl::ArrayProperty<P>
        ::InValue_t NativeJSContainerImpl::ArrayProperty<P>::InValue;
template<class P> constexpr typename NativeJSContainerImpl::ArrayProperty<P>
        ::FromValue_t NativeJSContainerImpl::ArrayProperty<P>::FromValue;

constexpr NativeJSContainerImpl::HasProperty::HasValue_t
        NativeJSContainerImpl::HasProperty::InValue;
constexpr NativeJSContainerImpl::HasProperty::HasValue_t
        NativeJSContainerImpl::HasProperty::FromValue;


NativeJSContainer::LocalRef
CreateNativeJSContainer(JSContext* cx, JS::HandleObject object)
{
    return NativeJSContainerImpl::CreateInstance(cx, object);
}

} // namespace widget
} // namespace mozilla