Bug 1362191 - 2. Move shutdown to GeckoApplication; r=snorp
authorJim Chen <nchen@mozilla.com>
Thu, 11 May 2017 16:39:30 -0400
changeset 357980 2458deaf60644dc8aaf1878b72ddc3c61b467507
parent 357979 11495bc7075515c06ea79d38c5c3d223304bccb7
child 357981 632e8d8dd0774748b3ad40776f73d309dbded9aa
push id31808
push usercbook@mozilla.com
push dateFri, 12 May 2017 12:37:49 +0000
treeherdermozilla-central@030c0a7c8781 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssnorp
bugs1362191
milestone55.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 1362191 - 2. Move shutdown to GeckoApplication; r=snorp Move the code that actually performs shutdown from BrowserApp to GeckoApplication, so we can shutdown even without an active BrowserApp. From inside GeckoApp/BrowserApp, all shutdown now go through finishAndShutdown(), which destroys the Activity before shutting down.
mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
mobile/android/base/java/org/mozilla/gecko/GeckoApplication.java
mobile/android/base/java/org/mozilla/gecko/Restarter.java
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -5,17 +5,16 @@
 
 package org.mozilla.gecko;
 
 import android.Manifest;
 import android.annotation.TargetApi;
 import android.app.DownloadManager;
 import android.content.ContentProviderClient;
 import android.os.Environment;
-import android.os.Process;
 import android.support.annotation.NonNull;
 import android.support.annotation.UiThread;
 
 import org.mozilla.gecko.activitystream.ActivityStream;
 import org.mozilla.gecko.adjust.AdjustBrowserAppDelegate;
 import org.mozilla.gecko.annotation.RobocopTarget;
 import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.DynamicToolbar.VisibilityTransition;
@@ -762,16 +761,17 @@ public class BrowserApp extends GeckoApp
             "Menu:Remove",
             "LightweightTheme:Update",
             "Tab:Added",
             "Video:Play",
             "CharEncoding:Data",
             "CharEncoding:State",
             "Settings:Show",
             "Updater:Launch",
+            "Sanitize:Finished",
             "Sanitize:OpenTabs",
             null);
 
         EventDispatcher.getInstance().registerBackgroundThreadListener(this,
             "Experiments:GetActive",
             "Experiments:SetOverride",
             "Experiments:ClearOverride",
             "Favicon:Request",
@@ -1562,16 +1562,17 @@ public class BrowserApp extends GeckoApp
             "Menu:Remove",
             "LightweightTheme:Update",
             "Tab:Added",
             "Video:Play",
             "CharEncoding:Data",
             "CharEncoding:State",
             "Settings:Show",
             "Updater:Launch",
+            "Sanitize:Finished",
             "Sanitize:OpenTabs",
             null);
 
         EventDispatcher.getInstance().unregisterBackgroundThreadListener(this,
             "Experiments:GetActive",
             "Experiments:SetOverride",
             "Experiments:ClearOverride",
             "Favicon:Request",
@@ -1611,38 +1612,16 @@ public class BrowserApp extends GeckoApp
             mFormAssistPopup.destroy();
         if (mTextSelection != null)
             mTextSelection.destroy();
         NotificationHelper.destroy();
         IntentHelper.destroy();
         GeckoNetworkManager.destroy();
 
         super.onDestroy();
-
-        if (!isFinishing()) {
-            // GeckoApp was not intentionally destroyed, so keep our process alive.
-            return;
-        }
-
-        // Wait for Gecko to handle our pause event sent in onPause.
-        if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
-            GeckoThread.waitOnGecko();
-        }
-
-        if (mRestartIntent != null) {
-            // Restarting, so let Restarter kill us.
-            final Intent intent = new Intent();
-            intent.setClass(getApplicationContext(), Restarter.class)
-                  .putExtra("pid", Process.myPid())
-                  .putExtra(Intent.EXTRA_INTENT, mRestartIntent);
-            startService(intent);
-        } else {
-            // Exiting, so kill our own process.
-            Process.killProcess(Process.myPid());
-        }
     }
 
     @Override
     protected void initializeChrome() {
         super.initializeChrome();
 
         mDoorHangerPopup.setAnchor(mBrowserToolbar.getDoorHangerAnchor());
         mDoorHangerPopup.setOnVisibilityChangeListener(this);
@@ -1984,16 +1963,24 @@ public class BrowserApp extends GeckoApp
                         .execute(IconsHelper.createBase64EventCallback(callback));
                 break;
 
             case "Feedback:MaybeLater":
                 SharedPreferences settings = getPreferences(Activity.MODE_PRIVATE);
                 settings.edit().putInt(getPackageName() + ".feedback_launch_count", 0).apply();
                 break;
 
+            case "Sanitize:Finished":
+                if (message.getBoolean("shutdown", false)) {
+                    // Gecko is shutting down and has called our sanitize handlers,
+                    // so we can start exiting, too.
+                    finishAndShutdown(/* restart */ false);
+                }
+                break;
+
             case "Sanitize:OpenTabs":
                 Tabs.getInstance().closeAll();
                 callback.sendSuccess(null);
                 break;
 
             case "Sanitize:ClearHistory":
                 BrowserDB.from(getProfile()).clearHistory(
                         getContentResolver(), message.getBoolean("clearSearchHistory", false));
@@ -3791,17 +3778,17 @@ public class BrowserApp extends GeckoApp
                 if (type == GuestModeDialog.ENTERING) {
                     GeckoProfile.enterGuestMode(context);
                 } else {
                     GeckoProfile.leaveGuestMode(context);
                     // Now's a good time to make sure we're not displaying the
                     // Guest Browsing notification.
                     GuestSession.hideNotification(context);
                 }
-                doRestart();
+                finishAndShutdown(/* restart */ true);
             }
         });
 
         Resources res = getResources();
         ps.setButtons(new String[] {
             res.getString(R.string.guest_session_dialog_continue),
             res.getString(R.string.guest_session_dialog_cancel)
         });
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
@@ -373,17 +373,18 @@ public abstract class GeckoApp
     private Telemetry.Timer mJavaUiStartupTimer;
     private Telemetry.Timer mGeckoReadyStartupTimer;
 
     private String mPrivateBrowsingSession;
 
     private volatile HealthRecorder mHealthRecorder;
     private volatile Locale mLastLocale;
 
-    protected Intent mRestartIntent;
+    private boolean mShutdownOnDestroy;
+    private boolean mRestartOnShutdown;
 
     private boolean mWasFirstTabShownAfterActivityUnhidden;
 
     abstract public int getLayout();
 
     abstract public View getDoorhangerOverlay();
 
     protected void processTabQueue() {};
@@ -667,17 +668,17 @@ public abstract class GeckoApp
 
                 final String sessionRestore =
                         getSessionRestorePreference(getSharedPreferences());
                 res.putBoolean("dontSaveSession", "quit".equals(sessionRestore));
             }
 
             EventDispatcher.getInstance().dispatch("Browser:Quit", res);
 
-            // We don't call doShutdown() here because this creates a race condition which
+            // We don't call shutdown here because this creates a race condition which
             // can cause the clearing of private data to fail. Instead, we shut down the
             // UI only after we're done sanitizing.
             return true;
         }
 
         return super.onOptionsItemSelected(item);
     }
 
@@ -751,27 +752,16 @@ public abstract class GeckoApp
             // Reset the crash loop counter if we remain alive for at least half a minute.
             ThreadUtils.postDelayedToBackgroundThread(new Runnable() {
                 @Override
                 public void run() {
                     getSharedPreferences().edit().putInt(PREFS_CRASHED_COUNT, 0).apply();
                 }
             }, STARTUP_PHASE_DURATION_MS);
 
-        } else if ("Gecko:Exited".equals(event)) {
-            // Gecko thread exited first; let GeckoApp die too.
-            doShutdown();
-
-        } else if ("Sanitize:Finished".equals(event)) {
-            if (message.getBoolean("shutdown", false)) {
-                // Gecko is shutting down and has called our sanitize handlers,
-                // so we can start exiting, too.
-                doShutdown();
-            }
-
         } else if ("Accessibility:Ready".equals(event)) {
             GeckoAccessibility.updateAccessibilitySettings(this);
 
         } else if ("Accessibility:Event".equals(event)) {
             GeckoAccessibility.sendAccessibilityEvent(message);
 
         } else if ("Bookmark:Insert".equals(event)) {
             final BrowserDB db = BrowserDB.from(getProfile());
@@ -1306,17 +1296,17 @@ public abstract class GeckoApp
         // the UI.
         // This is using a sledgehammer to crack a nut, but it'll do for
         // now.
         // Our OS locale pref will be detected as invalid after the
         // restart, and will be propagated to Gecko accordingly, so there's
         // no need to touch that here.
         if (BrowserLocaleManager.getInstance().systemLocaleDidChange()) {
             Log.i(LOGTAG, "System locale changed. Restarting.");
-            doRestart();
+            finishAndShutdown(/* restart */ true);
             return;
         }
 
         if (sAlreadyLoaded) {
             // This happens when the GeckoApp activity is destroyed by Android
             // without killing the entire application (see Bug 769269).
             // Now that we've got multiple GeckoApp-based activities, this can
             // also happen if we're not the first activity to run within a session.
@@ -1347,23 +1337,21 @@ public abstract class GeckoApp
                 // Start a speculative connection as soon as Gecko loads.
                 GeckoThread.speculativeConnect(uri);
             }
         }
 
         // To prevent races, register startup events before launching the Gecko thread.
         EventDispatcher.getInstance().registerGeckoThreadListener(this,
             "Accessibility:Ready",
-            "Gecko:Exited",
             "Gecko:Ready",
             "PluginHelper:playFlash",
             null);
 
         EventDispatcher.getInstance().registerUiThreadListener(this,
-            "Sanitize:Finished",
             "Update:Check",
             "Update:Download",
             "Update:Install",
             null);
 
         GeckoThread.launch();
 
         Bundle stateBundle = IntentUtils.getBundleExtraSafe(getIntent(), EXTRA_STATE_BUNDLE);
@@ -2587,23 +2575,21 @@ public abstract class GeckoApp
             // 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(this,
             "Accessibility:Ready",
-            "Gecko:Exited",
             "Gecko:Ready",
             "PluginHelper:playFlash",
             null);
 
         EventDispatcher.getInstance().unregisterUiThreadListener(this,
-            "Sanitize:Finished",
             "Update:Check",
             "Update:Download",
             "Update:Install",
             null);
 
         getAppEventDispatcher().unregisterGeckoThreadListener(this,
             "Accessibility:Event",
             "Locale:Set",
@@ -2644,16 +2630,21 @@ public abstract class GeckoApp
                     rec.close(GeckoApp.this);
                 }
             });
         }
 
         super.onDestroy();
 
         Tabs.unregisterOnTabsChangedListener(this);
+
+        if (mShutdownOnDestroy) {
+            GeckoApplication.shutdown(!mRestartOnShutdown ? null : new Intent(
+                    Intent.ACTION_MAIN, /* uri */ null, getApplicationContext(), getClass()));
+        }
     }
 
     public void showSDKVersionError() {
         final String message = getString(R.string.unsupported_sdk_version,
                 HardwareUtils.getRealAbi(), Integer.toString(Build.VERSION.SDK_INT));
         Toast.makeText(this, message, Toast.LENGTH_LONG).show();
     }
 
@@ -2708,16 +2699,29 @@ public abstract class GeckoApp
         while (envIter.hasNext()) {
             Map.Entry<String, String> entry = envIter.next();
             intent.putExtra("env" + c, entry.getKey() + "="
                             + entry.getValue());
             c++;
         }
     }
 
+    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+    protected void finishAndShutdown(final boolean restart) {
+        ThreadUtils.assertOnUiThread();
+
+        mShutdownOnDestroy = true;
+        mRestartOnShutdown = restart;
+
+        // Shut down the activity and then Gecko.
+        if (!isFinishing() && (Versions.preJBMR1 || !isDestroyed())) {
+            finish();
+        }
+    }
+
     @Override
     public void doRestart() {
         doRestart(null, null);
     }
 
     public void doRestart(String args) {
         doRestart(args, null);
     }
@@ -3104,34 +3108,38 @@ public abstract class GeckoApp
         // trigger a session transition and subsequent events will be recorded in an environment
         // with the wrong locale.
         final HealthRecorder rec = mHealthRecorder;
         if (rec != null) {
             rec.onAppLocaleChanged(locale);
             rec.onEnvironmentChanged(startNewSession, SESSION_END_LOCALE_CHANGED);
         }
 
-        if (!shouldRestart) {
-            ThreadUtils.postToUiThread(new Runnable() {
-                @Override
-                public void run() {
-                    GeckoApp.this.onLocaleReady(locale);
-                }
-            });
-            return;
-        }
-
-        // Do this in the background so that the health recorder has its
-        // time to finish.
-        ThreadUtils.postToBackgroundThread(new Runnable() {
+        final Runnable runnable = new Runnable() {
             @Override
             public void run() {
-                GeckoApp.this.doRestart();
+                if (!ThreadUtils.isOnUiThread()) {
+                    ThreadUtils.postToUiThread(this);
+                    return;
+                }
+                if (!shouldRestart) {
+                    GeckoApp.this.onLocaleReady(locale);
+                } else {
+                    finishAndShutdown(/* restart */ true);
+                }
             }
-        });
+        };
+
+        if (!shouldRestart) {
+            ThreadUtils.postToUiThread(runnable);
+        } else {
+            // Do this in the background so that the health recorder has its
+            // time to finish.
+            ThreadUtils.postToBackgroundThread(runnable);
+        }
     }
 
     /**
      * Use BrowserLocaleManager to change our persisted and current locales,
      * and poke the system to tell it of our changed state.
      */
     protected void setLocale(final String locale) {
         if (locale == null) {
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApplication.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApplication.java
@@ -2,18 +2,20 @@
  * 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 android.app.Application;
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.res.Configuration;
+import android.os.Process;
 import android.os.SystemClock;
 import android.util.Log;
 
 import com.squareup.leakcanary.LeakCanary;
 import com.squareup.leakcanary.RefWatcher;
 
 import org.mozilla.gecko.db.BrowserContract;
 import org.mozilla.gecko.db.BrowserDB;
@@ -73,16 +75,39 @@ public class GeckoApplication extends Ap
 
     /**
      * @return The string representation of an UUID that changes on each application startup.
      */
     public static String getSessionUUID() {
         return sSessionUUID;
     }
 
+    public static void shutdown(final Intent restartIntent) {
+        ThreadUtils.assertOnUiThread();
+
+        // Wait for Gecko to handle any pause events.
+        if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
+            GeckoThread.waitOnGecko();
+        }
+
+        if (restartIntent == null) {
+            // Exiting, so kill our own process.
+            Process.killProcess(Process.myPid());
+            return;
+        }
+
+        // Restarting, so let Restarter kill us.
+        final Context context = GeckoAppShell.getApplicationContext();
+        final Intent intent = new Intent();
+        intent.setClass(context, Restarter.class)
+              .putExtra("pid", Process.myPid())
+              .putExtra(Intent.EXTRA_INTENT, restartIntent);
+        context.startService(intent);
+    }
+
     @Override
     public Context getContext() {
         return this;
     }
 
     @Override
     public SharedPreferences getSharedPreferences() {
         return GeckoSharedPrefs.forApp(this);
@@ -195,18 +220,23 @@ public class GeckoApplication extends Ap
         GeckoAppShell.setNotificationListener(new NotificationClient(context));
         // This getInstance call will force initialization of the NotificationHelper, but does nothing with the result
         NotificationHelper.getInstance(context).init();
 
         MulticastDNSManager.getInstance(context).init();
 
         GeckoService.register();
 
-        EventDispatcher.getInstance().registerBackgroundThreadListener(new EventListener(),
-                "Profile:Create");
+        final EventListener listener = new EventListener();
+        EventDispatcher.getInstance().registerUiThreadListener(listener,
+                "Gecko:Exited",
+                null);
+        EventDispatcher.getInstance().registerBackgroundThreadListener(listener,
+                "Profile:Create",
+                null);
 
         super.onCreate();
     }
 
     public void onDelayedStartup() {
         if (AppConstants.MOZ_ANDROID_GCM) {
             // TODO: only run in main process.
             ThreadUtils.postToBackgroundThread(new Runnable() {
@@ -315,16 +345,28 @@ public class GeckoApplication extends Ap
         }
 
         @Override // BundleEventListener
         public void handleMessage(final String event, final GeckoBundle message,
                                   final EventCallback callback) {
             if ("Profile:Create".equals(event)) {
                 onProfileCreate(message.getString("name"),
                                 message.getString("path"));
+
+            } else if ("Gecko:Exited".equals(event)) {
+                // Gecko thread exited first; shutdown the application.
+                final Intent restartIntent;
+                if (message.getBoolean("restart")) {
+                    restartIntent = new Intent(Intent.ACTION_MAIN);
+                    restartIntent.setClassName(GeckoAppShell.getApplicationContext(),
+                                               AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);
+                } else {
+                    restartIntent = null;
+                }
+                shutdown(restartIntent);
             }
         }
     }
 
     public boolean isApplicationInBackground() {
         return mInBackground;
     }
 
--- a/mobile/android/base/java/org/mozilla/gecko/Restarter.java
+++ b/mobile/android/base/java/org/mozilla/gecko/Restarter.java
@@ -25,18 +25,17 @@ public class Restarter extends Service {
         try {
             Thread.sleep(100);
         } catch (final InterruptedException e) {
         }
 
         final Intent restartIntent = (Intent)intent.getParcelableExtra(Intent.EXTRA_INTENT);
         restartIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                      .putExtra("didRestart", true)
-                     .setClassName(getApplicationContext(),
-                                   AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);
+                     .setPackage(getApplicationContext().getPackageName());
         startActivity(restartIntent);
         Log.d(LOGTAG, "Launched " + restartIntent);
     }
 
     @Override
     public int onStartCommand(Intent intent, int flags, int startId) {
         doRestart(intent);
         stopSelf(startId);