From f77e9baa3aaf5eca6b0624d82259527a91f8aa57 Mon Sep 17 00:00:00 2001 From: Christian Sadilek Date: Thu, 21 Feb 2019 16:27:42 -0500 Subject: [PATCH] Closes #2150: Add engine API to install WebExtensions --- .../browser/engine/gecko/GeckoEngine.kt | 15 +++++++ .../org/mozilla/geckoview/WebExtension.kt | 8 ++++ .../browser/engine/gecko/GeckoEngineTest.kt | 22 +++++++++- .../components/concept/engine/Engine.kt | 11 +++++ .../engine/webextension/WebExtension.kt | 18 ++++++++ .../components/concept/engine/EngineTest.kt | 44 +++++++++++++++++++ .../engine/webextension/WebExtensionTest.kt | 23 ++++++++++ .../org/mozilla/samples/browser/Components.kt | 8 +++- .../extensions/helloworld/helloworld.js | 3 ++ .../extensions/helloworld/manifest.json | 12 +++++ 10 files changed, 161 insertions(+), 3 deletions(-) create mode 100644 components/browser/engine-gecko-nightly/src/main/java/org/mozilla/geckoview/WebExtension.kt create mode 100644 components/concept/engine/src/main/java/mozilla/components/concept/engine/webextension/WebExtension.kt create mode 100644 components/concept/engine/src/test/java/mozilla/components/concept/engine/EngineTest.kt create mode 100644 components/concept/engine/src/test/java/mozilla/components/concept/engine/webextension/WebExtensionTest.kt create mode 100644 samples/browser/src/main/assets/extensions/helloworld/helloworld.js create mode 100644 samples/browser/src/main/assets/extensions/helloworld/manifest.json diff --git a/components/browser/engine-gecko-nightly/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngine.kt b/components/browser/engine-gecko-nightly/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngine.kt index 905e25cd307..a0996c0e00e 100644 --- a/components/browser/engine-gecko-nightly/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngine.kt +++ b/components/browser/engine-gecko-nightly/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngine.kt @@ -13,9 +13,11 @@ import mozilla.components.concept.engine.EngineSessionState import mozilla.components.concept.engine.EngineView import mozilla.components.concept.engine.Settings import mozilla.components.concept.engine.history.HistoryTrackingDelegate +import mozilla.components.concept.engine.webextension.WebExtension import org.json.JSONObject import org.mozilla.geckoview.GeckoRuntime import org.mozilla.geckoview.GeckoWebExecutor +import org.mozilla.geckoview.WebExtension as GeckoWebExtension /** * Gecko-based implementation of Engine interface. @@ -59,6 +61,19 @@ class GeckoEngine( executor.speculativeConnect(url) } + /** + * See [Engine.installWebExtension]. + */ + override fun installWebExtension(ext: WebExtension) { + fakeRuntime.registerWebExtension(GeckoWebExtension(ext.id, ext.url)) + } + + // TODO remove once GV API lands + class FakeRuntime { + fun registerWebExtension(ext: GeckoWebExtension) { } + } + internal var fakeRuntime = FakeRuntime() + override fun name(): String = "Gecko" /** diff --git a/components/browser/engine-gecko-nightly/src/main/java/org/mozilla/geckoview/WebExtension.kt b/components/browser/engine-gecko-nightly/src/main/java/org/mozilla/geckoview/WebExtension.kt new file mode 100644 index 00000000000..0c33e0df677 --- /dev/null +++ b/components/browser/engine-gecko-nightly/src/main/java/org/mozilla/geckoview/WebExtension.kt @@ -0,0 +1,8 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.geckoview + +// TODO Remove this mock +data class WebExtension(val id: String, val location: String) diff --git a/components/browser/engine-gecko-nightly/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineTest.kt b/components/browser/engine-gecko-nightly/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineTest.kt index bc99703bdb9..294ce480aa9 100644 --- a/components/browser/engine-gecko-nightly/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineTest.kt +++ b/components/browser/engine-gecko-nightly/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineTest.kt @@ -8,6 +8,8 @@ import android.content.Context import mozilla.components.concept.engine.DefaultSettings import mozilla.components.concept.engine.EngineSession.TrackingProtectionPolicy import mozilla.components.concept.engine.UnsupportedSettingException +import mozilla.components.concept.engine.webextension.WebExtension +import mozilla.components.support.test.argumentCaptor import mozilla.components.support.test.mock import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse @@ -23,6 +25,7 @@ import org.mozilla.geckoview.ContentBlocking import org.mozilla.geckoview.GeckoRuntime import org.mozilla.geckoview.GeckoRuntimeSettings import org.mozilla.geckoview.GeckoWebExecutor +import org.mozilla.geckoview.WebExtension as GeckoWebExtension import org.robolectric.RobolectricTestRunner import org.robolectric.RuntimeEnvironment @@ -139,9 +142,24 @@ class GeckoEngineTest { val executor: GeckoWebExecutor = mock() val engine = GeckoEngine(context, runtime = runtime, executorProvider = { executor }) - engine.speculativeConnect("https://www.mozilla.org") - verify(executor).speculativeConnect("https://www.mozilla.org") } + + @Test + fun `install web extension`() { + val runtime = mock(GeckoRuntime::class.java) + val engine = GeckoEngine(context, runtime = runtime) + + // TODO remove once GV API lands + engine.fakeRuntime = mock(GeckoEngine.FakeRuntime::class.java) + + engine.installWebExtension(WebExtension("test-webext", "resource://android/assets/extensions/test")) + + val extCaptor = argumentCaptor() + verify(engine.fakeRuntime).registerWebExtension(extCaptor.capture()) + + assertEquals("test-webext", extCaptor.value.id) + assertEquals("resource://android/assets/extensions/test", extCaptor.value.location) + } } \ No newline at end of file diff --git a/components/concept/engine/src/main/java/mozilla/components/concept/engine/Engine.kt b/components/concept/engine/src/main/java/mozilla/components/concept/engine/Engine.kt index f874d6c3cb9..667a6284960 100644 --- a/components/concept/engine/src/main/java/mozilla/components/concept/engine/Engine.kt +++ b/components/concept/engine/src/main/java/mozilla/components/concept/engine/Engine.kt @@ -6,7 +6,9 @@ package mozilla.components.concept.engine import android.content.Context import android.util.AttributeSet +import mozilla.components.concept.engine.webextension.WebExtension import org.json.JSONObject +import java.lang.UnsupportedOperationException /** * Entry point for interacting with the engine implementation. @@ -56,6 +58,15 @@ interface Engine { */ fun speculativeConnect(url: String) + /** + * Installs the provided extension in this engine. + * + * @param ext the [WebExtension] to install. + * @throws UnsupportedOperationException if this engine doesn't support web extensions. + */ + fun installWebExtension(ext: WebExtension): Unit = + throw UnsupportedOperationException("Web extension support is not available in this engine") + /** * Provides access to the settings of this engine. */ diff --git a/components/concept/engine/src/main/java/mozilla/components/concept/engine/webextension/WebExtension.kt b/components/concept/engine/src/main/java/mozilla/components/concept/engine/webextension/WebExtension.kt new file mode 100644 index 00000000000..a3f51785af7 --- /dev/null +++ b/components/concept/engine/src/main/java/mozilla/components/concept/engine/webextension/WebExtension.kt @@ -0,0 +1,18 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.concept.engine.webextension + +/** + * Represents a browser extension based on the WebExtension API: + * https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions + * + * @property id the unique ID of this extension. + * @property url the url pointing to a resources path for locating the extension + * within the APK file e.g. resource://android/assets/extensions/my_web_ext. + */ +data class WebExtension( + val id: String, + val url: String +) diff --git a/components/concept/engine/src/test/java/mozilla/components/concept/engine/EngineTest.kt b/components/concept/engine/src/test/java/mozilla/components/concept/engine/EngineTest.kt new file mode 100644 index 00000000000..9801d11093c --- /dev/null +++ b/components/concept/engine/src/test/java/mozilla/components/concept/engine/EngineTest.kt @@ -0,0 +1,44 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.concept.engine + +import android.content.Context +import android.util.AttributeSet +import mozilla.components.concept.engine.webextension.WebExtension +import org.json.JSONObject +import org.junit.Test +import java.lang.UnsupportedOperationException + +class EngineTest { + + @Test(expected = UnsupportedOperationException::class) + fun `throws exception if webextensions not supported`() { + val engine = object : Engine { + override fun createView(context: Context, attrs: AttributeSet?): EngineView { + throw NotImplementedError("Not needed for test") + } + + override fun createSession(private: Boolean): EngineSession { + throw NotImplementedError("Not needed for test") + } + + override fun createSessionState(json: JSONObject): EngineSessionState { + throw NotImplementedError("Not needed for test") + } + + override fun name(): String { + throw NotImplementedError("Not needed for test") + } + + override fun speculativeConnect(url: String) { + throw NotImplementedError("Not needed for test") + } + + override val settings: Settings + get() = throw NotImplementedError("Not needed for test") + } + engine.installWebExtension(WebExtension("my-ext", "resource://path")) + } +} \ No newline at end of file diff --git a/components/concept/engine/src/test/java/mozilla/components/concept/engine/webextension/WebExtensionTest.kt b/components/concept/engine/src/test/java/mozilla/components/concept/engine/webextension/WebExtensionTest.kt new file mode 100644 index 00000000000..591f79143d1 --- /dev/null +++ b/components/concept/engine/src/test/java/mozilla/components/concept/engine/webextension/WebExtensionTest.kt @@ -0,0 +1,23 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.concept.engine.webextension + +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotEquals +import org.junit.Test + +class WebExtensionTest { + + @Test + fun createWebExtension() { + val ext1 = WebExtension("1", "url1") + val ext2 = WebExtension("2", "url2") + + assertNotEquals(ext1, ext2) + assertEquals(ext1, WebExtension("1", "url1")) + assertEquals("1", ext1.id) + assertEquals("url1", ext1.url) + } +} \ No newline at end of file diff --git a/samples/browser/src/geckoNightly/java/org/mozilla/samples/browser/Components.kt b/samples/browser/src/geckoNightly/java/org/mozilla/samples/browser/Components.kt index a6e7085dc19..c2a2b7e1ff4 100644 --- a/samples/browser/src/geckoNightly/java/org/mozilla/samples/browser/Components.kt +++ b/samples/browser/src/geckoNightly/java/org/mozilla/samples/browser/Components.kt @@ -6,12 +6,18 @@ package org.mozilla.samples.browser import android.content.Context import mozilla.components.browser.engine.gecko.GeckoEngine import mozilla.components.concept.engine.Engine +import mozilla.components.concept.engine.webextension.WebExtension /** * Helper class for lazily instantiating components needed by the application. */ class Components(private val applicationContext: Context) : DefaultComponents(applicationContext) { override val engine: Engine by lazy { - GeckoEngine(applicationContext, engineSettings) + GeckoEngine(applicationContext, engineSettings).apply { + + // TODO verify how to specify the extension path. This is based on: + // https://phabricator.services.mozilla.com/D16268#change-Fdft9eM6r9i1 + installWebExtension(WebExtension("mozac-helloworld", "resource://android/assets/extensions/helloworld")) + } } } diff --git a/samples/browser/src/main/assets/extensions/helloworld/helloworld.js b/samples/browser/src/main/assets/extensions/helloworld/helloworld.js new file mode 100644 index 00000000000..45341dd7bb2 --- /dev/null +++ b/samples/browser/src/main/assets/extensions/helloworld/helloworld.js @@ -0,0 +1,3 @@ +console.log("Hello World!"); + +document.body.style.border = "5px solid red"; \ No newline at end of file diff --git a/samples/browser/src/main/assets/extensions/helloworld/manifest.json b/samples/browser/src/main/assets/extensions/helloworld/manifest.json new file mode 100644 index 00000000000..ee3d8e5ab35 --- /dev/null +++ b/samples/browser/src/main/assets/extensions/helloworld/manifest.json @@ -0,0 +1,12 @@ +{ + "manifest_version": 2, + "name": "Mozilla Android Components - Hello World", + "version": "1.0", + "content_scripts": [ + { + "matches": ["*://*/*"], + "js": ["helloworld.js"], + "run_at": "document_end" + } + ] +}