Part 4c: Install MainProcessCrashHandler in GeckoApplication constructor. r=jchen draft
authorNick Alexander <nalexander@mozilla.com>
Thu, 17 Nov 2016 22:50:49 -0800
changeset 445772 f3c8ed63deebadeb9e0cffba2bcdbbf5780f192c
parent 445771 646e4054a4391f5709d5fa8725121a78f4ac92f7
child 445773 d5eb4f3b98bce9d342d3e17fe60eab7ecaa64e8f
push id37604
push usernalexander@mozilla.com
push dateWed, 30 Nov 2016 07:13:23 +0000
reviewersjchen
milestone53.0a1
Part 4c: Install MainProcessCrashHandler in GeckoApplication constructor. r=jchen This makes the previous patch compile, and moves crash handler installation very early in the main process initialization. This is simpler than trying to call "ensureCrashHandling" in all the different entry points into Fennec. MozReview-Commit-ID: Kh40qF6soqO
mobile/android/base/java/org/mozilla/gecko/CrashHandler.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/MainProcessCrashHandler.java
mobile/android/base/moz.build
mobile/android/geckoview/src/main/java/org/mozilla/gecko/BaseGeckoInterface.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java
--- a/mobile/android/base/java/org/mozilla/gecko/CrashHandler.java
+++ b/mobile/android/base/java/org/mozilla/gecko/CrashHandler.java
@@ -47,46 +47,30 @@ public class CrashHandler implements Thr
      * Terminate the current process.
      */
     public static void terminateProcess() {
         Process.killProcess(Process.myPid());
     }
 
     /**
      * Create and register a CrashHandler for all threads and thread groups.
-     */
-    public CrashHandler() {
-        this((Context) null);
-    }
-
-    /**
-     * Create and register a CrashHandler for all threads and thread groups.
      *
      * @param appContext A Context for retrieving application information.
      */
     public CrashHandler(final Context appContext) {
         this.appContext = appContext;
         this.handlerThread = null;
         this.systemUncaughtHandler = Thread.getDefaultUncaughtExceptionHandler();
         Thread.setDefaultUncaughtExceptionHandler(this);
     }
 
     /**
      * Create and register a CrashHandler for a particular thread.
      *
      * @param thread A thread to register the CrashHandler
-     */
-    public CrashHandler(final Thread thread) {
-        this(thread, null);
-    }
-
-    /**
-     * Create and register a CrashHandler for a particular thread.
-     *
-     * @param thread A thread to register the CrashHandler
      * @param appContext A Context for retrieving application information.
      */
     public CrashHandler(final Thread thread, final Context appContext) {
         this.appContext = appContext;
         this.handlerThread = thread;
         this.systemUncaughtHandler = thread.getUncaughtExceptionHandler();
         thread.setUncaughtExceptionHandler(this);
     }
@@ -377,17 +361,17 @@ public class CrashHandler implements Thr
      * Implements the default behavior for handling uncaught exceptions.
      *
      * @param thread The exception thread
      * @param exc An uncaught exception
      */
     @Override
     public void uncaughtException(Thread thread, Throwable exc) {
         if (this.crashing) {
-            // Prevent possible infinite recusions.
+            // Prevent possible infinite recursion.
             return;
         }
 
         if (thread == null) {
             // Gecko may pass in null for thread to denote the current thread.
             thread = Thread.currentThread();
         }
 
@@ -409,9 +393,34 @@ public class CrashHandler implements Thr
                 // Follow the chain of uncaught handlers.
                 systemUncaughtHandler.uncaughtException(thread, exc);
             }
         } finally {
             terminateProcess();
         }
     }
 
+    public static CrashHandler createDefaultCrashHandler(final Context context) {
+        return new CrashHandler(context) {
+            @Override
+            protected Bundle getCrashExtras(final Thread thread, final Throwable exc) {
+                final Bundle extras = super.getCrashExtras(thread, exc);
+
+                extras.putString("ProductName", AppConstants.MOZ_APP_BASENAME);
+                extras.putString("ProductID", AppConstants.MOZ_APP_ID);
+                extras.putString("Version", AppConstants.MOZ_APP_VERSION);
+                extras.putString("BuildID", AppConstants.MOZ_APP_BUILDID);
+                extras.putString("Vendor", AppConstants.MOZ_APP_VENDOR);
+                extras.putString("ReleaseChannel", AppConstants.MOZ_UPDATE_CHANNEL);
+                return extras;
+            }
+
+            @Override
+            public boolean reportException(final Thread thread, final Throwable exc) {
+                if (AppConstants.MOZ_CRASHREPORTER && AppConstants.MOZILLA_OFFICIAL) {
+                    // Only use Java crash reporter if enabled on official build.
+                    return super.reportException(thread, exc);
+                }
+                return false;
+            }
+        };
+    }
 }
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
@@ -2877,9 +2877,19 @@ public abstract class GeckoApp
 
     @Override
     public void openWindowForNotification() {
         final Intent intent = new Intent(Intent.ACTION_MAIN);
         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
         intent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);
         getApplicationContext().startActivity(intent);
     }
+
+    @Override
+    public void uncaughtException(Thread thread, Throwable e) {
+        // For historical reasons, GeckoApp implements GeckoInterface, even though the connection
+        // to Gecko might outlive the GeckoApp Activity.  Crash handling should not depend on the
+        // Activity, so we delegate to the GeckoApplication, which is a long-lived main process
+        // singleton.  This points to a deep inconsistency in the lifecycle and abstraction of
+        // GeckoInterface, which we hope to improve as GeckoView matures.
+        GeckoApplication.get().uncaughtException(thread, e);
+    }
 }
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApplication.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApplication.java
@@ -37,27 +37,31 @@ import org.mozilla.gecko.util.ThreadUtil
 import java.io.File;
 import java.lang.reflect.Method;
 
 public class GeckoApplication extends Application
     implements ContextGetter {
     private static final String LOG_TAG = "GeckoApplication";
 
     private static volatile GeckoApplication instance;
+    private final CrashHandler mCrashHandler;
 
     private boolean mInBackground;
     private boolean mPausedGecko;
 
     private LightweightTheme mLightweightTheme;
 
     private RefWatcher mRefWatcher;
 
     public GeckoApplication() {
         super();
         instance = this;
+        // Creating the crash handler also installs it.  This guarantees there is a single main
+        // process crash handler, installed before any other main process code executes.
+        mCrashHandler = new MainProcessCrashHandler(this);
     }
 
     public static GeckoApplication get() {
         return instance;
     }
 
     public static RefWatcher getRefWatcher(Context context) {
         GeckoApplication app = (GeckoApplication) context.getApplicationContext();
@@ -300,9 +304,14 @@ public class GeckoApplication extends Ap
 
     public LightweightTheme getLightweightTheme() {
         return mLightweightTheme;
     }
 
     public void prepareLightweightTheme() {
         mLightweightTheme = new LightweightTheme(this);
     }
+
+    public void uncaughtException(Thread thread, Throwable e) {
+        // Delegate!
+        mCrashHandler.uncaughtException(thread, e);
+    }
 }
--- a/mobile/android/base/java/org/mozilla/gecko/MainProcessCrashHandler.java
+++ b/mobile/android/base/java/org/mozilla/gecko/MainProcessCrashHandler.java
@@ -1,26 +1,31 @@
 /* -*- 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 android.content.Context;
 import android.content.SharedPreferences;
 import android.os.Bundle;
 
 import org.mozilla.gecko.util.CrashUtils;
 
 /**
  * A crash handler for use in the main process.
  * <p>
  * The main process is the process running the Gecko thread.
  */
 class MainProcessCrashHandler extends CrashHandler {
+    public MainProcessCrashHandler(Context appContext) {
+        super(appContext);
+    }
+
     @Override
     protected String getAppPackageName() {
         return AppConstants.ANDROID_PACKAGE_NAME;
     }
 
     @Override
     protected Bundle getCrashExtras(final Thread thread, final Throwable exc) {
         final Bundle extras = super.getCrashExtras(thread, exc);
@@ -45,17 +50,17 @@ class MainProcessCrashHandler extends Cr
 
         super.uncaughtException(thread, exc);
     }
 
     @Override
     public boolean reportException(final Thread thread, final Throwable exc) {
         try {
             if (exc instanceof OutOfMemoryError) {
-                SharedPreferences prefs = GeckoApplication.getSharedPreferences();
+                SharedPreferences prefs = GeckoSharedPrefs.forApp(getAppContext());
                 SharedPreferences.Editor editor = prefs.edit();
                 editor.putBoolean(GeckoAppShell.PREFS_OOM_EXCEPTION, true);
 
                 // Synchronously write to disk so we know it's done before we
                 // shutdown
                 editor.commit();
             }
 
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -235,17 +235,16 @@ if CONFIG['MOZ_WEBRTC']:
 gvjar = add_java_jar('gecko-view')
 
 gvjar.sources += [geckoview_source_dir + 'java/org/mozilla/gecko/' + x
                   for x in [
     'AlarmReceiver.java',
     'AndroidGamepadManager.java',
     'BaseGeckoInterface.java',
     'ContextGetter.java',
-    'CrashHandler.java',
     'EventDispatcher.java',
     'GeckoAccessibility.java',
     'GeckoAppShell.java',
     'GeckoBatteryManager.java',
     'GeckoEditable.java',
     'GeckoEditableClient.java',
     'GeckoEditableListener.java',
     'GeckoHalDefines.java',
@@ -336,21 +335,23 @@ gbjar.sources += ['java/org/mozilla/geck
     'activitystream/ActivityStream.java',
     'adjust/AdjustBrowserAppDelegate.java',
     'animation/AnimationUtils.java',
     'animation/HeightChangeAnimation.java',
     'animation/PropertyAnimator.java',
     'animation/Rotate3DAnimation.java',
     'animation/ViewHelper.java',
     'ANRReporter.java',
+    'BackgroundProcessCrashHandler.java',
     'BootReceiver.java',
     'BrowserApp.java',
     'BrowserLocaleManager.java',
     'cleanup/FileCleanupController.java',
     'cleanup/FileCleanupService.java',
+    'CrashHandler.java',
     'CustomEditText.java',
     'customtabs/CustomTabsActivity.java',
     'customtabs/GeckoCustomTabsService.java',
     'DataReportingNotification.java',
     'db/AbstractPerProfileDatabaseProvider.java',
     'db/AbstractTransactionalProvider.java',
     'db/BaseTable.java',
     'db/BrowserDatabaseHelper.java',
@@ -565,16 +566,17 @@ gbjar.sources += ['java/org/mozilla/geck
     'icons/storage/FailureCache.java',
     'icons/storage/MemoryStorage.java',
     'IntentHelper.java',
     'javaaddons/JavaAddonManager.java',
     'javaaddons/JavaAddonManagerV1.java',
     'LauncherActivity.java',
     'lwt/LightweightTheme.java',
     'lwt/LightweightThemeDrawable.java',
+    'MainProcessCrashHandler.java',
     'mdns/MulticastDNSManager.java',
     'media/AsyncCodec.java',
     'media/AsyncCodecFactory.java',
     'media/AudioFocusAgent.java',
     'media/Codec.java',
     'media/CodecProxy.java',
     'media/FormatParam.java',
     'media/GeckoMediaDrm.java',
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/BaseGeckoInterface.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/BaseGeckoInterface.java
@@ -173,9 +173,15 @@ public class BaseGeckoInterface implemen
         // By default, GeckoView consumers are not official Mozilla applications.
         return false;
     }
 
     @Override
     public void openWindowForNotification() {
         // By default, GeckoView consumers do not present notification UI.
     }
+
+    @Override
+    public void uncaughtException(Thread thread, Throwable e) {
+        // By default, just raise the uncaught exception.
+        throw new RuntimeException("Uncaught exception from Gecko", e);
+    }
 }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java
@@ -110,18 +110,16 @@ import android.widget.AbsoluteLayout;
 
 public class GeckoAppShell
 {
     private static final String LOGTAG = "GeckoAppShell";
 
     // We have static members only.
     private GeckoAppShell() { }
 
-    private static final CrashHandler CRASH_HANDLER = new MainProcessCrashHandler();
-
     private static volatile boolean locationHighAccuracyEnabled;
 
     // See also HardwareUtils.LOW_MEMORY_THRESHOLD_MB.
     private static final int HIGH_MEMORY_DEVICE_THRESHOLD_MB = 768;
 
     static private int sDensityDpi;
     static private int sScreenDepth;
 
@@ -249,17 +247,21 @@ public class GeckoAppShell
 
     @WrapForJNI(exceptionMode = "ignore")
     private static String getExceptionStackTrace(Throwable e) {
         return CrashUtils.getExceptionStackTrace(CrashUtils.getRootException(e));
     }
 
     @WrapForJNI(exceptionMode = "ignore")
     private static void handleUncaughtException(Throwable e) {
-        CRASH_HANDLER.uncaughtException(null, e);
+        GeckoInterface gi = getGeckoInterface();
+        if (gi == null) {
+            return;
+        }
+        gi.uncaughtException(null, e); // null corresponds to current thread.
     }
 
     @WrapForJNI
     public static void openWindowForNotification() {
         GeckoInterface gi = getGeckoInterface();
         if (gi == null) {
             return;
         }
@@ -1745,16 +1747,27 @@ public class GeckoAppShell
          * This is necessary because Gecko can be running when no Android Activity is alive, and,
          * for example, a Service Worker can request to display a Web Notification in response to
          * a Web Push.  Without an Android Activity, it's not possible to UI more complex than
          * toasts.
          * <p>
          * Right now, this is <b>Firefox only</b> because GeckoView does not support Web Push.
          */
         void openWindowForNotification();
+
+        /**
+         * Handle an uncaught exception in the Gecko native code.
+         * <p>
+         * This method is provided to allow crash reporting.  In most cases, this should crash
+         * the App.
+         *
+         * @param thread that raised exception, or null for the current thread.
+         * @param e      uncaught exception.
+         */
+        void uncaughtException(Thread thread, Throwable e);
     };
 
     private static GeckoInterface sGeckoInterface;
 
     public static GeckoInterface getGeckoInterface() {
         return sGeckoInterface;
     }