Bug 1294821 - De-singletonize EventDispatcher and make it application-specific. r=jchen
authorDylan Roeh <droeh@mozilla.com>
Tue, 27 Sep 2016 11:07:24 -0500
changeset 315388 893cd9c72641c100b7a84e7d34b63dc25180dbcf
parent 315387 e1b0aaa20f1098b08537b2e13f9e97ee9759d4c7
child 315389 8aee3d476c9b26ce13ebcc25c100b17987bfd60e
push id82149
push userdroeh@mozilla.com
push dateTue, 27 Sep 2016 19:54:00 +0000
treeherdermozilla-inbound@893cd9c72641 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjchen
bugs1294821
milestone52.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1294821 - De-singletonize EventDispatcher and make it application-specific. r=jchen
mobile/android/base/java/org/mozilla/gecko/AccountsHelper.java
mobile/android/base/java/org/mozilla/gecko/ActionBarTextSelection.java
mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
mobile/android/base/java/org/mozilla/gecko/DoorHangerPopup.java
mobile/android/base/java/org/mozilla/gecko/FindInPageBar.java
mobile/android/base/java/org/mozilla/gecko/FormAssistPopup.java
mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
mobile/android/base/java/org/mozilla/gecko/MediaCastingBar.java
mobile/android/base/java/org/mozilla/gecko/MediaPlayerManager.java
mobile/android/base/java/org/mozilla/gecko/SharedPreferencesHelper.java
mobile/android/base/java/org/mozilla/gecko/Tabs.java
mobile/android/base/java/org/mozilla/gecko/ZoomedView.java
mobile/android/base/java/org/mozilla/gecko/home/BrowserSearch.java
mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryPanel.java
mobile/android/base/java/org/mozilla/gecko/home/HomeBanner.java
mobile/android/base/java/org/mozilla/gecko/home/HomePanelsManager.java
mobile/android/base/java/org/mozilla/gecko/home/PanelInfoManager.java
mobile/android/base/java/org/mozilla/gecko/home/RecentTabsAdapter.java
mobile/android/base/java/org/mozilla/gecko/notifications/NotificationHelper.java
mobile/android/base/java/org/mozilla/gecko/preferences/GeckoPreferences.java
mobile/android/base/java/org/mozilla/gecko/preferences/SearchPreferenceCategory.java
mobile/android/base/java/org/mozilla/gecko/prompts/PromptService.java
mobile/android/base/java/org/mozilla/gecko/push/PushService.java
mobile/android/base/java/org/mozilla/gecko/reader/ReadingListHelper.java
mobile/android/base/java/org/mozilla/gecko/text/FloatingToolbarTextSelection.java
mobile/android/base/java/org/mozilla/gecko/toolbar/PageActionLayout.java
mobile/android/base/java/org/mozilla/gecko/toolbar/SiteIdentityPopup.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/BaseGeckoInterface.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/EventDispatcher.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoEditable.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoThread.java
mobile/android/tests/browser/robocop/src/org/mozilla/gecko/FennecNativeDriver.java
mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testFilePicker.java
mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testFindInPage.java
mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testTrackingProtection.java
--- a/mobile/android/base/java/org/mozilla/gecko/AccountsHelper.java
+++ b/mobile/android/base/java/org/mozilla/gecko/AccountsHelper.java
@@ -47,33 +47,33 @@ public class AccountsHelper implements N
 
     protected final Context mContext;
     protected final GeckoProfile mProfile;
 
     public AccountsHelper(Context context, GeckoProfile profile) {
         mContext = context;
         mProfile = profile;
 
-        EventDispatcher dispatcher = EventDispatcher.getInstance();
+        EventDispatcher dispatcher = GeckoApp.getEventDispatcher();
         if (dispatcher == null) {
             Log.e(LOGTAG, "Gecko event dispatcher must not be null", new RuntimeException());
             return;
         }
         dispatcher.registerGeckoThreadListener(this,
                 "Accounts:CreateFirefoxAccountFromJSON",
                 "Accounts:UpdateFirefoxAccountFromJSON",
                 "Accounts:Create",
                 "Accounts:DeleteFirefoxAccount",
                 "Accounts:Exist",
                 "Accounts:ProfileUpdated",
                 "Accounts:ShowSyncPreferences");
     }
 
     public synchronized void uninit() {
-        EventDispatcher dispatcher = EventDispatcher.getInstance();
+        EventDispatcher dispatcher = GeckoApp.getEventDispatcher();
         if (dispatcher == null) {
             Log.e(LOGTAG, "Gecko event dispatcher must not be null", new RuntimeException());
             return;
         }
         dispatcher.unregisterGeckoThreadListener(this,
                 "Accounts:CreateFirefoxAccountFromJSON",
                 "Accounts:UpdateFirefoxAccountFromJSON",
                 "Accounts:Create",
--- a/mobile/android/base/java/org/mozilla/gecko/ActionBarTextSelection.java
+++ b/mobile/android/base/java/org/mozilla/gecko/ActionBarTextSelection.java
@@ -64,17 +64,17 @@ class ActionBarTextSelection implements 
     }
 
     @Override
     public void create() {
         // Only register listeners if we have valid start/middle/end handles
         if (anchorHandle == null) {
             Log.e(LOGTAG, "Failed to initialize text selection because at least one handle is null");
         } else {
-            EventDispatcher.getInstance().registerGeckoThreadListener(this,
+            GeckoApp.getEventDispatcher().registerGeckoThreadListener(this,
                 "TextSelection:ActionbarInit",
                 "TextSelection:ActionbarStatus",
                 "TextSelection:ActionbarUninit",
                 "TextSelection:Update");
         }
     }
 
     @Override
@@ -83,17 +83,17 @@ class ActionBarTextSelection implements 
         return false;
     }
 
     @Override
     public void destroy() {
         if (anchorHandle == null) {
             Log.e(LOGTAG, "Do not unregister TextSelection:* listeners since anchorHandle is null");
         } else {
-            EventDispatcher.getInstance().unregisterGeckoThreadListener(this,
+            GeckoApp.getEventDispatcher().unregisterGeckoThreadListener(this,
                     "TextSelection:ActionbarInit",
                     "TextSelection:ActionbarStatus",
                     "TextSelection:ActionbarUninit",
                     "TextSelection:Update");
         }
     }
 
     @Override
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -573,16 +573,17 @@ public class BrowserApp extends GeckoApp
 
         final SafeIntent intent = new SafeIntent(getIntent());
         final boolean isInAutomation = getIsInAutomationFromEnvironment(intent);
 
         // This has to be prepared prior to calling GeckoApp.onCreate, because
         // widget code and BrowserToolbar need it, and they're created by the
         // layout, which GeckoApp takes care of.
         ((GeckoApplication) getApplication()).prepareLightweightTheme();
+
         super.onCreate(savedInstanceState);
 
         final Context appContext = getApplicationContext();
 
         initSwitchboard(this, intent, isInAutomation);
         initTelemetryUploader(isInAutomation);
 
         mBrowserChrome = (ViewGroup) findViewById(R.id.browser_chrome);
--- a/mobile/android/base/java/org/mozilla/gecko/DoorHangerPopup.java
+++ b/mobile/android/base/java/org/mozilla/gecko/DoorHangerPopup.java
@@ -37,26 +37,26 @@ public class DoorHangerPopup extends Anc
     // Whether or not the doorhanger popup is disabled.
     private boolean mDisabled;
 
     public DoorHangerPopup(Context context) {
         super(context);
 
         mDoorHangers = new HashSet<DoorHanger>();
 
-        EventDispatcher.getInstance().registerGeckoThreadListener(this,
+        GeckoApp.getEventDispatcher().registerGeckoThreadListener(this,
             "Doorhanger:Add",
             "Doorhanger:Remove");
         Tabs.registerOnTabsChangedListener(this);
 
         setOnDismissListener(this);
     }
 
     void destroy() {
-        EventDispatcher.getInstance().unregisterGeckoThreadListener(this,
+        GeckoApp.getEventDispatcher().unregisterGeckoThreadListener(this,
             "Doorhanger:Add",
             "Doorhanger:Remove");
         Tabs.unregisterOnTabsChangedListener(this);
     }
 
     /**
      * Temporarily disables the doorhanger popup. If the popup is disabled,
      * it will not be shown to the user, but it will continue to process
--- a/mobile/android/base/java/org/mozilla/gecko/FindInPageBar.java
+++ b/mobile/android/base/java/org/mozilla/gecko/FindInPageBar.java
@@ -62,17 +62,17 @@ public class FindInPageBar extends Linea
                 }
                 return false;
             }
         });
 
         mStatusText = (TextView) content.findViewById(R.id.find_status);
 
         mInflated = true;
-        EventDispatcher.getInstance().registerGeckoThreadListener(this, "TextSelection:Data");
+        GeckoApp.getEventDispatcher().registerGeckoThreadListener(this, "TextSelection:Data");
     }
 
     public void show() {
         if (!mInflated)
             inflateContent();
 
         setVisibility(VISIBLE);
         mFindText.requestFocus();
@@ -105,17 +105,17 @@ public class FindInPageBar extends Linea
         Context context = view.getContext();
         return (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
      }
 
     public void onDestroy() {
         if (!mInflated) {
             return;
         }
-        EventDispatcher.getInstance().unregisterGeckoThreadListener(this, "TextSelection:Data");
+        GeckoApp.getEventDispatcher().unregisterGeckoThreadListener(this, "TextSelection:Data");
     }
 
     // TextWatcher implementation
 
     @Override
     public void afterTextChanged(Editable s) {
         sendRequestToFinderHelper("FindInPage:Find", s.toString());
     }
--- a/mobile/android/base/java/org/mozilla/gecko/FormAssistPopup.java
+++ b/mobile/android/base/java/org/mozilla/gecko/FormAssistPopup.java
@@ -86,24 +86,24 @@ public class FormAssistPopup extends Rel
         super(context, attrs);
         mContext = context;
 
         mAnimation = AnimationUtils.loadAnimation(context, R.anim.grow_fade_in);
         mAnimation.setDuration(75);
 
         setFocusable(false);
 
-        EventDispatcher.getInstance().registerGeckoThreadListener(this,
+        GeckoApp.getEventDispatcher().registerGeckoThreadListener(this,
             "FormAssist:AutoComplete",
             "FormAssist:ValidationMessage",
             "FormAssist:Hide");
     }
 
     void destroy() {
-        EventDispatcher.getInstance().unregisterGeckoThreadListener(this,
+        GeckoApp.getEventDispatcher().unregisterGeckoThreadListener(this,
             "FormAssist:AutoComplete",
             "FormAssist:ValidationMessage",
             "FormAssist:Hide");
     }
 
     @Override
     public void handleMessage(String event, JSONObject message) {
         try {
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
@@ -196,16 +196,18 @@ public abstract class GeckoApp
     private View mFullScreenPluginView;
 
     private final HashMap<String, PowerManager.WakeLock> mWakeLocks = new HashMap<String, PowerManager.WakeLock>();
 
     protected boolean mLastSessionCrashed;
     protected boolean mShouldRestore;
     private boolean mSessionRestoreParsingFinished = false;
 
+    private EventDispatcher eventDispatcher;
+
     private static final class LastSessionParser extends SessionParser {
         private JSONArray tabs;
         private JSONObject windowObject;
         private boolean isExternalURL;
 
         private boolean selectNextTab;
         private boolean tabsWereSkipped;
         private boolean tabsWereProcessed;
@@ -1100,16 +1102,18 @@ public abstract class GeckoApp
      *
      * Here we initialize all of our profile settings, Firefox Health Report,
      * and other one-shot constructions.
      **/
     @Override
     public void onCreate(Bundle savedInstanceState) {
         GeckoAppShell.ensureCrashHandling();
 
+        eventDispatcher = new EventDispatcher();
+
         // Enable Android Strict Mode for developers' local builds (the "default" channel).
         if ("default".equals(AppConstants.MOZ_UPDATE_CHANNEL)) {
             enableStrictMode();
         }
 
         if (!HardwareUtils.isSupportedSystem()) {
             // This build does not support the Android version of the device: Show an error and finish the app.
             mIsAbortingAppLaunch = true;
@@ -1199,22 +1203,22 @@ public abstract class GeckoApp
                 // Start a speculative connection as soon as Gecko loads.
                 GeckoThread.speculativeConnect(uri);
             }
         }
 
         // GeckoThread has to register for "Gecko:Ready" first, so GeckoApp registers
         // for events after initializing GeckoThread but before launching it.
 
-        EventDispatcher.getInstance().registerGeckoThreadListener((GeckoEventListener)this,
+        getAppEventDispatcher().registerGeckoThreadListener((GeckoEventListener)this,
             "Gecko:Ready",
             "Gecko:Exited",
             "Accessibility:Event");
 
-        EventDispatcher.getInstance().registerGeckoThreadListener((NativeEventListener)this,
+        getAppEventDispatcher().registerGeckoThreadListener((NativeEventListener)this,
             "Accessibility:Ready",
             "Bookmark:Insert",
             "Contact:Add",
             "DevToolsAuth:Scan",
             "DOMFullScreen:Start",
             "DOMFullScreen:Stop",
             "Image:SetAs",
             "Locale:Set",
@@ -1378,17 +1382,17 @@ public abstract class GeckoApp
                 editor.putBoolean(GeckoApp.PREFS_WAS_STOPPED, false);
 
                 editor.apply();
 
                 // The lifecycle of mHealthRecorder is "shortly after onCreate"
                 // through "onDestroy" -- essentially the same as the lifecycle
                 // of the activity itself.
                 final String profilePath = getProfile().getDir().getAbsolutePath();
-                final EventDispatcher dispatcher = EventDispatcher.getInstance();
+                final EventDispatcher dispatcher = getAppEventDispatcher();
 
                 // This is the locale prior to fixing it up.
                 final Locale osLocale = Locale.getDefault();
 
                 // Both of these are Java-format locale strings: "en_US", not "en-US".
                 final String osLocaleString = osLocale.toString();
                 String appLocaleString = localeManager.getAndApplyPersistedLocale(GeckoApp.this);
                 Log.d(LOGTAG, "OS locale is " + osLocaleString + ", app locale is " + appLocaleString);
@@ -1687,21 +1691,21 @@ public abstract class GeckoApp
         // site loaded from the intent is on top (last loaded) and selected with all other tabs
         // being opened behind it. We process the tab queue first and request a callback from the JS - the
         // listener will open the url from the intent as normal when the tab queue has been processed.
         ThreadUtils.postToBackgroundThread(new Runnable() {
             @Override
             public void run() {
                 if (TabQueueHelper.TAB_QUEUE_ENABLED && TabQueueHelper.shouldOpenTabQueueUrls(GeckoApp.this)) {
 
-                    EventDispatcher.getInstance().registerGeckoThreadListener(new NativeEventListener() {
+                    getAppEventDispatcher().registerGeckoThreadListener(new NativeEventListener() {
                         @Override
                         public void handleMessage(String event, NativeJSObject message, EventCallback callback) {
                             if ("Tabs:TabsOpened".equals(event)) {
-                                EventDispatcher.getInstance().unregisterGeckoThreadListener(this, "Tabs:TabsOpened");
+                                getAppEventDispatcher().unregisterGeckoThreadListener(this, "Tabs:TabsOpened");
                                 openTabsRunnable.run();
                             }
                         }
                     }, "Tabs:TabsOpened");
                     TabQueueHelper.openQueuedUrls(GeckoApp.this, getProfile(), TabQueueHelper.FILE_NAME, true);
                 } else {
                     openTabsRunnable.run();
                 }
@@ -1750,16 +1754,26 @@ public abstract class GeckoApp
             JSONObject restoreData = new JSONObject();
             restoreData.put("sessionString", sessionString);
             return restoreData.toString();
         } catch (JSONException e) {
             throw new SessionRestoreException(e);
         }
     }
 
+    public static EventDispatcher getEventDispatcher() {
+        final GeckoApp geckoApp = (GeckoApp) GeckoAppShell.getGeckoInterface();
+        return geckoApp.getAppEventDispatcher();
+    }
+
+    @Override
+    public EventDispatcher getAppEventDispatcher() {
+        return eventDispatcher;
+    }
+
     @Override
     public GeckoProfile getProfile() {
         return GeckoThread.getActiveProfile();
     }
 
     /**
      * Check whether we've crashed during the last browsing session.
      *
@@ -2103,16 +2117,18 @@ public abstract class GeckoApp
     {
         // After an onPause, the activity is back in the foreground.
         // Undo whatever we did in onPause.
         super.onResume();
         if (mIsAbortingAppLaunch) {
             return;
         }
 
+        GeckoAppShell.setGeckoInterface(this);
+
         int newOrientation = getResources().getConfiguration().orientation;
         if (GeckoScreenOrientation.getInstance().update(newOrientation)) {
             refreshChrome();
         }
 
         if (!Versions.feature14Plus) {
             // Update accessibility settings in case it has been changed by the
             // user. On API14+, this is handled in LayerView by registering an
@@ -2254,22 +2270,22 @@ public abstract class GeckoApp
     public void onDestroy() {
         if (mIsAbortingAppLaunch) {
             // This build does not support the Android version of the device:
             // We did not initialize anything, so skip cleaning up.
             super.onDestroy();
             return;
         }
 
-        EventDispatcher.getInstance().unregisterGeckoThreadListener((GeckoEventListener)this,
+        getAppEventDispatcher().unregisterGeckoThreadListener((GeckoEventListener)this,
             "Gecko:Ready",
             "Gecko:Exited",
             "Accessibility:Event");
 
-        EventDispatcher.getInstance().unregisterGeckoThreadListener((NativeEventListener)this,
+        getAppEventDispatcher().unregisterGeckoThreadListener((NativeEventListener)this,
             "Accessibility:Ready",
             "Bookmark:Insert",
             "Contact:Add",
             "DevToolsAuth:Scan",
             "DOMFullScreen:Start",
             "DOMFullScreen:Stop",
             "Image:SetAs",
             "Locale:Set",
--- a/mobile/android/base/java/org/mozilla/gecko/MediaCastingBar.java
+++ b/mobile/android/base/java/org/mozilla/gecko/MediaCastingBar.java
@@ -1,14 +1,15 @@
 /* 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/. */
 
 package org.mozilla.gecko;
 
+import org.mozilla.gecko.GeckoApp;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import org.json.JSONObject;
 
 import android.content.Context;
 import android.text.TextUtils;
 import android.util.AttributeSet;
@@ -27,17 +28,17 @@ public class MediaCastingBar extends Rel
     private ImageButton mMediaPause;
     private ImageButton mMediaStop;
 
     private boolean mInflated;
 
     public MediaCastingBar(Context context, AttributeSet attrs) {
         super(context, attrs);
 
-        EventDispatcher.getInstance().registerGeckoThreadListener(this,
+        GeckoApp.getEventDispatcher().registerGeckoThreadListener(this,
             "Casting:Started",
             "Casting:Paused",
             "Casting:Playing",
             "Casting:Stopped");
     }
 
     public void inflateContent() {
         LayoutInflater inflater = LayoutInflater.from(getContext());
@@ -66,17 +67,17 @@ public class MediaCastingBar extends Rel
         setVisibility(VISIBLE);
     }
 
     public void hide() {
         setVisibility(GONE);
     }
 
     public void onDestroy() {
-        EventDispatcher.getInstance().unregisterGeckoThreadListener(this,
+        GeckoApp.getEventDispatcher().unregisterGeckoThreadListener(this,
             "Casting:Started",
             "Casting:Paused",
             "Casting:Playing",
             "Casting:Stopped");
     }
 
     // View.OnClickListener implementation
     @Override
--- a/mobile/android/base/java/org/mozilla/gecko/MediaPlayerManager.java
+++ b/mobile/android/base/java/org/mozilla/gecko/MediaPlayerManager.java
@@ -64,32 +64,32 @@ public class MediaPlayerManager extends 
     }
 
     protected MediaRouter mediaRouter = null;
     protected final Map<String, GeckoMediaPlayer> displays = new HashMap<String, GeckoMediaPlayer>();
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        EventDispatcher.getInstance().registerGeckoThreadListener(this,
-                "MediaPlayer:Load",
-                "MediaPlayer:Start",
-                "MediaPlayer:Stop",
-                "MediaPlayer:Play",
-                "MediaPlayer:Pause",
-                "MediaPlayer:End",
-                "MediaPlayer:Mirror",
-                "MediaPlayer:Message");
+        GeckoApp.getEventDispatcher().registerGeckoThreadListener(this,
+                                                                  "MediaPlayer:Load",
+                                                                  "MediaPlayer:Start",
+                                                                  "MediaPlayer:Stop",
+                                                                  "MediaPlayer:Play",
+                                                                  "MediaPlayer:Pause",
+                                                                  "MediaPlayer:End",
+                                                                  "MediaPlayer:Mirror",
+                                                                  "MediaPlayer:Message");
     }
 
     @Override
     @JNITarget
     public void onDestroy() {
         super.onDestroy();
-        EventDispatcher.getInstance().unregisterGeckoThreadListener(this,
+        GeckoApp.getEventDispatcher().unregisterGeckoThreadListener(this,
                                                                     "MediaPlayer:Load",
                                                                     "MediaPlayer:Start",
                                                                     "MediaPlayer:Stop",
                                                                     "MediaPlayer:Play",
                                                                     "MediaPlayer:Pause",
                                                                     "MediaPlayer:End",
                                                                     "MediaPlayer:Mirror",
                                                                     "MediaPlayer:Message");
--- a/mobile/android/base/java/org/mozilla/gecko/SharedPreferencesHelper.java
+++ b/mobile/android/base/java/org/mozilla/gecko/SharedPreferencesHelper.java
@@ -59,29 +59,29 @@ public final class SharedPreferencesHelp
     // handleObserve, which is called from Gecko serially.
     protected final Map<String, SharedPreferences.OnSharedPreferenceChangeListener> mListeners;
 
     public SharedPreferencesHelper(Context context) {
         mContext = context;
 
         mListeners = new HashMap<String, SharedPreferences.OnSharedPreferenceChangeListener>();
 
-        EventDispatcher dispatcher = EventDispatcher.getInstance();
+        EventDispatcher dispatcher = GeckoApp.getEventDispatcher();
         if (dispatcher == null) {
             Log.e(LOGTAG, "Gecko event dispatcher must not be null", new RuntimeException());
             return;
         }
         dispatcher.registerGeckoThreadListener(this,
             "SharedPreferences:Set",
             "SharedPreferences:Get",
             "SharedPreferences:Observe");
     }
 
     public synchronized void uninit() {
-        EventDispatcher dispatcher = EventDispatcher.getInstance();
+        EventDispatcher dispatcher = GeckoApp.getEventDispatcher();
         if (dispatcher == null) {
             Log.e(LOGTAG, "Gecko event dispatcher must not be null", new RuntimeException());
             return;
         }
         dispatcher.unregisterGeckoThreadListener(this,
             "SharedPreferences:Set",
             "SharedPreferences:Get",
             "SharedPreferences:Observe");
--- a/mobile/android/base/java/org/mozilla/gecko/Tabs.java
+++ b/mobile/android/base/java/org/mozilla/gecko/Tabs.java
@@ -484,17 +484,16 @@ public class Tabs implements GeckoEventL
                         return;
                     }
                 } else {
                     tab = addTab(id, url, message.getBoolean("external"),
                                           message.getInt("parentId"),
                                           message.getString("title"),
                                           message.getBoolean("isPrivate"),
                                           message.getInt("tabIndex"));
-
                     // If we added the tab as a stub, we should have already
                     // selected it, so ignore this flag for stubbed tabs.
                     if (message.getBoolean("selected"))
                         selectTab(id);
                 }
 
                 if (message.getBoolean("delayLoad"))
                     tab.setState(Tab.STATE_DELAYED);
--- a/mobile/android/base/java/org/mozilla/gecko/ZoomedView.java
+++ b/mobile/android/base/java/org/mozilla/gecko/ZoomedView.java
@@ -235,29 +235,29 @@ public class ZoomedView extends FrameLay
         animationStart = new PointF();
         requestRenderRunnable = new Runnable() {
             @Override
             public void run() {
                 requestZoomedViewRender();
             }
         };
         touchListener = new ZoomedViewTouchListener();
-        EventDispatcher.getInstance().registerGeckoThreadListener(this,
+        GeckoApp.getEventDispatcher().registerGeckoThreadListener(this,
                 "Gesture:clusteredLinksClicked", "Window:Resize", "Content:LocationChange",
                 "Gesture:CloseZoomedView", "Browser:ZoomToPageWidth", "Browser:ZoomToRect",
                 "FormAssist:AutoComplete", "FormAssist:Hide");
     }
 
     void destroy() {
         if (prefObserver != null) {
             PrefsHelper.removeObserver(prefObserver);
             prefObserver = null;
         }
         ThreadUtils.removeCallbacksFromUiThread(requestRenderRunnable);
-        EventDispatcher.getInstance().unregisterGeckoThreadListener(this,
+        GeckoApp.getEventDispatcher().unregisterGeckoThreadListener(this,
                 "Gesture:clusteredLinksClicked", "Window:Resize", "Content:LocationChange",
                 "Gesture:CloseZoomedView", "Browser:ZoomToPageWidth", "Browser:ZoomToRect",
                 "FormAssist:AutoComplete", "FormAssist:Hide");
     }
 
     // This method (onFinishInflate) is called only when the zoomed view class is used inside
     // an xml structure <org.mozilla.gecko.ZoomedView ...
     // It won't be called if the class is used from java code like "new  ZoomedView(context);"
--- a/mobile/android/base/java/org/mozilla/gecko/home/BrowserSearch.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/BrowserSearch.java
@@ -16,16 +16,17 @@ import java.util.List;
 import java.util.Locale;
 
 import android.content.SharedPreferences;
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import org.mozilla.gecko.EventDispatcher;
+import org.mozilla.gecko.GeckoApp;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoSharedPrefs;
 import org.mozilla.gecko.PrefsHelper;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.SuggestClient;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.Telemetry;
@@ -303,17 +304,17 @@ public class BrowserSearch extends HomeF
 
         return mView;
     }
 
     @Override
     public void onDestroyView() {
         super.onDestroyView();
 
-        EventDispatcher.getInstance().unregisterGeckoThreadListener(this,
+        GeckoApp.getEventDispatcher().unregisterGeckoThreadListener(this,
             "SearchEngines:Data");
 
         mSearchEngineBar.setAdapter(null);
         mSearchEngineBar = null;
 
         mList.setAdapter(null);
         mList = null;
 
@@ -403,17 +404,17 @@ public class BrowserSearch extends HomeF
                 if (selected instanceof SearchEngineRow) {
                     return selected.onKeyDown(keyCode, event);
                 }
                 return false;
             }
         });
 
         registerForContextMenu(mList);
-        EventDispatcher.getInstance().registerGeckoThreadListener(this,
+        GeckoApp.getEventDispatcher().registerGeckoThreadListener(this,
             "SearchEngines:Data");
 
         mSearchEngineBar.setOnSearchBarClickListener(this);
     }
 
     @Override
     public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
         if (!(menuInfo instanceof HomeContextMenuInfo)) {
--- a/mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryPanel.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryPanel.java
@@ -33,16 +33,17 @@ import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.Button;
 import android.widget.ImageView;
 import android.widget.TextView;
 import org.json.JSONException;
 import org.json.JSONObject;
 import org.mozilla.gecko.EventDispatcher;
+import org.mozilla.gecko.GeckoApp;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.RemoteClientsDialogFragment;
 import org.mozilla.gecko.fxa.FirefoxAccounts;
 import org.mozilla.gecko.fxa.FxAccountConstants;
 import org.mozilla.gecko.fxa.SyncStatusListener;
 import org.mozilla.gecko.home.CombinedHistoryPanel.OnPanelLevelChangeListener.PanelLevel;
@@ -527,16 +528,17 @@ public class CombinedHistoryPanel extend
         // Set clickable text.
         final ClickableSpan clickableSpan = new ClickableSpan() {
             @Override
             public void onClick(View widget) {
                 Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.BUTTON, "hint_private_browsing");
                 try {
                     final JSONObject json = new JSONObject();
                     json.put("type", "Menu:Open");
+                    GeckoApp.getEventDispatcher().dispatchEvent(json, null);
                     EventDispatcher.getInstance().dispatchEvent(json, null);
                 } catch (JSONException e) {
                     Log.e(LOGTAG, "Error forming JSON for Private Browsing contextual hint", e);
                 }
             }
         };
 
         ssb.setSpan(clickableSpan, 0, text.length(), 0);
--- a/mobile/android/base/java/org/mozilla/gecko/home/HomeBanner.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/HomeBanner.java
@@ -3,16 +3,17 @@
  * 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/. */
 
 package org.mozilla.gecko.home;
 
 import org.json.JSONObject;
 import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.EventDispatcher;
+import org.mozilla.gecko.GeckoApp;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.animation.PropertyAnimator;
 import org.mozilla.gecko.animation.PropertyAnimator.Property;
 import org.mozilla.gecko.animation.ViewHelper;
 import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.ThreadUtils;
@@ -109,24 +110,24 @@ public class HomeBanner extends LinearLa
             public void onClick(View v) {
                 HomeBanner.this.dismiss();
 
                 // Send the current message id back to JS.
                 GeckoAppShell.notifyObservers("HomeBanner:Click", (String) getTag());
             }
         });
 
-        EventDispatcher.getInstance().registerGeckoThreadListener(this, "HomeBanner:Data");
+        GeckoApp.getEventDispatcher().registerGeckoThreadListener(this, "HomeBanner:Data");
     }
 
     @Override
     public void onDetachedFromWindow() {
         super.onDetachedFromWindow();
 
-        EventDispatcher.getInstance().unregisterGeckoThreadListener(this, "HomeBanner:Data");
+        GeckoApp.getEventDispatcher().unregisterGeckoThreadListener(this, "HomeBanner:Data");
     }
 
     public void setScrollingPages(boolean scrollingPages) {
         mScrollingPages = scrollingPages;
     }
 
     public void setOnDismissListener(OnDismissListener listener) {
         mOnDismissListener = listener;
--- a/mobile/android/base/java/org/mozilla/gecko/home/HomePanelsManager.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/HomePanelsManager.java
@@ -68,33 +68,40 @@ public class HomePanelsManager implement
         public ConfigChange(ChangeType type, Object target) {
             this.type = type;
             this.target = target;
         }
     }
 
     private Context mContext;
     private HomeConfig mHomeConfig;
+    private boolean mInitialized;
 
     private final Queue<ConfigChange> mPendingChanges = new ConcurrentLinkedQueue<ConfigChange>();
     private final Runnable mInvalidationRunnable = new InvalidationRunnable();
 
     public static HomePanelsManager getInstance() {
         return sInstance;
     }
 
     public void init(Context context) {
+        if (mInitialized) {
+            return;
+        }
+
         mContext = context;
         mHomeConfig = HomeConfig.getDefault(context);
 
         EventDispatcher.getInstance().registerGeckoThreadListener(this,
             EVENT_HOMEPANELS_INSTALL,
             EVENT_HOMEPANELS_UNINSTALL,
             EVENT_HOMEPANELS_UPDATE,
             EVENT_HOMEPANELS_REFRESH);
+
+        mInitialized = true;
     }
 
     public void onLocaleReady(final String locale) {
         ThreadUtils.getBackgroundHandler().post(new Runnable() {
             @Override
             public void run() {
                 final String configLocale = mHomeConfig.getLocale();
                 if (configLocale == null || !configLocale.equals(locale)) {
--- a/mobile/android/base/java/org/mozilla/gecko/home/PanelInfoManager.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/PanelInfoManager.java
@@ -9,16 +9,17 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 import org.mozilla.gecko.EventDispatcher;
+import org.mozilla.gecko.GeckoApp;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.home.HomeConfig.PanelConfig;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.util.Log;
 import android.util.SparseArray;
 
@@ -72,17 +73,17 @@ public class PanelInfoManager implements
      * @param callback onComplete will be called on the UI thread.
      */
     public void requestPanelsById(Set<String> ids, RequestCallback callback) {
         final int requestId = sRequestId.getAndIncrement();
 
         synchronized (sCallbacks) {
             // If there are no pending callbacks, register the event listener.
             if (sCallbacks.size() == 0) {
-                EventDispatcher.getInstance().registerGeckoThreadListener(this,
+                GeckoApp.getEventDispatcher().registerGeckoThreadListener(this,
                     "HomePanels:Data");
             }
             sCallbacks.put(requestId, callback);
         }
 
         final JSONObject message = new JSONObject();
         try {
             message.put("requestId", requestId);
@@ -131,17 +132,17 @@ public class PanelInfoManager implements
             final int requestId = message.getInt("requestId");
 
             synchronized (sCallbacks) {
                 callback = sCallbacks.get(requestId);
                 sCallbacks.delete(requestId);
 
                 // Unregister the event listener if there are no more pending callbacks.
                 if (sCallbacks.size() == 0) {
-                    EventDispatcher.getInstance().unregisterGeckoThreadListener(this,
+                    GeckoApp.getEventDispatcher().unregisterGeckoThreadListener(this,
                         "HomePanels:Data");
                 }
             }
 
             ThreadUtils.postToUiThread(new Runnable() {
                 @Override
                 public void run() {
                     callback.onComplete(panelInfos);
--- a/mobile/android/base/java/org/mozilla/gecko/home/RecentTabsAdapter.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/RecentTabsAdapter.java
@@ -13,16 +13,17 @@ import android.view.View;
 import android.view.ViewGroup;
 import android.widget.TextView;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 import org.mozilla.gecko.AboutPages;
 import org.mozilla.gecko.EventDispatcher;
+import org.mozilla.gecko.GeckoApp;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.SessionParser;
 import org.mozilla.gecko.home.CombinedHistoryAdapter.RecentTabsUpdateHandler;
 import org.mozilla.gecko.home.CombinedHistoryPanel.PanelStateUpdateHandler;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.NativeEventListener;
@@ -75,32 +76,32 @@ public class RecentTabsAdapter extends R
         this.panelStateUpdateHandler = panelStateUpdateHandler;
         recentlyClosedTabs = new ClosedTab[0];
         lastSessionTabs = new ClosedTab[0];
 
         readPreviousSessionData();
     }
 
     public void startListeningForClosedTabs() {
-        EventDispatcher.getInstance().registerGeckoThreadListener(this, "ClosedTabs:Data");
+        GeckoApp.getEventDispatcher().registerGeckoThreadListener(this, "ClosedTabs:Data");
         GeckoAppShell.notifyObservers("ClosedTabs:StartNotifications", null);
     }
 
     public void stopListeningForClosedTabs() {
         GeckoAppShell.notifyObservers("ClosedTabs:StopNotifications", null);
-        EventDispatcher.getInstance().unregisterGeckoThreadListener(this, "ClosedTabs:Data");
+        GeckoApp.getEventDispatcher().unregisterGeckoThreadListener(this, "ClosedTabs:Data");
         recentlyClosedTabsReceived = false;
     }
 
     public void startListeningForHistorySanitize() {
-        EventDispatcher.getInstance().registerGeckoThreadListener(this, "Sanitize:Finished");
+        GeckoApp.getEventDispatcher().registerGeckoThreadListener(this, "Sanitize:Finished");
     }
 
     public void stopListeningForHistorySanitize() {
-        EventDispatcher.getInstance().unregisterGeckoThreadListener(this, "Sanitize:Finished");
+        GeckoApp.getEventDispatcher().unregisterGeckoThreadListener(this, "Sanitize:Finished");
     }
 
     @Override
     public void handleMessage(String event, NativeJSObject message, EventCallback callback) {
         switch (event) {
             case "ClosedTabs:Data":
                 updateRecentlyClosedTabs(message);
                 break;
--- a/mobile/android/base/java/org/mozilla/gecko/notifications/NotificationHelper.java
+++ b/mobile/android/base/java/org/mozilla/gecko/notifications/NotificationHelper.java
@@ -77,16 +77,20 @@ public final class NotificationHelper im
     private boolean mInitialized;
     private static NotificationHelper sInstance;
 
     private NotificationHelper(Context context) {
         mContext = context;
     }
 
     public void init() {
+        if (mInitialized) {
+            return;
+        }
+
         mClearableNotifications = new HashMap<String, String>();
         EventDispatcher.getInstance().registerGeckoThreadListener(this,
             "Notification:Show",
             "Notification:Hide");
         mInitialized = true;
     }
 
     public static NotificationHelper getInstance(Context context) {
--- a/mobile/android/base/java/org/mozilla/gecko/preferences/GeckoPreferences.java
+++ b/mobile/android/base/java/org/mozilla/gecko/preferences/GeckoPreferences.java
@@ -11,16 +11,17 @@ import org.mozilla.gecko.AdjustConstants
 import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.BrowserApp;
 import org.mozilla.gecko.BrowserLocaleManager;
 import org.mozilla.gecko.DataReportingNotification;
 import org.mozilla.gecko.DynamicToolbar;
 import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.GeckoActivityStatus;
+import org.mozilla.gecko.GeckoApp;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoApplication;
 import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.GeckoSharedPrefs;
 import org.mozilla.gecko.LocaleManager;
 import org.mozilla.gecko.Locales;
 import org.mozilla.gecko.PrefsHelper;
 import org.mozilla.gecko.R;
@@ -365,19 +366,19 @@ OnSharedPreferenceChangeListener
                 localeSwitchingIsEnabled = false;
                 throw new IllegalStateException("foobar");
             }
         }
 
         // Use setResourceToOpen to specify these extras.
         Bundle intentExtras = getIntent().getExtras();
 
-        EventDispatcher.getInstance().registerGeckoThreadListener(this, "Sanitize:Finished");
-
-        EventDispatcher.getInstance().registerGeckoThreadListener(this, "Snackbar:Show");
+        GeckoApp.getEventDispatcher().registerGeckoThreadListener(this,
+                                                                  "Sanitize:Finished",
+                                                                  "Snackbar:Show");
 
         // Add handling for long-press click.
         // This is only for Android 3.0 and below (which use the long-press-context-menu paradigm).
         final ListView mListView = getListView();
         mListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
             @Override
             public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
                 // Call long-click handler if it the item implements it.
@@ -501,19 +502,20 @@ OnSharedPreferenceChangeListener
         }
 
         Telemetry.sendUIEvent(TelemetryContract.Event.CANCEL, Method.BACK, "settings");
     }
 
     @Override
     protected void onDestroy() {
         super.onDestroy();
-        EventDispatcher.getInstance().unregisterGeckoThreadListener(this, "Sanitize:Finished");
 
-        EventDispatcher.getInstance().unregisterGeckoThreadListener(this, "Snackbar:Show");
+        GeckoApp.getEventDispatcher().unregisterGeckoThreadListener(this,
+                                                                    "Sanitize:Finished", 
+                                                                    "Snackbar:Show");
 
         if (mPrefsRequest != null) {
             PrefsHelper.removeObserver(mPrefsRequest);
             mPrefsRequest = null;
         }
     }
 
     @Override
--- a/mobile/android/base/java/org/mozilla/gecko/preferences/SearchPreferenceCategory.java
+++ b/mobile/android/base/java/org/mozilla/gecko/preferences/SearchPreferenceCategory.java
@@ -9,16 +9,17 @@ import android.preference.Preference;
 import android.util.AttributeSet;
 import android.util.Log;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import org.mozilla.gecko.EventDispatcher;
+import org.mozilla.gecko.GeckoApp;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.TelemetryContract.Method;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.ThreadUtils;
 
 public class SearchPreferenceCategory extends CustomListCategory implements GeckoEventListener {
@@ -36,25 +37,25 @@ public class SearchPreferenceCategory ex
         super(context, attrs, defStyle);
     }
 
     @Override
     protected void onAttachedToActivity() {
         super.onAttachedToActivity();
 
         // Register for SearchEngines messages and request list of search engines from Gecko.
-        EventDispatcher.getInstance().registerGeckoThreadListener(this, "SearchEngines:Data");
+        GeckoApp.getEventDispatcher().registerGeckoThreadListener(this, "SearchEngines:Data");
         GeckoAppShell.notifyObservers("SearchEngines:GetVisible", null);
     }
 
     @Override
     protected void onPrepareForRemoval() {
         super.onPrepareForRemoval();
 
-        EventDispatcher.getInstance().unregisterGeckoThreadListener(this, "SearchEngines:Data");
+        GeckoApp.getEventDispatcher().unregisterGeckoThreadListener(this, "SearchEngines:Data");
     }
 
     @Override
     public void setDefault(CustomListPreference item) {
         super.setDefault(item);
 
         sendGeckoEngineEvent("SearchEngines:SetDefault", item.getTitle().toString());
 
--- a/mobile/android/base/java/org/mozilla/gecko/prompts/PromptService.java
+++ b/mobile/android/base/java/org/mozilla/gecko/prompts/PromptService.java
@@ -3,37 +3,38 @@
  * 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/. */
 
 package org.mozilla.gecko.prompts;
 
 import org.json.JSONException;
 import org.json.JSONObject;
 import org.mozilla.gecko.EventDispatcher;
+import org.mozilla.gecko.GeckoApp;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.content.Context;
 import android.util.Log;
 
 public class PromptService implements GeckoEventListener {
     private static final String LOGTAG = "GeckoPromptService";
 
     private final Context mContext;
 
     public PromptService(Context context) {
-        EventDispatcher.getInstance().registerGeckoThreadListener(this,
+        GeckoApp.getEventDispatcher().registerGeckoThreadListener(this,
             "Prompt:Show",
             "Prompt:ShowTop");
         mContext = context;
     }
 
     public void destroy() {
-        EventDispatcher.getInstance().unregisterGeckoThreadListener(this,
+        GeckoApp.getEventDispatcher().unregisterGeckoThreadListener(this,
             "Prompt:Show",
             "Prompt:ShowTop");
     }
 
     public void show(final String aTitle, final String aText, final PromptListItem[] aMenuList,
                      final int aChoiceMode, final Prompt.PromptCallback callback) {
         // The dialog must be created on the UI thread.
         ThreadUtils.postToUiThread(new Runnable() {
--- a/mobile/android/base/java/org/mozilla/gecko/push/PushService.java
+++ b/mobile/android/base/java/org/mozilla/gecko/push/PushService.java
@@ -9,16 +9,17 @@ import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
 import android.support.annotation.NonNull;
 import android.util.Log;
 
 import org.json.JSONException;
 import org.json.JSONObject;
 import org.mozilla.gecko.EventDispatcher;
+import org.mozilla.gecko.GeckoApp;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.GeckoService;
 import org.mozilla.gecko.GeckoThread;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.annotation.ReflectionTarget;
 import org.mozilla.gecko.db.BrowserDB;
@@ -240,22 +241,22 @@ public class PushService implements Bund
         Log.i(LOG_TAG, "Delivering dom/push message to decode to Gecko!");
         GeckoAppShell.notifyObservers("FxAccountsPush:ReceivedPushMessageToDecode",
                                       message.toString(),
                                       GeckoThread.State.PROFILE_READY);
     }
 
     protected void registerGeckoEventListener() {
         Log.d(LOG_TAG, "Registered Gecko event listener.");
-        EventDispatcher.getInstance().registerBackgroundThreadListener(this, GECKO_EVENTS);
+        GeckoApp.getEventDispatcher().registerBackgroundThreadListener(this, GECKO_EVENTS);
     }
 
     protected void unregisterGeckoEventListener() {
         Log.d(LOG_TAG, "Unregistered Gecko event listener.");
-        EventDispatcher.getInstance().unregisterBackgroundThreadListener(this, GECKO_EVENTS);
+        GeckoApp.getEventDispatcher().unregisterBackgroundThreadListener(this, GECKO_EVENTS);
     }
 
     @Override
     public void handleMessage(final String event, final Bundle message, final EventCallback callback) {
         Log.i(LOG_TAG, "Handling event: " + event);
         ThreadUtils.assertOnBackgroundThread();
 
         final Context context = GeckoAppShell.getApplicationContext();
--- a/mobile/android/base/java/org/mozilla/gecko/reader/ReadingListHelper.java
+++ b/mobile/android/base/java/org/mozilla/gecko/reader/ReadingListHelper.java
@@ -4,16 +4,17 @@
 
 package org.mozilla.gecko.reader;
 
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import org.mozilla.gecko.AboutPages;
 import org.mozilla.gecko.EventDispatcher;
+import org.mozilla.gecko.GeckoApp;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.icons.IconRequest;
 import org.mozilla.gecko.icons.Icons;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.NativeEventListener;
 import org.mozilla.gecko.util.NativeJSObject;
@@ -30,22 +31,22 @@ public final class ReadingListHelper imp
 
     protected final Context context;
     private final BrowserDB db;
 
     public ReadingListHelper(Context context, GeckoProfile profile) {
         this.context = context;
         this.db = BrowserDB.from(profile);
 
-        EventDispatcher.getInstance().registerGeckoThreadListener((NativeEventListener) this,
+        GeckoApp.getEventDispatcher().registerGeckoThreadListener((NativeEventListener) this,
             "Reader:FaviconRequest", "Reader:AddedToCache");
     }
 
     public void uninit() {
-        EventDispatcher.getInstance().unregisterGeckoThreadListener((NativeEventListener) this,
+        GeckoApp.getEventDispatcher().unregisterGeckoThreadListener((NativeEventListener) this,
             "Reader:FaviconRequest", "Reader:AddedToCache");
     }
 
     @Override
     public void handleMessage(final String event, final NativeJSObject message,
                               final EventCallback callback) {
         switch (event) {
             case "Reader:FaviconRequest": {
--- a/mobile/android/base/java/org/mozilla/gecko/text/FloatingToolbarTextSelection.java
+++ b/mobile/android/base/java/org/mozilla/gecko/text/FloatingToolbarTextSelection.java
@@ -11,16 +11,17 @@ import android.os.Build;
 import android.util.Log;
 import android.util.TypedValue;
 import android.view.ActionMode;
 
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import org.mozilla.gecko.EventDispatcher;
+import org.mozilla.gecko.GeckoApp;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.gfx.LayerView;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import java.util.List;
@@ -89,26 +90,26 @@ public class FloatingToolbarTextSelectio
     }
 
     @Override
     public void destroy() {
         unregisterFromEvents();
     }
 
     private void registerForEvents() {
-        EventDispatcher.getInstance().registerGeckoThreadListener(this,
+        GeckoApp.getEventDispatcher().registerGeckoThreadListener(this,
                 "TextSelection:ActionbarInit",
                 "TextSelection:ActionbarStatus",
                 "TextSelection:ActionbarUninit",
                 "TextSelection:Update",
                 "TextSelection:Visibility");
     }
 
     private void unregisterFromEvents() {
-        EventDispatcher.getInstance().unregisterGeckoThreadListener(this,
+        GeckoApp.getEventDispatcher().unregisterGeckoThreadListener(this,
                 "TextSelection:ActionbarInit",
                 "TextSelection:ActionbarStatus",
                 "TextSelection:ActionbarUninit",
                 "TextSelection:Update",
                 "TextSelection:Visibility");
     }
 
     @Override
--- a/mobile/android/base/java/org/mozilla/gecko/toolbar/PageActionLayout.java
+++ b/mobile/android/base/java/org/mozilla/gecko/toolbar/PageActionLayout.java
@@ -1,16 +1,17 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.toolbar;
 
 import org.mozilla.gecko.EventDispatcher;
+import org.mozilla.gecko.GeckoApp;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.NativeEventListener;
 import org.mozilla.gecko.util.NativeJSObject;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.widget.GeckoPopupMenu;
@@ -56,24 +57,24 @@ public class PageActionLayout extends Li
         setNumberShown(DEFAULT_PAGE_ACTIONS_SHOWN);
         refreshPageActionIcons();
     }
 
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
 
-        EventDispatcher.getInstance().registerGeckoThreadListener(this,
+        GeckoApp.getEventDispatcher().registerGeckoThreadListener(this,
             "PageActions:Add",
             "PageActions:Remove");
     }
 
     @Override
     protected void onDetachedFromWindow() {
-        EventDispatcher.getInstance().unregisterGeckoThreadListener(this,
+        GeckoApp.getEventDispatcher().unregisterGeckoThreadListener(this,
             "PageActions:Add",
             "PageActions:Remove");
 
         super.onDetachedFromWindow();
     }
 
     private void setNumberShown(int count) {
         ThreadUtils.assertOnUiThread();
--- a/mobile/android/base/java/org/mozilla/gecko/toolbar/SiteIdentityPopup.java
+++ b/mobile/android/base/java/org/mozilla/gecko/toolbar/SiteIdentityPopup.java
@@ -15,16 +15,17 @@ import android.support.v4.content.Contex
 import android.widget.ImageView;
 import android.widget.Toast;
 import org.json.JSONException;
 import org.json.JSONArray;
 import org.mozilla.gecko.AboutPages;
 import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.R;
+import org.mozilla.gecko.GeckoApp;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.SiteIdentity;
 import org.mozilla.gecko.SiteIdentity.SecurityMode;
 import org.mozilla.gecko.SiteIdentity.MixedMode;
 import org.mozilla.gecko.SiteIdentity.TrackingMode;
 import org.mozilla.gecko.SnackbarBuilder;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
@@ -91,18 +92,20 @@ public class SiteIdentityPopup extends A
     private final OnButtonClickListener mContentButtonClickListener;
 
     public SiteIdentityPopup(Context context) {
         super(context);
 
         mResources = mContext.getResources();
 
         mContentButtonClickListener = new ContentNotificationButtonListener();
-        EventDispatcher.getInstance().registerGeckoThreadListener(this, "Doorhanger:Logins");
-        EventDispatcher.getInstance().registerGeckoThreadListener(this, "Permissions:CheckResult");
+
+        GeckoApp.getEventDispatcher().registerGeckoThreadListener(this,
+                                                                  "Doorhanger:Logins",
+                                                                  "Permissions:CheckResult");
     }
 
     @Override
     protected void init() {
         super.init();
 
         // Make the popup focusable so it doesn't inadvertently trigger click events elsewhere
         // which may reshow the popup (see bug 785156)
@@ -541,18 +544,19 @@ public class SiteIdentityPopup extends A
         }
 
         if (lastVisibleDoorHanger != null) {
             lastVisibleDoorHanger.hideDivider();
         }
     }
 
     void destroy() {
-        EventDispatcher.getInstance().unregisterGeckoThreadListener(this, "Doorhanger:Logins");
-        EventDispatcher.getInstance().unregisterGeckoThreadListener(this, "Permissions:CheckResult");
+        GeckoApp.getEventDispatcher().unregisterGeckoThreadListener(this,
+                                                                    "Doorhanger:Logins",
+                                                                    "Permissions:CheckResult");
     }
 
     @Override
     public void dismiss() {
         super.dismiss();
         removeTrackingContentNotification();
         removeSelectLoginDoorhanger();
         mTitle.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/BaseGeckoInterface.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/BaseGeckoInterface.java
@@ -21,19 +21,26 @@ public class BaseGeckoInterface implemen
     // Bug 908744: Implement GeckoEventListener
     // Bug 908752: Implement SensorEventListener
     // Bug 908755: Implement LocationListener
     // Bug 908756: Implement Tabs.OnTabsChangedListener
     // Bug 908760: Implement GeckoEventResponder
 
     private final Context mContext;
     private GeckoProfile mProfile;
+    private final EventDispatcher eventDispatcher;
 
     public BaseGeckoInterface(Context context) {
         mContext = context;
+        eventDispatcher = new EventDispatcher();
+    }
+
+    @Override
+    public EventDispatcher getAppEventDispatcher() {
+        return eventDispatcher;
     }
 
     @Override
     public GeckoProfile getProfile() {
         // Fall back to default profile if we didn't load a specific one
         if (mProfile == null) {
             mProfile = GeckoProfile.get(mContext);
         }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/EventDispatcher.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/EventDispatcher.java
@@ -27,17 +27,17 @@ import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.CopyOnWriteArrayList;
 
 @RobocopTarget
 public final class EventDispatcher {
     private static final String LOGTAG = "GeckoEventDispatcher";
-    private static final String GUID = "__guid__";
+    /* package */ static final String GUID = "__guid__";
     private static final String STATUS_ERROR = "error";
     private static final String STATUS_SUCCESS = "success";
 
     private static final EventDispatcher INSTANCE = new EventDispatcher();
 
     /**
      * The capacity of a HashMap is rounded up to the next power-of-2. Every time the size
      * of the map goes beyond 75% of the capacity, the map is rehashed. Therefore, to
@@ -58,17 +58,17 @@ public final class EventDispatcher {
     private final Map<String, List<BundleEventListener>> mBackgroundThreadListeners =
         new HashMap<String, List<BundleEventListener>>(DEFAULT_BACKGROUND_EVENTS_COUNT);
 
     @ReflectionTarget
     public static EventDispatcher getInstance() {
         return INSTANCE;
     }
 
-    private EventDispatcher() {
+    public EventDispatcher() {
     }
 
     private <T> void registerListener(final Class<?> listType,
                                       final Map<String, List<T>> listenersMap,
                                       final T listener,
                                       final String[] events) {
         try {
             synchronized (listenersMap) {
@@ -186,74 +186,89 @@ public final class EventDispatcher {
         unregisterListener(mUiThreadListeners, listener, events);
     }
 
     public void unregisterBackgroundThreadListener(final BundleEventListener listener,
                                                    final String... events) {
         unregisterListener(mBackgroundThreadListeners, listener, events);
     }
 
-    public void dispatchEvent(final NativeJSContainer message) {
+    private List<NativeEventListener> getNativeListeners(final String type) {
+        final List<NativeEventListener> listeners;
+        synchronized (mGeckoThreadNativeListeners) {
+            listeners = mGeckoThreadNativeListeners.get(type);
+        }
+        return listeners;
+    }
+
+    private List<GeckoEventListener> getGeckoListeners(final String type) {
+        final List<GeckoEventListener> listeners;
+        synchronized (mGeckoThreadJSONListeners) {
+            listeners = mGeckoThreadJSONListeners.get(type);
+        }
+        return listeners;
+    }
+
+    public boolean dispatchEvent(final NativeJSContainer message) {
         // First try native listeners.
         final String type = message.optString("type", null);
         if (type == null) {
             Log.e(LOGTAG, "JSON message must have a type property");
-            return;
+            return true; // It may seem odd to return true here, but it's necessary to preserve the correct behavior.
         }
 
-        final List<NativeEventListener> listeners;
-        synchronized (mGeckoThreadNativeListeners) {
-            listeners = mGeckoThreadNativeListeners.get(type);
-        }
+        final List<NativeEventListener> listeners = getNativeListeners(type);
 
         final String guid = message.optString(GUID, null);
         EventCallback callback = null;
         if (guid != null) {
             callback = new GeckoEventCallback(guid, type);
         }
 
         if (listeners != null) {
             if (listeners.isEmpty()) {
                 Log.w(LOGTAG, "No listeners for " + type);
 
-                // There were native listeners, and they're gone.  Dispatch an error rather than
-                // looking for JSON listeners.
-                if (callback != null) {
-                    callback.sendError("No listeners for request");
-                }
+                // There were native listeners, and they're gone.  Return a failure rather than
+                // looking for JSON listeners. This is an optimization, as we can safely assume
+                // that an event which previously had native listeners will never have JSON
+                // listeners.
+                return false;
             }
             try {
                 for (final NativeEventListener listener : listeners) {
                     listener.handleMessage(type, message, callback);
                 }
             } catch (final NativeJSObject.InvalidPropertyException e) {
                 Log.e(LOGTAG, "Exception occurred while handling " + type, e);
             }
             // If we found native listeners, we assume we don't have any other types of listeners
             // and return early. This assumption is checked when registering listeners.
-            return;
+            return true;
         }
 
         // Check for thread event listeners before checking for JSON event listeners,
         // because checking for thread listeners is very fast and doesn't require us to
         // serialize into JSON and construct a JSONObject.
         if (dispatchToThreads(type, message, /* bundle */ null, callback)) {
             // If we found thread listeners, we assume we don't have any other types of listeners
             // and return early. This assumption is checked when registering listeners.
-            return;
+            return true;
         }
 
         try {
             // If we didn't find native listeners, try JSON listeners.
-            dispatchEvent(new JSONObject(message.toString()), callback);
+            return dispatchEvent(new JSONObject(message.toString()), callback);
         } catch (final JSONException e) {
             Log.e(LOGTAG, "Cannot parse JSON", e);
         } catch (final UnsupportedOperationException e) {
             Log.e(LOGTAG, "Cannot convert message to JSON", e);
         }
+
+        return true;
     }
 
     /**
      * Dispatch event to any registered Bundle listeners (non-Gecko thread listeners).
      *
      * @param message Bundle message with "type" value specifying the event type.
      */
     public void dispatch(final Bundle message) {
@@ -310,17 +325,17 @@ public final class EventDispatcher {
         }
 
         if (dispatchToThread(type, jsMessage, bundleMessage, callback,
                              mBackgroundThreadListeners, ThreadUtils.getBackgroundHandler())) {
             return true;
         }
 
         if (jsMessage == null) {
-            Log.w(LOGTAG, "No listeners for " + type);
+            Log.w(LOGTAG, "No listeners for " + type + " in dispatchToThreads");
         }
 
         if (!AppConstants.RELEASE_BUILD && jsMessage == null) {
             // We're dispatching a Bundle message. Because Gecko thread listeners are not
             // supported for Bundle messages, do a sanity check to make sure we don't have
             // matching Gecko thread listeners.
             boolean hasGeckoListener = false;
             synchronized (mGeckoThreadNativeListeners) {
@@ -350,24 +365,20 @@ public final class EventDispatcher {
         // on a separate thread.
         synchronized (listenersMap) {
             final List<BundleEventListener> listeners = listenersMap.get(type);
             if (listeners == null) {
                 return false;
             }
 
             if (listeners.isEmpty()) {
-                Log.w(LOGTAG, "No listeners for " + type);
+                Log.w(LOGTAG, "No listeners for " + type + " in dispatchToThread");
 
                 // There were native listeners, and they're gone.
-                // Dispatch an error rather than looking for more listeners.
-                if (callback != null) {
-                    callback.sendError("No listeners for request");
-                }
-                return true;
+                return false;
             }
 
             final Bundle messageAsBundle;
             try {
                 messageAsBundle = jsMessage != null ? jsMessage.toBundle() : bundleMessage;
             } catch (final NativeJSObject.InvalidPropertyException e) {
                 Log.e(LOGTAG, "Exception occurred while handling " + type, e);
                 return true;
@@ -381,43 +392,40 @@ public final class EventDispatcher {
                         listener.handleMessage(type, messageAsBundle, callback);
                     }
                 });
             }
             return true;
         }
     }
 
-    public void dispatchEvent(final JSONObject message, final EventCallback callback) {
+    public boolean dispatchEvent(final JSONObject message, final EventCallback callback) {
         // {
         //   "type": "value",
         //   "event_specific": "value",
         //   ...
         try {
             final String type = message.getString("type");
 
-            List<GeckoEventListener> listeners;
-            synchronized (mGeckoThreadJSONListeners) {
-                listeners = mGeckoThreadJSONListeners.get(type);
-            }
-            if (listeners == null || listeners.isEmpty()) {
-                Log.w(LOGTAG, "No listeners for " + type);
+            final List<GeckoEventListener> listeners = getGeckoListeners(type);
 
-                // If there are no listeners, dispatch an error.
-                if (callback != null) {
-                    callback.sendError("No listeners for request");
-                }
-                return;
+            if (listeners == null || listeners.isEmpty()) {
+                Log.w(LOGTAG, "No listeners for " + type + " in dispatchEvent");
+
+                return false;
             }
+
             for (final GeckoEventListener listener : listeners) {
                 listener.handleMessage(type, message);
             }
         } catch (final JSONException e) {
             Log.e(LOGTAG, "handleGeckoMessage throws " + e, e);
         }
+
+        return true;
     }
 
     @RobocopTarget
     @Deprecated
     public static void sendResponse(JSONObject message, Object response) {
         sendResponseHelper(STATUS_SUCCESS, message, response);
     }
 
@@ -441,17 +449,17 @@ public final class EventDispatcher {
                 GeckoAppShell.notifyObservers(topic, wrapper.toString(),
                                               GeckoThread.State.PROFILE_READY);
             }
         } catch (final JSONException e) {
             Log.e(LOGTAG, "Unable to send response", e);
         }
     }
 
-    private static class GeckoEventCallback implements EventCallback {
+    /* package */ static class GeckoEventCallback implements EventCallback {
         private final String guid;
         private final String type;
         private boolean sent;
 
         public GeckoEventCallback(final String guid, final String type) {
             this.guid = guid;
             this.type = type;
         }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java
@@ -1714,16 +1714,17 @@ public class GeckoAppShell
 
     public interface AppStateListener {
         public void onPause();
         public void onResume();
         public void onOrientationChanged();
     }
 
     public interface GeckoInterface {
+        public EventDispatcher getAppEventDispatcher();
         public GeckoProfile getProfile();
         public Activity getActivity();
         public String getDefaultUAString();
         public void doRestart();
         public void setFullScreen(boolean fullscreen);
         public void addPluginView(View view);
         public void removePluginView(final View view);
         public void enableCameraView();
@@ -1944,17 +1945,25 @@ public class GeckoAppShell
      */
     @WrapForJNI(calledFrom = "gecko")
     private static void enableBatteryNotifications() {
         GeckoBatteryManager.enableNotifications();
     }
 
     @WrapForJNI(calledFrom = "gecko")
     private static void handleGeckoMessage(final NativeJSContainer message) {
-        EventDispatcher.getInstance().dispatchEvent(message);
+        final boolean success = EventDispatcher.getInstance().dispatchEvent(message) |
+                                getGeckoInterface().getAppEventDispatcher().dispatchEvent(message);
+        if (!success) {
+            final String type = message.optString("type", null);
+            final String guid = message.optString(EventDispatcher.GUID, null);
+            if (type != null && guid != null) {
+                (new EventDispatcher.GeckoEventCallback(guid, type)).sendError("No listeners for request");
+            }
+        }
         message.disposeNative();
     }
 
     @WrapForJNI(calledFrom = "gecko")
     private static void disableBatteryNotifications() {
         GeckoBatteryManager.disableNotifications();
     }
 
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoEditable.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoEditable.java
@@ -938,22 +938,22 @@ final class GeckoEditable extends JNIObj
 
         // Register/unregister Gecko-side text selection listeners
         // and update the mGeckoFocused flag.
         if (type == NOTIFY_IME_OF_BLUR && mGeckoFocused) {
             // Check for focus here because Gecko may send us a blur before a focus in some
             // cases, and we don't want to unregister an event that was not registered.
             mGeckoFocused = false;
             mSuppressCompositions = false;
-            EventDispatcher.getInstance().
+            GeckoAppShell.getGeckoInterface().getAppEventDispatcher().
                 unregisterGeckoThreadListener(this, "TextSelection:DraggingHandle");
         } else if (type == NOTIFY_IME_OF_FOCUS) {
             mGeckoFocused = true;
             mSuppressCompositions = false;
-            EventDispatcher.getInstance().
+            GeckoAppShell.getGeckoInterface().getAppEventDispatcher().
                 registerGeckoThreadListener(this, "TextSelection:DraggingHandle");
         }
     }
 
     @WrapForJNI(calledFrom = "gecko") @Override
     public void notifyIMEContext(final int state, final String typeHint,
                                  final String modeHint, final String actionHint) {
         if (DEBUG) {
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoThread.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoThread.java
@@ -513,16 +513,17 @@ public class GeckoThread extends Thread 
         GeckoLoader.nativeRun(args);
 
         // And... we're done.
         setState(State.EXITED);
 
         try {
             final JSONObject msg = new JSONObject();
             msg.put("type", "Gecko:Exited");
+            GeckoAppShell.getGeckoInterface().getAppEventDispatcher().dispatchEvent(msg, null);
             EventDispatcher.getInstance().dispatchEvent(msg, null);
         } catch (final JSONException e) {
             Log.e(LOGTAG, "unable to dispatch event", e);
         }
 
         // Remove pumpMessageLoop() idle handler
         Looper.myQueue().removeIdleHandler(idleHandler);
     }
--- a/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/FennecNativeDriver.java
+++ b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/FennecNativeDriver.java
@@ -243,17 +243,17 @@ public class FennecNativeDriver implemen
     }
     @Override
     public int getHeight() {
         return mHeight;
     }
 
     @Override
     public void setupScrollHandling() {
-        EventDispatcher.getInstance().registerGeckoThreadListener(new GeckoEventListener() {
+        GeckoApp.getEventDispatcher().registerGeckoThreadListener(new GeckoEventListener() {
             @Override
             public void handleMessage(final String event, final JSONObject message) {
                 try {
                     mScrollHeight = message.getInt("y");
                     mHeight = message.getInt("cheight");
                     // We don't want a height of 0. That means it's a bad response.
                     if (mHeight > 0) {
                         mPageHeight = message.getInt("height");
--- a/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testFilePicker.java
+++ b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testFilePicker.java
@@ -2,16 +2,17 @@
  * 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/. */
 
 package org.mozilla.gecko.tests;
 
 import static org.mozilla.gecko.tests.helpers.AssertionHelper.fFail;
 
 import org.mozilla.gecko.EventDispatcher;
+import org.mozilla.gecko.GeckoApp;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.util.GeckoEventListener;
 
 import org.json.JSONException;
 import org.json.JSONObject;
 
 public class testFilePicker extends JavascriptTest implements GeckoEventListener {
     private static final String TEST_FILENAME = "/mnt/sdcard/my-favorite-martian.png";
@@ -34,18 +35,18 @@ public class testFilePicker extends Java
             mActions.sendGeckoEvent("FilePicker:Result", message.toString());
         }
     }
 
     @Override
     public void setUp() throws Exception {
         super.setUp();
 
-        EventDispatcher.getInstance().registerGeckoThreadListener(this, "FilePicker:Show");
+        GeckoApp.getEventDispatcher().registerGeckoThreadListener(this, "FilePicker:Show");
     }
 
     @Override
     public void tearDown() throws Exception {
         super.tearDown();
 
-        EventDispatcher.getInstance().unregisterGeckoThreadListener(this, "FilePicker:Show");
+        GeckoApp.getEventDispatcher().unregisterGeckoThreadListener(this, "FilePicker:Show");
     }
 }
--- a/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testFindInPage.java
+++ b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testFindInPage.java
@@ -47,27 +47,27 @@ public class testFindInPage extends Java
         }
     }
 
     @Override
     public void setUp() throws Exception {
         super.setUp();
 
         EventDispatcher.getInstance().registerGeckoThreadListener(this,
-                "Test:FindInPage",
-                "Test:CloseFindInPage");
+                                                                  "Test:FindInPage",
+                                                                  "Test:CloseFindInPage");
     }
 
     @Override
     public void tearDown() throws Exception {
         super.tearDown();
 
         EventDispatcher.getInstance().unregisterGeckoThreadListener(this,
-                "Test:FindInPage",
-                "Test:CloseFindInPage");
+                                                                    "Test:FindInPage",
+                                                                    "Test:CloseFindInPage");
     }
 
     public void findText(String text, int nrOfMatches){
         selectMenuItem(mStringHelper.FIND_IN_PAGE_LABEL);
         close = mDriver.findElement(getActivity(), R.id.find_close);
         boolean success = waitForCondition ( new Condition() {
             @Override
             public boolean isSatisfied() {
--- a/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testTrackingProtection.java
+++ b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testTrackingProtection.java
@@ -2,16 +2,17 @@
  * 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/. */
 
 package org.mozilla.gecko.tests;
 
 import static org.mozilla.gecko.tests.helpers.AssertionHelper.fFail;
 
 import org.mozilla.gecko.EventDispatcher;
+import org.mozilla.gecko.GeckoApp;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.util.GeckoEventListener;
 
 import org.json.JSONException;
 import org.json.JSONObject;
 
 public class testTrackingProtection extends JavascriptTest implements GeckoEventListener {
     private String mLastTracking;
@@ -43,20 +44,22 @@ public class testTrackingProtection exte
             }
         }
     }
 
     @Override
     public void setUp() throws Exception {
         super.setUp();
 
-        EventDispatcher.getInstance().registerGeckoThreadListener(this, "Content:SecurityChange");
-        EventDispatcher.getInstance().registerGeckoThreadListener(this, "Test:Expected");
+        EventDispatcher.getInstance().registerGeckoThreadListener(this,
+                                                                  "Content:SecurityChange",
+                                                                  "Test:Expected");
     }
 
     @Override
     public void tearDown() throws Exception {
         super.tearDown();
 
-        EventDispatcher.getInstance().unregisterGeckoThreadListener(this, "Content:SecurityChange");
-        EventDispatcher.getInstance().unregisterGeckoThreadListener(this, "Test:Expected");
+        EventDispatcher.getInstance().unregisterGeckoThreadListener(this,
+                                                                    "Content:SecurityChange",
+                                                                    "Test:Expected");
     }
 }