Bug 832987 - Make sure the launch state is updated on Gecko:Ready even if GeckoApp isn't around at the time. r=cpeterson
authorKartikaya Gupta <kgupta@mozilla.com>
Wed, 23 Jan 2013 09:38:57 -0500
changeset 119644 a2cc396069a2a564e99f71fd7a7acfe48bebb34f
parent 119643 594b9c2a8ccc353b9708f1d04f59daea512c92fc
child 119645 9523a36fc9188ca1c2287da2e1829ec51855d9a2
push id24219
push userryanvm@gmail.com
push dateThu, 24 Jan 2013 17:36:06 +0000
treeherdermozilla-central@fa969919b1bb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscpeterson
bugs832987
milestone21.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 832987 - Make sure the launch state is updated on Gecko:Ready even if GeckoApp isn't around at the time. r=cpeterson
mobile/android/base/BrowserApp.java
mobile/android/base/GeckoApp.java
mobile/android/base/GeckoAppShell.java
mobile/android/base/GeckoApplication.java
mobile/android/base/GeckoConnectivityReceiver.java
mobile/android/base/GeckoThread.java
mobile/android/base/MarketplaceApp.java.in
mobile/android/base/MemoryMonitor.java
mobile/android/base/db/GeckoProvider.java.in
mobile/android/base/tests/BaseTest.java.in
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -966,17 +966,17 @@ abstract public class BrowserApp extends
       });
     }
 
     @Override
     public boolean onPrepareOptionsMenu(Menu aMenu) {
         if (aMenu == null)
             return false;
 
-        if (!checkLaunchState(LaunchState.GeckoRunning))
+        if (!GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoRunning))
             aMenu.findItem(R.id.settings).setEnabled(false);
 
         Tab tab = Tabs.getInstance().getSelectedTab();
         MenuItem bookmark = aMenu.findItem(R.id.bookmark);
         MenuItem forward = aMenu.findItem(R.id.forward);
         MenuItem share = aMenu.findItem(R.id.share);
         MenuItem saveAsPDF = aMenu.findItem(R.id.save_as_pdf);
         MenuItem charEncoding = aMenu.findItem(R.id.char_encoding);
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -182,47 +182,20 @@ abstract public class GeckoApp
 
     protected int mRestoreMode = RESTORE_NONE;
     protected boolean mInitialized = false;
     private Telemetry.Timer mJavaUiStartupTimer;
     private Telemetry.Timer mGeckoReadyStartupTimer;
 
     private String mPrivateBrowsingSession;
 
-    public enum LaunchState {Launching, WaitForDebugger,
-                             Launched, GeckoRunning, GeckoExiting};
-    private static LaunchState sLaunchState = LaunchState.Launching;
-
     abstract public int getLayout();
     abstract public boolean hasTabsSideBar();
     abstract protected String getDefaultProfileName();
 
-    public static boolean checkLaunchState(LaunchState checkState) {
-        synchronized(sLaunchState) {
-            return sLaunchState == checkState;
-        }
-    }
-
-    static void setLaunchState(LaunchState setState) {
-        synchronized(sLaunchState) {
-            sLaunchState = setState;
-        }
-    }
-
-    // if mLaunchState is equal to checkState this sets mLaunchState to setState
-    // and return true. Otherwise we return false.
-    static boolean checkAndSetLaunchState(LaunchState checkState, LaunchState setState) {
-        synchronized(sLaunchState) {
-            if (sLaunchState != checkState)
-                return false;
-            sLaunchState = setState;
-            return true;
-        }
-    }
-
     void toggleChrome(final boolean aShow) { }
 
     void focusChrome() { }
 
     public void onTabChanged(Tab tab, Tabs.TabEvents msg, Object data) {
         // When a tab is closed, it is always unselected first.
         // When a tab is unselected, another tab is always selected first.
         switch(msg) {
@@ -569,23 +542,20 @@ abstract public class GeckoApp
 
         return super.onMenuOpened(featureId, menu);
     }
 
     @Override
     public boolean onOptionsItemSelected(MenuItem item) {
         switch (item.getItemId()) {
             case R.id.quit:
-                synchronized(sLaunchState) {
-                    if (sLaunchState == LaunchState.GeckoRunning)
-                        GeckoAppShell.notifyGeckoOfEvent(
-                            GeckoEvent.createBroadcastEvent("Browser:Quit", null));
-                    else
-                        System.exit(0);
-                    sLaunchState = LaunchState.GeckoExiting;
+                if (GeckoThread.checkAndSetLaunchState(GeckoThread.LaunchState.GeckoRunning, GeckoThread.LaunchState.GeckoExiting)) {
+                    GeckoAppShell.notifyGeckoOfEvent(GeckoEvent.createBroadcastEvent("Browser:Quit", null));
+                } else {
+                    System.exit(0);
                 }
                 return true;
             default:
                 return super.onOptionsItemSelected(item);
         }
     }
 
     @Override
@@ -854,18 +824,16 @@ abstract public class GeckoApp
                 final String uri = message.getString("uri");
                 final String title = message.getString("title");
                 handleLoadError(tabId, uri, title);
             } else if (event.equals("Content:PageShow")) {
                 final int tabId = message.getInt("tabID");
                 handlePageShow(tabId);
             } else if (event.equals("Gecko:Ready")) {
                 mGeckoReadyStartupTimer.stop();
-                setLaunchState(GeckoApp.LaunchState.GeckoRunning);
-                GeckoAppShell.sendPendingEventsToGecko();
                 connectGeckoLayerClient();
             } else if (event.equals("ToggleChrome:Hide")) {
                 toggleChrome(false);
             } else if (event.equals("ToggleChrome:Show")) {
                 toggleChrome(true);
             } else if (event.equals("ToggleChrome:Focus")) {
                 focusChrome();
             } else if (event.equals("DOMFullScreen:Start")) {
@@ -1765,23 +1733,23 @@ abstract public class GeckoApp
         }
 
         Telemetry.HistogramAdd("FENNEC_STARTUP_GECKOAPP_ACTION", startupAction.ordinal());
 
         if (!mIsRestoringActivity) {
             sGeckoThread = new GeckoThread(intent, passedUri);
         }
         if (!ACTION_DEBUG.equals(action) &&
-            checkAndSetLaunchState(LaunchState.Launching, LaunchState.Launched)) {
+            GeckoThread.checkAndSetLaunchState(GeckoThread.LaunchState.Launching, GeckoThread.LaunchState.Launched)) {
             sGeckoThread.start();
         } else if (ACTION_DEBUG.equals(action) &&
-            checkAndSetLaunchState(LaunchState.Launching, LaunchState.WaitForDebugger)) {
+            GeckoThread.checkAndSetLaunchState(GeckoThread.LaunchState.Launching, GeckoThread.LaunchState.WaitForDebugger)) {
             mMainHandler.postDelayed(new Runnable() {
                 public void run() {
-                    setLaunchState(LaunchState.Launching);
+                    GeckoThread.setLaunchState(GeckoThread.LaunchState.Launching);
                     sGeckoThread.start();
                 }
             }, 1000 * 5 /* 5 seconds */);
         }
 
         //register for events
         registerEventListener("DOMContentLoaded");
         registerEventListener("DOMTitleChanged");
@@ -1868,24 +1836,24 @@ abstract public class GeckoApp
                   XXXX see bug 635342
                    We want to disable this code if possible.  It is about 145ms in runtime
                 SharedPreferences settings = getPreferences(Activity.MODE_PRIVATE);
                 String localeCode = settings.getString(getPackageName() + ".locale", "");
                 if (localeCode != null && localeCode.length() > 0)
                     GeckoAppShell.setSelectedLocale(localeCode);
                 */
 
-                if (!checkLaunchState(LaunchState.Launched)) {
+                if (!GeckoThread.checkLaunchState(GeckoThread.LaunchState.Launched)) {
                     return;
                 }
             }
         }, 50);
 
         if (mIsRestoringActivity) {
-            setLaunchState(GeckoApp.LaunchState.GeckoRunning);
+            GeckoThread.setLaunchState(GeckoThread.LaunchState.GeckoRunning);
             Tab selectedTab = Tabs.getInstance().getSelectedTab();
             if (selectedTab != null)
                 Tabs.getInstance().notifyListeners(selectedTab, Tabs.TabEvents.SELECTED);
             connectGeckoLayerClient();
             GeckoAppShell.setLayerClient(mLayerView.getLayerClient());
             GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Viewport:Flush", null));
         }
 
@@ -2038,17 +2006,17 @@ abstract public class GeckoApp
         new CopyOnWriteArrayList<String>(kRedirectWhiteListArray);
 
     private boolean isHostOnRedirectWhitelist(String host) {
         return kRedirectWhiteList.contains(host);
     }
 
     @Override
     protected void onNewIntent(Intent intent) {
-        if (checkLaunchState(LaunchState.GeckoExiting)) {
+        if (GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoExiting)) {
             // We're exiting and shouldn't try to do anything else just incase
             // we're hung for some reason we'll force the process to exit
             System.exit(0);
             return;
         }
 
         // if we were previously OOM killed, we can end up here when launching
         // from external shortcuts, so set this as the intent for initialization
@@ -2056,17 +2024,17 @@ abstract public class GeckoApp
             setIntent(intent);
             return;
         }
 
         // don't perform any actions if launching from recent apps
         if ((intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0)
             return;
 
-        if (checkLaunchState(LaunchState.Launched)) {
+        if (GeckoThread.checkLaunchState(GeckoThread.LaunchState.Launched)) {
             Uri data = intent.getData();
             Bundle bundle = intent.getExtras();
             // if the intent has data (i.e. a URI to be opened) and the scheme
             // is either http, we'll prefetch it, which means warming
             // up the radio and DNS cache by connecting and parsing the redirect
             // if the return code is between 300 and 400
             if (data != null && 
                 "http".equals(data.getScheme()) &&
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -579,17 +579,17 @@ public class GeckoAppShell
                 GeckoEvent e = gPendingEvents.removeFirst();
                 notifyGeckoOfEvent(e);
             }
         } catch (NoSuchElementException e) {}
     }
 
     /* This method is referenced by Robocop via reflection. */
     public static void sendEventToGecko(GeckoEvent e) {
-        if (GeckoApp.checkLaunchState(GeckoApp.LaunchState.GeckoRunning)) {
+        if (GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoRunning)) {
             notifyGeckoOfEvent(e);
         } else {
             gPendingEvents.addLast(e);
         }
     }
 
     public static void sendEventToGeckoSync(GeckoEvent e) {
         sendEventToGecko(e);
@@ -787,18 +787,18 @@ public class GeckoAppShell
     }
 
     public static void returnIMEQueryResult(String result, int selectionStart, int selectionLength) {
         // This method may be called from JNI to report Gecko's current selection indexes, but
         // Native Fennec doesn't care because the Java code already knows the selection indexes.
     }
 
     static void onXreExit() {
-        // mLaunchState can only be Launched or GeckoRunning at this point
-        GeckoApp.setLaunchState(GeckoApp.LaunchState.GeckoExiting);
+        // The launch state can only be Launched or GeckoRunning at this point
+        GeckoThread.setLaunchState(GeckoThread.LaunchState.GeckoExiting);
         if (gRestartScheduled) {
             GeckoApp.mAppContext.doRestart();
         } else {
             GeckoApp.mAppContext.finish();
         }
 
         Log.d(LOGTAG, "Killing via System.exit()");
         System.exit(0);
--- a/mobile/android/base/GeckoApplication.java
+++ b/mobile/android/base/GeckoApplication.java
@@ -36,17 +36,17 @@ public class GeckoApplication extends Ap
         mInBackground = true;
 
         GeckoAppShell.sendEventToGecko(GeckoEvent.createPauseEvent(true));
         GeckoConnectivityReceiver.getInstance().stop();
         GeckoNetworkManager.getInstance().stop();
     }
 
     protected void onActivityResume(GeckoActivity activity) {
-        if (GeckoApp.checkLaunchState(GeckoApp.LaunchState.GeckoRunning))
+        if (GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoRunning))
             GeckoAppShell.sendEventToGecko(GeckoEvent.createResumeEvent(true));
         GeckoConnectivityReceiver.getInstance().start();
         GeckoNetworkManager.getInstance().start();
 
         mInBackground = false;
     }
 
     public boolean isApplicationInBackground() {
--- a/mobile/android/base/GeckoConnectivityReceiver.java
+++ b/mobile/android/base/GeckoConnectivityReceiver.java
@@ -70,13 +70,13 @@ public class GeckoConnectivityReceiver e
         if (info == null) {
             status = LINK_DATA_UNKNOWN;
         } else if (!info.isConnected()) {
             status = LINK_DATA_DOWN;
         } else {
             status = LINK_DATA_UP;
         }
 
-        if (GeckoApp.checkLaunchState(GeckoApp.LaunchState.GeckoRunning)) {
+        if (GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoRunning)) {
             GeckoAppShell.onChangeNetworkLinkStatus(status);
         }
     }
 }
--- a/mobile/android/base/GeckoThread.java
+++ b/mobile/android/base/GeckoThread.java
@@ -1,35 +1,49 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; 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;
 
 import org.mozilla.gecko.gfx.GfxInfoThread;
+import org.mozilla.gecko.util.GeckoEventListener;
+
+import org.json.JSONObject;
 
 import android.content.Intent;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.os.SystemClock;
 import android.util.Log;
 
 import java.util.Locale;
 
-public class GeckoThread extends Thread {
+public class GeckoThread extends Thread implements GeckoEventListener {
     private static final String LOGTAG = "GeckoThread";
 
-    Intent mIntent;
-    String mUri;
+    public enum LaunchState {
+        Launching,
+        WaitForDebugger,
+        Launched,
+        GeckoRunning,
+        GeckoExiting
+    };
+
+    private static LaunchState sLaunchState = LaunchState.Launching;
+
+    private final Intent mIntent;
+    private final String mUri;
 
     GeckoThread(Intent intent, String uri) {
         mIntent = intent;
         mUri = uri;
         setName("Gecko");
+        GeckoAppShell.getEventDispatcher().registerEventListener("Gecko:Ready", this);
     }
 
     public void run() {
 
         // Here we start the GfxInfo thread, which will query OpenGL
         // system information for Gecko. This must be done early enough that the data will be
         // ready by the time it's needed to initialize the LayerManager (it takes about 100 ms
         // to obtain). Doing it here seems to have no negative effect on startup time. See bug 766251.
@@ -74,9 +88,42 @@ public class GeckoThread extends Thread 
 
         // and then fire us up
         Log.i(LOGTAG, "RunGecko - args = " + args);
         GeckoAppShell.runGecko(app.getApplication().getPackageResourcePath(),
                                args,
                                mUri,
                                type);
     }
+
+    public void handleMessage(String event, JSONObject message) {
+        if ("Gecko:Ready".equals(event)) {
+            GeckoAppShell.getEventDispatcher().unregisterEventListener(event, this);
+            setLaunchState(LaunchState.GeckoRunning);
+            GeckoAppShell.sendPendingEventsToGecko();
+        }
+    }
+
+    public static boolean checkLaunchState(LaunchState checkState) {
+        synchronized (sLaunchState) {
+            return sLaunchState == checkState;
+        }
+    }
+
+    static void setLaunchState(LaunchState setState) {
+        synchronized (sLaunchState) {
+            sLaunchState = setState;
+        }
+    }
+
+    /**
+     * Set the launch state to <code>setState</code> and return true if the current launch
+     * state is <code>checkState</code>; otherwise do nothing and return false.
+     */
+    static boolean checkAndSetLaunchState(LaunchState checkState, LaunchState setState) {
+        synchronized (sLaunchState) {
+            if (sLaunchState != checkState)
+                return false;
+            sLaunchState = setState;
+            return true;
+        }
+    }
 }
--- a/mobile/android/base/MarketplaceApp.java.in
+++ b/mobile/android/base/MarketplaceApp.java.in
@@ -13,16 +13,17 @@ import android.os.SystemClock;
 import android.util.Log;
 import android.view.MenuItem;
 
 import org.json.JSONObject;
 
 import org.mozilla.gecko.GeckoApp;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoEvent;
+import org.mozilla.gecko.GeckoThread;
 import org.mozilla.gecko.WebAppAllocator;
 
 /*
 * This is a stub activity, meant to just install the marketplace WebApp
 * and then launch it
 */
 public class MarketplaceApp extends WebApp {
     private static final String LOGTAG = "GeckoMarketplaceApp";
@@ -51,17 +52,17 @@ public class MarketplaceApp extends WebA
     protected String getDefaultProfileName() {
         return "default";
     }
 
     @Override
     protected void onNewIntent(Intent intent) {
         Log.w(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - onNewIntent");
 
-        if (checkLaunchState(LaunchState.GeckoExiting)) {
+        if (GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoExiting)) {
             // We're exiting and shouldn't try to do anything else just incase
             // we're hung for some reason we'll force the process to exit
             System.exit(0);
             return;
         }
 
         // if we were previously OOM killed, we can end up here when launching
         // from external shortcuts, so set this as the intent for initialization
--- a/mobile/android/base/MemoryMonitor.java
+++ b/mobile/android/base/MemoryMonitor.java
@@ -145,17 +145,17 @@ class MemoryMonitor extends BroadcastRec
             // if we're not going to a higher level we probably don't
             // need to run another round of the same memory reductions
             // we did on the last memory pressure increase.
             return;
         }
 
         // TODO hook in memory-reduction stuff for different levels here
         if (level >= MEMORY_PRESSURE_MEDIUM) {
-            if (GeckoApp.checkLaunchState(GeckoApp.LaunchState.GeckoRunning)) {
+            if (GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoRunning)) {
                 GeckoAppShell.onLowMemory();
             }
             GeckoAppShell.geckoEventSync();
             Favicons.getInstance().clearMemCache();
         }
     }
 
     private boolean decreaseMemoryPressure() {
@@ -203,17 +203,17 @@ class MemoryMonitor extends BroadcastRec
         private final Context mContext;
         public StorageReducer(final Context context) {
             this.mContext = context;
         }
 
         @Override
         public void run() {
             // this might get run right on startup, if so wait 10 seconds and try again
-            if (!GeckoApp.checkLaunchState(GeckoApp.LaunchState.GeckoRunning)) {
+            if (!GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoRunning)) {
                 GeckoAppShell.getHandler().postDelayed(this, 10000);
                 return;
             }
 
             if (!mStoragePressure) {
                 // pressure is off, so we can abort
                 return;
             }
--- a/mobile/android/base/db/GeckoProvider.java.in
+++ b/mobile/android/base/db/GeckoProvider.java.in
@@ -9,20 +9,20 @@ import java.io.File;
 import java.io.IOException;
 import java.lang.IllegalArgumentException;
 import java.util.HashMap;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.Random;
 
-import org.mozilla.gecko.GeckoApp;
 import org.mozilla.gecko.GeckoAppShell;
+import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.GeckoProfile;
-import org.mozilla.gecko.GeckoEvent;
+import org.mozilla.gecko.GeckoThread;
 import org.mozilla.gecko.db.BrowserContract.CommonColumns;
 import org.mozilla.gecko.db.DBUtils;
 import org.mozilla.gecko.db.BrowserContract.Passwords;
 import org.mozilla.gecko.db.BrowserContract.DeletedPasswords;
 import org.mozilla.gecko.db.BrowserContract.SyncColumns;
 import org.mozilla.gecko.db.BrowserContract;
 import org.mozilla.gecko.sqlite.SQLiteBridge;
 import org.mozilla.gecko.sqlite.SQLiteBridgeException;
@@ -127,17 +127,17 @@ public abstract class GeckoProvider exte
 
             // this will throw if the database can't be found
             // we should attempt to set it up if Gecko is running
             dbNeedsSetup = true;
             Log.e(mLogTag, "Error getting version ", ex);
 
             // if Gecko is not running, we should bail out. Otherwise we try to
             // let Gecko build the database for us
-            if (!GeckoApp.checkLaunchState(GeckoApp.LaunchState.GeckoRunning)) {
+            if (!GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoRunning)) {
                 Log.e(mLogTag, "Can not set up database. Gecko is not running");
                 return null;
             }
         }
 
         // If the database is not set up yet, or is the wrong schema version, we send an initialize
         // call to Gecko. Gecko will handle building the database file correctly, as well as any
         // migrations that are necessary
--- a/mobile/android/base/tests/BaseTest.java.in
+++ b/mobile/android/base/tests/BaseTest.java.in
@@ -50,23 +50,23 @@ abstract class BaseTest extends Activity
     private String mLogFile;
     protected String mProfile;
     private static ListView bookmarkList;
 
     protected void blockForGeckoReady() {
         try {
             Actions.EventExpecter geckoReadyExpector = mActions.expectGeckoEvent("Gecko:Ready");
             ClassLoader classLoader = getActivity().getClassLoader();
-            Class appsCls = classLoader.loadClass("org.mozilla.gecko.GeckoApp");
-            Class launchStateCls = classLoader.loadClass("org.mozilla.gecko.GeckoApp$LaunchState");
+            Class appsCls = classLoader.loadClass("org.mozilla.gecko.GeckoThread");
+            Class launchStateCls = classLoader.loadClass("org.mozilla.gecko.GeckoThread$LaunchState");
             Method checkLaunchState =  appsCls.getMethod("checkLaunchState", launchStateCls);
             Object states[] =  launchStateCls.getEnumConstants();
             Boolean ret = (Boolean)checkLaunchState.invoke(null, states[3]);
             if (!ret.booleanValue()) {
-	        geckoReadyExpector.blockForEvent();
+                geckoReadyExpector.blockForEvent();
             }
         } catch (Exception e) {
             mAsserter.dumpLog("Exception in blockForGeckoReady", e);
         }
     }
 
     static {
         try {