Skip to content

Commit

Permalink
Bug 1614295 - Provide a way for apps to know when an extension is ins…
Browse files Browse the repository at this point in the history
…talled. r=snorp,ochameau,esawin

This patch adds a `onExtensionListUpdated` method to `DebuggerDelegate` which
is called whenever devtools install a new extension.

This method provides an opportunity for apps to refresh the list of installed
extensions and sets appropriate delegates so that the new extension is
correctly recognized.

Differential Revision: https://phabricator.services.mozilla.com/D62333
  • Loading branch information
agi committed Feb 20, 2020
1 parent 18a3cbd commit 3243713
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 11 deletions.
3 changes: 3 additions & 0 deletions devtools/server/actors/addon/addons.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const { AddonManager } = require("resource://gre/modules/AddonManager.jsm");
const protocol = require("devtools/shared/protocol");
const { FileUtils } = require("resource://gre/modules/FileUtils.jsm");
const { addonsSpec } = require("devtools/shared/specs/addon/addons");
const { Services } = require("resource://gre/modules/Services.jsm");

// This actor is not used by DevTools, but is relied on externally by
// webext-run and the Firefox VS-Code plugin. see bug #1578108
Expand All @@ -26,6 +27,8 @@ const AddonsActor = protocol.ActorClassWithSpec(addonsSpec, {
throw new Error(`Could not install add-on at '${addonPath}': ${error}`);
}

Services.obs.notifyObservers(null, "devtools-installed-addon", addon.id);

// TODO: once the add-on actor has been refactored to use
// protocol.js, we could return it directly.
// return new AddonTargetActor(this.conn, addon);
Expand Down
5 changes: 5 additions & 0 deletions mobile/android/geckoview/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1598,13 +1598,18 @@ package org.mozilla.geckoview {
method @UiThread @Nullable public WebExtensionController.TabDelegate getTabDelegate();
method @NonNull @AnyThread public GeckoResult<WebExtension> install(@NonNull String);
method @AnyThread @NonNull public GeckoResult<List<WebExtension>> list();
method @UiThread public void setDebuggerDelegate(@NonNull WebExtensionController.DebuggerDelegate);
method @UiThread public void setPromptDelegate(@Nullable WebExtensionController.PromptDelegate);
method @AnyThread public void setTabActive(@NonNull GeckoSession, boolean);
method @UiThread public void setTabDelegate(@Nullable WebExtensionController.TabDelegate);
method @NonNull @AnyThread public GeckoResult<Void> uninstall(@NonNull WebExtension);
method @AnyThread @NonNull public GeckoResult<WebExtension> update(@NonNull WebExtension);
}

public static interface WebExtensionController.DebuggerDelegate {
method @UiThread default public void onExtensionListUpdated();
}

public static class WebExtensionController.EnableSource {
ctor public EnableSource();
field public static final int APP = 2;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
public class WebExtensionController {
private final static String LOGTAG = "WebExtension";

private DebuggerDelegate mDebuggerDelegate;
private PromptDelegate mPromptDelegate;
private final WebExtension.Listener mListener;

Expand Down Expand Up @@ -291,6 +292,23 @@ default GeckoResult<AllowOrDeny> onOptionalPrompt(
} */
}

public interface DebuggerDelegate {
/**
* Called whenever the list of installed extensions has been modified using the debugger
* with tools like web-ext.
*
* This is intended as an opportunity to refresh the list of installed extensions using
* {@link WebExtensionController#list} and to set delegates on the new {@link WebExtension}
* objects, e.g. using {@link WebExtension#setActionDelegate} and
* {@link WebExtension#setMessageDelegate}.
*
* @see <a href="https://extensionworkshop.com/documentation/develop/getting-started-with-web-ext">
* Getting started with web-ext</a>
*/
@UiThread
default void onExtensionListUpdated() {}
}

/**
* @return the current {@link PromptDelegate} instance.
* @see PromptDelegate
Expand Down Expand Up @@ -328,6 +346,29 @@ public void setPromptDelegate(final @Nullable PromptDelegate delegate) {
mPromptDelegate = delegate;
}

/**
* Set the {@link DebuggerDelegate} for this instance. This delegate will receive updates
* about extension changes using developer tools.
*
* @param delegate the Delegate instance
*/
@UiThread
public void setDebuggerDelegate(final @NonNull DebuggerDelegate delegate) {
if (delegate == null && mDebuggerDelegate != null) {
EventDispatcher.getInstance().unregisterUiThreadListener(
mInternals,
"GeckoView:WebExtension:DebuggerListUpdated"
);
} else if (delegate != null && mDebuggerDelegate == null) {
EventDispatcher.getInstance().registerUiThreadListener(
mInternals,
"GeckoView:WebExtension:DebuggerListUpdated"
);
}

mDebuggerDelegate = delegate;
}

private static class WebExtensionResult extends GeckoResult<WebExtension>
implements EventCallback {
/** These states should match gecko's AddonManager.STATE_* constants. */
Expand Down Expand Up @@ -663,6 +704,11 @@ public GeckoResult<WebExtension> update(final @NonNull WebExtension extension) {
} else if ("GeckoView:WebExtension:UpdatePrompt".equals(event)) {
updatePrompt(bundle, callback);
return;
} else if ("GeckoView:WebExtension:DebuggerListUpdated".equals(event)) {
if (mDebuggerDelegate != null) {
mDebuggerDelegate.onExtensionListUpdated();
}
return;
}

final String nativeApp = bundle.getString("nativeApp");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,12 @@ exclude: true
- ⚠️ Move [`GeckoSessionSettings.Builder#useMultiprocess`] to
[`GeckoRuntimeSettings.Builder#useMultiprocess`][75.1]. Multiprocess state is
no longer determined per session.
- Added [`DebuggerDelegate#onExtensionListUpdated`][75.2] to notify that a temporary
extension has been installed by the debugger.
([bug 1614295]({{bugzilla}}1614295))

[75.1]: {{javadoc_uri}}/GeckoRuntimeSettings.Builder.html#useMultiprocess-boolean-
[75.2]: {{javadoc_uri}}/WebExtensionController.DebuggerDelegate.html#onExtensionListUpdated--

## v74
- Added [`WebExtensionController.enable`][74.1] and [`disable`][74.2] to
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,14 @@ default TabSession getCurrentSession() {

class WebExtensionManager implements WebExtension.ActionDelegate,
WebExtensionController.PromptDelegate,
WebExtensionController.DebuggerDelegate,
TabSessionManager.TabObserver {
public WebExtension extension;

private LruCache<WebExtension.Icon, Bitmap> mBitmapCache = new LruCache<>(5);
private GeckoRuntime mRuntime;
private WebExtension.Action mDefaultAction;
private TabSessionManager mTabManager;

private WeakReference<BrowserActionDelegate> mActionDelegate;

Expand All @@ -109,6 +111,11 @@ public GeckoResult<AllowOrDeny> onInstallPrompt(final @NonNull WebExtension exte
return GeckoResult.fromValue(AllowOrDeny.ALLOW);
}

@Override
public void onExtensionListUpdated() {
refreshExtensionList();
}

// We only support either one browserAction or one pageAction
private void onAction(final WebExtension extension, final GeckoSession session,
final WebExtension.Action action) {
Expand Down Expand Up @@ -244,12 +251,12 @@ public void onCurrentSession(TabSession session) {
}
}

public GeckoResult<Void> unregisterExtension(TabSessionManager tabManager) {
public GeckoResult<Void> unregisterExtension() {
if (extension == null) {
return GeckoResult.fromValue(null);
}

tabManager.unregisterWebExtension();
mTabManager.unregisterWebExtension();

return mRuntime.getWebExtensionController().uninstall(extension).accept((unused) -> {
extension = null;
Expand All @@ -258,22 +265,24 @@ public GeckoResult<Void> unregisterExtension(TabSessionManager tabManager) {
});
}

public void registerExtension(WebExtension extension,
TabSessionManager tabManager) {
public void registerExtension(WebExtension extension) {
extension.setActionDelegate(this);
tabManager.setWebExtensionActionDelegate(extension, this);
mTabManager.setWebExtensionActionDelegate(extension, this);
this.extension = extension;
}

public WebExtensionManager(GeckoRuntime runtime,
TabSessionManager tabManager) {
runtime.getWebExtensionController()
private void refreshExtensionList() {
mRuntime.getWebExtensionController()
.list().accept(extensions -> {
for (final WebExtension extension : extensions) {
registerExtension(extension, tabManager);
registerExtension(extension);
}
});
}

public WebExtensionManager(GeckoRuntime runtime,
TabSessionManager tabManager) {
mTabManager = tabManager;
mRuntime = runtime;
}
}
Expand Down Expand Up @@ -415,6 +424,8 @@ public GeckoResult<AllowOrDeny> onCloseTab(WebExtension source, GeckoSession ses
sExtensionManager = new WebExtensionManager(sGeckoRuntime, mTabSessionManager);
mTabSessionManager.setTabObserver(sExtensionManager);

sGeckoRuntime.getWebExtensionController().setDebuggerDelegate(sExtensionManager);

// `getSystemService` call requires API level 23
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
sGeckoRuntime.setWebNotificationDelegate(new WebNotificationDelegate() {
Expand Down Expand Up @@ -797,12 +808,12 @@ private void installAddon() {
setPopupVisibility(false);
mPopupView = null;
mPopupSession = null;
sExtensionManager.unregisterExtension(mTabSessionManager).then(unused -> {
sExtensionManager.unregisterExtension().then(unused -> {
final WebExtensionController controller = sGeckoRuntime.getWebExtensionController();
controller.setPromptDelegate(sExtensionManager);
return controller.install(uri);
}).accept(extension ->
sExtensionManager.registerExtension(extension, mTabSessionManager));
sExtensionManager.registerExtension(extension));
});
builder.setNegativeButton(R.string.cancel, (dialog, which) -> {
// Nothing to do
Expand Down
14 changes: 14 additions & 0 deletions mobile/android/modules/geckoview/GeckoViewWebExtension.jsm
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,19 @@ async function updatePromptHandler(aInfo) {
}

var GeckoViewWebExtension = {
observe(aSubject, aTopic, aData) {
debug`observe ${aTopic}`;

switch (aTopic) {
case "devtools-installed-addon": {
EventDispatcher.instance.sendRequest({
type: "GeckoView:WebExtension:DebuggerListUpdated",
});
break;
}
}
},

async registerWebExtension(aId, aUri, allowContentMessaging, aCallback) {
const params = {
id: aId,
Expand Down Expand Up @@ -836,3 +849,4 @@ GeckoViewWebExtension.extensionScopes = new Map();
GeckoViewWebExtension.browserActions = new WeakMap();
// WeakMap[Extension -> PageAction]
GeckoViewWebExtension.pageActions = new WeakMap();
Services.obs.addObserver(GeckoViewWebExtension, "devtools-installed-addon");

0 comments on commit 3243713

Please sign in to comment.