Commit 05986a3a authored by MozLando's avatar MozLando
Browse files

Merge #5240



5240: Issue #4500: Add action / state to enable and disable extension r=Amejia481,psymoon a=csadilek

Included a small refactoring to add re-usable methods for updating both the global and tab-specific web extension state.

Co-authored-by: default avatarChristian Sadilek <christian.sadilek@gmail.com>
parents ac99d412 f89202a7
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -270,6 +270,12 @@ sealed class WebExtensionAction : BrowserAction() {
     */
    data class InstallWebExtensionAction(val extension: WebExtensionState) : WebExtensionAction()

    /**
     * Updates the [WebExtensionState.enabled] flag.
     */
    data class UpdateWebExtensionEnabledAction(val extensionId: String, val enabled: Boolean) :
        WebExtensionAction()

    /**
     * Updates a browser action of a given [extensionId].
     */
+34 −46
Original line number Diff line number Diff line
@@ -7,7 +7,6 @@ package mozilla.components.browser.state.reducer
import mozilla.components.browser.state.action.WebExtensionAction
import mozilla.components.browser.state.state.BrowserState
import mozilla.components.browser.state.state.SessionState
import mozilla.components.browser.state.state.TabSessionState
import mozilla.components.browser.state.state.WebExtensionState

internal object WebExtensionReducer {
@@ -27,71 +26,60 @@ internal object WebExtensionReducer {
                    state
                }
            }
            is WebExtensionAction.UpdateWebExtensionEnabledAction -> {
                state.updateWebExtensionState(action.extensionId) {
                    it.copy(enabled = action.enabled)
                }
            }
            is WebExtensionAction.UpdateBrowserAction -> {
                val newExtension = action.extensionId to WebExtensionState(
                    id = action.extensionId,
                    browserAction = action.browserAction
                )

                val updatedExtensions = state.extensions - action.extensionId

                state.copy(
                    extensions = updatedExtensions + newExtension
                )
                state.updateWebExtensionState(action.extensionId) {
                    it.copy(browserAction = action.browserAction)
                }
            }
            is WebExtensionAction.UpdateBrowserActionPopupSession -> {
                val existingExtension = state.extensions[action.extensionId]

                if (existingExtension == null) {
                    state
                } else {
                    val newExtension = action.extensionId to existingExtension.copy(
                        browserActionPopupSession = action.popupSessionId
                    )

                    val updatedExtensions = state.extensions - action.extensionId

                    state.copy(
                        extensions = updatedExtensions + newExtension
                    )
                state.updateWebExtensionState(action.extensionId) {
                    it.copy(browserActionPopupSession = action.popupSessionId)
                }
            }

            is WebExtensionAction.UpdateTabBrowserAction -> {
                state.updateTabState(action.sessionId) { tab ->
                    val existingExtension = tab.extensionState[action.extensionId]

                    val newExtension = action.extensionId to WebExtensionState(
                        id = action.extensionId, browserAction = action.browserAction
                    )

                    val updatedExtensions = if (existingExtension == null) {
                        tab.extensionState + newExtension
                    } else {
                        val newExtensions = tab.extensionState - action.extensionId
                        newExtensions + newExtension
                    }

                    tab.copy(
                        extensionState = updatedExtensions
                    )
                state.updateWebExtensionTabState(action.sessionId, action.extensionId) {
                    it.copy(browserAction = action.browserAction)
                }
            }
        }
    }

    private fun BrowserState.updateTabState(
    private fun BrowserState.updateWebExtensionTabState(
        tabId: String,
        update: (TabSessionState) -> TabSessionState
        extensionId: String,
        update: (WebExtensionState) -> WebExtensionState
    ): BrowserState {
        return copy(
            tabs = tabs.map { current ->
                if (current.id == tabId) {
                    update(current)
                    val existingExtension = current.extensionState[extensionId]
                    val newExtension = extensionId to update(existingExtension ?: WebExtensionState(extensionId))
                    val updatedExtensions = if (existingExtension == null) {
                        current.extensionState + newExtension
                    } else {
                        val newExtensions = current.extensionState - extensionId
                        newExtensions + newExtension
                    }
                    current.copy(extensionState = updatedExtensions)
                } else {
                    current
                }
            }
        )
    }

    private fun BrowserState.updateWebExtensionState(
        extensionId: String,
        update: (WebExtensionState) -> WebExtensionState
    ): BrowserState {
        val existingExtension = extensions[extensionId]
        val newExtension = extensionId to update(existingExtension ?: WebExtensionState(extensionId))
        val updatedExtensions = extensions - extensionId
        return copy(extensions = updatedExtensions + newExtension)
    }
}
+2 −0
Original line number Diff line number Diff line
@@ -12,6 +12,7 @@ typealias WebExtensionBrowserAction = mozilla.components.concept.engine.webexten
 * @property id The unique identifier for this web 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.
 * @property enabled Whether or not this web extension is enabled, defaults to true.
 * @property browserAction A list browser action that this web extension has.
 * @property browserActionPopupSession The ID of the session displaying
 * the browser action popup.
@@ -19,6 +20,7 @@ typealias WebExtensionBrowserAction = mozilla.components.concept.engine.webexten
data class WebExtensionState(
    val id: String,
    val url: String? = null,
    val enabled: Boolean = true,
    val browserAction: WebExtensionBrowserAction? = null,
    val browserActionPopupSession: String? = null
)
+22 −7
Original line number Diff line number Diff line
@@ -43,20 +43,19 @@ class WebExtensionActionTest {
    fun `UpdateGlobalBrowserAction - Updates a browser action of an existing WebExtensionState on the BrowserState`() {
        val store = BrowserStore()
        val mockedBrowserAction = mock<WebExtensionBrowserAction>()
        val mockedBrowserAction2 = mock<WebExtensionBrowserAction>()

        assertTrue(store.state.extensions.isEmpty())
        store.dispatch(WebExtensionAction.UpdateBrowserAction("id", mockedBrowserAction)).joinBlocking()
        assertEquals(mockedBrowserAction, store.state.extensions.values.first().browserAction)

        val extension = WebExtensionState("id", "url")
        store.dispatch(WebExtensionAction.InstallWebExtensionAction(extension)).joinBlocking()

        assertFalse(store.state.extensions.isEmpty())

        assertEquals(extension, store.state.extensions.values.first())

        store.dispatch(WebExtensionAction.UpdateBrowserAction("id", mockedBrowserAction))
            .joinBlocking()

        assertEquals(mockedBrowserAction, store.state.extensions.values.first().browserAction)

        store.dispatch(WebExtensionAction.UpdateBrowserAction("id", mockedBrowserAction2)).joinBlocking()
        assertEquals(mockedBrowserAction2, store.state.extensions.values.first().browserAction)
    }

    @Test
@@ -97,6 +96,7 @@ class WebExtensionActionTest {
                "extensionId" to WebExtensionState(
                    "extensionId",
                    "url",
                    true,
                    mockedBrowserAction1
                )
            )
@@ -133,4 +133,19 @@ class WebExtensionActionTest {
        store.dispatch(WebExtensionAction.UpdateBrowserActionPopupSession(extension.id, "popupId")).joinBlocking()
        assertEquals("popupId", store.state.extensions[extension.id]?.browserActionPopupSession)
    }

    @Test
    fun `UpdateWebExtensionEnabledAction - Updates enabled state of an existing web extension`() {
        val store = BrowserStore()
        val extension = WebExtensionState("id", "url")

        store.dispatch(WebExtensionAction.InstallWebExtensionAction(extension)).joinBlocking()
        assertTrue(store.state.extensions[extension.id]?.enabled!!)

        store.dispatch(WebExtensionAction.UpdateWebExtensionEnabledAction(extension.id, false)).joinBlocking()
        assertFalse(store.state.extensions[extension.id]?.enabled!!)

        store.dispatch(WebExtensionAction.UpdateWebExtensionEnabledAction(extension.id, true)).joinBlocking()
        assertTrue(store.state.extensions[extension.id]?.enabled!!)
    }
}
+2 −2
Original line number Diff line number Diff line
@@ -46,10 +46,10 @@ class WebExtensionToolbarFeatureTest {
            WebExtensionBrowserAction("overridden_title", false, mock(), "", 0, 0) {}
        val toolbar: Toolbar = mock()
        val extensions: Map<String, WebExtensionState> = mapOf(
            "id" to WebExtensionState("id", "url", defaultBrowserAction)
            "id" to WebExtensionState("id", "url", true, defaultBrowserAction)
        )
        val overriddenExtensions: Map<String, WebExtensionState> = mapOf(
            "id" to WebExtensionState("id", "url", overriddenBrowserAction)
            "id" to WebExtensionState("id", "url", true, overriddenBrowserAction)
        )
        val store = spy(
            BrowserStore(