From e93e623ef701b53b27a8339d7fd81623e789a8c3 Mon Sep 17 00:00:00 2001 From: William Durand Date: Tue, 11 Apr 2023 12:27:40 +0000 Subject: [PATCH] Bug 1826739 - Implement more AddonManager events. r=rpl,geckoview-reviewers,zmckenney Differential Revision: https://phabricator.services.mozilla.com/D174874 --- mobile/android/geckoview/api.txt | 5 + .../geckoview/test/WebExtensionTest.kt | 47 ++++++- .../geckoview/WebExtensionController.java | 124 +++++++++++++++++- .../mozilla/geckoview/doc-files/CHANGELOG.md | 4 +- .../geckoview/GeckoViewWebExtension.sys.mjs | 70 ++++++++++ 5 files changed, 239 insertions(+), 11 deletions(-) diff --git a/mobile/android/geckoview/api.txt b/mobile/android/geckoview/api.txt index 3fc78127085d2..f87bf865af131 100644 --- a/mobile/android/geckoview/api.txt +++ b/mobile/android/geckoview/api.txt @@ -2339,8 +2339,13 @@ package org.mozilla.geckoview { public static interface WebExtensionController.AddonManagerDelegate { method @UiThread default public void onDisabled(@NonNull WebExtension); + method @UiThread default public void onDisabling(@NonNull WebExtension); method @UiThread default public void onEnabled(@NonNull WebExtension); + method @UiThread default public void onEnabling(@NonNull WebExtension); + method @UiThread default public void onInstalled(@NonNull WebExtension); + method @UiThread default public void onInstalling(@NonNull WebExtension); method @UiThread default public void onUninstalled(@NonNull WebExtension); + method @UiThread default public void onUninstalling(@NonNull WebExtension); } public static interface WebExtensionController.DebuggerDelegate { diff --git a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/WebExtensionTest.kt b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/WebExtensionTest.kt index 7ac217ced048a..76916c5e7bf26 100644 --- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/WebExtensionTest.kt +++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/WebExtensionTest.kt @@ -224,14 +224,29 @@ class WebExtensionTest : BaseSessionTest() { { delegate -> controller.setAddonManagerDelegate(delegate) }, { controller.setAddonManagerDelegate(null) }, object : WebExtensionController.AddonManagerDelegate { + @AssertCalled(count = 3) + override fun onEnabling(extension: WebExtension) {} + @AssertCalled(count = 3) override fun onEnabled(extension: WebExtension) {} + @AssertCalled(count = 3) + override fun onDisabling(extension: WebExtension) {} + @AssertCalled(count = 3) override fun onDisabled(extension: WebExtension) {} + @AssertCalled(count = 1) + override fun onUninstalling(extension: WebExtension) {} + @AssertCalled(count = 1) override fun onUninstalled(extension: WebExtension) {} + + @AssertCalled(count = 1) + override fun onInstalling(extension: WebExtension) {} + + @AssertCalled(count = 1) + override fun onInstalled(extension: WebExtension) {} } ) @@ -2510,29 +2525,47 @@ class WebExtensionTest : BaseSessionTest() { } }) - val webExtension = sessionRule.waitForResult( - controller.install("https://example.org/tests/junit/update-1.xpi") - ) - - mainSession.reload() - sessionRule.waitForPageStop() - sessionRule.addExternalDelegateUntilTestEnd( WebExtensionController.AddonManagerDelegate::class, { delegate -> controller.setAddonManagerDelegate(delegate) }, { controller.setAddonManagerDelegate(null) }, object : WebExtensionController.AddonManagerDelegate { + @AssertCalled(count = 0) + override fun onEnabling(extension: WebExtension) {} + @AssertCalled(count = 0) override fun onEnabled(extension: WebExtension) {} + @AssertCalled(count = 1) + override fun onDisabling(extension: WebExtension) {} + @AssertCalled(count = 1) override fun onDisabled(extension: WebExtension) {} + @AssertCalled(count = 1) + override fun onUninstalling(extension: WebExtension) {} + @AssertCalled(count = 1) override fun onUninstalled(extension: WebExtension) {} + + // We expect onInstalling/onInstalled to be invoked twice + // because we first install the extension and then we update + // it, which results in a second install. + @AssertCalled(count = 2) + override fun onInstalling(extension: WebExtension) {} + + @AssertCalled(count = 2) + override fun onInstalled(extension: WebExtension) {} } ) + val webExtension = sessionRule.waitForResult( + controller.install("https://example.org/tests/junit/update-1.xpi") + ) + + mainSession.reload() + sessionRule.waitForPageStop() + val disabledWebExtension = sessionRule.waitForResult(controller.disable(webExtension, source)) when (source) { diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebExtensionController.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebExtensionController.java index 4e5d756ae8426..9e55b79a0e789 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebExtensionController.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebExtensionController.java @@ -328,6 +328,14 @@ default void onExtensionListUpdated() {} /** This delegate will be called whenever the state of an extension has changed. */ public interface AddonManagerDelegate { + /** + * Called whenever an extension is being disabled. + * + * @param extension The {@link WebExtension} that is being disabled. + */ + @UiThread + default void onDisabling(@NonNull WebExtension extension) {} + /** * Called whenever an extension has been disabled. * @@ -336,6 +344,14 @@ public interface AddonManagerDelegate { @UiThread default void onDisabled(final @NonNull WebExtension extension) {} + /** + * Called whenever an extension is being enabled. + * + * @param extension The {@link WebExtension} that is being enabled. + */ + @UiThread + default void onEnabling(final @NonNull WebExtension extension) {} + /** * Called whenever an extension has been enabled. * @@ -344,6 +360,14 @@ default void onDisabled(final @NonNull WebExtension extension) {} @UiThread default void onEnabled(final @NonNull WebExtension extension) {} + /** + * Called whenever an extension is being uninstalled. + * + * @param extension The {@link WebExtension} that is being uninstalled. + */ + @UiThread + default void onUninstalling(final @NonNull WebExtension extension) {} + /** * Called whenever an extension has been uninstalled. * @@ -351,6 +375,22 @@ default void onEnabled(final @NonNull WebExtension extension) {} */ @UiThread default void onUninstalled(final @NonNull WebExtension extension) {} + + /** + * Called whenever an extension is being installed. + * + * @param extension The {@link WebExtension} that is being installed. + */ + @UiThread + default void onInstalling(final @NonNull WebExtension extension) {} + + /** + * Called whenever an extension has been installed. + * + * @param extension The {@link WebExtension} that is being installed. + */ + @UiThread + default void onInstalled(final @NonNull WebExtension extension) {} } /** @@ -423,16 +463,26 @@ public void setAddonManagerDelegate(final @Nullable AddonManagerDelegate delegat EventDispatcher.getInstance() .unregisterUiThreadListener( mInternals, + "GeckoView:WebExtension:OnDisabling", "GeckoView:WebExtension:OnDisabled", + "GeckoView:WebExtension:OnEnabling", "GeckoView:WebExtension:OnEnabled", - "GeckoView:WebExtension:OnUninstalled"); + "GeckoView:WebExtension:OnUninstalling", + "GeckoView:WebExtension:OnUninstalled", + "GeckoView:WebExtension:OnInstalling", + "GeckoView:WebExtension:OnInstalled"); } else if (delegate != null && mAddonManagerDelegate == null) { EventDispatcher.getInstance() .registerUiThreadListener( mInternals, + "GeckoView:WebExtension:OnDisabling", "GeckoView:WebExtension:OnDisabled", + "GeckoView:WebExtension:OnEnabling", "GeckoView:WebExtension:OnEnabled", - "GeckoView:WebExtension:OnUninstalled"); + "GeckoView:WebExtension:OnUninstalling", + "GeckoView:WebExtension:OnUninstalled", + "GeckoView:WebExtension:OnInstalling", + "GeckoView:WebExtension:OnInstalled"); } mAddonManagerDelegate = delegate; @@ -786,15 +836,30 @@ public GeckoResult update(final @NonNull WebExtension extension) { mDebuggerDelegate.onExtensionListUpdated(); } return; + } else if ("GeckoView:WebExtension:OnDisabling".equals(event)) { + onDisabling(bundle); + return; } else if ("GeckoView:WebExtension:OnDisabled".equals(event)) { onDisabled(bundle); return; + } else if ("GeckoView:WebExtension:OnEnabling".equals(event)) { + onEnabling(bundle); + return; } else if ("GeckoView:WebExtension:OnEnabled".equals(event)) { onEnabled(bundle); return; + } else if ("GeckoView:WebExtension:OnUninstalling".equals(event)) { + onUninstalling(bundle); + return; } else if ("GeckoView:WebExtension:OnUninstalled".equals(event)) { onUninstalled(bundle); return; + } else if ("GeckoView:WebExtension:OnInstalling".equals(event)) { + onInstalling(bundle); + return; + } else if ("GeckoView:WebExtension:OnInstalled".equals(event)) { + onInstalled(bundle); + return; } extensionFromBundle(bundle) @@ -980,6 +1045,17 @@ private void optionalPrompt(final Message message, final WebExtension extension) })); } + private void onDisabling(final GeckoBundle bundle) { + if (mAddonManagerDelegate == null) { + Log.e(LOGTAG, "no AddonManager delegate registered"); + return; + } + + final GeckoBundle extensionBundle = bundle.getBundle("extension"); + final WebExtension extension = new WebExtension(mDelegateControllerProvider, extensionBundle); + mAddonManagerDelegate.onDisabling(extension); + } + private void onDisabled(final GeckoBundle bundle) { if (mAddonManagerDelegate == null) { Log.e(LOGTAG, "no AddonManager delegate registered"); @@ -991,6 +1067,17 @@ private void onDisabled(final GeckoBundle bundle) { mAddonManagerDelegate.onDisabled(extension); } + private void onEnabling(final GeckoBundle bundle) { + if (mAddonManagerDelegate == null) { + Log.e(LOGTAG, "no AddonManager delegate registered"); + return; + } + + final GeckoBundle extensionBundle = bundle.getBundle("extension"); + final WebExtension extension = new WebExtension(mDelegateControllerProvider, extensionBundle); + mAddonManagerDelegate.onEnabling(extension); + } + private void onEnabled(final GeckoBundle bundle) { if (mAddonManagerDelegate == null) { Log.e(LOGTAG, "no AddonManager delegate registered"); @@ -1002,6 +1089,17 @@ private void onEnabled(final GeckoBundle bundle) { mAddonManagerDelegate.onEnabled(extension); } + private void onUninstalling(final GeckoBundle bundle) { + if (mAddonManagerDelegate == null) { + Log.e(LOGTAG, "no AddonManager delegate registered"); + return; + } + + final GeckoBundle extensionBundle = bundle.getBundle("extension"); + final WebExtension extension = new WebExtension(mDelegateControllerProvider, extensionBundle); + mAddonManagerDelegate.onUninstalling(extension); + } + private void onUninstalled(final GeckoBundle bundle) { if (mAddonManagerDelegate == null) { Log.e(LOGTAG, "no AddonManager delegate registered"); @@ -1013,6 +1111,28 @@ private void onUninstalled(final GeckoBundle bundle) { mAddonManagerDelegate.onUninstalled(extension); } + private void onInstalling(final GeckoBundle bundle) { + if (mAddonManagerDelegate == null) { + Log.e(LOGTAG, "no AddonManager delegate registered"); + return; + } + + final GeckoBundle extensionBundle = bundle.getBundle("extension"); + final WebExtension extension = new WebExtension(mDelegateControllerProvider, extensionBundle); + mAddonManagerDelegate.onInstalling(extension); + } + + private void onInstalled(final GeckoBundle bundle) { + if (mAddonManagerDelegate == null) { + Log.e(LOGTAG, "no AddonManager delegate registered"); + return; + } + + final GeckoBundle extensionBundle = bundle.getBundle("extension"); + final WebExtension extension = new WebExtension(mDelegateControllerProvider, extensionBundle); + mAddonManagerDelegate.onInstalled(extension); + } + @SuppressLint("WrongThread") // for .toGeckoBundle private void getSettings(final Message message, final WebExtension extension) { final WebExtension.BrowsingDataDelegate delegate = mListener.getBrowsingDataDelegate(extension); diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md index 316b109b64244..7a6a7ec75cd57 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md @@ -20,7 +20,7 @@ exclude: true ([bug 1820567]({{bugzilla}}1820567)) - Add `ViewportMode` annotation to [`viewportMode`][113.7], [`getViewportMode`][113.8] and [`setViewportMode`][113.9]. ([bug 1820567]({{bugzilla}}1820567)) -- Add [`WebExtensionController.AddonManagerDelegate`][113.10] ([bug 1822763]({{bugzilla}}1822763)) +- Add [`WebExtensionController.AddonManagerDelegate`][113.10] ([bug 1822763]({{bugzilla}}1822763), [bug 1826739]({{bugzilla}}1826739)) [113.1]: {{javadoc_uri}}/GeckoSessionSettings.Builder.html#displayMode(int) [113.2]: {{javadoc_uri}}/GeckoSessionSettings.html#getDisplayMode() @@ -1346,4 +1346,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]: 04715b0dddf7887089933ed90637737e336fe190 +[api-version]: 3867469003995d18051e031bdc669690295c9171 diff --git a/mobile/android/modules/geckoview/GeckoViewWebExtension.sys.mjs b/mobile/android/modules/geckoview/GeckoViewWebExtension.sys.mjs index 13c4dc7523128..aed5ed7b6c4de 100644 --- a/mobile/android/modules/geckoview/GeckoViewWebExtension.sys.mjs +++ b/mobile/android/modules/geckoview/GeckoViewWebExtension.sys.mjs @@ -527,6 +527,20 @@ class AddonManagerListener { lazy.AddonManager.addAddonListener(this); } + async onDisabling(aAddon) { + debug`onDisabling ${aAddon.id}`; + + const extension = await exportExtension( + aAddon, + aAddon.userPermissions, + /* aSourceURI */ null + ); + lazy.EventDispatcher.instance.sendRequest({ + type: "GeckoView:WebExtension:OnDisabling", + extension, + }); + } + async onDisabled(aAddon) { debug`onDisabled ${aAddon.id}`; @@ -541,6 +555,20 @@ class AddonManagerListener { }); } + async onEnabling(aAddon) { + debug`onEnabling ${aAddon.id}`; + + const extension = await exportExtension( + aAddon, + aAddon.userPermissions, + /* aSourceURI */ null + ); + lazy.EventDispatcher.instance.sendRequest({ + type: "GeckoView:WebExtension:OnEnabling", + extension, + }); + } + async onEnabled(aAddon) { debug`onEnabled ${aAddon.id}`; @@ -555,6 +583,20 @@ class AddonManagerListener { }); } + async onUninstalling(aAddon) { + debug`onUninstalling ${aAddon.id}`; + + const extension = await exportExtension( + aAddon, + aAddon.userPermissions, + /* aSourceURI */ null + ); + lazy.EventDispatcher.instance.sendRequest({ + type: "GeckoView:WebExtension:OnUninstalling", + extension, + }); + } + async onUninstalled(aAddon) { debug`onUninstalled ${aAddon.id}`; @@ -568,6 +610,34 @@ class AddonManagerListener { extension, }); } + + async onInstalling(aAddon) { + debug`onInstalling ${aAddon.id}`; + + const extension = await exportExtension( + aAddon, + aAddon.userPermissions, + /* aSourceURI */ null + ); + lazy.EventDispatcher.instance.sendRequest({ + type: "GeckoView:WebExtension:OnInstalling", + extension, + }); + } + + async onInstalled(aAddon) { + debug`onInstalled ${aAddon.id}`; + + const extension = await exportExtension( + aAddon, + aAddon.userPermissions, + /* aSourceURI */ null + ); + lazy.EventDispatcher.instance.sendRequest({ + type: "GeckoView:WebExtension:OnInstalled", + extension, + }); + } } new AddonManagerListener();