Commit 33cc6bd0 authored by Simon Chae's avatar Simon Chae
Browse files

Closes #5355: Add web extension action browser-menu support

parent 27dff7ac
Loading
Loading
Loading
Loading
+4 −1
Original line number Diff line number Diff line
@@ -22,9 +22,12 @@ android {
}

dependencies {
    implementation project(':concept-engine')
    implementation project(':browser-state')
    implementation project(':support-base')
    implementation project(':support-ktx')
    implementation project(':ui-colors')
    implementation project(':ui-icons')

    implementation Dependencies.androidx_appcompat
    implementation Dependencies.androidx_core_ktx
@@ -40,7 +43,7 @@ dependencies {
    testImplementation Dependencies.androidx_test_junit
    testImplementation Dependencies.testing_robolectric
    testImplementation Dependencies.testing_mockito
    testImplementation Dependencies.androidx_test_core
    testImplementation Dependencies.testing_coroutines

}

+7 −5
Original line number Diff line number Diff line
@@ -28,10 +28,10 @@ import mozilla.components.support.ktx.android.view.onNextGlobalLayout
/**
 * A popup menu composed of BrowserMenuItem objects.
 */
class BrowserMenu internal constructor(
    private val adapter: BrowserMenuAdapter
open class BrowserMenu internal constructor(
    internal val adapter: BrowserMenuAdapter
) {
    private var currentPopup: PopupWindow? = null
    protected var currentPopup: PopupWindow? = null
    private var menuList: RecyclerView? = null

    /**
@@ -41,7 +41,7 @@ class BrowserMenu internal constructor(
     *  the top of the menu is always visible.
     */
    @SuppressLint("InflateParams")
    fun show(
    open fun show(
        anchor: View,
        orientation: Orientation = DOWN,
        endOfMenuAlwaysVisible: Boolean = false,
@@ -61,7 +61,9 @@ class BrowserMenu internal constructor(
        menuList?.setAccessibilityDelegate(object : View.AccessibilityDelegate() {
            override fun onInitializeAccessibilityNodeInfo(host: View?, info: AccessibilityNodeInfo?) {
                super.onInitializeAccessibilityNodeInfo(host, info)
                info?.collectionInfo = AccessibilityNodeInfo.CollectionInfo.obtain(adapter.interactiveCount, 0, false)
                info?.collectionInfo = AccessibilityNodeInfo.CollectionInfo.obtain(
                    adapter.interactiveCount, 0, false
                )
            }
        })

+5 −2
Original line number Diff line number Diff line
@@ -14,12 +14,15 @@ import android.content.Context
 * @param endOfMenuAlwaysVisible when is set to true makes sure the bottom of the menu is always visible otherwise,
 *  the top of the menu is always visible.
 */
class BrowserMenuBuilder(
open class BrowserMenuBuilder(
    val items: List<BrowserMenuItem>,
    val extras: Map<String, Any> = emptyMap(),
    val endOfMenuAlwaysVisible: Boolean = false
) {
    fun build(context: Context): BrowserMenu {
    /**
     * Builds and returns a browser menu with [items]
     */
    open fun build(context: Context): BrowserMenu {
        val adapter = BrowserMenuAdapter(context, items)
        return BrowserMenu(adapter)
    }
+95 −0
Original line number Diff line number Diff line
/* 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.menu

import android.view.View
import android.widget.PopupWindow
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.collect
import mozilla.components.browser.menu.item.WebExtensionBrowserMenuItem
import mozilla.components.browser.state.selector.selectedTab
import mozilla.components.browser.state.state.BrowserState
import mozilla.components.browser.state.state.SessionState
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.concept.engine.webextension.BrowserAction
import mozilla.components.lib.state.ext.flowScoped
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged

typealias WebExtensionBrowserAction = BrowserAction

/**
 * A popup menu for [WebExtensionBrowserAction] menu items.
 */
class WebExtensionBrowserMenu internal constructor(
    adapter: BrowserMenuAdapter,
    private val store: BrowserStore
) : BrowserMenu(adapter) {
    private var scope: CoroutineScope? = null

    @kotlinx.coroutines.ExperimentalCoroutinesApi
    override fun show(
        anchor: View,
        orientation: Orientation,
        endOfMenuAlwaysVisible: Boolean,
        onDismiss: () -> Unit
    ): PopupWindow {
        scope = store.flowScoped { flow ->
            flow.ifChanged { it.selectedTab }
                .collect { state ->
                    getOrUpdateWebExtensionMenuItems(state, state.selectedTab)
                    invalidate()
                }
        }

        return super.show(
            anchor,
            orientation,
            endOfMenuAlwaysVisible,
            onDismiss
        ).apply {
            setOnDismissListener {
                adapter.menu = null
                currentPopup = null
                scope?.cancel()
                webExtensionBrowserActions.clear()
                onDismiss()
            }
        }
    }

    companion object {
        internal val webExtensionBrowserActions = HashMap<String, WebExtensionBrowserMenuItem>()

        internal fun getOrUpdateWebExtensionMenuItems(
            state: BrowserState,
            tab: SessionState? = null
        ): List<BrowserMenuItem> {
            val menuItems = ArrayList<BrowserMenuItem>()
            val extensions = state.extensions.values.toList()
            extensions.filter { it.enabled }.forEach { extension ->
                extension.browserAction?.let { browserAction ->
                    // Add the global browser action if it doesn't exist
                    val browserMenuItem = webExtensionBrowserActions.getOrPut(extension.id) {
                        val browserMenuItem = WebExtensionBrowserMenuItem(
                            browserAction = browserAction,
                            listener = browserAction.onClick
                        )
                        browserMenuItem
                    }

                    // Apply tab-specific override of browser action
                    tab?.extensionState?.get(extension.id)?.browserAction?.let {
                        browserMenuItem.browserAction = browserAction.copyWithOverride(it)
                    }

                    menuItems.add(browserMenuItem)
                }
            }

            return menuItems
        }
    }
}
+44 −0
Original line number Diff line number Diff line
/* 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.menu

import android.content.Context
import mozilla.components.browser.state.selector.selectedTab
import mozilla.components.browser.state.store.BrowserStore

/**
 * Browser menu builder with web extension support. It allows [WebExtensionBrowserMenu] to add
 * web extension browser actions.
 *
 * @param store [BrowserStore] required to render web extension browser actions
 * @param appendExtensionActionAtStart true if web extensions appear at the top (start) of the menu,
 * false if web extensions appear at the bottom of the menu. Default to false (bottom).
 */
class WebExtensionBrowserMenuBuilder(
    items: List<BrowserMenuItem>,
    extras: Map<String, Any> = emptyMap(),
    endOfMenuAlwaysVisible: Boolean = false,
    private val store: BrowserStore,
    private val appendExtensionActionAtStart: Boolean = false
) : BrowserMenuBuilder(items, extras, endOfMenuAlwaysVisible) {

    /**
     * Builds and returns a browser menu with combination of [items] and web extension browser actions.
     */
    override fun build(context: Context): BrowserMenu {
        val extensionMenuItems =
            WebExtensionBrowserMenu.getOrUpdateWebExtensionMenuItems(store.state, store.state.selectedTab)

        val menuItems =
            if (appendExtensionActionAtStart) {
                extensionMenuItems + items
            } else {
                items + extensionMenuItems
            }

        val adapter = BrowserMenuAdapter(context, menuItems)
        return WebExtensionBrowserMenu(adapter, store)
    }
}
Loading