Skip to content
This repository has been archived by the owner on Nov 1, 2022. It is now read-only.

Closes #2243: Add engine API for WebExtension messaging #2836

Merged
merged 1 commit into from
Apr 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading