Bug 1557096 - Add ContentDelegate.onKill() to differentiate between content process crashes and kills. r=geckoview-reviewers,agi,snorp

Differential Revision: https://phabricator.services.mozilla.com/D35874

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Alvina Waseem 2019-07-11 18:34:55 +00:00
Родитель 37f4196a65
Коммит 133133e12f
9 изменённых файлов: 116 добавлений и 18 удалений

Просмотреть файл

@ -478,6 +478,7 @@ package org.mozilla.geckoview {
method @UiThread default public void onFirstComposite(@NonNull GeckoSession);
method @UiThread default public void onFocusRequest(@NonNull GeckoSession);
method @UiThread default public void onFullScreen(@NonNull GeckoSession, boolean);
method @UiThread default public void onKill(@NonNull GeckoSession);
method @UiThread default public void onTitleChange(@NonNull GeckoSession, @Nullable String);
method @UiThread default public void onWebAppManifest(@NonNull GeckoSession, @NonNull JSONObject);
}

Просмотреть файл

@ -4,12 +4,15 @@
package org.mozilla.geckoview.test
import android.app.ActivityManager
import android.content.Context
import android.graphics.Matrix
import android.graphics.SurfaceTexture
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.LocaleList
import android.os.Process
import org.mozilla.geckoview.AllowOrDeny
import org.mozilla.geckoview.GeckoResult
import org.mozilla.geckoview.GeckoSession
@ -19,6 +22,7 @@ import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.IgnoreCrash
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDisplay
import org.mozilla.geckoview.test.util.Callbacks
import android.support.annotation.AnyThread
import android.support.test.filters.MediumTest
import android.support.test.filters.SdkSuppress
import android.support.test.runner.AndroidJUnit4
@ -37,6 +41,7 @@ import org.json.JSONObject
import org.junit.Assume.assumeThat
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.gecko.GeckoAppShell
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule
@RunWith(AndroidJUnit4::class)
@ -99,7 +104,7 @@ class ContentDelegateTest : BaseSessionTest() {
assertThat("Session should be closed after a crash",
session.isOpen, equalTo(false))
}
});
})
// Recover immediately
mainSession.open()
@ -170,6 +175,62 @@ class ContentDelegateTest : BaseSessionTest() {
}
}
@AnyThread
fun killContentProcess() {
val context = GeckoAppShell.getApplicationContext()
val manager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
for (info in manager.runningAppProcesses) {
if (info.processName.endsWith(":tab")) {
Process.killProcess(info.pid)
}
}
}
@IgnoreCrash
@Test fun killContent() {
assumeThat(sessionRule.env.isMultiprocess, equalTo(true))
assumeThat(sessionRule.env.isDebugBuild && sessionRule.env.isX86,
equalTo(false))
killContentProcess()
mainSession.waitUntilCalled(object : Callbacks.ContentDelegate {
@AssertCalled(count = 1)
override fun onKill(session: GeckoSession) {
assertThat("Session should be closed after being killed",
session.isOpen, equalTo(false))
}
})
mainSession.open()
mainSession.loadTestPath(HELLO_HTML_PATH)
mainSession.waitUntilCalled(object : Callbacks.ProgressDelegate {
@AssertCalled(count = 1)
override fun onPageStop(session: GeckoSession, success: Boolean) {
assertThat("Page should load successfully", success, equalTo(true))
}
})
}
@IgnoreCrash
@Test fun killContentMultipleSessions() {
assumeThat(sessionRule.env.isMultiprocess, equalTo(true))
assumeThat(sessionRule.env.isDebugBuild && sessionRule.env.isX86,
equalTo(false))
val newSession = sessionRule.createOpenSession()
killContentProcess()
val remainingSessions = mutableListOf(newSession, mainSession)
while (remainingSessions.isNotEmpty()) {
sessionRule.waitUntilCalled(object : Callbacks.ContentDelegate {
@AssertCalled(count = 1)
override fun onKill(session: GeckoSession) {
remainingSessions.remove(session)
}
})
}
}
// TextInputDelegateTest is parameterized, so we put this test under ContentDelegateTest.
@SdkSuppress(minSdkVersion = 23)
@Test fun autofill() {

Просмотреть файл

@ -103,7 +103,7 @@ public class GeckoSessionTestRule implements TestRule {
sOnNewSession = GeckoSession.NavigationDelegate.class.getMethod(
"onNewSession", GeckoSession.class, String.class);
sOnCrash = GeckoSession.ContentDelegate.class.getMethod(
"onCrash", GeckoSession.class);
"onKill", GeckoSession.class);
} catch (final NoSuchMethodException e) {
throw new RuntimeException(e);
}

Просмотреть файл

@ -81,11 +81,8 @@ public class RuntimeCreator {
runtimeSettingsBuilder.arguments(new String[]{"-purgecaches"})
.extras(InstrumentationRegistry.getArguments())
.remoteDebuggingEnabled(true)
.consoleOutput(true);
if (new Environment().isAutomation()) {
runtimeSettingsBuilder.crashHandler(TestCrashHandler.class);
}
.consoleOutput(true)
.crashHandler(TestCrashHandler.class);
TEST_SUPPORT_WEB_EXTENSION.setMessageDelegate(sMessageDelegate, "browser");

Просмотреть файл

@ -194,7 +194,7 @@ public final class GeckoRuntime implements Parcelable {
if ("Gecko:Exited".equals(event) && mDelegate != null) {
mDelegate.onShutdown();
EventDispatcher.getInstance().unregisterUiThreadListener(mEventListener, "Gecko:Exited");
} else if ("GeckoView:ContentCrash".equals(event) && crashHandler != null) {
} else if ("GeckoView:ContentCrashReport".equals(event) && crashHandler != null) {
final Context context = GeckoAppShell.getApplicationContext();
Intent i = new Intent(ACTION_CRASHED, null,
context, crashHandler);
@ -244,7 +244,7 @@ public final class GeckoRuntime implements Parcelable {
throw new IllegalArgumentException("Crash handler service must run in a separate process");
}
EventDispatcher.getInstance().registerUiThreadListener(mEventListener, "GeckoView:ContentCrash");
EventDispatcher.getInstance().registerUiThreadListener(mEventListener, "GeckoView:ContentCrashReport");
flags |= GeckoThread.FLAG_ENABLE_NATIVE_CRASHREPORTER;
} catch (PackageManager.NameNotFoundException e) {

Просмотреть файл

@ -440,6 +440,7 @@ public class GeckoSession implements Parcelable {
"GeckoViewContent", this,
new String[]{
"GeckoView:ContentCrash",
"GeckoView:ContentKill",
"GeckoView:ContextMenu",
"GeckoView:DOMTitleChanged",
"GeckoView:DOMWindowClose",
@ -455,10 +456,12 @@ public class GeckoSession implements Parcelable {
final String event,
final GeckoBundle message,
final EventCallback callback) {
if ("GeckoView:ContentCrash".equals(event)) {
close();
delegate.onCrash(GeckoSession.this);
} else if ("GeckoView:ContentKill".equals(event)) {
close();
delegate.onKill(GeckoSession.this);
} else if ("GeckoView:ContextMenu".equals(event)) {
final ContentDelegate.ContextElement elem =
new ContentDelegate.ContextElement(
@ -3123,11 +3126,24 @@ public class GeckoSession implements Parcelable {
* is preserved. Most applications will want to call
* {@link #loadUri(Uri)} or {@link #restoreState(SessionState)} at this point.
*
* @param session The GeckoSession that crashed.
* @param session The GeckoSession for which the content process has crashed.
*/
@UiThread
default void onCrash(@NonNull GeckoSession session) {}
/**
* The content process hosting this GeckoSession has been killed. The
* GeckoSession is now closed and unusable. You may call
* {@link #open(GeckoRuntime)} to recover the session, but no state
* is preserved. Most applications will want to call
* {@link #loadUri(Uri)} or {@link #restoreState(SessionState)} at this point.
*
* @param session The GeckoSession for which the content process has been killed.
*/
@UiThread
default void onKill(@NonNull GeckoSession session) {}
/**
* Notification that the first content composition has occurred.
* This callback is invoked for the first content composite after either

Просмотреть файл

@ -34,6 +34,9 @@ exclude: true
[`browser.tabs.create`][69.6] calls by WebExtensions.
[69.6]: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/tabs/create
[69.7]: ../GeckoSession.ContentDelegate.html#onKill
- Created `onKill` to `ContentDelegate` to differentiate from crashes.
## v68
- Added [`GeckoRuntime#configurationChanged`][68.1] to notify the device
@ -347,4 +350,4 @@ exclude: true
[65.24]: ../CrashReporter.html#sendCrashReport-android.content.Context-android.os.Bundle-java.lang.String-
[65.25]: ../GeckoResult.html
[api-version]: 3d726407275d906a54c1fc86eb92f2c3bfaaa3d0
[api-version]: 9c4bbb50d706a50069a9e5fb04da50d0cbb927f4

Просмотреть файл

@ -65,7 +65,7 @@ var ContentCrashHandler = {
const [minidumpPath, extrasPath] = getPendingMinidump(dumpID);
EventDispatcher.instance.sendRequest({
type: "GeckoView:ContentCrash",
type: "GeckoView:ContentCrashReport",
minidumpPath,
extrasPath,
success: true,

Просмотреть файл

@ -60,6 +60,7 @@ class GeckoViewContent extends GeckoViewModule {
);
Services.obs.addObserver(this, "oop-frameloader-crashed");
Services.obs.addObserver(this, "ipc:content-shutdown");
}
onDisable() {
@ -89,6 +90,7 @@ class GeckoViewContent extends GeckoViewModule {
);
Services.obs.removeObserver(this, "oop-frameloader-crashed");
Services.obs.removeObserver(this, "ipc:content-shutdown");
}
// Bundle event handler.
@ -195,17 +197,35 @@ class GeckoViewContent extends GeckoViewModule {
// nsIObserver event handler
observe(aSubject, aTopic, aData) {
debug`observe: ${aTopic}`;
this._contentCrashed = false;
const browser = aSubject.ownerElement;
switch (aTopic) {
case "oop-frameloader-crashed": {
const browser = aSubject.ownerElement;
if (!browser || browser != this.browser) {
return;
}
this.eventDispatcher.sendRequest({
type: "GeckoView:ContentCrash",
});
this.window.setTimeout(() => {
if (this._contentCrashed) {
this.eventDispatcher.sendRequest({
type: "GeckoView:ContentCrash",
});
} else {
this.eventDispatcher.sendRequest({
type: "GeckoView:ContentKill",
});
}
}, 250);
break;
}
case "ipc:content-shutdown": {
aSubject.QueryInterface(Ci.nsIPropertyBag2);
if (aSubject.get("dumpID")) {
if (browser && aSubject.get("childID") != browser.frameLoader.childID) {
return;
}
this._contentCrashed = true;
}
break;
}
}