forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			1008 lines
		
	
	
	
		
			32 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1008 lines
		
	
	
	
		
			32 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/* -*- Mode: c++; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
 | 
						|
 * vim: set sw=4 ts=4 expandtab:
 | 
						|
 * 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 "EventDispatcher.h"
 | 
						|
 | 
						|
#include "nsAppShell.h"
 | 
						|
#include "nsIXPConnect.h"
 | 
						|
#include "nsJSUtils.h"
 | 
						|
#include "xpcpublic.h"
 | 
						|
 | 
						|
#include "mozilla/ScopeExit.h"
 | 
						|
#include "mozilla/dom/ScriptSettings.h"
 | 
						|
 | 
						|
namespace mozilla {
 | 
						|
namespace widget {
 | 
						|
 | 
						|
namespace detail {
 | 
						|
 | 
						|
bool
 | 
						|
CheckJS(JSContext* aCx, bool aResult)
 | 
						|
{
 | 
						|
    if (!aResult) {
 | 
						|
        JS_ClearPendingException(aCx);
 | 
						|
    }
 | 
						|
    return aResult;
 | 
						|
}
 | 
						|
 | 
						|
nsresult
 | 
						|
BoxString(JSContext* aCx, JS::HandleValue aData, jni::Object::LocalRef& aOut)
 | 
						|
{
 | 
						|
    if (aData.isNullOrUndefined()) {
 | 
						|
        aOut = nullptr;
 | 
						|
        return NS_OK;
 | 
						|
    }
 | 
						|
 | 
						|
    MOZ_ASSERT(aData.isString());
 | 
						|
 | 
						|
    JS::RootedString str(aCx, aData.toString());
 | 
						|
 | 
						|
    if (JS_StringHasLatin1Chars(str)) {
 | 
						|
        nsAutoJSString autoStr;
 | 
						|
        NS_ENSURE_TRUE(CheckJS(aCx, autoStr.init(aCx, str)), NS_ERROR_FAILURE);
 | 
						|
 | 
						|
        // StringParam can automatically convert a nsString to jstring.
 | 
						|
        aOut = jni::StringParam(autoStr, aOut.Env());
 | 
						|
        return NS_OK;
 | 
						|
    }
 | 
						|
 | 
						|
    // Two-byte string
 | 
						|
    JNIEnv* const env = aOut.Env();
 | 
						|
    const char16_t* chars;
 | 
						|
    {
 | 
						|
        JS::AutoCheckCannotGC nogc;
 | 
						|
        size_t len = 0;
 | 
						|
        chars = JS_GetTwoByteStringCharsAndLength(aCx, nogc, str, &len);
 | 
						|
        if (chars) {
 | 
						|
            aOut = jni::String::LocalRef::Adopt(env,
 | 
						|
                    env->NewString(reinterpret_cast<const jchar*>(chars), len));
 | 
						|
        }
 | 
						|
    }
 | 
						|
    if (NS_WARN_IF(!CheckJS(aCx, !!chars) || !aOut)) {
 | 
						|
        env->ExceptionClear();
 | 
						|
        return NS_ERROR_FAILURE;
 | 
						|
    }
 | 
						|
    return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
nsresult
 | 
						|
BoxObject(JSContext* aCx, JS::HandleValue aData, jni::Object::LocalRef& aOut);
 | 
						|
 | 
						|
template<typename Type,
 | 
						|
         bool (JS::Value::*IsType)() const,
 | 
						|
         Type (JS::Value::*ToType)() const,
 | 
						|
         class ArrayType,
 | 
						|
         typename ArrayType::LocalRef (*NewArray)(const Type*, size_t)> nsresult
 | 
						|
BoxArrayPrimitive(JSContext* aCx, JS::HandleObject aData,
 | 
						|
                  jni::Object::LocalRef& aOut, size_t aLength,
 | 
						|
                  JS::HandleValue aElement)
 | 
						|
{
 | 
						|
    JS::RootedValue element(aCx);
 | 
						|
    auto data = MakeUnique<Type[]>(aLength);
 | 
						|
    data[0] = (aElement.get().*ToType)();
 | 
						|
 | 
						|
    for (size_t i = 1; i < aLength; i++) {
 | 
						|
        NS_ENSURE_TRUE(CheckJS(aCx, JS_GetElement(aCx, aData, i, &element)),
 | 
						|
                       NS_ERROR_FAILURE);
 | 
						|
        NS_ENSURE_TRUE((element.get().*IsType)(), NS_ERROR_INVALID_ARG);
 | 
						|
 | 
						|
        data[i] = (element.get().*ToType)();
 | 
						|
    }
 | 
						|
    aOut = (*NewArray)(data.get(), aLength);
 | 
						|
    return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
template<class Type,
 | 
						|
         nsresult (*Box)(JSContext*, JS::HandleValue, jni::Object::LocalRef&),
 | 
						|
         typename IsType>
 | 
						|
nsresult
 | 
						|
BoxArrayObject(JSContext* aCx, JS::HandleObject aData,
 | 
						|
               jni::Object::LocalRef& aOut, size_t aLength,
 | 
						|
               JS::HandleValue aElement,
 | 
						|
               IsType&& aIsType)
 | 
						|
{
 | 
						|
    auto out = jni::ObjectArray::New<Type>(aLength);
 | 
						|
    JS::RootedValue element(aCx);
 | 
						|
    jni::Object::LocalRef jniElement(aOut.Env());
 | 
						|
 | 
						|
    nsresult rv = (*Box)(aCx, aElement, jniElement);
 | 
						|
    NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
    out->SetElement(0, jniElement);
 | 
						|
 | 
						|
    for (size_t i = 1; i < aLength; i++) {
 | 
						|
        NS_ENSURE_TRUE(CheckJS(aCx, JS_GetElement(aCx, aData, i, &element)),
 | 
						|
                       NS_ERROR_FAILURE);
 | 
						|
        NS_ENSURE_TRUE(element.isNullOrUndefined() || aIsType(element),
 | 
						|
                       NS_ERROR_INVALID_ARG);
 | 
						|
 | 
						|
        rv = (*Box)(aCx, element, jniElement);
 | 
						|
        NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
        out->SetElement(i, jniElement);
 | 
						|
    }
 | 
						|
    aOut = out;
 | 
						|
    return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
nsresult
 | 
						|
BoxArray(JSContext* aCx, JS::HandleObject aData, jni::Object::LocalRef& aOut)
 | 
						|
{
 | 
						|
    uint32_t length = 0;
 | 
						|
    NS_ENSURE_TRUE(CheckJS(aCx, JS_GetArrayLength(aCx, aData, &length)),
 | 
						|
                   NS_ERROR_FAILURE);
 | 
						|
 | 
						|
    if (!length) {
 | 
						|
        // Always represent empty arrays as an empty boolean array.
 | 
						|
        aOut = java::GeckoBundle::EMPTY_BOOLEAN_ARRAY();
 | 
						|
        return NS_OK;
 | 
						|
    }
 | 
						|
 | 
						|
    // We only check the first element's type. If the array has mixed types,
 | 
						|
    // we'll throw an error during actual conversion.
 | 
						|
    JS::RootedValue element(aCx);
 | 
						|
    NS_ENSURE_TRUE(CheckJS(aCx, JS_GetElement(aCx, aData, 0, &element)),
 | 
						|
                   NS_ERROR_FAILURE);
 | 
						|
 | 
						|
    if (element.isBoolean()) {
 | 
						|
        return BoxArrayPrimitive<bool, &JS::Value::isBoolean, &JS::Value::toBoolean,
 | 
						|
                                 jni::BooleanArray, &jni::BooleanArray::New>(
 | 
						|
                aCx, aData, aOut, length, element);
 | 
						|
    }
 | 
						|
 | 
						|
    if (element.isInt32()) {
 | 
						|
        nsresult rv = BoxArrayPrimitive<
 | 
						|
                int32_t, &JS::Value::isInt32, &JS::Value::toInt32,
 | 
						|
                jni::IntArray, &jni::IntArray::New>(aCx, aData, aOut,
 | 
						|
                                                    length, element);
 | 
						|
        if (rv != NS_ERROR_INVALID_ARG) {
 | 
						|
            return rv;
 | 
						|
        }
 | 
						|
        // Not int32, but we can still try a double array.
 | 
						|
    }
 | 
						|
 | 
						|
    if (element.isNumber()) {
 | 
						|
        return BoxArrayPrimitive<
 | 
						|
                double, &JS::Value::isNumber, &JS::Value::toNumber,
 | 
						|
                jni::DoubleArray, &jni::DoubleArray::New>(aCx, aData, aOut,
 | 
						|
                                                          length, element);
 | 
						|
    }
 | 
						|
 | 
						|
    if (element.isNullOrUndefined() || element.isString()) {
 | 
						|
        const auto isString = [] (JS::HandleValue val) -> bool {
 | 
						|
            return val.isString();
 | 
						|
        };
 | 
						|
        nsresult rv = BoxArrayObject<jni::String, &BoxString>(
 | 
						|
                aCx, aData, aOut, length, element, isString);
 | 
						|
        if (element.isString() || rv != NS_ERROR_INVALID_ARG) {
 | 
						|
            return rv;
 | 
						|
        }
 | 
						|
        // First element was null/undefined, so it may still be an object array.
 | 
						|
    }
 | 
						|
 | 
						|
    const auto isObject = [aCx] (JS::HandleValue val) -> bool {
 | 
						|
        if (!val.isObject()) {
 | 
						|
            return false;
 | 
						|
        }
 | 
						|
        bool array = false;
 | 
						|
        JS::RootedObject obj(aCx, &val.toObject());
 | 
						|
        // We don't support array of arrays.
 | 
						|
        return CheckJS(aCx, JS_IsArrayObject(aCx, obj, &array)) && !array;
 | 
						|
    };
 | 
						|
 | 
						|
    if (element.isNullOrUndefined() || isObject(element)) {
 | 
						|
        return BoxArrayObject<java::GeckoBundle, &BoxObject>(
 | 
						|
                aCx, aData, aOut, length, element, isObject);
 | 
						|
    }
 | 
						|
 | 
						|
    NS_WARNING("Unknown type");
 | 
						|
    return NS_ERROR_INVALID_ARG;
 | 
						|
}
 | 
						|
 | 
						|
nsresult
 | 
						|
BoxValue(JSContext* aCx, JS::HandleValue aData, jni::Object::LocalRef& aOut);
 | 
						|
 | 
						|
nsresult
 | 
						|
BoxObject(JSContext* aCx, JS::HandleValue aData, jni::Object::LocalRef& aOut)
 | 
						|
{
 | 
						|
    if (aData.isNullOrUndefined()) {
 | 
						|
        aOut = nullptr;
 | 
						|
        return NS_OK;
 | 
						|
    }
 | 
						|
 | 
						|
    MOZ_ASSERT(aData.isObject());
 | 
						|
 | 
						|
    JS::Rooted<JS::IdVector> ids(aCx, JS::IdVector(aCx));
 | 
						|
    JS::RootedObject obj(aCx, &aData.toObject());
 | 
						|
 | 
						|
    bool isArray = false;
 | 
						|
    if (CheckJS(aCx, JS_IsArrayObject(aCx, obj, &isArray)) && isArray) {
 | 
						|
        return BoxArray(aCx, obj, aOut);
 | 
						|
    }
 | 
						|
 | 
						|
    NS_ENSURE_TRUE(CheckJS(aCx, JS_Enumerate(aCx, obj, &ids)),
 | 
						|
                   NS_ERROR_FAILURE);
 | 
						|
 | 
						|
    const size_t length = ids.length();
 | 
						|
    auto keys = jni::ObjectArray::New<jni::String>(length);
 | 
						|
    auto values = jni::ObjectArray::New<jni::Object>(length);
 | 
						|
 | 
						|
    // Iterate through each property of the JS object.
 | 
						|
    for (size_t i = 0; i < ids.length(); i++) {
 | 
						|
        const JS::RootedId id(aCx, ids[i]);
 | 
						|
        JS::RootedValue idVal(aCx);
 | 
						|
        JS::RootedValue val(aCx);
 | 
						|
        jni::Object::LocalRef key(aOut.Env());
 | 
						|
        jni::Object::LocalRef value(aOut.Env());
 | 
						|
 | 
						|
        NS_ENSURE_TRUE(CheckJS(aCx, JS_IdToValue(aCx, id, &idVal)),
 | 
						|
                       NS_ERROR_FAILURE);
 | 
						|
 | 
						|
        JS::RootedString idStr(aCx, JS::ToString(aCx, idVal));
 | 
						|
        NS_ENSURE_TRUE(CheckJS(aCx, !!idStr), NS_ERROR_FAILURE);
 | 
						|
 | 
						|
        idVal.setString(idStr);
 | 
						|
        NS_ENSURE_SUCCESS(BoxString(aCx, idVal, key), NS_ERROR_FAILURE);
 | 
						|
        NS_ENSURE_TRUE(CheckJS(aCx, JS_GetPropertyById(aCx, obj, id, &val)),
 | 
						|
                       NS_ERROR_FAILURE);
 | 
						|
 | 
						|
        nsresult rv = BoxValue(aCx, val, value);
 | 
						|
        if (rv == NS_ERROR_INVALID_ARG && !JS_IsExceptionPending(aCx)) {
 | 
						|
            nsAutoJSString autoStr;
 | 
						|
            if (CheckJS(aCx, autoStr.init(aCx, idVal.toString()))) {
 | 
						|
                JS_ReportErrorUTF8(
 | 
						|
                        aCx, u8"Invalid event data property %s",
 | 
						|
                        NS_ConvertUTF16toUTF8(autoStr).get());
 | 
						|
            }
 | 
						|
        }
 | 
						|
        NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
 | 
						|
        keys->SetElement(i, key);
 | 
						|
        values->SetElement(i, value);
 | 
						|
    }
 | 
						|
 | 
						|
    aOut = java::GeckoBundle::New(keys, values);
 | 
						|
    return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
nsresult
 | 
						|
BoxValue(JSContext* aCx, JS::HandleValue aData, jni::Object::LocalRef& aOut)
 | 
						|
{
 | 
						|
    if (aData.isNullOrUndefined()) {
 | 
						|
        aOut = nullptr;
 | 
						|
    } else if (aData.isBoolean()) {
 | 
						|
        aOut = java::GeckoBundle::Box(aData.toBoolean());
 | 
						|
    } else if (aData.isInt32()) {
 | 
						|
        aOut = java::GeckoBundle::Box(aData.toInt32());
 | 
						|
    } else if (aData.isNumber()) {
 | 
						|
        aOut = java::GeckoBundle::Box(aData.toNumber());
 | 
						|
    } else if (aData.isString()) {
 | 
						|
        return BoxString(aCx, aData, aOut);
 | 
						|
    } else if (aData.isObject()) {
 | 
						|
        return BoxObject(aCx, aData, aOut);
 | 
						|
    } else {
 | 
						|
        NS_WARNING("Unknown type");
 | 
						|
        return NS_ERROR_INVALID_ARG;
 | 
						|
    }
 | 
						|
    return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
nsresult
 | 
						|
BoxData(const nsAString& aEvent, JSContext* aCx, JS::HandleValue aData,
 | 
						|
        jni::Object::LocalRef& aOut, bool aObjectOnly)
 | 
						|
{
 | 
						|
    nsresult rv = NS_ERROR_INVALID_ARG;
 | 
						|
 | 
						|
    if (!aObjectOnly) {
 | 
						|
        rv = BoxValue(aCx, aData, aOut);
 | 
						|
    } else if (aData.isObject() || aData.isNullOrUndefined()) {
 | 
						|
        rv = BoxObject(aCx, aData, aOut);
 | 
						|
    }
 | 
						|
    if (rv != NS_ERROR_INVALID_ARG) {
 | 
						|
        return rv;
 | 
						|
    }
 | 
						|
 | 
						|
    NS_ConvertUTF16toUTF8 event(aEvent);
 | 
						|
    if (JS_IsExceptionPending(aCx)) {
 | 
						|
        JS_ReportWarningUTF8(aCx, "Error dispatching %s", event.get());
 | 
						|
    } else {
 | 
						|
        JS_ReportErrorUTF8(aCx, "Invalid event data for %s", event.get());
 | 
						|
    }
 | 
						|
    return NS_ERROR_INVALID_ARG;
 | 
						|
}
 | 
						|
 | 
						|
nsresult
 | 
						|
UnboxString(JSContext* aCx, const jni::Object::LocalRef& aData,
 | 
						|
            JS::MutableHandleValue aOut)
 | 
						|
{
 | 
						|
    if (!aData) {
 | 
						|
        aOut.setNull();
 | 
						|
        return NS_OK;
 | 
						|
    }
 | 
						|
 | 
						|
    MOZ_ASSERT(aData.IsInstanceOf<jni::String>());
 | 
						|
 | 
						|
    JNIEnv* const env = aData.Env();
 | 
						|
    const jstring jstr = jstring(aData.Get());
 | 
						|
    const size_t len = env->GetStringLength(jstr);
 | 
						|
    const jchar* const jchars = env->GetStringChars(jstr, nullptr);
 | 
						|
 | 
						|
    if (NS_WARN_IF(!jchars)) {
 | 
						|
        env->ExceptionClear();
 | 
						|
        return NS_ERROR_FAILURE;
 | 
						|
    }
 | 
						|
 | 
						|
    auto releaseStr = MakeScopeExit([env, jstr, jchars] {
 | 
						|
        env->ReleaseStringChars(jstr, jchars);
 | 
						|
        env->ExceptionClear();
 | 
						|
    });
 | 
						|
 | 
						|
    JS::RootedString str(aCx, JS_NewUCStringCopyN(
 | 
						|
            aCx, reinterpret_cast<const char16_t*>(jchars), len));
 | 
						|
    NS_ENSURE_TRUE(CheckJS(aCx, !!str), NS_ERROR_FAILURE);
 | 
						|
 | 
						|
    aOut.setString(str);
 | 
						|
    return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
nsresult
 | 
						|
UnboxValue(JSContext* aCx, const jni::Object::LocalRef& aData,
 | 
						|
           JS::MutableHandleValue aOut);
 | 
						|
 | 
						|
nsresult
 | 
						|
UnboxBundle(JSContext* aCx, const jni::Object::LocalRef& aData,
 | 
						|
            JS::MutableHandleValue aOut)
 | 
						|
{
 | 
						|
    if (!aData) {
 | 
						|
        aOut.setNull();
 | 
						|
        return NS_OK;
 | 
						|
    }
 | 
						|
 | 
						|
    MOZ_ASSERT(aData.IsInstanceOf<java::GeckoBundle>());
 | 
						|
 | 
						|
    JNIEnv* const env = aData.Env();
 | 
						|
    const auto& bundle = java::GeckoBundle::Ref::From(aData);
 | 
						|
    jni::ObjectArray::LocalRef keys = bundle->Keys();
 | 
						|
    jni::ObjectArray::LocalRef values = bundle->Values();
 | 
						|
    const size_t len = keys->Length();
 | 
						|
    JS::RootedObject obj(aCx, JS_NewPlainObject(aCx));
 | 
						|
 | 
						|
    NS_ENSURE_TRUE(CheckJS(aCx, !!obj), NS_ERROR_FAILURE);
 | 
						|
    NS_ENSURE_TRUE(values->Length() == len, NS_ERROR_FAILURE);
 | 
						|
 | 
						|
    for (size_t i = 0; i < len; i++) {
 | 
						|
        jni::String::LocalRef key = keys->GetElement(i);
 | 
						|
        const size_t keyLen = env->GetStringLength(key.Get());
 | 
						|
        const jchar* const keyChars = env->GetStringChars(key.Get(), nullptr);
 | 
						|
        if (NS_WARN_IF(!keyChars)) {
 | 
						|
            env->ExceptionClear();
 | 
						|
            return NS_ERROR_FAILURE;
 | 
						|
        }
 | 
						|
 | 
						|
        auto releaseKeyChars = MakeScopeExit([env, &key, keyChars] {
 | 
						|
            env->ReleaseStringChars(key.Get(), keyChars);
 | 
						|
            env->ExceptionClear();
 | 
						|
        });
 | 
						|
 | 
						|
        JS::RootedValue value(aCx);
 | 
						|
        nsresult rv = UnboxValue(aCx, values->GetElement(i), &value);
 | 
						|
        if (rv == NS_ERROR_INVALID_ARG && !JS_IsExceptionPending(aCx)) {
 | 
						|
            JS_ReportErrorUTF8(
 | 
						|
                    aCx, u8"Invalid event data property %s",
 | 
						|
                    NS_ConvertUTF16toUTF8(nsString(reinterpret_cast<
 | 
						|
                            const char16_t*>(keyChars), keyLen)).get());
 | 
						|
        }
 | 
						|
        NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
 | 
						|
        NS_ENSURE_TRUE(CheckJS(aCx, JS_SetUCProperty(aCx, obj, reinterpret_cast<
 | 
						|
                               const char16_t*>(keyChars), keyLen, value)),
 | 
						|
                       NS_ERROR_FAILURE);
 | 
						|
    }
 | 
						|
 | 
						|
    aOut.setObject(*obj);
 | 
						|
    return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
template<typename Type, typename JNIType, typename ArrayType,
 | 
						|
         JNIType* (JNIEnv::*GetElements)(ArrayType, jboolean*),
 | 
						|
         void (JNIEnv::*ReleaseElements)(ArrayType, JNIType*, jint),
 | 
						|
         JS::Value (*ToValue)(Type)> nsresult
 | 
						|
UnboxArrayPrimitive(JSContext* aCx, const jni::Object::LocalRef& aData,
 | 
						|
                    JS::MutableHandleValue aOut)
 | 
						|
{
 | 
						|
    JNIEnv* const env = aData.Env();
 | 
						|
    const ArrayType jarray = ArrayType(aData.Get());
 | 
						|
    JNIType* const array = (env->*GetElements)(jarray, nullptr);
 | 
						|
    JS::AutoValueVector elements(aCx);
 | 
						|
 | 
						|
    if (NS_WARN_IF(!array)) {
 | 
						|
        env->ExceptionClear();
 | 
						|
        return NS_ERROR_FAILURE;
 | 
						|
    }
 | 
						|
 | 
						|
    auto releaseArray = MakeScopeExit([env, jarray, array] {
 | 
						|
        (env->*ReleaseElements)(jarray, array, JNI_ABORT);
 | 
						|
        env->ExceptionClear();
 | 
						|
    });
 | 
						|
 | 
						|
    const size_t len = env->GetArrayLength(jarray);
 | 
						|
    NS_ENSURE_TRUE(elements.initCapacity(len), NS_ERROR_FAILURE);
 | 
						|
 | 
						|
    for (size_t i = 0; i < len; i++) {
 | 
						|
        NS_ENSURE_TRUE(elements.append((*ToValue)(Type(array[i]))),
 | 
						|
                       NS_ERROR_FAILURE);
 | 
						|
    }
 | 
						|
 | 
						|
    JS::RootedObject obj(aCx, JS_NewArrayObject(
 | 
						|
            aCx, JS::HandleValueArray(elements)));
 | 
						|
    NS_ENSURE_TRUE(CheckJS(aCx, !!obj), NS_ERROR_FAILURE);
 | 
						|
 | 
						|
    aOut.setObject(*obj);
 | 
						|
    return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
struct StringArray : jni::ObjectBase<StringArray>
 | 
						|
{
 | 
						|
    static const char name[];
 | 
						|
};
 | 
						|
 | 
						|
struct GeckoBundleArray : jni::ObjectBase<GeckoBundleArray>
 | 
						|
{
 | 
						|
    static const char name[];
 | 
						|
};
 | 
						|
 | 
						|
const char StringArray::name[] = "[Ljava/lang/String;";
 | 
						|
const char GeckoBundleArray::name[] = "[Lorg/mozilla/gecko/util/GeckoBundle;";
 | 
						|
 | 
						|
template<nsresult (*Unbox)(JSContext*, const jni::Object::LocalRef&,
 | 
						|
                           JS::MutableHandleValue)> nsresult
 | 
						|
UnboxArrayObject(JSContext* aCx, const jni::Object::LocalRef& aData,
 | 
						|
                 JS::MutableHandleValue aOut)
 | 
						|
{
 | 
						|
    jni::ObjectArray::LocalRef array(
 | 
						|
            aData.Env(), jni::ObjectArray::Ref::From(aData));
 | 
						|
    const size_t len = array->Length();
 | 
						|
    JS::RootedObject obj(aCx, JS_NewArrayObject(aCx, len));
 | 
						|
    NS_ENSURE_TRUE(CheckJS(aCx, !!obj), NS_ERROR_FAILURE);
 | 
						|
 | 
						|
    for (size_t i = 0; i < len; i++) {
 | 
						|
        jni::Object::LocalRef element = array->GetElement(i);
 | 
						|
        JS::RootedValue value(aCx);
 | 
						|
        nsresult rv = (*Unbox)(aCx, element, &value);
 | 
						|
        NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
 | 
						|
        NS_ENSURE_TRUE(CheckJS(aCx, JS_SetElement(aCx, obj, i, value)),
 | 
						|
                       NS_ERROR_FAILURE);
 | 
						|
    }
 | 
						|
 | 
						|
    aOut.setObject(*obj);
 | 
						|
    return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
nsresult
 | 
						|
UnboxValue(JSContext* aCx, const jni::Object::LocalRef& aData,
 | 
						|
           JS::MutableHandleValue aOut)
 | 
						|
{
 | 
						|
    if (!aData) {
 | 
						|
        aOut.setNull();
 | 
						|
    } else if (aData.IsInstanceOf<jni::Boolean>()) {
 | 
						|
        aOut.setBoolean(java::GeckoBundle::UnboxBoolean(aData));
 | 
						|
    } else if (aData.IsInstanceOf<jni::Integer>() ||
 | 
						|
            aData.IsInstanceOf<jni::Byte>() ||
 | 
						|
            aData.IsInstanceOf<jni::Short>()) {
 | 
						|
        aOut.setInt32(java::GeckoBundle::UnboxInteger(aData));
 | 
						|
    } else if (aData.IsInstanceOf<jni::Double>() ||
 | 
						|
            aData.IsInstanceOf<jni::Float>() ||
 | 
						|
            aData.IsInstanceOf<jni::Long>()) {
 | 
						|
        aOut.setNumber(java::GeckoBundle::UnboxDouble(aData));
 | 
						|
    } else if (aData.IsInstanceOf<jni::String>()) {
 | 
						|
        return UnboxString(aCx, aData, aOut);
 | 
						|
    } else if (aData.IsInstanceOf<jni::Character>()) {
 | 
						|
        return UnboxString(aCx, java::GeckoBundle::UnboxString(aData), aOut);
 | 
						|
    } else if (aData.IsInstanceOf<java::GeckoBundle>()) {
 | 
						|
        return UnboxBundle(aCx, aData, aOut);
 | 
						|
 | 
						|
    } else if (aData.IsInstanceOf<jni::BooleanArray>()) {
 | 
						|
        return UnboxArrayPrimitive<bool, jboolean, jbooleanArray,
 | 
						|
                &JNIEnv::GetBooleanArrayElements,
 | 
						|
                &JNIEnv::ReleaseBooleanArrayElements,
 | 
						|
                &JS::BooleanValue>(aCx, aData, aOut);
 | 
						|
 | 
						|
    } else if (aData.IsInstanceOf<jni::IntArray>()) {
 | 
						|
        return UnboxArrayPrimitive<int32_t, jint, jintArray,
 | 
						|
                &JNIEnv::GetIntArrayElements,
 | 
						|
                &JNIEnv::ReleaseIntArrayElements,
 | 
						|
                &JS::Int32Value>(aCx, aData, aOut);
 | 
						|
 | 
						|
    } else if (aData.IsInstanceOf<jni::DoubleArray>()) {
 | 
						|
        return UnboxArrayPrimitive<double, jdouble, jdoubleArray,
 | 
						|
                &JNIEnv::GetDoubleArrayElements,
 | 
						|
                &JNIEnv::ReleaseDoubleArrayElements,
 | 
						|
                &JS::DoubleValue>(aCx, aData, aOut);
 | 
						|
 | 
						|
    } else if (aData.IsInstanceOf<StringArray>()) {
 | 
						|
        return UnboxArrayObject<&UnboxString>(aCx, aData, aOut);
 | 
						|
    } else if (aData.IsInstanceOf<GeckoBundleArray>()) {
 | 
						|
        return UnboxArrayObject<&UnboxBundle>(aCx, aData, aOut);
 | 
						|
    } else {
 | 
						|
        NS_WARNING("Invalid type");
 | 
						|
        return NS_ERROR_INVALID_ARG;
 | 
						|
    }
 | 
						|
    return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
nsresult
 | 
						|
UnboxData(jni::String::Param aEvent, JSContext* aCx, jni::Object::Param aData,
 | 
						|
          JS::MutableHandleValue aOut, bool aBundleOnly)
 | 
						|
{
 | 
						|
    MOZ_ASSERT(NS_IsMainThread());
 | 
						|
 | 
						|
    jni::Object::LocalRef jniData(jni::GetGeckoThreadEnv(), aData);
 | 
						|
    nsresult rv = NS_ERROR_INVALID_ARG;
 | 
						|
 | 
						|
    if (!aBundleOnly) {
 | 
						|
        rv = UnboxValue(aCx, jniData, aOut);
 | 
						|
    } else if (!jniData || jniData.IsInstanceOf<java::GeckoBundle>()) {
 | 
						|
        rv = UnboxBundle(aCx, jniData, aOut);
 | 
						|
    }
 | 
						|
    if (rv != NS_ERROR_INVALID_ARG || !aEvent) {
 | 
						|
        return rv;
 | 
						|
    }
 | 
						|
 | 
						|
    nsCString event = aEvent->ToCString();
 | 
						|
    if (JS_IsExceptionPending(aCx)) {
 | 
						|
        JS_ReportWarningUTF8(aCx, "Error dispatching %s", event.get());
 | 
						|
    } else {
 | 
						|
        JS_ReportErrorUTF8(aCx, "Invalid event data for %s", event.get());
 | 
						|
    }
 | 
						|
    return NS_ERROR_INVALID_ARG;
 | 
						|
}
 | 
						|
 | 
						|
class JavaCallbackDelegate final : public nsIAndroidEventCallback
 | 
						|
{
 | 
						|
    java::EventCallback::GlobalRef mCallback;
 | 
						|
 | 
						|
    virtual ~JavaCallbackDelegate() {}
 | 
						|
 | 
						|
    NS_IMETHOD Call(JS::HandleValue aData,
 | 
						|
                    void (java::EventCallback::*aCall)(
 | 
						|
                            jni::Object::Param) const)
 | 
						|
    {
 | 
						|
        MOZ_ASSERT(NS_IsMainThread());
 | 
						|
        AutoJSContext cx;
 | 
						|
 | 
						|
        jni::Object::LocalRef data(jni::GetGeckoThreadEnv());
 | 
						|
        nsresult rv = BoxData(NS_LITERAL_STRING("callback"), cx, aData, data,
 | 
						|
                              /* ObjectOnly */ false);
 | 
						|
        NS_ENSURE_SUCCESS(rv, JS_IsExceptionPending(cx) ? NS_OK : rv);
 | 
						|
 | 
						|
        dom::AutoNoJSAPI nojsapi;
 | 
						|
        (java::EventCallback(*mCallback).*aCall)(data);
 | 
						|
        return NS_OK;
 | 
						|
    }
 | 
						|
 | 
						|
public:
 | 
						|
    JavaCallbackDelegate(java::EventCallback::Param aCallback)
 | 
						|
        : mCallback(jni::GetGeckoThreadEnv(), aCallback)
 | 
						|
    {}
 | 
						|
 | 
						|
    NS_DECL_ISUPPORTS
 | 
						|
 | 
						|
    NS_IMETHOD OnSuccess(JS::HandleValue aData) override
 | 
						|
    {
 | 
						|
        return Call(aData, &java::EventCallback::SendSuccess);
 | 
						|
    }
 | 
						|
 | 
						|
    NS_IMETHOD OnError(JS::HandleValue aData) override
 | 
						|
    {
 | 
						|
        return Call(aData, &java::EventCallback::SendError);
 | 
						|
    }
 | 
						|
};
 | 
						|
 | 
						|
NS_IMPL_ISUPPORTS(JavaCallbackDelegate, nsIAndroidEventCallback)
 | 
						|
 | 
						|
class NativeCallbackDelegateSupport final :
 | 
						|
    public java::EventDispatcher::NativeCallbackDelegate
 | 
						|
            ::Natives<NativeCallbackDelegateSupport>
 | 
						|
{
 | 
						|
    using CallbackDelegate = java::EventDispatcher::NativeCallbackDelegate;
 | 
						|
    using Base = CallbackDelegate::Natives<NativeCallbackDelegateSupport>;
 | 
						|
 | 
						|
    const nsCOMPtr<nsIAndroidEventCallback> mCallback;
 | 
						|
    const nsCOMPtr<nsPIDOMWindowOuter> mWindow;
 | 
						|
 | 
						|
    void Call(jni::Object::Param aData,
 | 
						|
              nsresult (nsIAndroidEventCallback::*aCall)(JS::HandleValue))
 | 
						|
    {
 | 
						|
        MOZ_ASSERT(NS_IsMainThread());
 | 
						|
 | 
						|
        // Use the same compartment as the wrapped JS object if possible,
 | 
						|
        // otherwise use either the attached window's compartment or a default
 | 
						|
        // compartment.
 | 
						|
        nsCOMPtr<nsIXPConnectWrappedJS> wrappedJS(do_QueryInterface(mCallback));
 | 
						|
 | 
						|
        dom::AutoJSAPI jsapi;
 | 
						|
        if (!wrappedJS && mWindow) {
 | 
						|
            NS_ENSURE_TRUE_VOID(jsapi.Init(mWindow->GetCurrentInnerWindow()));
 | 
						|
        } else {
 | 
						|
            NS_ENSURE_TRUE_VOID(jsapi.Init(wrappedJS ?
 | 
						|
                    wrappedJS->GetJSObject() : xpc::PrivilegedJunkScope()));
 | 
						|
        }
 | 
						|
 | 
						|
        JS::RootedValue data(jsapi.cx());
 | 
						|
        nsresult rv = UnboxData(NS_LITERAL_STRING("callback"), jsapi.cx(),
 | 
						|
                                aData, &data, /* BundleOnly */ false);
 | 
						|
        NS_ENSURE_SUCCESS_VOID(rv);
 | 
						|
 | 
						|
        dom::AutoNoJSAPI nojsapi;
 | 
						|
        rv = (mCallback->*aCall)(data);
 | 
						|
        NS_ENSURE_SUCCESS_VOID(rv);
 | 
						|
    }
 | 
						|
 | 
						|
public:
 | 
						|
    using Base::AttachNative;
 | 
						|
 | 
						|
    template<typename Functor>
 | 
						|
    static void OnNativeCall(Functor&& aCall)
 | 
						|
    {
 | 
						|
        if (NS_IsMainThread()) {
 | 
						|
            // Invoke callbacks synchronously if we're already on Gecko thread.
 | 
						|
            return aCall();
 | 
						|
        }
 | 
						|
        NS_DispatchToMainThread(NS_NewRunnableFunction("OnNativeCall", Move(aCall)));
 | 
						|
    }
 | 
						|
 | 
						|
    static void Finalize(const CallbackDelegate::LocalRef& aInstance)
 | 
						|
    {
 | 
						|
        DisposeNative(aInstance);
 | 
						|
    }
 | 
						|
 | 
						|
    NativeCallbackDelegateSupport(nsIAndroidEventCallback* callback,
 | 
						|
                                  nsPIDOMWindowOuter* domWindow)
 | 
						|
        : mCallback(callback)
 | 
						|
        , mWindow(domWindow)
 | 
						|
    {}
 | 
						|
 | 
						|
    void SendSuccess(jni::Object::Param aData)
 | 
						|
    {
 | 
						|
        Call(aData, &nsIAndroidEventCallback::OnSuccess);
 | 
						|
    }
 | 
						|
 | 
						|
    void SendError(jni::Object::Param aData)
 | 
						|
    {
 | 
						|
        Call(aData, &nsIAndroidEventCallback::OnError);
 | 
						|
    }
 | 
						|
};
 | 
						|
 | 
						|
} // namespace detail
 | 
						|
 | 
						|
using namespace detail;
 | 
						|
 | 
						|
NS_IMPL_ISUPPORTS(EventDispatcher, nsIAndroidEventDispatcher)
 | 
						|
 | 
						|
nsresult
 | 
						|
EventDispatcher::DispatchOnGecko(ListenersList* list, const nsAString& aEvent,
 | 
						|
                                 JS::HandleValue aData,
 | 
						|
                                 nsIAndroidEventCallback* aCallback)
 | 
						|
{
 | 
						|
    MOZ_ASSERT(NS_IsMainThread());
 | 
						|
    dom::AutoNoJSAPI nojsapi;
 | 
						|
 | 
						|
    list->lockCount++;
 | 
						|
 | 
						|
    auto iteratingScope = MakeScopeExit([list] {
 | 
						|
        list->lockCount--;
 | 
						|
        if (list->lockCount || !list->unregistering) {
 | 
						|
            return;
 | 
						|
        }
 | 
						|
 | 
						|
        list->unregistering = false;
 | 
						|
        for (ssize_t i = list->listeners.Count() - 1; i >= 0; i--) {
 | 
						|
            if (list->listeners[i]) {
 | 
						|
                continue;
 | 
						|
            }
 | 
						|
            list->listeners.RemoveObjectAt(i);
 | 
						|
        }
 | 
						|
    });
 | 
						|
 | 
						|
    const size_t count = list->listeners.Count();
 | 
						|
    for (size_t i = 0; i < count; i++) {
 | 
						|
        if (!list->listeners[i]) {
 | 
						|
            // Unregistered.
 | 
						|
            continue;
 | 
						|
        }
 | 
						|
        const nsresult rv = list->listeners[i]->OnEvent(
 | 
						|
                aEvent, aData, aCallback);
 | 
						|
        Unused << NS_WARN_IF(NS_FAILED(rv));
 | 
						|
    }
 | 
						|
    return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
java::EventDispatcher::NativeCallbackDelegate::LocalRef
 | 
						|
EventDispatcher::WrapCallback(nsIAndroidEventCallback* aCallback)
 | 
						|
{
 | 
						|
    java::EventDispatcher::NativeCallbackDelegate::LocalRef
 | 
						|
            callback(jni::GetGeckoThreadEnv());
 | 
						|
 | 
						|
    if (aCallback) {
 | 
						|
        callback = java::EventDispatcher::NativeCallbackDelegate::New();
 | 
						|
        NativeCallbackDelegateSupport::AttachNative(
 | 
						|
                callback,
 | 
						|
                MakeUnique<NativeCallbackDelegateSupport>(
 | 
						|
                        aCallback, mDOMWindow));
 | 
						|
    }
 | 
						|
    return callback;
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
EventDispatcher::Dispatch(JS::HandleValue aEvent, JS::HandleValue aData,
 | 
						|
                          nsIAndroidEventCallback* aCallback, JSContext* aCx)
 | 
						|
{
 | 
						|
    MOZ_ASSERT(NS_IsMainThread());
 | 
						|
 | 
						|
    if (!aEvent.isString()) {
 | 
						|
        NS_WARNING("Invalid event name");
 | 
						|
        return NS_ERROR_INVALID_ARG;
 | 
						|
    }
 | 
						|
 | 
						|
    nsAutoJSString event;
 | 
						|
    NS_ENSURE_TRUE(CheckJS(aCx, event.init(aCx, aEvent.toString())),
 | 
						|
                   NS_ERROR_OUT_OF_MEMORY);
 | 
						|
 | 
						|
    // Don't need to lock here because we're on the main thread, and we can't
 | 
						|
    // race against Register/UnregisterListener.
 | 
						|
 | 
						|
    ListenersList* list = mListenersMap.Get(event);
 | 
						|
    if (list) {
 | 
						|
        return DispatchOnGecko(list, event, aData, aCallback);
 | 
						|
    }
 | 
						|
 | 
						|
    if (!mDispatcher) {
 | 
						|
        return NS_OK;
 | 
						|
    }
 | 
						|
 | 
						|
    jni::Object::LocalRef data(jni::GetGeckoThreadEnv());
 | 
						|
    nsresult rv = BoxData(event, aCx, aData, data, /* ObjectOnly */ true);
 | 
						|
    NS_ENSURE_SUCCESS(rv, JS_IsExceptionPending(aCx) ? NS_OK : rv);
 | 
						|
 | 
						|
    dom::AutoNoJSAPI nojsapi;
 | 
						|
    mDispatcher->DispatchToThreads(event, data, WrapCallback(aCallback));
 | 
						|
    return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
nsresult
 | 
						|
EventDispatcher::Dispatch(const char16_t* aEvent,
 | 
						|
                          java::GeckoBundle::Param aData,
 | 
						|
                          nsIAndroidEventCallback* aCallback)
 | 
						|
{
 | 
						|
    nsDependentString event(aEvent);
 | 
						|
 | 
						|
    ListenersList* list = mListenersMap.Get(event);
 | 
						|
    if (list) {
 | 
						|
        dom::AutoJSAPI jsapi;
 | 
						|
        JS::RootedValue data(jsapi.cx());
 | 
						|
        nsresult rv = UnboxData(/* Event */ nullptr, jsapi.cx(), aData, &data,
 | 
						|
                                /* BundleOnly */ true);
 | 
						|
        NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
        return DispatchOnGecko(list, event, data, aCallback);
 | 
						|
    }
 | 
						|
 | 
						|
    if (!mDispatcher) {
 | 
						|
        return NS_OK;
 | 
						|
    }
 | 
						|
 | 
						|
    mDispatcher->DispatchToThreads(event, aData, WrapCallback(aCallback));
 | 
						|
    return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
nsresult
 | 
						|
EventDispatcher::IterateEvents(JSContext* aCx, JS::HandleValue aEvents,
 | 
						|
                               IterateEventsCallback aCallback,
 | 
						|
                               nsIAndroidEventListener* aListener)
 | 
						|
{
 | 
						|
    MOZ_ASSERT(NS_IsMainThread());
 | 
						|
 | 
						|
    MutexAutoLock lock(mLock);
 | 
						|
 | 
						|
    auto processEvent = [this, aCx, aCallback, aListener]
 | 
						|
            (JS::HandleValue event) -> nsresult {
 | 
						|
        nsAutoJSString str;
 | 
						|
        NS_ENSURE_TRUE(CheckJS(aCx, str.init(aCx, event.toString())),
 | 
						|
                       NS_ERROR_OUT_OF_MEMORY);
 | 
						|
        return (this->*aCallback)(str, aListener);
 | 
						|
    };
 | 
						|
 | 
						|
    if (aEvents.isString()) {
 | 
						|
        return processEvent(aEvents);
 | 
						|
    }
 | 
						|
 | 
						|
    bool isArray = false;
 | 
						|
    NS_ENSURE_TRUE(aEvents.isObject(), NS_ERROR_INVALID_ARG);
 | 
						|
    NS_ENSURE_TRUE(CheckJS(aCx, JS_IsArrayObject(aCx, aEvents, &isArray)),
 | 
						|
                   NS_ERROR_INVALID_ARG);
 | 
						|
    NS_ENSURE_TRUE(isArray, NS_ERROR_INVALID_ARG);
 | 
						|
 | 
						|
    JS::RootedObject events(aCx, &aEvents.toObject());
 | 
						|
    uint32_t length = 0;
 | 
						|
    NS_ENSURE_TRUE(CheckJS(aCx, JS_GetArrayLength(aCx, events, &length)),
 | 
						|
                   NS_ERROR_INVALID_ARG);
 | 
						|
    NS_ENSURE_TRUE(length, NS_ERROR_INVALID_ARG);
 | 
						|
 | 
						|
    for (size_t i = 0; i < length; i++) {
 | 
						|
        JS::RootedValue event(aCx);
 | 
						|
        NS_ENSURE_TRUE(CheckJS(aCx, JS_GetElement(aCx, events, i, &event)),
 | 
						|
                       NS_ERROR_INVALID_ARG);
 | 
						|
        NS_ENSURE_TRUE(event.isString(), NS_ERROR_INVALID_ARG);
 | 
						|
 | 
						|
        const nsresult rv = processEvent(event);
 | 
						|
        NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
    }
 | 
						|
    return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
nsresult
 | 
						|
EventDispatcher::RegisterEventLocked(const nsAString& aEvent,
 | 
						|
                                     nsIAndroidEventListener* aListener)
 | 
						|
{
 | 
						|
    ListenersList* list = mListenersMap.Get(aEvent);
 | 
						|
    if (!list) {
 | 
						|
        list = new ListenersList();
 | 
						|
        mListenersMap.Put(aEvent, list);
 | 
						|
    }
 | 
						|
 | 
						|
#ifdef DEBUG
 | 
						|
    for (ssize_t i = 0; i < list->listeners.Count(); i++) {
 | 
						|
        NS_ENSURE_TRUE(list->listeners[i] != aListener,
 | 
						|
                       NS_ERROR_ALREADY_INITIALIZED);
 | 
						|
    }
 | 
						|
#endif
 | 
						|
 | 
						|
    list->listeners.AppendObject(aListener);
 | 
						|
    return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
EventDispatcher::RegisterListener(nsIAndroidEventListener* aListener,
 | 
						|
                                  JS::HandleValue aEvents, JSContext *aCx)
 | 
						|
{
 | 
						|
    return IterateEvents(aCx, aEvents, &EventDispatcher::RegisterEventLocked,
 | 
						|
                         aListener);
 | 
						|
}
 | 
						|
 | 
						|
nsresult
 | 
						|
EventDispatcher::UnregisterEventLocked(const nsAString& aEvent,
 | 
						|
                                       nsIAndroidEventListener* aListener)
 | 
						|
{
 | 
						|
    ListenersList* list = mListenersMap.Get(aEvent);
 | 
						|
#ifdef DEBUG
 | 
						|
    NS_ENSURE_TRUE(list, NS_ERROR_NOT_INITIALIZED);
 | 
						|
#else
 | 
						|
    NS_ENSURE_TRUE(list, NS_OK);
 | 
						|
#endif
 | 
						|
 | 
						|
    DebugOnly<bool> found = false;
 | 
						|
    for (ssize_t i = list->listeners.Count() - 1; i >= 0; i--) {
 | 
						|
        if (list->listeners[i] != aListener) {
 | 
						|
            continue;
 | 
						|
        }
 | 
						|
        if (list->lockCount) {
 | 
						|
            // Only mark for removal when list is locked.
 | 
						|
            list->listeners.ReplaceObjectAt(nullptr, i);
 | 
						|
            list->unregistering = true;
 | 
						|
        } else {
 | 
						|
            list->listeners.RemoveObjectAt(i);
 | 
						|
        }
 | 
						|
        found = true;
 | 
						|
    }
 | 
						|
#ifdef DEBUG
 | 
						|
    return found ? NS_OK : NS_ERROR_NOT_INITIALIZED;
 | 
						|
#else
 | 
						|
    return NS_OK;
 | 
						|
#endif
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
EventDispatcher::UnregisterListener(nsIAndroidEventListener* aListener,
 | 
						|
                                    JS::HandleValue aEvents, JSContext *aCx)
 | 
						|
{
 | 
						|
    return IterateEvents(aCx, aEvents, &EventDispatcher::UnregisterEventLocked,
 | 
						|
                         aListener);
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
EventDispatcher::Attach(java::EventDispatcher::Param aDispatcher,
 | 
						|
                        nsPIDOMWindowOuter* aDOMWindow)
 | 
						|
{
 | 
						|
    MOZ_ASSERT(NS_IsMainThread());
 | 
						|
    MOZ_ASSERT(aDispatcher);
 | 
						|
 | 
						|
    if (mDispatcher) {
 | 
						|
        if (mDispatcher == aDispatcher) {
 | 
						|
            // Only need to update the window.
 | 
						|
            mDOMWindow = aDOMWindow;
 | 
						|
            return;
 | 
						|
        }
 | 
						|
        mDispatcher->SetAttachedToGecko(java::EventDispatcher::REATTACHING);
 | 
						|
    }
 | 
						|
 | 
						|
    java::EventDispatcher::LocalRef dispatcher(aDispatcher);
 | 
						|
 | 
						|
    NativesBase::AttachNative(dispatcher, this);
 | 
						|
    mDispatcher = dispatcher;
 | 
						|
    mDOMWindow = aDOMWindow;
 | 
						|
 | 
						|
    dispatcher->SetAttachedToGecko(java::EventDispatcher::ATTACHED);
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
EventDispatcher::Detach()
 | 
						|
{
 | 
						|
    MOZ_ASSERT(NS_IsMainThread());
 | 
						|
    MOZ_ASSERT(mDispatcher);
 | 
						|
 | 
						|
    // SetAttachedToGecko will call disposeNative for us. disposeNative will be
 | 
						|
    // called later on the Gecko thread to make sure all pending
 | 
						|
    // dispatchToGecko calls have completed.
 | 
						|
    mDispatcher->SetAttachedToGecko(java::EventDispatcher::DETACHED);
 | 
						|
    mDispatcher = nullptr;
 | 
						|
    mDOMWindow = nullptr;
 | 
						|
}
 | 
						|
 | 
						|
bool
 | 
						|
EventDispatcher::HasGeckoListener(jni::String::Param aEvent)
 | 
						|
{
 | 
						|
    // Can be called from any thread.
 | 
						|
    MutexAutoLock lock(mLock);
 | 
						|
    return !!mListenersMap.Get(aEvent->ToString());
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
EventDispatcher::DispatchToGecko(jni::String::Param aEvent,
 | 
						|
                                 jni::Object::Param aData,
 | 
						|
                                 jni::Object::Param aCallback)
 | 
						|
{
 | 
						|
    MOZ_ASSERT(NS_IsMainThread());
 | 
						|
 | 
						|
    // Don't need to lock here because we're on the main thread, and we can't
 | 
						|
    // race against Register/UnregisterListener.
 | 
						|
 | 
						|
    nsString event = aEvent->ToString();
 | 
						|
    ListenersList* list = mListenersMap.Get(event);
 | 
						|
    if (!list || list->listeners.IsEmpty()) {
 | 
						|
        return;
 | 
						|
    }
 | 
						|
 | 
						|
    // Use the same compartment as the attached window if possible, otherwise
 | 
						|
    // use a default compartment.
 | 
						|
    dom::AutoJSAPI jsapi;
 | 
						|
    if (mDOMWindow) {
 | 
						|
        NS_ENSURE_TRUE_VOID(jsapi.Init(mDOMWindow->GetCurrentInnerWindow()));
 | 
						|
    } else {
 | 
						|
        NS_ENSURE_TRUE_VOID(jsapi.Init(xpc::PrivilegedJunkScope()));
 | 
						|
    }
 | 
						|
 | 
						|
    JS::RootedValue data(jsapi.cx());
 | 
						|
    nsresult rv = UnboxData(aEvent, jsapi.cx(), aData, &data,
 | 
						|
                            /* BundleOnly */ true);
 | 
						|
    NS_ENSURE_SUCCESS_VOID(rv);
 | 
						|
 | 
						|
    nsCOMPtr<nsIAndroidEventCallback> callback;
 | 
						|
    if (aCallback) {
 | 
						|
        callback = new JavaCallbackDelegate(
 | 
						|
                java::EventCallback::Ref::From(aCallback));
 | 
						|
    }
 | 
						|
 | 
						|
    DispatchOnGecko(list, event, data, callback);
 | 
						|
}
 | 
						|
 | 
						|
/* static */
 | 
						|
nsresult
 | 
						|
EventDispatcher::UnboxBundle(JSContext* aCx, jni::Object::Param aData,
 | 
						|
                             JS::MutableHandleValue aOut)
 | 
						|
{
 | 
						|
    return detail::UnboxBundle(aCx, aData, aOut);
 | 
						|
}
 | 
						|
 | 
						|
} // namespace widget
 | 
						|
} // namespace mozilla
 |