зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
34b69f4bac
Коммит
36a6807488
|
@ -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
|
||||
})
|
||||
|
|
Загрузка…
Ссылка в новой задаче