mobile/android/base/EventDispatcher.java

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this
     3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 package org.mozilla.gecko;
     7 import org.mozilla.gecko.GeckoAppShell;
     8 import org.mozilla.gecko.GeckoEvent;
     9 import org.mozilla.gecko.util.EventCallback;
    10 import org.mozilla.gecko.util.GeckoEventListener;
    11 import org.mozilla.gecko.util.NativeEventListener;
    12 import org.mozilla.gecko.util.NativeJSContainer;
    14 import org.json.JSONException;
    15 import org.json.JSONObject;
    17 import android.util.Log;
    19 import java.util.HashMap;
    20 import java.util.List;
    21 import java.util.Map;
    22 import java.util.concurrent.CopyOnWriteArrayList;
    24 public final class EventDispatcher {
    25     private static final String LOGTAG = "GeckoEventDispatcher";
    26     private static final String GUID = "__guid__";
    27     private static final String STATUS_CANCEL = "cancel";
    28     private static final String STATUS_ERROR = "error";
    29     private static final String STATUS_SUCCESS = "success";
    31     /**
    32      * The capacity of a HashMap is rounded up to the next power-of-2. Every time the size
    33      * of the map goes beyond 75% of the capacity, the map is rehashed. Therefore, to
    34      * empirically determine the initial capacity that avoids rehashing, we need to
    35      * determine the initial size, divide it by 75%, and round up to the next power-of-2.
    36      */
    37     private static final int GECKO_NATIVE_EVENTS_COUNT = 0; // Default for HashMap
    38     private static final int GECKO_JSON_EVENTS_COUNT = 256; // Empirically measured
    40     private final Map<String, List<NativeEventListener>> mGeckoThreadNativeListeners =
    41         new HashMap<String, List<NativeEventListener>>(GECKO_NATIVE_EVENTS_COUNT);
    42     private final Map<String, List<GeckoEventListener>> mGeckoThreadJSONListeners =
    43         new HashMap<String, List<GeckoEventListener>>(GECKO_JSON_EVENTS_COUNT);
    45     private <T> void registerListener(final Class<? extends List<T>> listType,
    46                                       final Map<String, List<T>> listenersMap,
    47                                       final T listener,
    48                                       final String[] events) {
    49         try {
    50             synchronized (listenersMap) {
    51                 for (final String event : events) {
    52                     List<T> listeners = listenersMap.get(event);
    53                     if (listeners == null) {
    54                         listeners = listType.newInstance();
    55                         listenersMap.put(event, listeners);
    56                     }
    57                     if (!AppConstants.RELEASE_BUILD && listeners.contains(listener)) {
    58                         throw new IllegalStateException("Already registered " + event);
    59                     }
    60                     listeners.add(listener);
    61                 }
    62             }
    63         } catch (final IllegalAccessException e) {
    64             throw new IllegalArgumentException("Invalid new list type", e);
    65         } catch (final InstantiationException e) {
    66             throw new IllegalArgumentException("Invalid new list type", e);
    67         }
    68     }
    70     private <T> void checkNotRegistered(final Map<String, List<T>> listenersMap,
    71                                         final String[] events) {
    72         synchronized (listenersMap) {
    73             for (final String event: events) {
    74                 if (listenersMap.get(event) != null) {
    75                     throw new IllegalStateException(
    76                         "Already registered " + event + " under a different type");
    77                 }
    78             }
    79         }
    80     }
    82     private <T> void unregisterListener(final Map<String, List<T>> listenersMap,
    83                                         final T listener,
    84                                         final String[] events) {
    85         synchronized (listenersMap) {
    86             for (final String event : events) {
    87                 List<T> listeners = listenersMap.get(event);
    88                 if ((listeners == null ||
    89                      !listeners.remove(listener)) && !AppConstants.RELEASE_BUILD) {
    90                     throw new IllegalArgumentException(event + " was not registered");
    91                 }
    92             }
    93         }
    94     }
    96     @SuppressWarnings("unchecked")
    97     public void registerGeckoThreadListener(final NativeEventListener listener,
    98                                             final String... events) {
    99         checkNotRegistered(mGeckoThreadJSONListeners, events);
   101         // For listeners running on the Gecko thread, we want to notify the listeners
   102         // outside of our synchronized block, because the listeners may take an
   103         // indeterminate amount of time to run. Therefore, to ensure concurrency when
   104         // iterating the list outside of the synchronized block, we use a
   105         // CopyOnWriteArrayList.
   106         registerListener((Class)CopyOnWriteArrayList.class,
   107                          mGeckoThreadNativeListeners, listener, events);
   108     }
   110     @Deprecated // Use NativeEventListener instead
   111     @SuppressWarnings("unchecked")
   112     private void registerGeckoThreadListener(final GeckoEventListener listener,
   113                                              final String... events) {
   114         checkNotRegistered(mGeckoThreadNativeListeners, events);
   116         registerListener((Class)CopyOnWriteArrayList.class,
   117                          mGeckoThreadJSONListeners, listener, events);
   118     }
   120     public void unregisterGeckoThreadListener(final NativeEventListener listener,
   121                                               final String... events) {
   122         unregisterListener(mGeckoThreadNativeListeners, listener, events);
   123     }
   125     @Deprecated // Use NativeEventListener instead
   126     private void unregisterGeckoThreadListener(final GeckoEventListener listener,
   127                                                final String... events) {
   128         unregisterListener(mGeckoThreadJSONListeners, listener, events);
   129     }
   131     @Deprecated // Use one of the variants above.
   132     public void registerEventListener(final String event, final GeckoEventListener listener) {
   133         registerGeckoThreadListener(listener, event);
   134     }
   136     @Deprecated // Use one of the variants above
   137     public void unregisterEventListener(final String event, final GeckoEventListener listener) {
   138         unregisterGeckoThreadListener(listener, event);
   139     }
   141     public void dispatchEvent(final NativeJSContainer message) {
   142         EventCallback callback = null;
   143         try {
   144             // First try native listeners.
   145             final String type = message.getString("type");
   147             final List<NativeEventListener> listeners;
   148             synchronized (mGeckoThreadNativeListeners) {
   149                 listeners = mGeckoThreadNativeListeners.get(type);
   150             }
   152             final String guid = message.optString(GUID, null);
   153             if (guid != null) {
   154                 callback = new GeckoEventCallback(guid, type);
   155             }
   157             if (listeners != null) {
   158                 if (listeners.size() == 0) {
   159                     Log.w(LOGTAG, "No listeners for " + type);
   160                 }
   161                 for (final NativeEventListener listener : listeners) {
   162                     listener.handleMessage(type, message, callback);
   163                 }
   164                 // If we found native listeners, we assume we don't have any JSON listeners
   165                 // and return early. This assumption is checked when registering listeners.
   166                 return;
   167             }
   168         } catch (final IllegalArgumentException e) {
   169             // Message doesn't have a "type" property, fallback to JSON
   170         }
   171         try {
   172             // If we didn't find native listeners, try JSON listeners.
   173             dispatchEvent(new JSONObject(message.toString()), callback);
   174         } catch (final JSONException e) {
   175             Log.e(LOGTAG, "Cannot parse JSON");
   176         } catch (final UnsupportedOperationException e) {
   177             Log.e(LOGTAG, "Cannot convert message to JSON");
   178         }
   179     }
   181     public void dispatchEvent(final JSONObject message, final EventCallback callback) {
   182         // {
   183         //   "type": "value",
   184         //   "event_specific": "value",
   185         //   ...
   186         try {
   187             final String type = message.getString("type");
   189             List<GeckoEventListener> listeners;
   190             synchronized (mGeckoThreadJSONListeners) {
   191                 listeners = mGeckoThreadJSONListeners.get(type);
   192             }
   193             if (listeners == null || listeners.size() == 0) {
   194                 Log.w(LOGTAG, "No listeners for " + type);
   196                 // If there are no listeners, cancel the callback to prevent Gecko-side observers
   197                 // from being leaked.
   198                 if (callback != null) {
   199                     callback.sendCancel();
   200                 }
   201                 return;
   202             }
   203             for (final GeckoEventListener listener : listeners) {
   204                 listener.handleMessage(type, message);
   205             }
   206         } catch (final JSONException e) {
   207             Log.e(LOGTAG, "handleGeckoMessage throws " + e, e);
   208         }
   209     }
   211     @Deprecated
   212     public static void sendResponse(JSONObject message, Object response) {
   213         sendResponseHelper(STATUS_SUCCESS, message, response);
   214     }
   216     @Deprecated
   217     public static void sendError(JSONObject message, Object response) {
   218         sendResponseHelper(STATUS_ERROR, message, response);
   219     }
   221     @Deprecated
   222     private static void sendResponseHelper(String status, JSONObject message, Object response) {
   223         try {
   224             final JSONObject wrapper = new JSONObject();
   225             wrapper.put(GUID, message.getString(GUID));
   226             wrapper.put("status", status);
   227             wrapper.put("response", response);
   228             GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent(
   229                     message.getString("type") + ":Response", wrapper.toString()));
   230         } catch (final JSONException e) {
   231             Log.e(LOGTAG, "Unable to send response", e);
   232         }
   233     }
   235     private static class GeckoEventCallback implements EventCallback {
   236         private final String guid;
   237         private final String type;
   238         private boolean sent;
   240         public GeckoEventCallback(final String guid, final String type) {
   241             this.guid = guid;
   242             this.type = type;
   243         }
   245         public void sendSuccess(final Object response) {
   246             sendResponse(STATUS_SUCCESS, response);
   247         }
   249         public void sendError(final Object response) {
   250             sendResponse(STATUS_ERROR, response);
   251         }
   253         public void sendCancel() {
   254             sendResponse(STATUS_CANCEL, null);
   255         }
   257         private void sendResponse(final String status, final Object response) {
   258             if (sent) {
   259                 throw new IllegalStateException("Callback has already been executed for type=" +
   260                         type + ", guid=" + guid);
   261             }
   263             sent = true;
   265             try {
   266                 final JSONObject wrapper = new JSONObject();
   267                 wrapper.put(GUID, guid);
   268                 wrapper.put("status", status);
   269                 wrapper.put("response", response);
   270                 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent(type + ":Response",
   271                         wrapper.toString()));
   272             } catch (final JSONException e) {
   273                 Log.e(LOGTAG, "Unable to send response for: " + type, e);
   274             }
   275         }
   276     }
   277 }

mercurial