Skip to content

Commit

Permalink
Closes mozilla-mobile#2243: Add engine API for WebExtension messaging
Browse files Browse the repository at this point in the history
  • Loading branch information
csadilek committed Apr 25, 2019
1 parent 838f04b commit b98206c
Show file tree
Hide file tree
Showing 19 changed files with 672 additions and 99 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package mozilla.components.browser.engine.gecko

import android.content.Context
import android.util.AttributeSet
import mozilla.components.browser.engine.gecko.webextension.GeckoWebExtension
import mozilla.components.concept.engine.Engine
import mozilla.components.concept.engine.EngineSession
import mozilla.components.concept.engine.EngineSession.TrackingProtectionPolicy
Expand All @@ -19,7 +20,6 @@ import org.mozilla.geckoview.GeckoResult
import org.mozilla.geckoview.GeckoRuntime
import org.mozilla.geckoview.GeckoSession
import org.mozilla.geckoview.GeckoWebExecutor
import org.mozilla.geckoview.WebExtension as GeckoWebExtension

/**
* Gecko-based implementation of Engine interface.
Expand Down Expand Up @@ -79,18 +79,21 @@ class GeckoEngine(
* See [Engine.installWebExtension].
*/
override fun installWebExtension(
ext: WebExtension,
id: String,
url: String,
allowContentMessaging: Boolean,
onSuccess: ((WebExtension) -> Unit),
onError: ((WebExtension, Throwable) -> Unit)
onError: ((String, Throwable) -> Unit)
) {
val result = runtime.registerWebExtension(GeckoWebExtension(ext.url, ext.id))
result.then({
onSuccess(ext)
GeckoResult<Void>()
}, {
throwable -> onError(ext, throwable)
GeckoResult<Void>()
})
GeckoWebExtension(id, url).also { ext ->
runtime.registerWebExtension(ext.nativeExtension).then({
onSuccess(ext)
GeckoResult<Void>()
}, {
throwable -> onError(id, throwable)
GeckoResult<Void>()
})
}
}

override fun name(): String = "Gecko"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package mozilla.components.browser.engine.gecko.webextension

import mozilla.components.concept.engine.EngineSession
import mozilla.components.concept.engine.webextension.MessageHandler
import mozilla.components.concept.engine.webextension.WebExtension
import org.mozilla.geckoview.WebExtension as GeckoNativeWebExtension

/**
* Gecko-based implementation of [WebExtension], wrapping the native web
* extension object provided by GeckoView.
*/
class GeckoWebExtension(
id: String,
url: String,
val nativeExtension: GeckoNativeWebExtension = GeckoNativeWebExtension(url, id)
) : WebExtension(id, url) {

// Not supported in beta
override fun registerContentMessageHandler(
session: EngineSession,
name: String,
messageHandler: MessageHandler
) = Unit

// Not supported in beta
override fun registerBackgroundMessageHandler(name: String, messageHandler: MessageHandler) = Unit

// Not supported in beta
override fun hasContentMessageHandler(session: EngineSession, name: String): Boolean = false
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ 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.any
import mozilla.components.support.test.argumentCaptor
import mozilla.components.support.test.mock
Expand Down Expand Up @@ -174,7 +173,8 @@ class GeckoEngineTest {

`when`(runtime.registerWebExtension(any())).thenReturn(result)
engine.installWebExtension(
WebExtension("test-webext", "resource://android/assets/extensions/test"),
"test-webext",
"resource://android/assets/extensions/test",
onSuccess = { onSuccessCalled = true },
onError = { _, _ -> onErrorCalled = true }
)
Expand All @@ -198,7 +198,7 @@ class GeckoEngineTest {

var throwable: Throwable? = null
`when`(runtime.registerWebExtension(any())).thenReturn(result)
engine.installWebExtension(WebExtension("test-webext-error", "resource://android/assets/extensions/error")) { _, e ->
engine.installWebExtension("test-webext-error", "resource://android/assets/extensions/error") { _, e ->
onErrorCalled = true
throwable = e
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/* 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.browser.engine.gecko.prompt

import mozilla.components.browser.engine.gecko.webextension.GeckoWebExtension
import mozilla.components.concept.engine.EngineSession
import mozilla.components.concept.engine.webextension.MessageHandler
import mozilla.components.support.test.mock
import org.junit.Assert.assertFalse
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner

@RunWith(RobolectricTestRunner::class)
class GeckoWebExtensionTest {

// There is not much to test here in beta. All functionality is in nightly only.
@Test
fun `create gecko web extension`() {
val session: EngineSession = mock()
val contentMessageHandler: MessageHandler = mock()
val backgroundMessageHandler: MessageHandler = mock()
val appName = "mozac-test"

val ext = GeckoWebExtension(appName, "url")

ext.registerBackgroundMessageHandler(appName, backgroundMessageHandler)
ext.registerContentMessageHandler(session, appName, contentMessageHandler)
assertFalse(ext.hasContentMessageHandler(session, appName))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import android.util.AttributeSet
import mozilla.components.browser.engine.gecko.integration.LocaleSettingUpdater
import mozilla.components.browser.engine.gecko.mediaquery.from
import mozilla.components.browser.engine.gecko.mediaquery.toGeckoValue
import mozilla.components.browser.engine.gecko.webextension.GeckoWebExtension
import mozilla.components.concept.engine.Engine
import mozilla.components.concept.engine.EngineSession
import mozilla.components.concept.engine.EngineSession.TrackingProtectionPolicy
Expand All @@ -24,7 +25,6 @@ import org.mozilla.geckoview.GeckoRuntime
import org.mozilla.geckoview.GeckoRuntimeSettings
import org.mozilla.geckoview.GeckoSession
import org.mozilla.geckoview.GeckoWebExecutor
import org.mozilla.geckoview.WebExtension as GeckoWebExtension

/**
* Gecko-based implementation of Engine interface.
Expand Down Expand Up @@ -86,18 +86,21 @@ class GeckoEngine(
* See [Engine.installWebExtension].
*/
override fun installWebExtension(
ext: WebExtension,
id: String,
url: String,
allowContentMessaging: Boolean,
onSuccess: ((WebExtension) -> Unit),
onError: ((WebExtension, Throwable) -> Unit)
onError: ((String, Throwable) -> Unit)
) {
val result = runtime.registerWebExtension(GeckoWebExtension(ext.url, ext.id, true))
result.then({
onSuccess(ext)
GeckoResult<Void>()
}, {
throwable -> onError(ext, throwable)
GeckoResult<Void>()
})
GeckoWebExtension(id, url, allowContentMessaging).also { ext ->
runtime.registerWebExtension(ext.nativeExtension).then({
onSuccess(ext)
GeckoResult<Void>()
}, {
throwable -> onError(id, throwable)
GeckoResult<Void>()
})
}
}

override fun name(): String = "Gecko"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package mozilla.components.browser.engine.gecko.webextension

import mozilla.components.browser.engine.gecko.GeckoEngineSession
import mozilla.components.concept.engine.EngineSession
import mozilla.components.concept.engine.webextension.MessageHandler
import mozilla.components.concept.engine.webextension.Port
import mozilla.components.concept.engine.webextension.WebExtension
import org.json.JSONObject
import org.mozilla.geckoview.GeckoResult
import org.mozilla.geckoview.WebExtension as GeckoNativeWebExtension

/**
* Gecko-based implementation of [WebExtension], wrapping the native web
* extension object provided by GeckoView.
*/
class GeckoWebExtension(
id: String,
url: String,
allowContentMessaging: Boolean = true,
val nativeExtension: GeckoNativeWebExtension = GeckoNativeWebExtension(url, id, allowContentMessaging)
) : WebExtension(id, url) {

/**
* See [WebExtension.registerBackgroundMessageHandler].
*/
override fun registerBackgroundMessageHandler(name: String, messageHandler: MessageHandler) {
val portDelegate = object : GeckoNativeWebExtension.PortDelegate {

override fun onPortMessage(message: Any, port: GeckoNativeWebExtension.Port) {
messageHandler.onPortMessage(message, GeckoPort(port))
}

override fun onDisconnect(port: GeckoNativeWebExtension.Port) {
messageHandler.onPortDisconnected(GeckoPort(port))
}
}

val messageDelegate = object : GeckoNativeWebExtension.MessageDelegate {

override fun onConnect(port: GeckoNativeWebExtension.Port) {
port.setDelegate(portDelegate)
messageHandler.onPortConnected(GeckoPort(port))
}

override fun onMessage(message: Any, sender: GeckoNativeWebExtension.MessageSender): GeckoResult<Any>? {
return GeckoResult.fromValue(messageHandler.onMessage(message, null))
}
}

nativeExtension.setMessageDelegate(messageDelegate, name)
}

/**
* See [WebExtension.registerContentMessageHandler].
*/
override fun registerContentMessageHandler(session: EngineSession, name: String, messageHandler: MessageHandler) {
val portDelegate = object : GeckoNativeWebExtension.PortDelegate {

override fun onPortMessage(message: Any, port: GeckoNativeWebExtension.Port) {
messageHandler.onPortMessage(message, GeckoPort(port, session))
}

override fun onDisconnect(port: GeckoNativeWebExtension.Port) {
messageHandler.onPortDisconnected(GeckoPort(port, session))
}
}

val messageDelegate = object : GeckoNativeWebExtension.MessageDelegate {

override fun onConnect(port: GeckoNativeWebExtension.Port) {
port.setDelegate(portDelegate)
messageHandler.onPortConnected(GeckoPort(port, session))
}

override fun onMessage(message: Any, sender: GeckoNativeWebExtension.MessageSender): GeckoResult<Any>? {
return GeckoResult.fromValue(messageHandler.onMessage(message, session))
}
}

val geckoSession = (session as GeckoEngineSession).geckoSession
geckoSession.setMessageDelegate(messageDelegate, name)
}

/**
* See [WebExtension.hasContentMessageHandler].
*/
override fun hasContentMessageHandler(session: EngineSession, name: String): Boolean {
val geckoSession = (session as GeckoEngineSession).geckoSession
return geckoSession.getMessageDelegate(name) != null
}
}

/**
* Gecko-based implementation of [Port], wrapping the native port provided by GeckoView.
*/
class GeckoPort(
internal val nativePort: GeckoNativeWebExtension.Port,
engineSession: EngineSession? = null
) : Port(engineSession) {

override fun postMessage(message: Any) {
nativePort.postMessage(message as JSONObject)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import mozilla.components.concept.engine.DefaultSettings
import mozilla.components.concept.engine.EngineSession.TrackingProtectionPolicy
import mozilla.components.concept.engine.UnsupportedSettingException
import mozilla.components.concept.engine.mediaquery.PreferredColorScheme
import mozilla.components.concept.engine.webextension.WebExtension
import mozilla.components.support.test.any
import mozilla.components.support.test.argumentCaptor
import mozilla.components.support.test.mock
Expand Down Expand Up @@ -199,7 +198,8 @@ class GeckoEngineTest {

`when`(runtime.registerWebExtension(any())).thenReturn(result)
engine.installWebExtension(
WebExtension("test-webext", "resource://android/assets/extensions/test"),
"test-webext",
"resource://android/assets/extensions/test",
onSuccess = { onSuccessCalled = true },
onError = { _, _ -> onErrorCalled = true }
)
Expand All @@ -209,6 +209,34 @@ class GeckoEngineTest {
verify(runtime).registerWebExtension(extCaptor.capture())
assertEquals("test-webext", extCaptor.value.id)
assertEquals("resource://android/assets/extensions/test", extCaptor.value.location)
assertTrue(extCaptor.value.allowContentMessaging)
assertTrue(onSuccessCalled)
assertFalse(onErrorCalled)
}

@Test
fun `install web extension successfully but do not allow content messaging`() {
val runtime = mock(GeckoRuntime::class.java)
val engine = GeckoEngine(context, runtime = runtime)
var onSuccessCalled = false
var onErrorCalled = false
var result = GeckoResult<Void>()

`when`(runtime.registerWebExtension(any())).thenReturn(result)
engine.installWebExtension(
"test-webext",
"resource://android/assets/extensions/test",
allowContentMessaging = false,
onSuccess = { onSuccessCalled = true },
onError = { _, _ -> onErrorCalled = true }
)
result.complete(null)

val extCaptor = argumentCaptor<GeckoWebExtension>()
verify(runtime).registerWebExtension(extCaptor.capture())
assertEquals("test-webext", extCaptor.value.id)
assertEquals("resource://android/assets/extensions/test", extCaptor.value.location)
assertFalse(extCaptor.value.allowContentMessaging)
assertTrue(onSuccessCalled)
assertFalse(onErrorCalled)
}
Expand All @@ -223,7 +251,7 @@ class GeckoEngineTest {

var throwable: Throwable? = null
`when`(runtime.registerWebExtension(any())).thenReturn(result)
engine.installWebExtension(WebExtension("test-webext-error", "resource://android/assets/extensions/error")) { _, e ->
engine.installWebExtension("test-webext-error", "resource://android/assets/extensions/error") { _, e ->
onErrorCalled = true
throwable = e
}
Expand Down

0 comments on commit b98206c

Please sign in to comment.