Commit a9d4e2d2 authored by Christian Sadilek's avatar Christian Sadilek
Browse files

Closes #5090: Wire up GeckoView action delegate for BrowserActions

parent 97139d8b
......@@ -136,10 +136,11 @@ class GeckoEngine(
id: String,
url: String,
allowContentMessaging: Boolean,
supportActions: Boolean,
onSuccess: ((WebExtension) -> Unit),
onError: ((String, Throwable) -> Unit)
) {
GeckoWebExtension(id, url, allowContentMessaging).also { ext ->
GeckoWebExtension(id, url, allowContentMessaging, supportActions).also { ext ->
runtime.registerWebExtension(ext.nativeExtension).then({
webExtensionDelegate?.onInstalled(ext)
onSuccess(ext)
......
......@@ -6,6 +6,7 @@ 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.ActionHandler
import mozilla.components.concept.engine.webextension.MessageHandler
import mozilla.components.concept.engine.webextension.Port
import mozilla.components.concept.engine.webextension.WebExtension
......@@ -21,13 +22,14 @@ class GeckoWebExtension(
id: String,
url: String,
allowContentMessaging: Boolean = true,
supportActions: Boolean = false,
val nativeExtension: GeckoNativeWebExtension = GeckoNativeWebExtension(
url,
id,
createWebExtensionFlags(allowContentMessaging)
),
private val connectedPorts: MutableMap<PortId, Port> = mutableMapOf()
) : WebExtension(id, url) {
) : WebExtension(id, url, supportActions) {
/**
* Uniquely identifies a port using its name and the session it
......@@ -142,6 +144,11 @@ class GeckoWebExtension(
connectedPorts.remove(portId)
}
}
// Not yet supported in beta
override fun registerActionHandler(actionHandler: ActionHandler) = Unit
override fun registerActionHandler(session: EngineSession, actionHandler: ActionHandler) = Unit
override fun hasActionHandler(session: EngineSession) = false
}
/**
......
......@@ -35,7 +35,13 @@ class GeckoWebExtensionTest {
val portCaptor = argumentCaptor<Port>()
val portDelegateCaptor = argumentCaptor<WebExtension.PortDelegate>()
val extension = GeckoWebExtension("mozacTest", "url", true, nativeGeckoWebExt)
val extension = GeckoWebExtension(
id = "mozacTest",
url = "url",
allowContentMessaging = true,
supportActions = true,
nativeExtension = nativeGeckoWebExt
)
extension.registerBackgroundMessageHandler("mozacTest", messageHandler)
verify(nativeGeckoWebExt).setMessageDelegate(messageDelegateCaptor.capture(), eq("mozacTest"))
......@@ -86,7 +92,13 @@ class GeckoWebExtensionTest {
whenever(session.geckoSession).thenReturn(geckoSession)
val extension = GeckoWebExtension("mozacTest", "url", true, nativeGeckoWebExt)
val extension = GeckoWebExtension(
id = "mozacTest",
url = "url",
allowContentMessaging = true,
supportActions = true,
nativeExtension = nativeGeckoWebExt
)
assertFalse(extension.hasContentMessageHandler(session, "mozacTest"))
extension.registerContentMessageHandler(session, "mozacTest", messageHandler)
verify(geckoSession).setMessageDelegate(eq(nativeGeckoWebExt), messageDelegateCaptor.capture(), eq("mozacTest"))
......@@ -138,7 +150,13 @@ class GeckoWebExtensionTest {
whenever(session.geckoSession).thenReturn(geckoSession)
val extension = GeckoWebExtension("mozacTest", "url", true, nativeGeckoWebExt)
val extension = GeckoWebExtension(
id = "mozacTest",
url = "url",
allowContentMessaging = true,
supportActions = true,
nativeExtension = nativeGeckoWebExt
)
extension.registerContentMessageHandler(session, "mozacTest", messageHandler)
verify(geckoSession).setMessageDelegate(eq(nativeGeckoWebExt), messageDelegateCaptor.capture(), eq("mozacTest"))
......@@ -158,7 +176,13 @@ class GeckoWebExtensionTest {
val nativeGeckoWebExt: WebExtension = mock()
val messageHandler: MessageHandler = mock()
val messageDelegateCaptor = argumentCaptor<WebExtension.MessageDelegate>()
val extension = GeckoWebExtension("mozacTest", "url", true, nativeGeckoWebExt)
val extension = GeckoWebExtension(
id = "mozacTest",
url = "url",
allowContentMessaging = true,
supportActions = true,
nativeExtension = nativeGeckoWebExt
)
extension.registerBackgroundMessageHandler("mozacTest", messageHandler)
verify(nativeGeckoWebExt).setMessageDelegate(messageDelegateCaptor.capture(), eq("mozacTest"))
......
......@@ -144,10 +144,11 @@ class GeckoEngine(
id: String,
url: String,
allowContentMessaging: Boolean,
supportActions: Boolean,
onSuccess: ((WebExtension) -> Unit),
onError: ((String, Throwable) -> Unit)
) {
GeckoWebExtension(id, url, allowContentMessaging).also { ext ->
GeckoWebExtension(id, url, allowContentMessaging, supportActions).also { ext ->
runtime.registerWebExtension(ext.nativeExtension).then({
webExtensionDelegate?.onInstalled(ext)
onSuccess(ext)
......
......@@ -5,13 +5,19 @@
package mozilla.components.browser.engine.gecko.webextension
import mozilla.components.browser.engine.gecko.GeckoEngineSession
import mozilla.components.browser.engine.gecko.await
import mozilla.components.concept.engine.EngineSession
import mozilla.components.concept.engine.webextension.ActionHandler
import mozilla.components.concept.engine.webextension.BrowserAction
import mozilla.components.concept.engine.webextension.MessageHandler
import mozilla.components.concept.engine.webextension.Port
import mozilla.components.concept.engine.webextension.WebExtension
import mozilla.components.support.base.log.logger.Logger
import org.json.JSONObject
import org.mozilla.geckoview.GeckoResult
import org.mozilla.geckoview.GeckoSession
import org.mozilla.geckoview.WebExtension as GeckoNativeWebExtension
import org.mozilla.geckoview.WebExtension.Action as GeckoNativeWebExtensionAction
/**
* Gecko-based implementation of [WebExtension], wrapping the native web
......@@ -21,13 +27,16 @@ class GeckoWebExtension(
id: String,
url: String,
allowContentMessaging: Boolean = true,
supportActions: Boolean = false,
val nativeExtension: GeckoNativeWebExtension = GeckoNativeWebExtension(
url,
id,
createWebExtensionFlags(allowContentMessaging)
),
private val connectedPorts: MutableMap<PortId, Port> = mutableMapOf()
) : WebExtension(id, url) {
) : WebExtension(id, url, supportActions) {
private val logger = Logger("GeckoWebExtension")
/**
* Uniquely identifies a port using its name and the session it
......@@ -142,6 +151,64 @@ class GeckoWebExtension(
connectedPorts.remove(portId)
}
}
/**
* See [WebExtension.registerActionHandler].
*/
override fun registerActionHandler(actionHandler: ActionHandler) {
if (!supportActions) {
logger.error("Attempt to register default action handler but browser and page " +
"action support is turned off for this extension: $id")
return
}
val actionDelegate = object : GeckoNativeWebExtension.ActionDelegate {
override fun onBrowserAction(
ext: GeckoNativeWebExtension,
// Session will always be null here for the global default delegate
session: GeckoSession?,
action: GeckoNativeWebExtensionAction
) {
actionHandler.onBrowserAction(this@GeckoWebExtension, null, action.toBrowserAction())
}
}
nativeExtension.setActionDelegate(actionDelegate)
}
/**
* See [WebExtension.registerActionHandler].
*/
override fun registerActionHandler(session: EngineSession, actionHandler: ActionHandler) {
if (!supportActions) {
logger.error("Attempt to register action handler on session but browser and page " +
"action support is turned off for this extension: $id")
return
}
val actionDelegate = object : GeckoNativeWebExtension.ActionDelegate {
override fun onBrowserAction(
ext: GeckoNativeWebExtension,
geckoSession: GeckoSession?,
action: GeckoNativeWebExtensionAction
) {
actionHandler.onBrowserAction(this@GeckoWebExtension, session, action.toBrowserAction())
}
}
val geckoSession = (session as GeckoEngineSession).geckoSession
geckoSession.setWebExtensionActionDelegate(nativeExtension, actionDelegate)
}
/**
* See [WebExtension.hasActionHandler].
*/
override fun hasActionHandler(session: EngineSession): Boolean {
val geckoSession = (session as GeckoEngineSession).geckoSession
return geckoSession.getWebExtensionActionDelegate(nativeExtension) != null
}
}
/**
......@@ -172,3 +239,14 @@ private fun createWebExtensionFlags(allowContentMessaging: Boolean): Long {
GeckoNativeWebExtension.Flags.NONE
}
}
private fun GeckoNativeWebExtensionAction.toBrowserAction() =
BrowserAction(
title ?: "",
enabled ?: true,
{ size -> icon?.get(size)?.await() },
badgeText ?: "",
badgeTextColor ?: 0,
badgeBackgroundColor ?: 0,
{ click() }
)
......@@ -6,6 +6,8 @@ package mozilla.components.browser.engine.gecko.webextension
import androidx.test.ext.junit.runners.AndroidJUnit4
import mozilla.components.browser.engine.gecko.GeckoEngineSession
import mozilla.components.concept.engine.webextension.ActionHandler
import mozilla.components.concept.engine.webextension.BrowserAction
import mozilla.components.concept.engine.webextension.MessageHandler
import mozilla.components.concept.engine.webextension.Port
import mozilla.components.support.test.argumentCaptor
......@@ -17,8 +19,10 @@ import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertSame
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mozilla.geckoview.GeckoSession
......@@ -35,9 +39,15 @@ class GeckoWebExtensionTest {
val portCaptor = argumentCaptor<Port>()
val portDelegateCaptor = argumentCaptor<WebExtension.PortDelegate>()
val extension = GeckoWebExtension("mozacTest", "url", true, nativeGeckoWebExt)
extension.registerBackgroundMessageHandler("mozacTest", messageHandler)
val extension = GeckoWebExtension(
id = "mozacTest",
url = "url",
allowContentMessaging = true,
supportActions = true,
nativeExtension = nativeGeckoWebExt
)
extension.registerBackgroundMessageHandler("mozacTest", messageHandler)
verify(nativeGeckoWebExt).setMessageDelegate(messageDelegateCaptor.capture(), eq("mozacTest"))
// Verify messages are forwarded to message handler
......@@ -86,7 +96,13 @@ class GeckoWebExtensionTest {
whenever(session.geckoSession).thenReturn(geckoSession)
val extension = GeckoWebExtension("mozacTest", "url", true, nativeGeckoWebExt)
val extension = GeckoWebExtension(
id = "mozacTest",
url = "url",
allowContentMessaging = true,
supportActions = true,
nativeExtension = nativeGeckoWebExt
)
assertFalse(extension.hasContentMessageHandler(session, "mozacTest"))
extension.registerContentMessageHandler(session, "mozacTest", messageHandler)
verify(geckoSession).setMessageDelegate(eq(nativeGeckoWebExt), messageDelegateCaptor.capture(), eq("mozacTest"))
......@@ -138,7 +154,13 @@ class GeckoWebExtensionTest {
whenever(session.geckoSession).thenReturn(geckoSession)
val extension = GeckoWebExtension("mozacTest", "url", true, nativeGeckoWebExt)
val extension = GeckoWebExtension(
id = "mozacTest",
url = "url",
allowContentMessaging = true,
supportActions = true,
nativeExtension = nativeGeckoWebExt
)
extension.registerContentMessageHandler(session, "mozacTest", messageHandler)
verify(geckoSession).setMessageDelegate(eq(nativeGeckoWebExt), messageDelegateCaptor.capture(), eq("mozacTest"))
......@@ -158,7 +180,13 @@ class GeckoWebExtensionTest {
val nativeGeckoWebExt: WebExtension = mock()
val messageHandler: MessageHandler = mock()
val messageDelegateCaptor = argumentCaptor<WebExtension.MessageDelegate>()
val extension = GeckoWebExtension("mozacTest", "url", true, nativeGeckoWebExt)
val extension = GeckoWebExtension(
id = "mozacTest",
url = "url",
allowContentMessaging = true,
supportActions = true,
nativeExtension = nativeGeckoWebExt
)
extension.registerBackgroundMessageHandler("mozacTest", messageHandler)
verify(nativeGeckoWebExt).setMessageDelegate(messageDelegateCaptor.capture(), eq("mozacTest"))
......@@ -173,4 +201,82 @@ class GeckoWebExtensionTest {
verify(port).disconnect()
assertNull(extension.getConnectedPort("mozacTest"))
}
}
\ No newline at end of file
@Test
fun `register global default action handler`() {
val nativeGeckoWebExt: WebExtension = mock()
val actionHandler: ActionHandler = mock()
val actionDelegateCaptor = argumentCaptor<WebExtension.ActionDelegate>()
val actionCaptor = argumentCaptor<BrowserAction>()
val nativeBrowserAction: WebExtension.Action = mock()
// Verify actions will not be acted on when not supported
val extensionWithActions = GeckoWebExtension(
"mozacTest",
"url",
false,
false,
nativeGeckoWebExt
)
extensionWithActions.registerActionHandler(actionHandler)
verify(nativeGeckoWebExt, never()).setActionDelegate(actionDelegateCaptor.capture())
// Create extension and register global default action handler
val extension = GeckoWebExtension(
id = "mozacTest",
url = "url",
allowContentMessaging = true,
supportActions = true,
nativeExtension = nativeGeckoWebExt
)
extension.registerActionHandler(actionHandler)
verify(nativeGeckoWebExt).setActionDelegate(actionDelegateCaptor.capture())
// Verify that browser actions are forwarded to the handler
actionDelegateCaptor.value.onBrowserAction(nativeGeckoWebExt, null, nativeBrowserAction)
verify(actionHandler).onBrowserAction(eq(extension), eq(null), actionCaptor.capture())
// We don't have access to the native WebExtension.Action fields and
// can't mock them either, but we can verify that we've linked
// the actions by simulating a click.
actionCaptor.value.onClick()
verify(nativeBrowserAction).click()
}
@Test
fun `register session-specific action handler`() {
val session: GeckoEngineSession = mock()
val geckoSession: GeckoSession = mock()
whenever(session.geckoSession).thenReturn(geckoSession)
val nativeGeckoWebExt: WebExtension = mock()
val actionHandler: ActionHandler = mock()
val actionDelegateCaptor = argumentCaptor<WebExtension.ActionDelegate>()
val actionCaptor = argumentCaptor<BrowserAction>()
val nativeBrowserAction: WebExtension.Action = mock()
// Create extension and register action handler for session
val extension = GeckoWebExtension(
id = "mozacTest",
url = "url",
allowContentMessaging = true,
supportActions = true,
nativeExtension = nativeGeckoWebExt
)
extension.registerActionHandler(session, actionHandler)
verify(geckoSession).setWebExtensionActionDelegate(eq(nativeGeckoWebExt), actionDelegateCaptor.capture())
whenever(geckoSession.getWebExtensionActionDelegate(nativeGeckoWebExt)).thenReturn(actionDelegateCaptor.value)
assertTrue(extension.hasActionHandler(session))
// Verify that browser actions are forwarded to the handler
actionDelegateCaptor.value.onBrowserAction(nativeGeckoWebExt, null, nativeBrowserAction)
verify(actionHandler).onBrowserAction(eq(extension), eq(session), actionCaptor.capture())
// We don't have access to the native WebExtension.Action fields and
// can't mock them either, but we can verify that we've linked
// the actions by simulating a click.
actionCaptor.value.onClick()
verify(nativeBrowserAction).click()
}
}
......@@ -136,10 +136,11 @@ class GeckoEngine(
id: String,
url: String,
allowContentMessaging: Boolean,
supportActions: Boolean,
onSuccess: ((WebExtension) -> Unit),
onError: ((String, Throwable) -> Unit)
) {
GeckoWebExtension(id, url, allowContentMessaging).also { ext ->
GeckoWebExtension(id, url, allowContentMessaging, supportActions).also { ext ->
runtime.registerWebExtension(ext.nativeExtension).then({
webExtensionDelegate?.onInstalled(ext)
onSuccess(ext)
......
......@@ -6,6 +6,7 @@ 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.ActionHandler
import mozilla.components.concept.engine.webextension.MessageHandler
import mozilla.components.concept.engine.webextension.Port
import mozilla.components.concept.engine.webextension.WebExtension
......@@ -21,13 +22,14 @@ class GeckoWebExtension(
id: String,
url: String,
allowContentMessaging: Boolean = true,
supportActions: Boolean = false,
val nativeExtension: GeckoNativeWebExtension = GeckoNativeWebExtension(
url,
id,
createWebExtensionFlags(allowContentMessaging)
),
private val connectedPorts: MutableMap<PortId, Port> = mutableMapOf()
) : WebExtension(id, url) {
) : WebExtension(id, url, supportActions) {
/**
* Uniquely identifies a port using its name and the session it
......@@ -132,6 +134,11 @@ class GeckoWebExtension(
connectedPorts.remove(portId)
}
}
// Not yet supported in release
override fun registerActionHandler(actionHandler: ActionHandler) = Unit
override fun registerActionHandler(session: EngineSession, actionHandler: ActionHandler) = Unit
override fun hasActionHandler(session: EngineSession) = false
}
/**
......
......@@ -35,7 +35,13 @@ class GeckoWebExtensionTest {
val portCaptor = argumentCaptor<Port>()
val portDelegateCaptor = argumentCaptor<WebExtension.PortDelegate>()
val extension = GeckoWebExtension("mozacTest", "url", true, nativeGeckoWebExt)
val extension = GeckoWebExtension(
id = "mozacTest",
url = "url",
allowContentMessaging = true,
supportActions = true,
nativeExtension = nativeGeckoWebExt
)
extension.registerBackgroundMessageHandler("mozacTest", messageHandler)
verify(nativeGeckoWebExt).setMessageDelegate(messageDelegateCaptor.capture(), eq("mozacTest"))
......@@ -86,7 +92,13 @@ class GeckoWebExtensionTest {
whenever(session.geckoSession).thenReturn(geckoSession)
val extension = GeckoWebExtension("mozacTest", "url", true, nativeGeckoWebExt)
val extension = GeckoWebExtension(
id = "mozacTest",
url = "url",
allowContentMessaging = true,
supportActions = true,
nativeExtension = nativeGeckoWebExt
)
assertFalse(extension.hasContentMessageHandler(session, "mozacTest"))
extension.registerContentMessageHandler(session, "mozacTest", messageHandler)
verify(geckoSession).setMessageDelegate(eq(nativeGeckoWebExt), messageDelegateCaptor.capture(), eq("mozacTest"))
......@@ -138,7 +150,13 @@ class GeckoWebExtensionTest {
whenever(session.geckoSession).thenReturn(geckoSession)
val extension = GeckoWebExtension("mozacTest", "url", true, nativeGeckoWebExt)
val extension = GeckoWebExtension(
id = "mozacTest",
url = "url",
allowContentMessaging = true,
supportActions = true,
nativeExtension = nativeGeckoWebExt
)
extension.registerContentMessageHandler(session, "mozacTest", messageHandler)
verify(geckoSession).setMessageDelegate(eq(nativeGeckoWebExt), messageDelegateCaptor.capture(), eq("mozacTest"))
......@@ -158,7 +176,13 @@ class GeckoWebExtensionTest {
val nativeGeckoWebExt: WebExtension = mock()
val messageHandler: MessageHandler = mock()
val messageDelegateCaptor = argumentCaptor<WebExtension.MessageDelegate>()
val extension = GeckoWebExtension("mozacTest", "url", true, nativeGeckoWebExt)
val extension = GeckoWebExtension(
id = "mozacTest",
url = "url",
allowContentMessaging = true,
supportActions = true,
nativeExtension = nativeGeckoWebExt
)
extension.registerBackgroundMessageHandler("mozacTest", messageHandler)
verify(nativeGeckoWebExt).setMessageDelegate(messageDelegateCaptor.capture(), eq("mozacTest"))
......
......@@ -13,7 +13,6 @@ import mozilla.components.browser.session.engine.request.LoadRequestOption
import mozilla.components.browser.session.ext.syncDispatch
import mozilla.components.browser.state.action.ContentAction
import mozilla.components.browser.state.action.TrackingProtectionAction
import mozilla.components.browser.state.action.WebExtensionAction
import mozilla.components.browser.state.state.content.DownloadState
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.concept.engine.EngineSession
......@@ -24,7 +23,6 @@ import mozilla.components.concept.engine.media.Media
import mozilla.components.concept.engine.media.RecordingDevice
import mozilla.components.concept.engine.permission.PermissionRequest
import mozilla.components.concept.engine.prompt.PromptRequest
import mozilla.components.concept.engine.webextension.BrowserAction
import mozilla.components.concept.engine.window.WindowRequest
import mozilla.components.support.base.observer.Consumable
......@@ -220,16 +218,6 @@ internal class EngineObserver(
media.unregisterObservers()
}
override fun onBrowserActionChange(webExtensionId: String, action: BrowserAction) {
store?.dispatch(
WebExtensionAction.UpdateTabBrowserAction(
session.id,
webExtensionId,
action
)
)
}