Bug 1465480 - Add ContentDelegate.onCrash() r=jchen,droeh

This will give applications the opportunity to recover from a content
process crash.

MozReview-Commit-ID: IAfVNy3ndS4
This commit is contained in:
James Willcox 2018-05-30 13:10:54 -05:00
parent 1383f7b956
commit 2a505d2d75
10 changed files with 152 additions and 2 deletions

View File

@ -900,6 +900,12 @@ public abstract class GeckoApp extends GeckoActivity
@Override
public void onExternalResponse(final GeckoSession session, final GeckoSession.WebResponseInfo request) {
// Won't happen, as we don't use the GeckoView download support in Fennec
}
@Override
public void onCrash(final GeckoSession session) {
// Won't happen, as we don't use e10s in Fennec
}
protected void setFullScreen(final boolean fullscreen) {

View File

@ -722,6 +722,12 @@ public class CustomTabsActivity extends AppCompatActivity
@Override
public void onExternalResponse(final GeckoSession session, final GeckoSession.WebResponseInfo request) {
// Won't happen, as we don't use the GeckoView download support in Fennec
}
@Override
public void onCrash(final GeckoSession session) {
// Won't happen, as we don't use e10s in Fennec
}
@Override // ActionModePresenter

View File

@ -369,6 +369,12 @@ public class WebAppActivity extends AppCompatActivity
@Override // GeckoSession.ContentDelegate
public void onExternalResponse(final GeckoSession session, final GeckoSession.WebResponseInfo request) {
// Won't happen, as we don't use the GeckoView download support in Fennec
}
@Override // GeckoSession.ContentDelegate
public void onCrash(final GeckoSession session) {
// Won't happen, as we don't use e10s in Fennec
}
@Override // GeckoSession.ContentDelegate

View File

@ -7,11 +7,14 @@ package org.mozilla.geckoview.test
import org.mozilla.geckoview.GeckoResponse
import org.mozilla.geckoview.GeckoSession
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.AssertCalled
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.IgnoreCrash
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.ReuseSession
import org.mozilla.geckoview.test.util.Callbacks
import android.support.test.filters.MediumTest
import android.support.test.runner.AndroidJUnit4
import org.hamcrest.Matchers.*
import org.junit.Assume.assumeThat
import org.junit.Test
import org.junit.runner.RunWith
@ -57,4 +60,53 @@ class ContentDelegateTest : BaseSessionTest() {
})
}
@IgnoreCrash
@ReuseSession(false)
@Test fun crashContent() {
// This test doesn't make sense without multiprocess
assumeThat(sessionRule.env.isMultiprocess, equalTo(true))
sessionRule.session.loadUri(CONTENT_CRASH_URL)
sessionRule.waitUntilCalled(object : Callbacks.ContentDelegate {
@AssertCalled(count = 1)
override fun onCrash(session: GeckoSession) {
assertThat("Session should be closed after a crash", session.isOpen, equalTo(false))
// Recover immediately
session.open()
session.loadTestPath(HELLO_HTML_PATH)
}
});
sessionRule.waitForPageStop()
sessionRule.forCallbacksDuringWait(object: Callbacks.ProgressDelegate {
@AssertCalled(count = 1)
override fun onPageStop(session: GeckoSession, success: Boolean) {
assertThat("Page should load successfully", success, equalTo(true))
}
})
}
@IgnoreCrash
@ReuseSession(false)
@Test fun crashContentMultipleSessions() {
// This test doesn't make sense without multiprocess
assumeThat(sessionRule.env.isMultiprocess, equalTo(true))
// We need to make sure all sessions in a given content process
// receive onCrash(). If we add multiple content processes, this
// test will need fixed to ensure the test sessions go into the
// same one.
sessionRule.createOpenSession()
sessionRule.session.loadUri(CONTENT_CRASH_URL)
sessionRule.waitUntilCalled(object : Callbacks.ContentDelegate {
@AssertCalled(count = 2)
override fun onCrash(session: GeckoSession) {
assertThat("Session should be closed after a crash", session.isOpen, equalTo(false))
}
});
}
}

View File

@ -85,6 +85,10 @@ public class TestRunnerActivity extends Activity {
@Override
public void onExternalResponse(GeckoSession session, GeckoSession.WebResponseInfo request) {
}
@Override
public void onCrash(GeckoSession session) {
}
};
private GeckoSession createSession() {

View File

@ -37,6 +37,9 @@ class Callbacks private constructor() {
override fun onExternalResponse(session: GeckoSession, response: GeckoSession.WebResponseInfo) {
}
override fun onCrash(session: GeckoSession) {
}
}
interface NavigationDelegate : GeckoSession.NavigationDelegate {

View File

@ -104,13 +104,14 @@ public class GeckoSession extends LayerSession
new GeckoSessionHandler<ContentDelegate>(
"GeckoViewContent", this,
new String[]{
"GeckoView:ContentCrash",
"GeckoView:ContextMenu",
"GeckoView:DOMTitleChanged",
"GeckoView:DOMWindowFocus",
"GeckoView:DOMWindowClose",
"GeckoView:ExternalResponse",
"GeckoView:FullScreenEnter",
"GeckoView:FullScreenExit"
"GeckoView:FullScreenExit",
}
) {
@Override
@ -119,7 +120,10 @@ public class GeckoSession extends LayerSession
final GeckoBundle message,
final EventCallback callback) {
if ("GeckoView:ContextMenu".equals(event)) {
if ("GeckoView:ContentCrash".equals(event)) {
close();
delegate.onCrash(GeckoSession.this);
} else if ("GeckoView:ContextMenu".equals(event)) {
final int type = getContentElementType(
message.getString("elementType"));
@ -1891,6 +1895,17 @@ public class GeckoSession extends LayerSession
* @param response the WebResponseInfo for the external response
*/
void onExternalResponse(GeckoSession session, WebResponseInfo response);
/**
* The content process hosting this GeckoSession has crashed. 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 that crashed.
*/
void onCrash(GeckoSession session);
}
public interface SelectionActionDelegate {

View File

@ -209,6 +209,13 @@ public class GeckoViewActivity extends Activity {
@Override
public void onExternalResponse(GeckoSession session, GeckoSession.WebResponseInfo request) {
}
@Override
public void onCrash(GeckoSession session) {
Log.e(LOGTAG, "Crashed, reopening session");
session.open(sGeckoRuntime);
session.loadUri(DEFAULT_URL);
}
}
private class MyGeckoViewProgress implements GeckoSession.ProgressDelegate {

View File

@ -9,6 +9,10 @@ var EXPORTED_SYMBOLS = ["GeckoViewContent"];
ChromeUtils.import("resource://gre/modules/GeckoViewModule.jsm");
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetters(this, {
Services: "resource://gre/modules/Services.jsm",
});
class GeckoViewContent extends GeckoViewModule {
onInit() {
this.registerListener([
@ -30,6 +34,8 @@ class GeckoViewContent extends GeckoViewModule {
this.messageManager.addMessageListener("GeckoView:DOMFullscreenExit", this);
this.messageManager.addMessageListener("GeckoView:DOMFullscreenRequest", this);
Services.obs.addObserver(this, "oop-frameloader-crashed");
}
onDisable() {
@ -40,6 +46,8 @@ class GeckoViewContent extends GeckoViewModule {
this.messageManager.removeMessageListener("GeckoView:DOMFullscreenExit", this);
this.messageManager.removeMessageListener("GeckoView:DOMFullscreenRequest", this);
Services.obs.removeObserver(this, "oop-frameloader-crashed");
}
// Bundle event handler.
@ -121,4 +129,23 @@ class GeckoViewContent extends GeckoViewModule {
break;
}
}
// nsIObserver event handler
observe(aSubject, aTopic, aData) {
debug `observe: ${aTopic}`;
switch (aTopic) {
case "oop-frameloader-crashed": {
const browser = aSubject.ownerElement;
if (!browser || browser != this.browser) {
return;
}
this.eventDispatcher.sendRequest({
type: "GeckoView:ContentCrash"
});
}
break;
}
}
}

View File

@ -194,6 +194,7 @@ class GeckoViewProgress extends GeckoViewModule {
.createInstance(Ci.nsIWebProgress);
this.progressFilter.addProgressListener(this, flags);
this.browser.addProgressListener(this.progressFilter, flags);
Services.obs.addObserver(this, "oop-frameloader-crashed");
}
onDisable() {
@ -203,6 +204,8 @@ class GeckoViewProgress extends GeckoViewModule {
this.progressFilter.removeProgressListener(this);
this.browser.removeProgressListener(this.progressFilter);
}
Services.obs.removeObserver(this, "oop-frameloader-crashed");
}
onSettingsUpdate() {
@ -225,6 +228,7 @@ class GeckoViewProgress extends GeckoViewModule {
debug `onStateChange: uri=${uriSpec}`;
if (aStateFlags & Ci.nsIWebProgressListener.STATE_START) {
this._inProgress = true;
const message = {
type: "GeckoView:PageStart",
uri: uriSpec,
@ -233,6 +237,7 @@ class GeckoViewProgress extends GeckoViewModule {
this.eventDispatcher.sendRequest(message);
} else if ((aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) &&
!aWebProgress.isLoadingDocument) {
this._inProgress = false;
let message = {
type: "GeckoView:PageStop",
success: !aStatus
@ -276,4 +281,23 @@ class GeckoViewProgress extends GeckoViewModule {
});
}
}
// nsIObserver event handler
observe(aSubject, aTopic, aData) {
debug `observe: topic=${aTopic}`;
switch (aTopic) {
case "oop-frameloader-crashed": {
const browser = aSubject.ownerElement;
if (!browser || browser != this.browser || !this._inProgress) {
return;
}
this.eventDispatcher.sendRequest({
type: "GeckoView:PageStop",
success: false
});
}
}
}
}