Bug 1819737 - Listen for extension process crashes and pass to new delegate methods r=geckoview-reviewers,willdurand,owlish,amejiamarmol

Differential Revision: https://phabricator.services.mozilla.com/D184071
This commit is contained in:
Zac McKenney 2023-08-25 13:49:03 +00:00
parent 86b2ace5b5
commit 14cca9efa4
6 changed files with 145 additions and 1 deletions

View File

@ -192,6 +192,7 @@ export class GeckoViewStartup {
"GeckoView:WebExtension:SetPBAllowed",
"GeckoView:WebExtension:Uninstall",
"GeckoView:WebExtension:Update",
"GeckoView:WebExtension:EnableProcessSpawning",
],
observers: [
"devtools-installed-addon",

View File

@ -2491,6 +2491,7 @@ package org.mozilla.geckoview {
method @Nullable @UiThread public WebExtension.Download createDownload(int);
method @AnyThread @NonNull public GeckoResult<WebExtension> disable(@NonNull WebExtension, int);
method @AnyThread @NonNull public GeckoResult<WebExtension> enable(@NonNull WebExtension, int);
method @AnyThread public void enableExtensionProcessSpawning();
method @AnyThread @NonNull public GeckoResult<WebExtension> ensureBuiltIn(@NonNull String, @Nullable String);
method @Nullable @UiThread public WebExtensionController.PromptDelegate getPromptDelegate();
method @AnyThread @NonNull public GeckoResult<WebExtension> install(@NonNull String);
@ -2499,6 +2500,7 @@ package org.mozilla.geckoview {
method @UiThread public void setAddonManagerDelegate(@Nullable WebExtensionController.AddonManagerDelegate);
method @AnyThread @NonNull public GeckoResult<WebExtension> setAllowedInPrivateBrowsing(@NonNull WebExtension, boolean);
method @UiThread public void setDebuggerDelegate(@NonNull WebExtensionController.DebuggerDelegate);
method @UiThread public void setExtensionProcessDelegate(@Nullable WebExtensionController.ExtensionProcessDelegate);
method @UiThread public void setPromptDelegate(@Nullable WebExtensionController.PromptDelegate);
method @AnyThread public void setTabActive(@NonNull GeckoSession, boolean);
method @AnyThread @NonNull public GeckoResult<Void> uninstall(@NonNull WebExtension);
@ -2529,6 +2531,10 @@ package org.mozilla.geckoview {
@Retention(value=RetentionPolicy.SOURCE) public static interface WebExtensionController.EnableSources {
}
public static interface WebExtensionController.ExtensionProcessDelegate {
method @UiThread default public void onDisabledProcessSpawning();
}
@UiThread public static interface WebExtensionController.PromptDelegate {
method @Nullable default public GeckoResult<AllowOrDeny> onInstallPrompt(@NonNull WebExtension);
method @Nullable default public GeckoResult<AllowOrDeny> onOptionalPrompt(@NonNull WebExtension, @NonNull String[], @NonNull String[]);

View File

@ -3126,4 +3126,42 @@ class WebExtensionTest : BaseSessionTest() {
equalTo(true),
)
}
fun extensionProcessCrash() {
sessionRule.setPrefsUntilTestEnd(
mapOf(
"extensions.webextensions.remote" to true,
"dom.ipc.keepProcessesAlive.extension" to 1,
"xpinstall.signatures.required" to false,
),
)
sessionRule.delegateDuringNextWait(object : WebExtensionController.PromptDelegate {
@AssertCalled(count = 1)
override fun onInstallPrompt(extension: WebExtension): GeckoResult<AllowOrDeny>? {
return GeckoResult.allow()
}
})
sessionRule.addExternalDelegateUntilTestEnd(
WebExtensionController.ExtensionProcessDelegate::class,
{ delegate -> controller.setExtensionProcessDelegate(delegate) },
{ controller.setExtensionProcessDelegate(null) },
object : WebExtensionController.ExtensionProcessDelegate {
@AssertCalled(count = 1)
override fun onDisabledProcessSpawning() {}
},
)
val borderify = sessionRule.waitForResult(
controller.install("resource://android/assets/web_extensions/borderify.xpi"),
)
val list = extensionsMap(sessionRule.waitForResult(controller.list()))
assertTrue(list.containsKey(borderify.id))
mainSession.loadUri("about:crashextensions")
sessionRule.waitForResult(controller.uninstall(borderify))
}
}

View File

@ -33,6 +33,7 @@ public class WebExtensionController {
private static final String LOGTAG = "WebExtension";
private AddonManagerDelegate mAddonManagerDelegate;
private ExtensionProcessDelegate mExtensionProcessDelegate;
private DebuggerDelegate mDebuggerDelegate;
private PromptDelegate mPromptDelegate;
private final WebExtension.Listener<WebExtension.TabDelegate> mListener;
@ -393,6 +394,13 @@ public class WebExtensionController {
default void onInstalled(final @NonNull WebExtension extension) {}
}
/** This delegate is used to notify of extension process state changes. */
public interface ExtensionProcessDelegate {
/** Called when extension process spawning has been disabled. */
@UiThread
default void onDisabledProcessSpawning() {}
}
/**
* @return the current {@link PromptDelegate} instance.
* @see PromptDelegate
@ -488,6 +496,42 @@ public class WebExtensionController {
mAddonManagerDelegate = delegate;
}
/**
* Set the {@link ExtensionProcessDelegate} for this instance. This delegate will be used to
* notify when the state of the extension process has changed.
*
* @param delegate the extension process delegate
* @see ExtensionProcessDelegate
*/
@UiThread
public void setExtensionProcessDelegate(final @Nullable ExtensionProcessDelegate delegate) {
if (delegate == null && mExtensionProcessDelegate != null) {
EventDispatcher.getInstance()
.unregisterUiThreadListener(
mInternals, "GeckoView:WebExtension:OnDisabledProcessSpawning");
} else if (delegate != null && mExtensionProcessDelegate == null) {
EventDispatcher.getInstance()
.registerUiThreadListener(mInternals, "GeckoView:WebExtension:OnDisabledProcessSpawning");
}
mExtensionProcessDelegate = delegate;
}
/**
* Enable extension process spawning.
*
* <p>Extension process spawning can be disabled when the extension process has been killed or
* crashed beyond the threshold set for Gecko. This method can be called to reset the threshold
* count and allow the spawning again. If the threshold is reached again, {@link
* ExtensionProcessDelegate#onDisabledProcessSpawning()} will still be called.
*
* @see ExtensionProcessDelegate#onDisabledProcessSpawning()
*/
@AnyThread
public void enableExtensionProcessSpawning() {
EventDispatcher.getInstance().dispatch("GeckoView:WebExtension:EnableProcessSpawning", null);
}
private static class InstallCanceller implements GeckoResult.CancellationDelegate {
public final String installId;
@ -860,6 +904,9 @@ public class WebExtensionController {
} else if ("GeckoView:WebExtension:OnInstalled".equals(event)) {
onInstalled(bundle);
return;
} else if ("GeckoView:WebExtension:OnDisabledProcessSpawning".equals(event)) {
onDisabledProcessSpawning();
return;
}
extensionFromBundle(bundle)
@ -1133,6 +1180,15 @@ public class WebExtensionController {
mAddonManagerDelegate.onInstalled(extension);
}
private void onDisabledProcessSpawning() {
if (mExtensionProcessDelegate == null) {
Log.e(LOGTAG, "no extension process delegate registered");
return;
}
mExtensionProcessDelegate.onDisabledProcessSpawning();
}
@SuppressLint("WrongThread") // for .toGeckoBundle
private void getSettings(final Message message, final WebExtension extension) {
final WebExtension.BrowsingDataDelegate delegate = mListener.getBrowsingDataDelegate(extension);

View File

@ -25,6 +25,8 @@ exclude: true
- Added [`getExperimentDelegate`][118.9] and [`setExperimentDelegate`][118.10] to the GeckoSession allow GeckoView to get and set the experiment delegate for the session. Default is to use the runtime delegate.
- ⚠️ Deprecated [`onGetNimbusFeature`][115.5] by 122, please use `ExperimentDelegate.onGetExperimentFeature` instead.
- Added [`GeckoRuntimeSettings.Builder.extensionsProcessEnabled`][118.11] for setting whether extensions process is enabled. ([bug 1843926]({{bugzilla}}1843926))
- Added [`ExtensionProcessDelegate`][118.12] to allow GeckoView to notify disabling of the extension process spawning due to excessive crash/kill. ([bug 1819737]({{bugzilla}}1819737))
- Added [`enableExtensionProcessSpawning`][118.13] for enabling the extension process spawning
[118.1]: {{javadoc_uri}}/ExperimentDelegate.html
[118.2]: {{javadoc_uri}}/WebExtension.InstallException.ErrorCodes.html#ERROR_BLOCKLISTED
@ -37,6 +39,8 @@ exclude: true
[118.9]: {{javadoc_uri}}/GeckoSession.html#getExperimentDelegate()
[118.10]: {{javadoc_uri}}/GeckoSession.html#setExperimentDelegate(org.mozilla.geckoview.ExperimentDelegate)
[118.11]: {{javadoc_uri}}/GeckoRuntimeSettings.Builder.html#extensionsProcessEnabled(Boolean)
[118.12]: {{javadoc_uri}}/WebExtensionController.ExtensionProcessDelegate.html
[118.13]: {{javadoc_uri}}/WebExtensionController.html#enableExtensionProcessSpawning
## v116
- Added [`GeckoSession.didPrintPageContent`][116.1] to included extra print status for a standard print and new `GeckoPrintException.ERROR_NO_PRINT_DELEGATE`
@ -1417,4 +1421,4 @@ to allow adding gecko profiler markers.
[65.24]: {{javadoc_uri}}/CrashReporter.html#sendCrashReport(android.content.Context,android.os.Bundle,java.lang.String)
[65.25]: {{javadoc_uri}}/GeckoResult.html
[api-version]: 5743799f9da8509cfb6546e154f0e2d7989edc72
[api-version]: 1b85a4f582547bb6ca01fad7bc36344a3179b28f

View File

@ -651,6 +651,45 @@ class AddonManagerListener {
new AddonManagerListener();
class ExtensionProcessListener {
constructor() {
this.onExtensionProcessCrash = this.onExtensionProcessCrash.bind(this);
lazy.Management.on("extension-process-crash", this.onExtensionProcessCrash);
lazy.EventDispatcher.instance.registerListener(this, [
"GeckoView:WebExtension:EnableProcessSpawning",
]);
}
async onEvent(aEvent, aData, aCallback) {
debug`onEvent ${aEvent} ${aData}`;
switch (aEvent) {
case "GeckoView:WebExtension:EnableProcessSpawning": {
//TODO: Bug 1848426 - Add ability to reset threshold counters and allow for process spawning
}
}
}
async onExtensionProcessCrash(name, { childID, disabledProcessSpawning }) {
debug`Extension process crash -> childID=${childID} disabledProcessSpawning=${disabledProcessSpawning}`;
// When an extension process has crashed too many times, Gecko will set `disabledProcessSpawning`
// and no longer allow the extension process spawning. We only want to send a request
// to the embedder when we are disabling the process spawning.
// If process spawning is still enabled then we short circuit and don't notify the embedder.
if (!disabledProcessSpawning) {
return;
}
lazy.EventDispatcher.instance.sendRequest({
type: "GeckoView:WebExtension:OnDisabledProcessSpawning",
});
}
}
new ExtensionProcessListener();
class MobileWindowTracker extends EventEmitter {
constructor() {
super();