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

Commit

Permalink
Closes #12190: Create a middleware that allows to change tabs priority.
Browse files Browse the repository at this point in the history
  • Loading branch information
Amejia481 committed May 26, 2022
1 parent 433dac8 commit bfabdd5
Show file tree
Hide file tree
Showing 6 changed files with 234 additions and 1 deletion.
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

0 comments on commit bfabdd5

Please sign in to comment.