Bug 1551278 - Make WebExtension listeners per-extension. r=snorp a=RyanVM
authorAgi Sferro <agi@sferro.dev>
Thu, 11 Jul 2019 14:41:04 -0700
changeset 544511 da83a38eed7117793dbd1c64e53d4aab10635095
parent 544510 54a42dbdebfb029af49afff316949000c7de6da5
child 544512 5c505910ebf036335a437daeacbea60e9f48c2f3
push id2131
push userffxbld-merge
push dateMon, 26 Aug 2019 18:30:20 +0000
treeherdermozilla-release@b19ffb3ca153 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssnorp, RyanVM
bugs1551278
milestone69.0
Bug 1551278 - Make WebExtension listeners per-extension. r=snorp a=RyanVM Right now listeners for a given native app receive messages from all WebExtensions. This is wrong as listeners should be extension specific so that only the intended extension can send messages to the app. Differential Revision: https://phabricator.services.mozilla.com/D35948
mobile/android/geckoview/api.txt
mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/WebExtensionTest.kt
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebExtension.java
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebExtensionEventDispatcher.java
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md
--- a/mobile/android/geckoview/api.txt
+++ b/mobile/android/geckoview/api.txt
@@ -388,17 +388,17 @@ package org.mozilla.geckoview {
     method @UiThread @NonNull public CompositorController getCompositorController();
     method @AnyThread @Nullable public ContentBlocking.Delegate getContentBlockingDelegate();
     method @UiThread @Nullable public GeckoSession.ContentDelegate getContentDelegate();
     method @AnyThread @NonNull public static String getDefaultUserAgent();
     method @UiThread @NonNull public DynamicToolbarAnimator getDynamicToolbarAnimator();
     method @AnyThread @NonNull public SessionFinder getFinder();
     method @AnyThread @Nullable public GeckoSession.HistoryDelegate getHistoryDelegate();
     method @AnyThread @Nullable public GeckoSession.MediaDelegate getMediaDelegate();
-    method @AnyThread @Nullable public WebExtension.MessageDelegate getMessageDelegate(@NonNull String);
+    method @AnyThread @Nullable public WebExtension.MessageDelegate getMessageDelegate(@NonNull WebExtension, @NonNull String);
     method @UiThread @Nullable public GeckoSession.NavigationDelegate getNavigationDelegate();
     method @UiThread @NonNull public OverscrollEdgeEffect getOverscrollEdgeEffect();
     method @UiThread public void getPageToScreenMatrix(@NonNull Matrix);
     method @UiThread public void getPageToSurfaceMatrix(@NonNull Matrix);
     method @UiThread @NonNull public PanZoomController getPanZoomController();
     method @UiThread @Nullable public GeckoSession.PermissionDelegate getPermissionDelegate();
     method @UiThread @Nullable public GeckoSession.ProgressDelegate getProgressDelegate();
     method @AnyThread @Nullable public GeckoSession.PromptDelegate getPromptDelegate();
@@ -426,17 +426,17 @@ package org.mozilla.geckoview {
     method @AnyThread public void reload();
     method @AnyThread public void restoreState(@NonNull GeckoSession.SessionState);
     method @AnyThread public void setActive(boolean);
     method @AnyThread public void setContentBlockingDelegate(@Nullable ContentBlocking.Delegate);
     method @UiThread public void setContentDelegate(@Nullable GeckoSession.ContentDelegate);
     method @AnyThread public void setFocused(boolean);
     method @AnyThread public void setHistoryDelegate(@Nullable GeckoSession.HistoryDelegate);
     method @AnyThread public void setMediaDelegate(@Nullable GeckoSession.MediaDelegate);
-    method @AnyThread public void setMessageDelegate(@Nullable WebExtension.MessageDelegate, @NonNull String);
+    method @AnyThread public void setMessageDelegate(@NonNull WebExtension, @Nullable WebExtension.MessageDelegate, @NonNull String);
     method @UiThread public void setNavigationDelegate(@Nullable GeckoSession.NavigationDelegate);
     method @UiThread public void setPermissionDelegate(@Nullable GeckoSession.PermissionDelegate);
     method @UiThread public void setProgressDelegate(@Nullable GeckoSession.ProgressDelegate);
     method @AnyThread public void setPromptDelegate(@Nullable GeckoSession.PromptDelegate);
     method @UiThread public void setScrollDelegate(@Nullable GeckoSession.ScrollDelegate);
     method @UiThread public void setSelectionActionDelegate(@Nullable GeckoSession.SelectionActionDelegate);
     method @AnyThread public void stop();
     method @UiThread protected void setShouldPinOnScreen(boolean);
--- 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
@@ -126,17 +126,17 @@ class WebExtensionTest : BaseSessionTest
         val uuid = "{${UUID.randomUUID()}}"
 
         if (background) {
             webExtension = WebExtension(MESSAGING_BACKGROUND, uuid, WebExtension.Flags.NONE)
             webExtension.setMessageDelegate(messageDelegate, "browser")
         } else {
             webExtension = WebExtension(MESSAGING_CONTENT, uuid,
                     WebExtension.Flags.ALLOW_CONTENT_MESSAGING)
-            sessionRule.session.setMessageDelegate(messageDelegate, "browser");
+            sessionRule.session.setMessageDelegate(webExtension, messageDelegate, "browser");
         }
 
         return webExtension
     }
 
     @Test
     @WithDevToolsAPI
     fun contentMessaging() {
@@ -432,17 +432,17 @@ class WebExtensionTest : BaseSessionTest
                 }
 
                 return null
             }
         }
 
         messaging = WebExtension("resource://android/assets/web_extensions/messaging-iframe/",
                 "{${UUID.randomUUID()}}", WebExtension.Flags.ALLOW_CONTENT_MESSAGING)
-        sessionRule.session.setMessageDelegate(messageDelegate, "browser");
+        sessionRule.session.setMessageDelegate(messaging, messageDelegate, "browser");
 
         sessionRule.waitForResult(sessionRule.runtime.registerWebExtension(messaging))
         sessionRule.waitForResult(portTopLevel)
         sessionRule.waitForResult(portIframe)
         sessionRule.waitForResult(messageTopLevel)
         sessionRule.waitForResult(messageIframe)
         sessionRule.waitForResult(sessionRule.runtime.unregisterWebExtension(messaging))
     }
@@ -478,17 +478,17 @@ class WebExtensionTest : BaseSessionTest
 
                 return null
             }
         }
 
         extension = WebExtension("resource://android/assets/web_extensions/extension-page-update/")
 
         sessionRule.waitForResult(sessionRule.runtime.registerWebExtension(extension))
-        mainSession.setMessageDelegate(messageDelegate, "browser")
+        mainSession.setMessageDelegate(extension, messageDelegate, "browser")
 
         mainSession.loadUri("http://example.com");
 
         mainSession.waitUntilCalled(object : Callbacks.NavigationDelegate, Callbacks.ProgressDelegate {
             @GeckoSessionTestRule.AssertCalled(count = 1)
             override fun onLocationChange(session: GeckoSession, url: String?) {
                 assertThat("Url should load example.com first",
                         url, equalTo("http://example.com/"))
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
@@ -334,38 +334,69 @@ public class GeckoSession implements Par
                             callback.sendError("Failed to fetch visited statuses for URIs");
                             return null;
                         }
                     });
                 }
             }
         };
 
+    private static class WebExtensionSender {
+        public String webExtensionId;
+        public String nativeApp;
+
+        public WebExtensionSender(final String webExtensionId, final String nativeApp) {
+            this.webExtensionId = webExtensionId;
+            this.nativeApp = nativeApp;
+        }
+
+        @Override
+        public boolean equals(final Object other) {
+            if (!(other instanceof WebExtensionSender)) {
+                return false;
+            }
+
+            WebExtensionSender o = (WebExtensionSender) other;
+            return webExtensionId.equals(o.webExtensionId) &&
+                    nativeApp.equals(o.nativeApp);
+        }
+
+        @Override
+        public int hashCode() {
+            int result = 17;
+            result = 31 * result + (webExtensionId != null ? webExtensionId.hashCode() : 0);
+            result = 31 * result + (nativeApp != null ? nativeApp.hashCode() : 0);
+            return result;
+        }
+    }
+
     private final class WebExtensionListener implements BundleEventListener {
-        final private HashMap<String, WebExtension.MessageDelegate> mMessageDelegates;
+        final private HashMap<WebExtensionSender, WebExtension.MessageDelegate> mMessageDelegates;
 
         public WebExtensionListener() {
             mMessageDelegates = new HashMap<>();
         }
 
         /* package */ void registerListeners() {
             getEventDispatcher().registerUiThreadListener(this,
                     "GeckoView:WebExtension:Message",
                     "GeckoView:WebExtension:PortMessage",
                     "GeckoView:WebExtension:Connect",
                     null);
         }
 
-        public void setDelegate(final WebExtension.MessageDelegate delegate,
+        public void setDelegate(final WebExtension webExtension,
+                                final WebExtension.MessageDelegate delegate,
                                 final String nativeApp) {
-            mMessageDelegates.put(nativeApp, delegate);
+            mMessageDelegates.put(new WebExtensionSender(webExtension.id, nativeApp), delegate);
         }
 
-        public WebExtension.MessageDelegate getDelegate(final String nativeApp) {
-            return mMessageDelegates.get(nativeApp);
+        public WebExtension.MessageDelegate getDelegate(final WebExtension webExtension,
+                                                        final String nativeApp) {
+            return mMessageDelegates.get(new WebExtensionSender(webExtension.id, nativeApp));
         }
 
         @Override
         public void handleMessage(final String event, final GeckoBundle message,
                                   final EventCallback callback) {
             if (mWindow == null || mWindow.runtime.getWebExtensionDispatcher() == null) {
                 return;
             }
@@ -379,50 +410,56 @@ public class GeckoSession implements Par
         }
     }
 
     private final WebExtensionListener mWebExtensionListener;
 
     /**
      * Get the message delegate for <code>nativeApp</code>.
      *
+     * @param webExtension {@link WebExtension} that this delegate receives messages from.
      * @param nativeApp identifier for the native app
      * @return The {@link WebExtension.MessageDelegate} attached to the
      *         <code>nativeApp</code>.  <code>null</code> if no delegate is
      *         present.
      */
     @AnyThread
     public @Nullable WebExtension.MessageDelegate getMessageDelegate(
+            final @NonNull WebExtension webExtension,
             final @NonNull String nativeApp) {
-        return mWebExtensionListener.getDelegate(nativeApp);
+        return mWebExtensionListener.getDelegate(webExtension, nativeApp);
     }
 
     /**
      * Defines a message delegate for a Native App.
      *
      * If a delegate is already present, this delegate will replace the
      * existing one.
      *
      * This message delegate will be responsible for handling messaging between
      * a WebExtension content script running on the {@link GeckoSession}.
      *
      * Note: To receive messages from content scripts, the WebExtension needs
      * to explicitely allow it in {@link WebExtension#WebExtension} by setting
      * {@link WebExtension.Flags#ALLOW_CONTENT_MESSAGING}.
      *
+     * @param webExtension {@link WebExtension} that this delegate receives
+     *                     messages from.
+     *
      * @param delegate {@link WebExtension.MessageDelegate} that will receive
      *                 messages from this session.
      * @param nativeApp which native app id this message delegate will handle
      *                  messaging for.
      * @see WebExtension#setMessageDelegate
      */
     @AnyThread
-    public void setMessageDelegate(final @Nullable WebExtension.MessageDelegate delegate,
+    public void setMessageDelegate(final @NonNull WebExtension webExtension,
+                                   final @Nullable WebExtension.MessageDelegate delegate,
                                    final @NonNull String nativeApp) {
-        mWebExtensionListener.setDelegate(delegate, nativeApp);
+        mWebExtensionListener.setDelegate(webExtension, delegate, nativeApp);
     }
 
     private final GeckoSessionHandler<ContentDelegate> mContentHandler =
         new GeckoSessionHandler<ContentDelegate>(
             "GeckoViewContent", this,
             new String[]{
                 "GeckoView:ContentCrash",
                 "GeckoView:ContextMenu",
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebExtension.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebExtension.java
@@ -45,16 +45,24 @@ public class WebExtension {
      * {@link Flags} for this WebExtension.
      */
     public final @WebExtensionFlags long flags;
     /**
      * Delegates that handle messaging between this WebExtension and the app.
      */
     /* package */ final @NonNull Map<String, MessageDelegate> messageDelegates;
 
+    @Override
+    public String toString() {
+        return "WebExtension {" +
+                "location=" + location + ", " +
+                "id=" + id + ", " +
+                "flags=" + flags + "}";
+    }
+
     private final static String LOGTAG = "WebExtension";
 
     public static class Flags {
         /*
          * Default flags for this WebExtension.
          */
         public static final long NONE = 0;
         /**
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebExtensionEventDispatcher.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebExtensionEventDispatcher.java
@@ -143,17 +143,17 @@ import java.util.Map;
                 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(nativeApp);
+            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;
         }
--- 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
@@ -10,16 +10,24 @@ exclude: true
 
 ## v69
 - Modified behavior of ['setAutomaticFontSizeAdjustment'][69.1] so that it no 
   longer has any effect on ['setFontInflationEnabled'][69.2]
 
 [69.1]: ./GeckoRuntimeSettings.html#setAutomaticFontSizeAdjustment-boolean-
 [69.2]: ./GeckoRuntimeSettings.html#setFontInflationEnabled-boolean-
 
+- [`GeckoSession.setMessageDelegate`][69.2] callers must now specify the
+  [`WebExtension`][69.3] that the [`MessageDelegate`][69.4] will receive
+  messages from.
+
+[69.2]: ../GeckoSession.html#setMessageDelegate-org.mozilla.geckoview.WebExtension-org.mozilla.geckoview.WebExtension.MessageDelegate-java.lang.String-
+[69.3]: ../WebExtension.html
+[69.4]: ../WebExtension.MessageDelegate.html
+
 ## v68
 - Added [`GeckoRuntime#configurationChanged`][68.1] to notify the device
   configuration has changed.
 
 [68.1]: ../GeckoRuntime.html#configurationChanged
 
 - Added `onSessionStateChange` to [`ProgressDelegate`][68.2] and removed `saveState`.
 
@@ -322,9 +330,9 @@ exclude: true
 [65.23]: ../GeckoSession.FinderResult.html
 
 - Update [`CrashReporter#sendCrashReport`][65.24] to return the crash ID as a
   [`GeckoResult<String>`][65.25].
 
 [65.24]: ../CrashReporter.html#sendCrashReport-android.content.Context-android.os.Bundle-java.lang.String-
 [65.25]: ../GeckoResult.html
 
-[api-version]: 29ff764a2ca4aaa16dbe79a269d6c1c8a166775e
+[api-version]: d143ce8f7ed19ff68f5d48243e34aed609767c5a