mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-17 23:35:34 +00:00
Bug 1599578 - Merge WebExtensionController and EventDispatcher. r=snorp
Differential Revision: https://phabricator.services.mozilla.com/D54810 --HG-- extra : moz-landing-system : lando
This commit is contained in:
parent
7d30f98797
commit
543a4cfa26
@ -83,7 +83,6 @@ import org.mozilla.geckoview.SlowScriptResponse;
|
||||
import org.mozilla.geckoview.StorageController;
|
||||
import org.mozilla.geckoview.WebExtension;
|
||||
import org.mozilla.geckoview.WebExtensionController;
|
||||
import org.mozilla.geckoview.WebExtensionEventDispatcher;
|
||||
import org.mozilla.geckoview.WebMessage;
|
||||
import org.mozilla.geckoview.WebNotification;
|
||||
import org.mozilla.geckoview.WebNotificationDelegate;
|
||||
@ -1458,7 +1457,6 @@ package org.mozilla.geckoview {
|
||||
}
|
||||
|
||||
public class WebExtensionController {
|
||||
ctor protected WebExtensionController(GeckoRuntime, WebExtensionEventDispatcher);
|
||||
method @UiThread @Nullable public WebExtensionController.TabDelegate getTabDelegate();
|
||||
method @UiThread public void setTabDelegate(@Nullable WebExtensionController.TabDelegate);
|
||||
}
|
||||
|
@ -164,15 +164,13 @@ public final class GeckoRuntime implements Parcelable {
|
||||
private ServiceWorkerDelegate mServiceWorkerDelegate;
|
||||
private WebNotificationDelegate mNotificationDelegate;
|
||||
private RuntimeTelemetry mTelemetry;
|
||||
private final WebExtensionEventDispatcher mWebExtensionDispatcher;
|
||||
private StorageController mStorageController;
|
||||
private final WebExtensionController mWebExtensionController;
|
||||
private WebPushController mPushController;
|
||||
private final ContentBlockingController mContentBlockingController;
|
||||
|
||||
private GeckoRuntime() {
|
||||
mWebExtensionDispatcher = new WebExtensionEventDispatcher();
|
||||
mWebExtensionController = new WebExtensionController(this, mWebExtensionDispatcher);
|
||||
mWebExtensionController = new WebExtensionController(this);
|
||||
mContentBlockingController = new ContentBlockingController();
|
||||
if (sRuntime != null) {
|
||||
throw new IllegalStateException("Only one GeckoRuntime instance is allowed");
|
||||
@ -455,7 +453,7 @@ public final class GeckoRuntime implements Parcelable {
|
||||
bundle.putBoolean("allowContentMessaging",
|
||||
(webExtension.flags & WebExtension.Flags.ALLOW_CONTENT_MESSAGING) > 0);
|
||||
|
||||
mWebExtensionDispatcher.registerWebExtension(webExtension);
|
||||
mWebExtensionController.registerWebExtension(webExtension);
|
||||
|
||||
EventDispatcher.getInstance().dispatch("GeckoView:RegisterWebExtension",
|
||||
bundle, result);
|
||||
@ -485,17 +483,13 @@ public final class GeckoRuntime implements Parcelable {
|
||||
final GeckoBundle bundle = new GeckoBundle(1);
|
||||
bundle.putString("id", webExtension.id);
|
||||
|
||||
mWebExtensionDispatcher.unregisterWebExtension(webExtension);
|
||||
mWebExtensionController.unregisterWebExtension(webExtension);
|
||||
|
||||
EventDispatcher.getInstance().dispatch("GeckoView:UnregisterWebExtension", bundle, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/* protected */ @NonNull WebExtensionEventDispatcher getWebExtensionDispatcher() {
|
||||
return mWebExtensionDispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new runtime with the given settings and attach it to the given
|
||||
* context.
|
||||
|
@ -491,7 +491,7 @@ public class WebExtension {
|
||||
|| "GeckoView:PageAction:OpenPopup".equals(event)
|
||||
|| "GeckoView:BrowserAction:Update".equals(event)
|
||||
|| "GeckoView:BrowserAction:OpenPopup".equals(event)) {
|
||||
runtime.getWebExtensionDispatcher()
|
||||
runtime.getWebExtensionController()
|
||||
.handleMessage(event, message, callback, mSession);
|
||||
return;
|
||||
} else if ("GeckoView:WebExtension:CloseTab".equals(event)) {
|
||||
|
@ -3,13 +3,49 @@ package org.mozilla.geckoview;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.util.Log;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.mozilla.gecko.EventDispatcher;
|
||||
import org.mozilla.gecko.util.BundleEventListener;
|
||||
import org.mozilla.gecko.util.EventCallback;
|
||||
import org.mozilla.gecko.util.GeckoBundle;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
public class WebExtensionController {
|
||||
private final static String LOGTAG = "WebExtension";
|
||||
|
||||
private GeckoRuntime mRuntime;
|
||||
|
||||
private boolean mHandlerRegistered = false;
|
||||
|
||||
private TabDelegate mTabDelegate;
|
||||
|
||||
private Map<String, WebExtension> mExtensions = new HashMap<>();
|
||||
private Map<Long, WebExtension.Port> mPorts = new HashMap<>();
|
||||
|
||||
private Internals mInternals = new Internals();
|
||||
|
||||
// Avoids exposing listeners to the API
|
||||
private class Internals implements BundleEventListener,
|
||||
WebExtension.Port.DisconnectDelegate {
|
||||
@Override
|
||||
public void handleMessage(final String event, final GeckoBundle message,
|
||||
final EventCallback callback) {
|
||||
WebExtensionController.this.handleMessage(event, message, callback, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisconnectFromApp(final WebExtension.Port port) {
|
||||
// If the port has been disconnected from the app side, we don't need to notify anyone and
|
||||
// we just need to remove it from our list of ports.
|
||||
mPorts.remove(port.id);
|
||||
}
|
||||
}
|
||||
|
||||
public interface TabDelegate {
|
||||
/**
|
||||
* Called when tabs.create is invoked, this method returns a *newly-created* session
|
||||
@ -47,26 +83,9 @@ public class WebExtensionController {
|
||||
}
|
||||
}
|
||||
|
||||
private GeckoRuntime mRuntime;
|
||||
private WebExtensionEventDispatcher mDispatcher;
|
||||
private TabDelegate mTabDelegate;
|
||||
private final EventListener mEventListener;
|
||||
|
||||
protected WebExtensionController(final GeckoRuntime runtime, final WebExtensionEventDispatcher dispatcher) {
|
||||
mRuntime = runtime;
|
||||
mDispatcher = dispatcher;
|
||||
mEventListener = new EventListener();
|
||||
}
|
||||
|
||||
private class EventListener implements BundleEventListener {
|
||||
@Override
|
||||
public void handleMessage(final String event, final GeckoBundle message,
|
||||
final EventCallback callback) {
|
||||
if ("GeckoView:WebExtension:NewTab".equals(event)) {
|
||||
newTab(message, callback);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@UiThread
|
||||
public @Nullable TabDelegate getTabDelegate() {
|
||||
return mTabDelegate;
|
||||
}
|
||||
|
||||
@UiThread
|
||||
@ -74,14 +93,14 @@ public class WebExtensionController {
|
||||
if (delegate == null) {
|
||||
if (mTabDelegate != null) {
|
||||
EventDispatcher.getInstance().unregisterUiThreadListener(
|
||||
mEventListener,
|
||||
mInternals,
|
||||
"GeckoView:WebExtension:NewTab"
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (mTabDelegate == null) {
|
||||
EventDispatcher.getInstance().registerUiThreadListener(
|
||||
mEventListener,
|
||||
mInternals,
|
||||
"GeckoView:WebExtension:NewTab"
|
||||
);
|
||||
}
|
||||
@ -89,9 +108,78 @@ public class WebExtensionController {
|
||||
mTabDelegate = delegate;
|
||||
}
|
||||
|
||||
@UiThread
|
||||
public @Nullable TabDelegate getTabDelegate() {
|
||||
return mTabDelegate;
|
||||
/* package */ WebExtensionController(final GeckoRuntime runtime) {
|
||||
mRuntime = runtime;
|
||||
}
|
||||
|
||||
/* package */ void registerWebExtension(final WebExtension webExtension) {
|
||||
if (!mHandlerRegistered) {
|
||||
EventDispatcher.getInstance().registerUiThreadListener(
|
||||
mInternals,
|
||||
"GeckoView:WebExtension:Message",
|
||||
"GeckoView:WebExtension:PortMessage",
|
||||
"GeckoView:WebExtension:Connect",
|
||||
"GeckoView:WebExtension:Disconnect",
|
||||
|
||||
// {Browser,Page}Actions
|
||||
"GeckoView:BrowserAction:Update",
|
||||
"GeckoView:BrowserAction:OpenPopup",
|
||||
"GeckoView:PageAction:Update",
|
||||
"GeckoView:PageAction:OpenPopup"
|
||||
);
|
||||
mHandlerRegistered = true;
|
||||
}
|
||||
|
||||
mExtensions.put(webExtension.id, webExtension);
|
||||
}
|
||||
|
||||
/* package */ void handleMessage(final String event, final GeckoBundle message,
|
||||
final EventCallback callback, final GeckoSession session) {
|
||||
if ("GeckoView:WebExtension:NewTab".equals(event)) {
|
||||
newTab(message, callback);
|
||||
return;
|
||||
} else if ("GeckoView:WebExtension:Disconnect".equals(event)) {
|
||||
disconnect(message.getLong("portId", -1), callback);
|
||||
return;
|
||||
} else if ("GeckoView:WebExtension:PortMessage".equals(event)) {
|
||||
portMessage(message, callback);
|
||||
return;
|
||||
} else if ("GeckoView:BrowserAction:Update".equals(event)) {
|
||||
actionUpdate(message, session, WebExtension.Action.TYPE_BROWSER_ACTION);
|
||||
return;
|
||||
} else if ("GeckoView:PageAction:Update".equals(event)) {
|
||||
actionUpdate(message, session, WebExtension.Action.TYPE_PAGE_ACTION);
|
||||
return;
|
||||
} else if ("GeckoView:BrowserAction:OpenPopup".equals(event)) {
|
||||
openPopup(message, session, WebExtension.Action.TYPE_BROWSER_ACTION);
|
||||
return;
|
||||
} else if ("GeckoView:PageAction:OpenPopup".equals(event)) {
|
||||
openPopup(message, session, WebExtension.Action.TYPE_PAGE_ACTION);
|
||||
return;
|
||||
}
|
||||
|
||||
final String nativeApp = message.getString("nativeApp");
|
||||
if (nativeApp == null) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
throw new RuntimeException("Missing required nativeApp message parameter.");
|
||||
}
|
||||
callback.sendError("Missing nativeApp parameter.");
|
||||
return;
|
||||
}
|
||||
|
||||
final WebExtension.MessageSender sender = fromBundle(message.getBundle("sender"), session);
|
||||
if (sender == null) {
|
||||
if (callback != null) {
|
||||
callback.sendError("Could not find recipient for " + message.getBundle("sender"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if ("GeckoView:WebExtension:Connect".equals(event)) {
|
||||
connect(nativeApp, message.getLong("portId", -1), callback, sender);
|
||||
} else if ("GeckoView:WebExtension:Message".equals(event)) {
|
||||
message(nativeApp, message, callback, sender);
|
||||
}
|
||||
}
|
||||
|
||||
private void newTab(final GeckoBundle message, final EventCallback callback) {
|
||||
@ -100,7 +188,7 @@ public class WebExtensionController {
|
||||
return;
|
||||
}
|
||||
|
||||
WebExtension extension = mDispatcher.getWebExtension(message.getString("extensionId"));
|
||||
WebExtension extension = mExtensions.get(message.getString("extensionId"));
|
||||
|
||||
final GeckoResult<GeckoSession> result = mTabDelegate.onNewTab(extension, message.getString("uri"));
|
||||
|
||||
@ -131,7 +219,7 @@ public class WebExtensionController {
|
||||
return;
|
||||
}
|
||||
|
||||
WebExtension extension = mRuntime.getWebExtensionDispatcher().getWebExtension(message.getString("extensionId"));
|
||||
WebExtension extension = mExtensions.get(message.getString("extensionId"));
|
||||
|
||||
GeckoResult<AllowOrDeny> result = mTabDelegate.onCloseTab(extension, session);
|
||||
|
||||
@ -143,4 +231,251 @@ public class WebExtensionController {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/* package */ void unregisterWebExtension(final WebExtension webExtension) {
|
||||
mExtensions.remove(webExtension.id);
|
||||
|
||||
// Some ports may still be open so we need to go through the list and close all of the
|
||||
// ports tied to this web extension
|
||||
Iterator<Map.Entry<Long, WebExtension.Port>> it = mPorts.entrySet().iterator();
|
||||
while (it.hasNext()) {
|
||||
WebExtension.Port port = it.next().getValue();
|
||||
|
||||
if (port.sender.webExtension.equals(webExtension)) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* package */ @Nullable WebExtension getWebExtension(final String id) {
|
||||
return mExtensions.get(id);
|
||||
}
|
||||
|
||||
private WebExtension.MessageSender fromBundle(final GeckoBundle sender,
|
||||
final GeckoSession session) {
|
||||
final String extensionId = sender.getString("extensionId");
|
||||
WebExtension extension = mExtensions.get(extensionId);
|
||||
|
||||
if (extension == null) {
|
||||
// All senders should have an extension
|
||||
return null;
|
||||
}
|
||||
|
||||
final String envType = sender.getString("envType");
|
||||
@WebExtension.MessageSender.EnvType int environmentType;
|
||||
|
||||
if ("content_child".equals(envType)) {
|
||||
environmentType = WebExtension.MessageSender.ENV_TYPE_CONTENT_SCRIPT;
|
||||
} else if ("addon_child".equals(envType)) {
|
||||
// TODO Bug 1554277: check that this message is coming from the right process
|
||||
environmentType = WebExtension.MessageSender.ENV_TYPE_EXTENSION;
|
||||
} else {
|
||||
environmentType = WebExtension.MessageSender.ENV_TYPE_UNKNOWN;
|
||||
}
|
||||
|
||||
if (environmentType == WebExtension.MessageSender.ENV_TYPE_UNKNOWN) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
throw new RuntimeException("Missing or unknown envType.");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
final String url = sender.getString("url");
|
||||
boolean isTopLevel;
|
||||
if (session == null) {
|
||||
// This message is coming from the background page
|
||||
isTopLevel = true;
|
||||
} else {
|
||||
// If session is present we are either receiving this message from a content script or
|
||||
// an extension page, let's make sure we have the proper identification so that
|
||||
// embedders can check the origin of this message.
|
||||
if (!sender.containsKey("frameId") || !sender.containsKey("url") ||
|
||||
// -1 is an invalid frame id
|
||||
sender.getInt("frameId", -1) == -1) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
throw new RuntimeException("Missing sender information.");
|
||||
}
|
||||
|
||||
// This message does not have the proper identification and may be compromised,
|
||||
// let's ignore it.
|
||||
return null;
|
||||
}
|
||||
|
||||
isTopLevel = sender.getInt("frameId", -1) == 0;
|
||||
}
|
||||
|
||||
return new WebExtension.MessageSender(extension, session, url, environmentType, isTopLevel);
|
||||
}
|
||||
|
||||
private void disconnect(final long portId, final EventCallback callback) {
|
||||
final WebExtension.Port port = mPorts.get(portId);
|
||||
if (port == null) {
|
||||
Log.d(LOGTAG, "Could not find recipient for port " + portId);
|
||||
return;
|
||||
}
|
||||
|
||||
port.delegate.onDisconnect(port);
|
||||
mPorts.remove(portId);
|
||||
|
||||
if (callback != null) {
|
||||
callback.sendSuccess(true);
|
||||
}
|
||||
}
|
||||
|
||||
private WebExtension.MessageDelegate getDelegate(
|
||||
final String nativeApp, final WebExtension.MessageSender sender,
|
||||
final EventCallback callback) {
|
||||
if ((sender.webExtension.flags & WebExtension.Flags.ALLOW_CONTENT_MESSAGING) == 0 &&
|
||||
sender.environmentType == WebExtension.MessageSender.ENV_TYPE_CONTENT_SCRIPT) {
|
||||
callback.sendError("This NativeApp can't receive messages from Content Scripts.");
|
||||
return null;
|
||||
}
|
||||
|
||||
WebExtension.MessageDelegate delegate = null;
|
||||
|
||||
if (sender.session != null) {
|
||||
delegate = sender.session.getMessageDelegate(sender.webExtension, nativeApp);
|
||||
} else if (sender.environmentType == WebExtension.MessageSender.ENV_TYPE_EXTENSION) {
|
||||
delegate = sender.webExtension.messageDelegates.get(nativeApp);
|
||||
}
|
||||
|
||||
if (delegate == null) {
|
||||
callback.sendError("Native app not found or this WebExtension does not have permissions.");
|
||||
return null;
|
||||
}
|
||||
|
||||
return delegate;
|
||||
}
|
||||
|
||||
private void connect(final String nativeApp, final long portId, final EventCallback callback,
|
||||
final WebExtension.MessageSender sender) {
|
||||
if (portId == -1) {
|
||||
callback.sendError("Missing portId.");
|
||||
return;
|
||||
}
|
||||
|
||||
final WebExtension.Port port = new WebExtension.Port(nativeApp, portId, sender, mInternals);
|
||||
mPorts.put(port.id, port);
|
||||
|
||||
final WebExtension.MessageDelegate delegate = getDelegate(nativeApp, sender, callback);
|
||||
if (delegate == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
delegate.onConnect(port);
|
||||
callback.sendSuccess(true);
|
||||
}
|
||||
|
||||
private void portMessage(final GeckoBundle message, final EventCallback callback) {
|
||||
final long portId = message.getLong("portId", -1);
|
||||
final WebExtension.Port port = mPorts.get(portId);
|
||||
if (port == null) {
|
||||
callback.sendError("Could not find recipient for port " + portId);
|
||||
return;
|
||||
}
|
||||
|
||||
final Object content;
|
||||
try {
|
||||
content = message.toJSONObject().get("data");
|
||||
} catch (JSONException ex) {
|
||||
callback.sendError(ex);
|
||||
return;
|
||||
}
|
||||
|
||||
port.delegate.onPortMessage(content, port);
|
||||
callback.sendSuccess(null);
|
||||
}
|
||||
|
||||
private void message(final String nativeApp, final GeckoBundle message,
|
||||
final EventCallback callback, final WebExtension.MessageSender sender) {
|
||||
final Object content;
|
||||
try {
|
||||
content = message.toJSONObject().get("data");
|
||||
} catch (JSONException ex) {
|
||||
callback.sendError(ex);
|
||||
return;
|
||||
}
|
||||
|
||||
final WebExtension.MessageDelegate delegate = getDelegate(nativeApp, sender, callback);
|
||||
if (delegate == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final GeckoResult<Object> response = delegate.onMessage(nativeApp, content, sender);
|
||||
if (response == null) {
|
||||
callback.sendSuccess(null);
|
||||
return;
|
||||
}
|
||||
|
||||
response.accept(
|
||||
value -> callback.sendSuccess(value),
|
||||
exception -> callback.sendError(exception));
|
||||
}
|
||||
|
||||
private WebExtension extensionFromBundle(final GeckoBundle message) {
|
||||
final String extensionId = message.getString("extensionId");
|
||||
|
||||
final WebExtension extension = mExtensions.get(extensionId);
|
||||
if (extension == null) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
// TODO: Bug 1582185 Some gecko tests install WebExtensions that we
|
||||
// don't know about and cause this to trigger.
|
||||
// throw new RuntimeException("Could not find extension: " + extensionId);
|
||||
}
|
||||
Log.e(LOGTAG, "Could not find extension: " + extensionId);
|
||||
}
|
||||
|
||||
return extension;
|
||||
}
|
||||
|
||||
private void openPopup(final GeckoBundle message, final GeckoSession session,
|
||||
final @WebExtension.Action.ActionType int actionType) {
|
||||
final WebExtension extension = extensionFromBundle(message);
|
||||
if (extension == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final WebExtension.Action action = new WebExtension.Action(
|
||||
actionType, message.getBundle("action"), extension);
|
||||
|
||||
final WebExtension.ActionDelegate delegate = actionDelegateFor(extension, session);
|
||||
if (delegate == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final GeckoResult<GeckoSession> popup = delegate.onOpenPopup(extension, action);
|
||||
action.openPopup(popup);
|
||||
}
|
||||
|
||||
private WebExtension.ActionDelegate actionDelegateFor(final WebExtension extension,
|
||||
final GeckoSession session) {
|
||||
if (session == null) {
|
||||
return extension.actionDelegate;
|
||||
}
|
||||
|
||||
return session.getWebExtensionActionDelegate(extension);
|
||||
}
|
||||
|
||||
private void actionUpdate(final GeckoBundle message, final GeckoSession session,
|
||||
final @WebExtension.Action.ActionType int actionType) {
|
||||
final WebExtension extension = extensionFromBundle(message);
|
||||
if (extension == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final WebExtension.ActionDelegate delegate = actionDelegateFor(extension, session);
|
||||
if (delegate == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final WebExtension.Action action = new WebExtension.Action(
|
||||
actionType, message.getBundle("action"), extension);
|
||||
if (actionType == WebExtension.Action.TYPE_BROWSER_ACTION) {
|
||||
delegate.onBrowserAction(extension, session, action);
|
||||
} else if (actionType == WebExtension.Action.TYPE_PAGE_ACTION) {
|
||||
delegate.onPageAction(extension, session, action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,350 +0,0 @@
|
||||
package org.mozilla.geckoview;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.mozilla.gecko.EventDispatcher;
|
||||
import org.mozilla.gecko.util.BundleEventListener;
|
||||
import org.mozilla.gecko.util.EventCallback;
|
||||
import org.mozilla.gecko.util.GeckoBundle;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
/* protected */ class WebExtensionEventDispatcher implements BundleEventListener,
|
||||
WebExtension.Port.DisconnectDelegate {
|
||||
private final static String LOGTAG = "WebExtension";
|
||||
|
||||
private boolean mHandlerRegistered = false;
|
||||
|
||||
private Map<String, WebExtension> mExtensions = new HashMap<>();
|
||||
private Map<Long, WebExtension.Port> mPorts = new HashMap<>();
|
||||
|
||||
public void registerWebExtension(final WebExtension webExtension) {
|
||||
if (!mHandlerRegistered) {
|
||||
EventDispatcher.getInstance().registerUiThreadListener(
|
||||
this,
|
||||
"GeckoView:WebExtension:Message",
|
||||
"GeckoView:WebExtension:PortMessage",
|
||||
"GeckoView:WebExtension:Connect",
|
||||
"GeckoView:WebExtension:Disconnect",
|
||||
|
||||
// {Browser,Page}Actions
|
||||
"GeckoView:BrowserAction:Update",
|
||||
"GeckoView:BrowserAction:OpenPopup",
|
||||
"GeckoView:PageAction:Update",
|
||||
"GeckoView:PageAction:OpenPopup"
|
||||
);
|
||||
mHandlerRegistered = true;
|
||||
}
|
||||
|
||||
mExtensions.put(webExtension.id, webExtension);
|
||||
}
|
||||
|
||||
public void unregisterWebExtension(final WebExtension webExtension) {
|
||||
mExtensions.remove(webExtension.id);
|
||||
|
||||
// Some ports may still be open so we need to go through the list and close all of the
|
||||
// ports tied to this web extension
|
||||
Iterator<Map.Entry<Long, WebExtension.Port>> it = mPorts.entrySet().iterator();
|
||||
while (it.hasNext()) {
|
||||
WebExtension.Port port = it.next().getValue();
|
||||
|
||||
if (port.sender.webExtension.equals(webExtension)) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable WebExtension getWebExtension(final String id) {
|
||||
return mExtensions.get(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(final String event, final GeckoBundle message,
|
||||
final EventCallback callback) {
|
||||
handleMessage(event, message, callback, null);
|
||||
}
|
||||
|
||||
private WebExtension.MessageSender fromBundle(final GeckoBundle sender,
|
||||
final GeckoSession session) {
|
||||
final String extensionId = sender.getString("extensionId");
|
||||
WebExtension extension = mExtensions.get(extensionId);
|
||||
|
||||
if (extension == null) {
|
||||
// All senders should have an extension
|
||||
return null;
|
||||
}
|
||||
|
||||
final String envType = sender.getString("envType");
|
||||
@WebExtension.MessageSender.EnvType int environmentType;
|
||||
|
||||
if ("content_child".equals(envType)) {
|
||||
environmentType = WebExtension.MessageSender.ENV_TYPE_CONTENT_SCRIPT;
|
||||
} else if ("addon_child".equals(envType)) {
|
||||
// TODO Bug 1554277: check that this message is coming from the right process
|
||||
environmentType = WebExtension.MessageSender.ENV_TYPE_EXTENSION;
|
||||
} else {
|
||||
environmentType = WebExtension.MessageSender.ENV_TYPE_UNKNOWN;
|
||||
}
|
||||
|
||||
if (environmentType == WebExtension.MessageSender.ENV_TYPE_UNKNOWN) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
throw new RuntimeException("Missing or unknown envType.");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
final String url = sender.getString("url");
|
||||
boolean isTopLevel;
|
||||
if (session == null) {
|
||||
// This message is coming from the background page
|
||||
isTopLevel = true;
|
||||
} else {
|
||||
// If session is present we are either receiving this message from a content script or
|
||||
// an extension page, let's make sure we have the proper identification so that
|
||||
// embedders can check the origin of this message.
|
||||
if (!sender.containsKey("frameId") || !sender.containsKey("url") ||
|
||||
// -1 is an invalid frame id
|
||||
sender.getInt("frameId", -1) == -1) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
throw new RuntimeException("Missing sender information.");
|
||||
}
|
||||
|
||||
// This message does not have the proper identification and may be compromised,
|
||||
// let's ignore it.
|
||||
return null;
|
||||
}
|
||||
|
||||
isTopLevel = sender.getInt("frameId", -1) == 0;
|
||||
}
|
||||
|
||||
return new WebExtension.MessageSender(extension, session, url, environmentType, isTopLevel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisconnectFromApp(final WebExtension.Port port) {
|
||||
// If the port has been disconnected from the app side, we don't need to notify anyone and
|
||||
// we just need to remove it from our list of ports.
|
||||
mPorts.remove(port.id);
|
||||
}
|
||||
|
||||
private void disconnect(final long portId, final EventCallback callback) {
|
||||
final WebExtension.Port port = mPorts.get(portId);
|
||||
if (port == null) {
|
||||
Log.d(LOGTAG, "Could not find recipient for port " + portId);
|
||||
return;
|
||||
}
|
||||
|
||||
port.delegate.onDisconnect(port);
|
||||
mPorts.remove(portId);
|
||||
|
||||
if (callback != null) {
|
||||
callback.sendSuccess(true);
|
||||
}
|
||||
}
|
||||
|
||||
private WebExtension.MessageDelegate getDelegate(
|
||||
final String nativeApp, final WebExtension.MessageSender sender,
|
||||
final EventCallback callback) {
|
||||
if ((sender.webExtension.flags & WebExtension.Flags.ALLOW_CONTENT_MESSAGING) == 0 &&
|
||||
sender.environmentType == WebExtension.MessageSender.ENV_TYPE_CONTENT_SCRIPT) {
|
||||
callback.sendError("This NativeApp can't receive messages from Content Scripts.");
|
||||
return null;
|
||||
}
|
||||
|
||||
WebExtension.MessageDelegate delegate = null;
|
||||
|
||||
if (sender.session != null) {
|
||||
delegate = sender.session.getMessageDelegate(sender.webExtension, nativeApp);
|
||||
} else if (sender.environmentType == WebExtension.MessageSender.ENV_TYPE_EXTENSION) {
|
||||
delegate = sender.webExtension.messageDelegates.get(nativeApp);
|
||||
}
|
||||
|
||||
if (delegate == null) {
|
||||
callback.sendError("Native app not found or this WebExtension does not have permissions.");
|
||||
return null;
|
||||
}
|
||||
|
||||
return delegate;
|
||||
}
|
||||
|
||||
private void connect(final String nativeApp, final long portId, final EventCallback callback,
|
||||
final WebExtension.MessageSender sender) {
|
||||
if (portId == -1) {
|
||||
callback.sendError("Missing portId.");
|
||||
return;
|
||||
}
|
||||
|
||||
final WebExtension.Port port = new WebExtension.Port(nativeApp, portId, sender, this);
|
||||
mPorts.put(port.id, port);
|
||||
|
||||
final WebExtension.MessageDelegate delegate = getDelegate(nativeApp, sender, callback);
|
||||
if (delegate == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
delegate.onConnect(port);
|
||||
callback.sendSuccess(true);
|
||||
}
|
||||
|
||||
private void portMessage(final GeckoBundle message, final EventCallback callback) {
|
||||
final long portId = message.getLong("portId", -1);
|
||||
final WebExtension.Port port = mPorts.get(portId);
|
||||
if (port == null) {
|
||||
callback.sendError("Could not find recipient for port " + portId);
|
||||
return;
|
||||
}
|
||||
|
||||
final Object content;
|
||||
try {
|
||||
content = message.toJSONObject().get("data");
|
||||
} catch (JSONException ex) {
|
||||
callback.sendError(ex);
|
||||
return;
|
||||
}
|
||||
|
||||
port.delegate.onPortMessage(content, port);
|
||||
callback.sendSuccess(null);
|
||||
}
|
||||
|
||||
private void message(final String nativeApp, final GeckoBundle message,
|
||||
final EventCallback callback, final WebExtension.MessageSender sender) {
|
||||
final Object content;
|
||||
try {
|
||||
content = message.toJSONObject().get("data");
|
||||
} catch (JSONException ex) {
|
||||
callback.sendError(ex);
|
||||
return;
|
||||
}
|
||||
|
||||
final WebExtension.MessageDelegate delegate = getDelegate(nativeApp, sender, callback);
|
||||
if (delegate == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final GeckoResult<Object> response = delegate.onMessage(nativeApp, content, sender);
|
||||
if (response == null) {
|
||||
callback.sendSuccess(null);
|
||||
return;
|
||||
}
|
||||
|
||||
response.accept(
|
||||
value -> callback.sendSuccess(value),
|
||||
exception -> callback.sendError(exception));
|
||||
}
|
||||
|
||||
private WebExtension extensionFromBundle(final GeckoBundle message) {
|
||||
final String extensionId = message.getString("extensionId");
|
||||
|
||||
final WebExtension extension = mExtensions.get(extensionId);
|
||||
if (extension == null) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
// TODO: Bug 1582185 Some gecko tests install WebExtensions that we
|
||||
// don't know about and cause this to trigger.
|
||||
// throw new RuntimeException("Could not find extension: " + extensionId);
|
||||
}
|
||||
Log.e(LOGTAG, "Could not find extension: " + extensionId);
|
||||
}
|
||||
|
||||
return extension;
|
||||
}
|
||||
|
||||
private void openPopup(final GeckoBundle message, final GeckoSession session,
|
||||
final @WebExtension.Action.ActionType int actionType) {
|
||||
final WebExtension extension = extensionFromBundle(message);
|
||||
if (extension == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final WebExtension.Action action = new WebExtension.Action(
|
||||
actionType, message.getBundle("action"), extension);
|
||||
|
||||
final WebExtension.ActionDelegate delegate = actionDelegateFor(extension, session);
|
||||
if (delegate == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final GeckoResult<GeckoSession> popup = delegate.onOpenPopup(extension, action);
|
||||
action.openPopup(popup);
|
||||
}
|
||||
|
||||
private WebExtension.ActionDelegate actionDelegateFor(final WebExtension extension,
|
||||
final GeckoSession session) {
|
||||
if (session == null) {
|
||||
return extension.actionDelegate;
|
||||
}
|
||||
|
||||
return session.getWebExtensionActionDelegate(extension);
|
||||
}
|
||||
|
||||
private void actionUpdate(final GeckoBundle message, final GeckoSession session,
|
||||
final @WebExtension.Action.ActionType int actionType) {
|
||||
final WebExtension extension = extensionFromBundle(message);
|
||||
if (extension == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final WebExtension.ActionDelegate delegate = actionDelegateFor(extension, session);
|
||||
if (delegate == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final WebExtension.Action action = new WebExtension.Action(
|
||||
actionType, message.getBundle("action"), extension);
|
||||
if (actionType == WebExtension.Action.TYPE_BROWSER_ACTION) {
|
||||
delegate.onBrowserAction(extension, session, action);
|
||||
} else if (actionType == WebExtension.Action.TYPE_PAGE_ACTION) {
|
||||
delegate.onPageAction(extension, session, action);
|
||||
}
|
||||
}
|
||||
|
||||
public void handleMessage(final String event, final GeckoBundle message,
|
||||
final EventCallback callback, final GeckoSession session) {
|
||||
if ("GeckoView:WebExtension:Disconnect".equals(event)) {
|
||||
disconnect(message.getLong("portId", -1), callback);
|
||||
return;
|
||||
} else if ("GeckoView:WebExtension:PortMessage".equals(event)) {
|
||||
portMessage(message, callback);
|
||||
return;
|
||||
} else if ("GeckoView:BrowserAction:Update".equals(event)) {
|
||||
actionUpdate(message, session, WebExtension.Action.TYPE_BROWSER_ACTION);
|
||||
return;
|
||||
} else if ("GeckoView:PageAction:Update".equals(event)) {
|
||||
actionUpdate(message, session, WebExtension.Action.TYPE_PAGE_ACTION);
|
||||
return;
|
||||
} else if ("GeckoView:BrowserAction:OpenPopup".equals(event)) {
|
||||
openPopup(message, session, WebExtension.Action.TYPE_BROWSER_ACTION);
|
||||
return;
|
||||
} else if ("GeckoView:PageAction:OpenPopup".equals(event)) {
|
||||
openPopup(message, session, WebExtension.Action.TYPE_PAGE_ACTION);
|
||||
return;
|
||||
}
|
||||
|
||||
final String nativeApp = message.getString("nativeApp");
|
||||
if (nativeApp == null) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
throw new RuntimeException("Missing required nativeApp message parameter.");
|
||||
}
|
||||
callback.sendError("Missing nativeApp parameter.");
|
||||
return;
|
||||
}
|
||||
|
||||
final WebExtension.MessageSender sender = fromBundle(message.getBundle("sender"), session);
|
||||
if (sender == null) {
|
||||
if (callback != null) {
|
||||
callback.sendError("Could not find recipient for " + message.getBundle("sender"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if ("GeckoView:WebExtension:Connect".equals(event)) {
|
||||
connect(nativeApp, message.getLong("portId", -1), callback, sender);
|
||||
} else if ("GeckoView:WebExtension:Message".equals(event)) {
|
||||
message(nativeApp, message, callback, sender);
|
||||
}
|
||||
}
|
||||
}
|
@ -465,4 +465,4 @@ exclude: true
|
||||
[65.24]: {{javadoc_uri}}/CrashReporter.html#sendCrashReport-android.content.Context-android.os.Bundle-java.lang.String-
|
||||
[65.25]: {{javadoc_uri}}/GeckoResult.html
|
||||
|
||||
[api-version]: 4c9f04038d8478206efac05b518920819faeacea
|
||||
[api-version]: f7f3675072cd2a0e4d65010942c8f02b4e4266f1
|
||||
|
Loading…
Reference in New Issue
Block a user