Bug 1325156 - 2. Convert JavaAddonManager events to bundle events; r=sebastian

Convert events used in the two JavaAddonManager implementations to
GeckoBundle/BundleEventListener events. Gecko thread events are used to
keep with previous behavior. The external interface for addons is kept
the same (using Bundle/JSONObject as container objects), in order to
preserve compatibility, while the internal implementation is changed to
use GeckoBundle.
This commit is contained in:
Jim Chen 2017-01-10 23:00:57 -05:00
Родитель 34b69f4bac
Коммит 36a6807488
4 изменённых файлов: 133 добавлений и 151 удалений

Просмотреть файл

@ -14,7 +14,9 @@ import dalvik.system.DexClassLoader;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.EventDispatcher;
import org.mozilla.gecko.util.GeckoEventListener;
import org.mozilla.gecko.util.BundleEventListener;
import org.mozilla.gecko.util.EventCallback;
import org.mozilla.gecko.util.GeckoBundle;
import java.io.File;
import java.lang.reflect.Constructor;
@ -45,13 +47,13 @@ import java.util.Map;
* dispatcher, they can do so by inserting the response string into the bundle
* under the key "response".
*/
public class JavaAddonManager implements GeckoEventListener {
public class JavaAddonManager implements BundleEventListener {
private static final String LOGTAG = "GeckoJavaAddonManager";
private static JavaAddonManager sInstance;
private final EventDispatcher mDispatcher;
private final Map<String, Map<String, GeckoEventListener>> mAddonCallbacks;
private final Map<String, Map<String, BundleEventListener>> mAddonCallbacks;
private Context mApplicationContext;
@ -64,7 +66,7 @@ public class JavaAddonManager implements GeckoEventListener {
private JavaAddonManager() {
mDispatcher = EventDispatcher.getInstance();
mAddonCallbacks = new HashMap<String, Map<String, GeckoEventListener>>();
mAddonCallbacks = new HashMap<>();
}
public void init(Context applicationContext) {
@ -79,46 +81,51 @@ public class JavaAddonManager implements GeckoEventListener {
JavaAddonManagerV1.getInstance().init(applicationContext);
}
@Override
public void handleMessage(String event, JSONObject message) {
try {
if (event.equals("Dex:Load")) {
String zipFile = message.getString("zipfile");
String implClass = message.getString("impl");
Log.d(LOGTAG, "Attempting to load classes.dex file from " + zipFile + " and instantiate " + implClass);
@Override // BundleEventListener
public void handleMessage(final String event, final GeckoBundle message,
final EventCallback callback) {
if ("Dex:Load".equals(event)) {
final String zipFile = message.getString("zipfile");
final String implClass = message.getString("impl");
Log.d(LOGTAG, "Attempting to load classes.dex file from " + zipFile +
" and instantiate " + implClass);
try {
final File tmpDir = mApplicationContext.getDir("dex", 0);
final DexClassLoader loader = new DexClassLoader(
zipFile, tmpDir.getAbsolutePath(),
null, mApplicationContext.getClassLoader());
final Class<?> c = loader.loadClass(implClass);
try {
File tmpDir = mApplicationContext.getDir("dex", 0);
DexClassLoader loader = new DexClassLoader(zipFile, tmpDir.getAbsolutePath(), null, mApplicationContext.getClassLoader());
Class<?> c = loader.loadClass(implClass);
try {
Constructor<?> constructor = c.getDeclaredConstructor(Map.class);
Map<String, Handler.Callback> callbacks = new HashMap<String, Handler.Callback>();
constructor.newInstance(callbacks);
registerCallbacks(zipFile, callbacks);
} catch (NoSuchMethodException nsme) {
Log.d(LOGTAG, "Did not find constructor with parameters Map<String, Handler.Callback>. Falling back to default constructor...");
// fallback for instances with no constructor that takes a Map<String, Handler.Callback>
c.newInstance();
}
} catch (Exception e) {
Log.e(LOGTAG, "Unable to load dex successfully", e);
final Constructor<?> constructor = c.getDeclaredConstructor(Map.class);
final Map<String, Handler.Callback> callbacks =
new HashMap<String, Handler.Callback>();
constructor.newInstance(callbacks);
registerCallbacks(zipFile, callbacks);
} catch (final NoSuchMethodException nsme) {
Log.d(LOGTAG, "Did not find constructor with parameters " +
"Map<String, Handler.Callback>. Falling back " +
"to default constructor...");
// fallback for instances with no constructor that takes a Map<String,
// Handler.Callback>
c.newInstance();
}
} else if (event.equals("Dex:Unload")) {
String zipFile = message.getString("zipfile");
unregisterCallbacks(zipFile);
} catch (final Exception e) {
Log.e(LOGTAG, "Unable to load dex successfully", e);
}
} catch (JSONException e) {
Log.e(LOGTAG, "Exception handling message [" + event + "]:", e);
} else if ("Dex:Unload".equals(event)) {
final String zipFile = message.getString("zipfile");
unregisterCallbacks(zipFile);
}
}
private void registerCallbacks(String zipFile, Map<String, Handler.Callback> callbacks) {
Map<String, GeckoEventListener> addonCallbacks = mAddonCallbacks.get(zipFile);
Map<String, BundleEventListener> addonCallbacks = mAddonCallbacks.get(zipFile);
if (addonCallbacks != null) {
Log.w(LOGTAG, "Found pre-existing callbacks for zipfile [" + zipFile + "]; aborting re-registration!");
return;
}
addonCallbacks = new HashMap<String, GeckoEventListener>();
addonCallbacks = new HashMap<>();
for (String event : callbacks.keySet()) {
CallbackWrapper wrapper = new CallbackWrapper(callbacks.get(event));
mDispatcher.registerGeckoThreadListener(wrapper, event);
@ -128,9 +135,10 @@ public class JavaAddonManager implements GeckoEventListener {
}
private void unregisterCallbacks(String zipFile) {
Map<String, GeckoEventListener> callbacks = mAddonCallbacks.remove(zipFile);
Map<String, BundleEventListener> callbacks = mAddonCallbacks.remove(zipFile);
if (callbacks == null) {
Log.w(LOGTAG, "Attempting to unregister callbacks from zipfile [" + zipFile + "] which has no callbacks registered.");
Log.w(LOGTAG, "Attempting to unregister callbacks from zipfile [" + zipFile +
"] which has no callbacks registered.");
return;
}
for (String event : callbacks.keySet()) {
@ -138,58 +146,25 @@ public class JavaAddonManager implements GeckoEventListener {
}
}
private static class CallbackWrapper implements GeckoEventListener {
private static class CallbackWrapper implements BundleEventListener {
private final Handler.Callback mDelegate;
private Bundle mBundle;
CallbackWrapper(Handler.Callback delegate) {
mDelegate = delegate;
}
private Bundle jsonToBundle(JSONObject json) {
// XXX right now we only support primitive types;
// we don't recurse down into JSONArray or JSONObject instances
Bundle b = new Bundle();
for (Iterator<?> keys = json.keys(); keys.hasNext(); ) {
try {
String key = (String)keys.next();
Object value = json.get(key);
if (value instanceof Integer) {
b.putInt(key, (Integer)value);
} else if (value instanceof String) {
b.putString(key, (String)value);
} else if (value instanceof Boolean) {
b.putBoolean(key, (Boolean)value);
} else if (value instanceof Long) {
b.putLong(key, (Long)value);
} else if (value instanceof Double) {
b.putDouble(key, (Double)value);
}
} catch (JSONException e) {
Log.d(LOGTAG, "Error during JSON->bundle conversion", e);
}
}
return b;
}
@Override // BundleEventListener
public void handleMessage(final String event, final GeckoBundle message,
final EventCallback callback) {
final Message msg = new Message();
final Bundle data = message.toBundle();
data.putString("type", event);
msg.setData(data);
mDelegate.handleMessage(msg);
@Override
public void handleMessage(String event, JSONObject json) {
try {
if (mBundle != null) {
Log.w(LOGTAG, "Event [" + event + "] handler is re-entrant; response messages may be lost");
}
mBundle = jsonToBundle(json);
Message msg = new Message();
msg.setData(mBundle);
mDelegate.handleMessage(msg);
JSONObject obj = new JSONObject();
obj.put("response", mBundle.getString("response"));
EventDispatcher.sendResponse(json, obj);
mBundle = null;
} catch (Exception e) {
Log.e(LOGTAG, "Caught exception thrown from wrapped addon message handler", e);
}
final GeckoBundle response = new GeckoBundle(1);
response.putString("response", data.getString("response"));
callback.sendSuccess(response);
}
}
}

Просмотреть файл

@ -13,9 +13,11 @@ import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.sync.Utils;
import org.mozilla.gecko.util.BundleEventListener;
import org.mozilla.gecko.util.EventCallback;
import org.mozilla.gecko.util.GeckoBundle;
import org.mozilla.gecko.util.GeckoJarReader;
import org.mozilla.gecko.util.GeckoRequest;
import org.mozilla.gecko.util.NativeEventListener;
import org.mozilla.gecko.util.NativeJSObject;
import org.mozilla.javaaddons.JavaAddonInterfaceV1;
@ -27,7 +29,7 @@ import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Map;
public class JavaAddonManagerV1 implements NativeEventListener {
public class JavaAddonManagerV1 implements BundleEventListener {
private static final String LOGTAG = "GeckoJavaAddonMgrV1";
public static final String MESSAGE_LOAD = "JavaAddonManagerV1:Load";
public static final String MESSAGE_UNLOAD = "JavaAddonManagerV1:Unload";
@ -77,7 +79,7 @@ public class JavaAddonManagerV1 implements NativeEventListener {
protected synchronized EventDispatcherImpl registerNewInstance(String classname, String filename)
throws ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, IOException {
Log.d(LOGTAG, "Attempting to instantiate " + classname + "from filename " + filename);
Log.d(LOGTAG, "Attempting to instantiate " + classname + " from filename " + filename);
// It's important to maintain the extension, either .dex, .apk, .jar.
final String extension = getExtension(filename);
@ -103,41 +105,43 @@ public class JavaAddonManagerV1 implements NativeEventListener {
}
}
@Override
public synchronized void handleMessage(String event, NativeJSObject message, org.mozilla.gecko.util.EventCallback callback) {
try {
switch (event) {
case MESSAGE_LOAD: {
if (callback == null) {
throw new IllegalArgumentException("callback must not be null");
}
final String classname = message.getString("classname");
final String filename = message.getString("filename");
final EventDispatcherImpl dispatcher = registerNewInstance(classname, filename);
@Override // BundleEventListener
public synchronized void handleMessage(final String event, final GeckoBundle message,
final EventCallback callback) {
switch (event) {
case MESSAGE_LOAD: {
if (callback == null) {
throw new IllegalArgumentException("callback must not be null");
}
final String classname = message.getString("classname");
final String filename = message.getString("filename");
final EventDispatcherImpl dispatcher;
try {
dispatcher = registerNewInstance(classname, filename);
callback.sendSuccess(dispatcher.guid);
} catch (final Exception e) {
Log.e(LOGTAG, "Unable to load dex successfully", e);
callback.sendError(e.toString());
}
break;
case MESSAGE_UNLOAD: {
if (callback == null) {
throw new IllegalArgumentException("callback must not be null");
}
final String guid = message.getString("guid");
final EventDispatcherImpl dispatcher = mGUIDToDispatcherMap.remove(guid);
if (dispatcher == null) {
Log.w(LOGTAG, "Attempting to unload addon with unknown associated dispatcher; ignoring.");
callback.sendSuccess(false);
} else {
dispatcher.unregisterAllEventListeners();
callback.sendSuccess(true);
}
}
break;
case MESSAGE_UNLOAD: {
if (callback == null) {
throw new IllegalArgumentException("callback must not be null");
}
final String guid = message.getString("guid");
final EventDispatcherImpl dispatcher = mGUIDToDispatcherMap.remove(guid);
if (dispatcher == null) {
Log.w(LOGTAG, "Attempting to unload addon with unknown " +
"associated dispatcher; ignoring.");
callback.sendSuccess(false);
} else {
dispatcher.unregisterAllEventListeners();
callback.sendSuccess(true);
}
break;
}
} catch (Exception e) {
Log.e(LOGTAG, "Exception handling message [" + event + "]", e);
if (callback != null) {
callback.sendError("Exception handling message [" + event + "]: " + e.toString());
}
break;
}
}
@ -155,60 +159,62 @@ public class JavaAddonManagerV1 implements NativeEventListener {
private final String dexFileName;
// Protected by synchronized (this).
private final Map<JavaAddonInterfaceV1.EventListener, Pair<NativeEventListener, String[]>> mListenerToWrapperMap = new IdentityHashMap<>();
private final Map<JavaAddonInterfaceV1.EventListener, Pair<BundleEventListener, String[]>>
mListenerToWrapperMap = new IdentityHashMap<>();
public EventDispatcherImpl(String guid, String dexFileName) {
this.guid = guid;
this.dexFileName = dexFileName;
}
protected class ListenerWrapper implements NativeEventListener {
protected class ListenerWrapper implements BundleEventListener {
private final JavaAddonInterfaceV1.EventListener listener;
public ListenerWrapper(JavaAddonInterfaceV1.EventListener listener) {
this.listener = listener;
}
@Override
public void handleMessage(String prefixedEvent, NativeJSObject message, final org.mozilla.gecko.util.EventCallback callback) {
@Override // BundleEventListener
public void handleMessage(final String prefixedEvent, final GeckoBundle message,
final EventCallback callback) {
if (!prefixedEvent.startsWith(guid + ":")) {
return;
}
final String event = prefixedEvent.substring(guid.length() + 1); // Skip "guid:".
try {
JavaAddonInterfaceV1.EventCallback callbackAdapter = null;
if (callback != null) {
callbackAdapter = new JavaAddonInterfaceV1.EventCallback() {
@Override
public void sendSuccess(Object response) {
callback.sendSuccess(response);
}
JavaAddonInterfaceV1.EventCallback callbackAdapter = null;
if (callback != null) {
callbackAdapter = new JavaAddonInterfaceV1.EventCallback() {
@Override
public void sendSuccess(Object response) {
callback.sendSuccess(response);
}
@Override
public void sendError(Object response) {
callback.sendError(response);
}
};
}
final JSONObject json = new JSONObject(message.toString());
listener.handleMessage(mApplicationContext, event, json, callbackAdapter);
} catch (Exception e) {
Log.e(LOGTAG, "Exception handling message [" + prefixedEvent + "]", e);
if (callback != null) {
callback.sendError("Got exception handling message [" + prefixedEvent + "]: " + e.toString());
}
@Override
public void sendError(Object response) {
callback.sendError(response);
}
};
}
final JSONObject json;
try {
json = message.toJSONObject();
} catch (final JSONException e) {
Log.e(LOGTAG, "Exception handling message [" + prefixedEvent + "]", e);
return;
}
listener.handleMessage(mApplicationContext, event, json, callbackAdapter);
}
}
@Override
public synchronized void registerEventListener(final JavaAddonInterfaceV1.EventListener listener, String... events) {
public synchronized void registerEventListener(
final JavaAddonInterfaceV1.EventListener listener, String... events) {
if (mListenerToWrapperMap.containsKey(listener)) {
Log.e(LOGTAG, "Attempting to register listener which is already registered; ignoring.");
return;
}
final NativeEventListener listenerWrapper = new ListenerWrapper(listener);
final BundleEventListener listenerWrapper = new ListenerWrapper(listener);
final String[] prefixedEvents = new String[events.length];
for (int i = 0; i < events.length; i++) {
@ -219,8 +225,9 @@ public class JavaAddonManagerV1 implements NativeEventListener {
}
@Override
public synchronized void unregisterEventListener(final JavaAddonInterfaceV1.EventListener listener) {
final Pair<NativeEventListener, String[]> pair = mListenerToWrapperMap.remove(listener);
public synchronized void unregisterEventListener(
final JavaAddonInterfaceV1.EventListener listener) {
final Pair<BundleEventListener, String[]> pair = mListenerToWrapperMap.remove(listener);
if (pair == null) {
Log.e(LOGTAG, "Attempting to unregister listener which is not registered; ignoring.");
return;
@ -228,17 +235,17 @@ public class JavaAddonManagerV1 implements NativeEventListener {
mDispatcher.unregisterGeckoThreadListener(pair.first, pair.second);
}
protected synchronized void unregisterAllEventListeners() {
// Unregister everything, then forget everything.
for (Pair<NativeEventListener, String[]> pair : mListenerToWrapperMap.values()) {
for (Pair<BundleEventListener, String[]> pair : mListenerToWrapperMap.values()) {
mDispatcher.unregisterGeckoThreadListener(pair.first, pair.second);
}
mListenerToWrapperMap.clear();
}
@Override
public void sendRequestToGecko(final String event, final JSONObject message, final JavaAddonInterfaceV1.RequestCallback callback) {
public void sendRequestToGecko(final String event, final JSONObject message,
final JavaAddonInterfaceV1.RequestCallback callback) {
final String prefixedEvent = guid + ":" + event;
GeckoAppShell.sendRequestToGecko(new GeckoRequest(prefixedEvent, message) {
@Override
@ -249,7 +256,7 @@ public class JavaAddonManagerV1 implements NativeEventListener {
}
try {
final JSONObject json = new JSONObject(nativeJSObject.toString());
callback.onResponse(GeckoAppShell.getContext(), json);
callback.onResponse(GeckoAppShell.getApplicationContext(), json);
} catch (JSONException e) {
// No way to report failure.
Log.e(LOGTAG, "Exception handling response to request [" + event + "]:", e);

Просмотреть файл

@ -2140,7 +2140,7 @@ var NativeWindow = {
},
loadDex: function(zipFile, implClass) {
Messaging.sendRequest({
GlobalEventDispatcher.sendRequest({
type: "Dex:Load",
zipfile: zipFile,
impl: implClass || "Main"
@ -2148,7 +2148,7 @@ var NativeWindow = {
},
unloadDex: function(zipFile) {
Messaging.sendRequest({
GlobalEventDispatcher.sendRequest({
type: "Dex:Unload",
zipfile: zipFile
});

Просмотреть файл

@ -37,7 +37,7 @@ var JavaAddonManager = Object.freeze({
if (!filename) {
throw new Error("filename cannot be null");
}
return Messaging.sendRequestForResult({
return EventDispatcher.instance.sendRequestForResult({
type: "JavaAddonManagerV1:Load",
classname: classname,
filename: resolveGeckoURI(filename)
@ -73,7 +73,7 @@ JavaAddonV1.prototype = Object.freeze({
return;
}
Messaging.sendRequestForResult({
EventDispatcher.instance.sendRequestForResult({
type: "JavaAddonManagerV1:Unload",
guid: this._guid
})