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

Closes #12190: Create a middleware that allows to change tabs priority. #12192

Merged
merged 1 commit into from
May 26, 2022
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 @@ -429,6 +429,12 @@ class GeckoEngineSession(
runtime.webExtensionController.setTabActive(geckoSession, active)
}

/**
* See [EngineSession.updateSessionPriority].
*/
override fun updateSessionPriority(priority: SessionPriority) {
geckoSession.setPriorityHint(priority.id)
}
/**
* Purges the history for the session (back and forward history).
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/* 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.state.engine.middleware

import androidx.annotation.VisibleForTesting
import mozilla.components.browser.state.action.BrowserAction
import mozilla.components.browser.state.action.EngineAction
import mozilla.components.browser.state.action.TabListAction
import mozilla.components.browser.state.selector.findTab
import mozilla.components.browser.state.state.BrowserState
import mozilla.components.concept.engine.EngineSession
import mozilla.components.concept.engine.EngineSession.SessionPriority.DEFAULT
import mozilla.components.concept.engine.EngineSession.SessionPriority.HIGH
import mozilla.components.lib.state.Middleware
import mozilla.components.lib.state.MiddlewareContext
import mozilla.components.support.base.log.logger.Logger

/**
* [Middleware] implementation responsible for updating the priority of the selected [EngineSession]
* to [HIGH] and the rest to [DEFAULT].
*/
class SessionPrioritizationMiddleware : Middleware<BrowserState, BrowserAction> {
private val logger = Logger("SessionPrioritizationMiddleware")

@VisibleForTesting
internal var previousHighestPriorityTabId = ""

override fun invoke(
context: MiddlewareContext<BrowserState, BrowserAction>,
next: (BrowserAction) -> Unit,
action: BrowserAction
) {

when (action) {
is EngineAction.UnlinkEngineSessionAction -> {
val activeTab = context.state.findTab(action.tabId)
activeTab?.engineState?.engineSession?.updateSessionPriority(DEFAULT)
logger.info("Update the tab ${activeTab?.id} priority to ${DEFAULT.name}")
}
else -> {
// no-op
}
}

next(action)

when (action) {
is TabListAction,
is EngineAction.LinkEngineSessionAction -> {
val state = context.state
if (previousHighestPriorityTabId != state.selectedTabId) {
updatePriorityIfNeeded(state)
}
}
else -> {
// no-op
}
}
}

private fun updatePriorityIfNeeded(state: BrowserState) {
val currentSelectedTab = state.selectedTabId?.let { state.findTab(it) }
val previousSelectedTab = state.findTab(previousHighestPriorityTabId)
val currentEngineSession: EngineSession? = currentSelectedTab?.engineState?.engineSession

// We need to make sure, we alter the previousHighestPriorityTabId, after the session is linked.
// So we update the priority on the engine session, as we could get actions where the tab
// is selected but not linked yet, causing out sync issues,
// when previousHighestPriorityTabId didn't call updateSessionPriority()
if (currentEngineSession != null) {
previousSelectedTab?.engineState?.engineSession?.updateSessionPriority(DEFAULT)
currentEngineSession.updateSessionPriority(HIGH)
logger.info("Update the currentSelectedTab ${currentSelectedTab.id} priority to ${HIGH.name}")
logger.info("Update the previousSelectedTab ${previousSelectedTab?.id} priority to ${DEFAULT.name}")
previousHighestPriorityTabId = currentSelectedTab.id
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/* 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.state.engine.middleware

import mozilla.components.browser.state.action.EngineAction
import mozilla.components.browser.state.action.TabListAction
import mozilla.components.browser.state.state.BrowserState
import mozilla.components.browser.state.state.createTab
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.concept.engine.EngineSession
import mozilla.components.concept.engine.EngineSession.SessionPriority.DEFAULT
import mozilla.components.concept.engine.EngineSession.SessionPriority.HIGH
import mozilla.components.support.test.ext.joinBlocking
import mozilla.components.support.test.mock
import org.junit.Assert.assertEquals
import org.junit.Test
import org.mockito.Mockito.verify

class SessionPrioritizationMiddlewareTest {

@Test
fun `GIVEN a linked session WHEN UnlinkEngineSessionAction THEN set the DEFAULT priority to the unlinked tab`() {
val middleware = SessionPrioritizationMiddleware()
val store = BrowserStore(
initialState = BrowserState(
tabs = listOf(
createTab("https://www.mozilla.org", id = "1"),
)
),
middleware = listOf(middleware)
)
val engineSession1: EngineSession = mock()

store.dispatch(EngineAction.LinkEngineSessionAction("1", engineSession1)).joinBlocking()
store.dispatch(EngineAction.UnlinkEngineSessionAction("1")).joinBlocking()

verify(engineSession1).updateSessionPriority(DEFAULT)
assertEquals("", middleware.previousHighestPriorityTabId)
}

@Test
fun `GIVEN a previous selected tab WHEN LinkEngineSessionAction THEN update the selected linked tab priority to HIGH`() {
val middleware = SessionPrioritizationMiddleware()
val store = BrowserStore(
initialState = BrowserState(
tabs = listOf(
createTab("https://www.mozilla.org", id = "1"),
)
),
middleware = listOf(middleware)
)
val engineSession1: EngineSession = mock()

store.dispatch(TabListAction.SelectTabAction("1")).joinBlocking()

assertEquals("", middleware.previousHighestPriorityTabId)

store.dispatch(EngineAction.LinkEngineSessionAction("1", engineSession1)).joinBlocking()

assertEquals("1", middleware.previousHighestPriorityTabId)
verify(engineSession1).updateSessionPriority(HIGH)
}

@Test
fun `GIVEN a previous selected tab with priority DEFAULT WHEN selecting and linking a new tab THEN update the previous tab to DEFAULT and the new one to HIGH`() {
val middleware = SessionPrioritizationMiddleware()
val store = BrowserStore(
initialState = BrowserState(
tabs = listOf(
createTab("https://www.mozilla.org", id = "1"),
createTab("https://www.firefox.com", id = "2")
)
),
middleware = listOf(middleware)
)
val engineSession1: EngineSession = mock()
val engineSession2: EngineSession = mock()

store.dispatch(TabListAction.SelectTabAction("1")).joinBlocking()

assertEquals("", middleware.previousHighestPriorityTabId)

store.dispatch(EngineAction.LinkEngineSessionAction("1", engineSession1)).joinBlocking()

assertEquals("1", middleware.previousHighestPriorityTabId)
verify(engineSession1).updateSessionPriority(HIGH)

store.dispatch(TabListAction.SelectTabAction("2")).joinBlocking()

assertEquals("1", middleware.previousHighestPriorityTabId)

store.dispatch(EngineAction.LinkEngineSessionAction("2", engineSession2)).joinBlocking()

assertEquals("2", middleware.previousHighestPriorityTabId)
verify(engineSession1).updateSessionPriority(DEFAULT)
verify(engineSession2).updateSessionPriority(HIGH)
}

@Test
fun `GIVEN no linked tab WHEN SelectTabAction THEN no changes in priority show happened`() {
val middleware = SessionPrioritizationMiddleware()
val store = BrowserStore(
initialState = BrowserState(
tabs = listOf(
createTab("https://www.mozilla.org", id = "1"),
createTab("https://www.firefox.com", id = "2")
)
),
middleware = listOf(middleware)
)

store.dispatch(TabListAction.SelectTabAction("1")).joinBlocking()

assertEquals("", middleware.previousHighestPriorityTabId)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,23 @@ abstract class EngineSession(
override fun hashCode() = value
}

/**
* Represents a session priority, which signals to the engine that it should give
* a different prioritization to a given session.
*/
@Suppress("MagicNumber")
enum class SessionPriority(val id: Int) {
/**
* Signals to the engine that this session has a default priority.
*/
DEFAULT(0),
/**
* Signals to the engine that this session is important, and the Engine should keep
* the session alive for as long as possible.
*/
HIGH(1)
}

/**
* Loads the given URL.
*
Expand Down Expand Up @@ -695,6 +712,13 @@ abstract class EngineSession(
*/
open fun markActiveForWebExtensions(active: Boolean) = Unit

/**
* Updates the priority for this session.
*
* @param priority the new priority for this session.
*/
open fun updateSessionPriority(priority: SessionPriority) = Unit

/**
* Purges the history for the session (back and forward history).
*/
Expand Down
3 changes: 3 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ permalink: /changelog/
* [Gecko](https://github.com/mozilla-mobile/android-components/blob/main/buildSrc/src/main/java/Gecko.kt)
* [Configuration](https://github.com/mozilla-mobile/android-components/blob/main/.config.yml)

* **browser-state**:
* 🌟️ Add support for tab prioritization via `SessionPrioritizationMiddleware` for more information see [#12190](https://github.com/mozilla-mobile/android-components/issues/12190).

* **service-pocket**
* 🌟️ Add support for rotating and pacing Pocket sponsored stories. [#12184](https://github.com/mozilla-mobile/android-components/issues/12184)
* See component's [README](https://github.com/mozilla-mobile/android-components/blob/main/components/service/pocket/README.md) to get more info.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import mozilla.components.browser.menu.item.BrowserMenuItemToolbar
import mozilla.components.browser.menu.item.SimpleBrowserMenuItem
import mozilla.components.browser.session.storage.SessionStorage
import mozilla.components.browser.state.engine.EngineMiddleware
import mozilla.components.browser.state.engine.middleware.SessionPrioritizationMiddleware
import mozilla.components.browser.state.selector.selectedTab
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.browser.storage.sync.PlacesHistoryStorage
Expand Down Expand Up @@ -162,7 +163,8 @@ open class DefaultComponents(private val applicationContext: Context) {
SearchMiddleware(applicationContext),
RecordingDevicesMiddleware(applicationContext),
LastAccessMiddleware(),
PromptMiddleware()
PromptMiddleware(),
SessionPrioritizationMiddleware()
) + EngineMiddleware.create(engine)
)
}
Expand Down