зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
37f4196a65
Коммит
133133e12f
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче