Loading components/browser/session/src/main/java/mozilla/components/browser/session/LegacySessionManager.kt +4 −1 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ import kotlin.math.min @Suppress("TooManyFunctions", "LargeClass") class LegacySessionManager( val engine: Engine, private val engineSessionLinker: SessionManager.EngineSessionLinker, delegate: Observable<SessionManager.Observer> = ObserverRegistry() ) : Observable<SessionManager.Observer> by delegate { // It's important that any access to `values` is synchronized; Loading Loading @@ -236,7 +237,7 @@ class LegacySessionManager( * - once snapshot has been restored, and appropriate session has been selected, onSessionsRestored * notification will fire. * * @param snapshot A [Snapshot] which may be produced by [createSnapshot]. * @param snapshot A [SessionManager.Snapshot] which may be produced by [createSnapshot]. * @param updateSelection Whether the selected session should be updated from the restored snapshot. */ fun restore(snapshot: SessionManager.Snapshot, updateSelection: Boolean = true) = synchronized(values) { Loading Loading @@ -299,6 +300,7 @@ class LegacySessionManager( engineSession.loadUrl(session.url) } } engineSessionLinker.link(session, engineSession) } private fun unlink(session: Session) { Loading @@ -311,6 +313,7 @@ class LegacySessionManager( engineObserver = null } } engineSessionLinker.unlink(session) } /** Loading components/browser/session/src/main/java/mozilla/components/browser/session/SessionManager.kt +30 −5 Original line number Diff line number Diff line Loading @@ -8,6 +8,9 @@ import mozilla.components.browser.session.ext.syncDispatch import mozilla.components.browser.session.ext.toCustomTabSessionState import mozilla.components.browser.session.ext.toTabSessionState import mozilla.components.browser.state.action.CustomTabListAction import mozilla.components.browser.state.action.EngineAction.LinkEngineSessionAction import mozilla.components.browser.state.action.EngineAction.UpdateEngineSessionStateAction import mozilla.components.browser.state.action.EngineAction.UnlinkEngineSessionAction import mozilla.components.browser.state.action.SystemAction import mozilla.components.browser.state.action.TabListAction import mozilla.components.browser.state.store.BrowserStore Loading @@ -24,8 +27,24 @@ import java.lang.IllegalArgumentException class SessionManager( val engine: Engine, private val store: BrowserStore? = null, private val delegate: LegacySessionManager = LegacySessionManager(engine) private val delegate: LegacySessionManager = LegacySessionManager(engine, EngineSessionLinker(store)) ) : Observable<SessionManager.Observer> by delegate { /** * This class only exists for migrating from browser-session * to browser-state. We need a way to dispatch the corresponding browser * actions when an engine session is linked and unlinked. */ class EngineSessionLinker(private val store: BrowserStore?) { fun link(session: Session, engineSession: EngineSession) { store?.syncDispatch(LinkEngineSessionAction(session.id, engineSession)) } fun unlink(session: Session) { store?.syncDispatch(UnlinkEngineSessionAction(session.id)) } } /** * Returns the number of session including CustomTab sessions. */ Loading Loading @@ -158,13 +177,14 @@ class SessionManager( delegate.restore(snapshot, updateSelection) val tabs = snapshot.sessions val items = snapshot.sessions .filter { // A restored snapshot should not contain any custom tab so we should be able to safely ignore // them here. !it.session.isCustomTabSession() } .map { item -> val tabs = items.map { item -> item.session.toTabSessionState() } Loading @@ -180,6 +200,11 @@ class SessionManager( } store?.syncDispatch(TabListAction.RestoreAction(tabs, selectedTabId)) items.forEach { item -> item.engineSession?.let { store?.syncDispatch(LinkEngineSessionAction(item.session.id, it)) } item.engineSessionState?.let { store?.syncDispatch(UpdateEngineSessionStateAction(item.session.id, it)) } } } /** Loading components/browser/session/src/test/java/mozilla/components/browser/session/SessionManagerMigrationTest.kt +68 −0 Original line number Diff line number Diff line Loading @@ -7,6 +7,9 @@ package mozilla.components.browser.session import mozilla.components.browser.state.selector.findTab import mozilla.components.browser.state.selector.selectedTab import mozilla.components.browser.state.store.BrowserStore import mozilla.components.concept.engine.Engine import mozilla.components.concept.engine.EngineSession import mozilla.components.concept.engine.EngineSessionState import mozilla.components.concept.engine.HitResult import mozilla.components.concept.engine.content.blocking.Tracker import mozilla.components.support.base.observer.Consumable Loading @@ -18,6 +21,7 @@ import org.junit.Assert.assertNull import org.junit.Assert.assertSame import org.junit.Assert.assertTrue import org.junit.Test import org.mockito.Mockito.doReturn import org.mockito.Mockito.`when` /** Loading Loading @@ -737,4 +741,68 @@ class SessionManagerMigrationTest { assertNull(tab.content.download) } } @Test fun `Linking session to engine session`() { val store = BrowserStore() val engine: Engine = mock() val engineSession1: EngineSession = mock() doReturn(engineSession1).`when`(engine).createSession(false) val sessionManager = SessionManager(engine, store) val session = Session(id = "session", initialUrl = "https://www.mozilla.org") sessionManager.add(session) assertNull(sessionManager.getEngineSession(session)) assertEquals(engineSession1, sessionManager.getOrCreateEngineSession(session)) store.state.findTab("session")!!.also { tab -> assertEquals(engineSession1, tab.engineState.engineSession) } // Force unlink and link again val engineSession2: EngineSession = mock() doReturn(engineSession2).`when`(engine).createSession(false) session.engineSessionHolder.engineSession = null assertEquals(engineSession2, sessionManager.getOrCreateEngineSession(session)) store.state.findTab("session")!!.also { tab -> assertEquals(engineSession2, tab.engineState.engineSession) } } @Test fun `Restoring engine session with state`() { val engine: Engine = mock() val store = BrowserStore() val sessionManager = SessionManager(engine, store) val engineSession: EngineSession = mock() val engineSessionState: EngineSessionState = mock() val snapshot = SessionManager.Snapshot( listOf( SessionManager.Snapshot.Item( session = Session(id = "session1", initialUrl = "http://www.firefox.com"), engineSessionState = engineSessionState ), SessionManager.Snapshot.Item( session = Session(id = "session2", initialUrl = "http://www.mozilla.org"), engineSession = engineSession ) ), selectedSessionIndex = 0 ) sessionManager.restore(snapshot) assertEquals(2, sessionManager.size) store.state.findTab("session1")!!.also { tab -> assertEquals(engineSessionState, tab.engineState.engineSessionState) } store.state.findTab("session2")!!.also { tab -> assertEquals(engineSession, tab.engineState.engineSession) } } } components/browser/state/src/main/java/mozilla/components/browser/state/action/BrowserAction.kt +28 −0 Original line number Diff line number Diff line Loading @@ -8,11 +8,14 @@ import android.graphics.Bitmap import mozilla.components.browser.state.state.BrowserState import mozilla.components.browser.state.state.ContentState import mozilla.components.browser.state.state.CustomTabSessionState import mozilla.components.browser.state.state.EngineState import mozilla.components.browser.state.state.SecurityInfoState import mozilla.components.browser.state.state.SessionState import mozilla.components.browser.state.state.TabSessionState import mozilla.components.browser.state.state.TrackingProtectionState import mozilla.components.browser.state.state.content.DownloadState import mozilla.components.concept.engine.EngineSession import mozilla.components.concept.engine.EngineSessionState import mozilla.components.concept.engine.HitResult import mozilla.components.concept.engine.content.blocking.Tracker import mozilla.components.lib.state.Action Loading Loading @@ -215,3 +218,28 @@ sealed class TrackingProtectionAction : BrowserAction() { */ data class ClearTrackersAction(val tabId: String) : TrackingProtectionAction() } /** * [BrowserAction] implementations related to updating the [EngineState] of a single [SessionState] inside * [BrowserState]. */ sealed class EngineAction : BrowserAction() { /** * Attaches the provided [EngineSession] to the session with the provided [sessionId]. */ data class LinkEngineSessionAction(val sessionId: String, val engineSession: EngineSession) : EngineAction() /** * Detaches the current [EngineSession] from the session with the provided [sessionId]. */ data class UnlinkEngineSessionAction(val sessionId: String) : EngineAction() /** * Updates the [EngineSessionState] of the session with the provided [sessionId]. */ data class UpdateEngineSessionStateAction( val sessionId: String, val engineSessionState: EngineSessionState ) : EngineAction() } components/browser/state/src/main/java/mozilla/components/browser/state/reducer/BrowserStateReducer.kt +2 −0 Original line number Diff line number Diff line Loading @@ -7,6 +7,7 @@ package mozilla.components.browser.state.reducer import mozilla.components.browser.state.action.BrowserAction import mozilla.components.browser.state.action.ContentAction import mozilla.components.browser.state.action.CustomTabListAction import mozilla.components.browser.state.action.EngineAction import mozilla.components.browser.state.action.SystemAction import mozilla.components.browser.state.action.TabListAction import mozilla.components.browser.state.action.TrackingProtectionAction Loading @@ -28,6 +29,7 @@ internal object BrowserStateReducer { is SystemAction -> SystemReducer.reduce(state, action) is TabListAction -> TabListReducer.reduce(state, action) is TrackingProtectionAction -> TrackingProtectionStateReducer.reduce(state, action) is EngineAction -> EngineStateReducer.reduce(state, action) } } } Loading
components/browser/session/src/main/java/mozilla/components/browser/session/LegacySessionManager.kt +4 −1 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ import kotlin.math.min @Suppress("TooManyFunctions", "LargeClass") class LegacySessionManager( val engine: Engine, private val engineSessionLinker: SessionManager.EngineSessionLinker, delegate: Observable<SessionManager.Observer> = ObserverRegistry() ) : Observable<SessionManager.Observer> by delegate { // It's important that any access to `values` is synchronized; Loading Loading @@ -236,7 +237,7 @@ class LegacySessionManager( * - once snapshot has been restored, and appropriate session has been selected, onSessionsRestored * notification will fire. * * @param snapshot A [Snapshot] which may be produced by [createSnapshot]. * @param snapshot A [SessionManager.Snapshot] which may be produced by [createSnapshot]. * @param updateSelection Whether the selected session should be updated from the restored snapshot. */ fun restore(snapshot: SessionManager.Snapshot, updateSelection: Boolean = true) = synchronized(values) { Loading Loading @@ -299,6 +300,7 @@ class LegacySessionManager( engineSession.loadUrl(session.url) } } engineSessionLinker.link(session, engineSession) } private fun unlink(session: Session) { Loading @@ -311,6 +313,7 @@ class LegacySessionManager( engineObserver = null } } engineSessionLinker.unlink(session) } /** Loading
components/browser/session/src/main/java/mozilla/components/browser/session/SessionManager.kt +30 −5 Original line number Diff line number Diff line Loading @@ -8,6 +8,9 @@ import mozilla.components.browser.session.ext.syncDispatch import mozilla.components.browser.session.ext.toCustomTabSessionState import mozilla.components.browser.session.ext.toTabSessionState import mozilla.components.browser.state.action.CustomTabListAction import mozilla.components.browser.state.action.EngineAction.LinkEngineSessionAction import mozilla.components.browser.state.action.EngineAction.UpdateEngineSessionStateAction import mozilla.components.browser.state.action.EngineAction.UnlinkEngineSessionAction import mozilla.components.browser.state.action.SystemAction import mozilla.components.browser.state.action.TabListAction import mozilla.components.browser.state.store.BrowserStore Loading @@ -24,8 +27,24 @@ import java.lang.IllegalArgumentException class SessionManager( val engine: Engine, private val store: BrowserStore? = null, private val delegate: LegacySessionManager = LegacySessionManager(engine) private val delegate: LegacySessionManager = LegacySessionManager(engine, EngineSessionLinker(store)) ) : Observable<SessionManager.Observer> by delegate { /** * This class only exists for migrating from browser-session * to browser-state. We need a way to dispatch the corresponding browser * actions when an engine session is linked and unlinked. */ class EngineSessionLinker(private val store: BrowserStore?) { fun link(session: Session, engineSession: EngineSession) { store?.syncDispatch(LinkEngineSessionAction(session.id, engineSession)) } fun unlink(session: Session) { store?.syncDispatch(UnlinkEngineSessionAction(session.id)) } } /** * Returns the number of session including CustomTab sessions. */ Loading Loading @@ -158,13 +177,14 @@ class SessionManager( delegate.restore(snapshot, updateSelection) val tabs = snapshot.sessions val items = snapshot.sessions .filter { // A restored snapshot should not contain any custom tab so we should be able to safely ignore // them here. !it.session.isCustomTabSession() } .map { item -> val tabs = items.map { item -> item.session.toTabSessionState() } Loading @@ -180,6 +200,11 @@ class SessionManager( } store?.syncDispatch(TabListAction.RestoreAction(tabs, selectedTabId)) items.forEach { item -> item.engineSession?.let { store?.syncDispatch(LinkEngineSessionAction(item.session.id, it)) } item.engineSessionState?.let { store?.syncDispatch(UpdateEngineSessionStateAction(item.session.id, it)) } } } /** Loading
components/browser/session/src/test/java/mozilla/components/browser/session/SessionManagerMigrationTest.kt +68 −0 Original line number Diff line number Diff line Loading @@ -7,6 +7,9 @@ package mozilla.components.browser.session import mozilla.components.browser.state.selector.findTab import mozilla.components.browser.state.selector.selectedTab import mozilla.components.browser.state.store.BrowserStore import mozilla.components.concept.engine.Engine import mozilla.components.concept.engine.EngineSession import mozilla.components.concept.engine.EngineSessionState import mozilla.components.concept.engine.HitResult import mozilla.components.concept.engine.content.blocking.Tracker import mozilla.components.support.base.observer.Consumable Loading @@ -18,6 +21,7 @@ import org.junit.Assert.assertNull import org.junit.Assert.assertSame import org.junit.Assert.assertTrue import org.junit.Test import org.mockito.Mockito.doReturn import org.mockito.Mockito.`when` /** Loading Loading @@ -737,4 +741,68 @@ class SessionManagerMigrationTest { assertNull(tab.content.download) } } @Test fun `Linking session to engine session`() { val store = BrowserStore() val engine: Engine = mock() val engineSession1: EngineSession = mock() doReturn(engineSession1).`when`(engine).createSession(false) val sessionManager = SessionManager(engine, store) val session = Session(id = "session", initialUrl = "https://www.mozilla.org") sessionManager.add(session) assertNull(sessionManager.getEngineSession(session)) assertEquals(engineSession1, sessionManager.getOrCreateEngineSession(session)) store.state.findTab("session")!!.also { tab -> assertEquals(engineSession1, tab.engineState.engineSession) } // Force unlink and link again val engineSession2: EngineSession = mock() doReturn(engineSession2).`when`(engine).createSession(false) session.engineSessionHolder.engineSession = null assertEquals(engineSession2, sessionManager.getOrCreateEngineSession(session)) store.state.findTab("session")!!.also { tab -> assertEquals(engineSession2, tab.engineState.engineSession) } } @Test fun `Restoring engine session with state`() { val engine: Engine = mock() val store = BrowserStore() val sessionManager = SessionManager(engine, store) val engineSession: EngineSession = mock() val engineSessionState: EngineSessionState = mock() val snapshot = SessionManager.Snapshot( listOf( SessionManager.Snapshot.Item( session = Session(id = "session1", initialUrl = "http://www.firefox.com"), engineSessionState = engineSessionState ), SessionManager.Snapshot.Item( session = Session(id = "session2", initialUrl = "http://www.mozilla.org"), engineSession = engineSession ) ), selectedSessionIndex = 0 ) sessionManager.restore(snapshot) assertEquals(2, sessionManager.size) store.state.findTab("session1")!!.also { tab -> assertEquals(engineSessionState, tab.engineState.engineSessionState) } store.state.findTab("session2")!!.also { tab -> assertEquals(engineSession, tab.engineState.engineSession) } } }
components/browser/state/src/main/java/mozilla/components/browser/state/action/BrowserAction.kt +28 −0 Original line number Diff line number Diff line Loading @@ -8,11 +8,14 @@ import android.graphics.Bitmap import mozilla.components.browser.state.state.BrowserState import mozilla.components.browser.state.state.ContentState import mozilla.components.browser.state.state.CustomTabSessionState import mozilla.components.browser.state.state.EngineState import mozilla.components.browser.state.state.SecurityInfoState import mozilla.components.browser.state.state.SessionState import mozilla.components.browser.state.state.TabSessionState import mozilla.components.browser.state.state.TrackingProtectionState import mozilla.components.browser.state.state.content.DownloadState import mozilla.components.concept.engine.EngineSession import mozilla.components.concept.engine.EngineSessionState import mozilla.components.concept.engine.HitResult import mozilla.components.concept.engine.content.blocking.Tracker import mozilla.components.lib.state.Action Loading Loading @@ -215,3 +218,28 @@ sealed class TrackingProtectionAction : BrowserAction() { */ data class ClearTrackersAction(val tabId: String) : TrackingProtectionAction() } /** * [BrowserAction] implementations related to updating the [EngineState] of a single [SessionState] inside * [BrowserState]. */ sealed class EngineAction : BrowserAction() { /** * Attaches the provided [EngineSession] to the session with the provided [sessionId]. */ data class LinkEngineSessionAction(val sessionId: String, val engineSession: EngineSession) : EngineAction() /** * Detaches the current [EngineSession] from the session with the provided [sessionId]. */ data class UnlinkEngineSessionAction(val sessionId: String) : EngineAction() /** * Updates the [EngineSessionState] of the session with the provided [sessionId]. */ data class UpdateEngineSessionStateAction( val sessionId: String, val engineSessionState: EngineSessionState ) : EngineAction() }
components/browser/state/src/main/java/mozilla/components/browser/state/reducer/BrowserStateReducer.kt +2 −0 Original line number Diff line number Diff line Loading @@ -7,6 +7,7 @@ package mozilla.components.browser.state.reducer import mozilla.components.browser.state.action.BrowserAction import mozilla.components.browser.state.action.ContentAction import mozilla.components.browser.state.action.CustomTabListAction import mozilla.components.browser.state.action.EngineAction import mozilla.components.browser.state.action.SystemAction import mozilla.components.browser.state.action.TabListAction import mozilla.components.browser.state.action.TrackingProtectionAction Loading @@ -28,6 +29,7 @@ internal object BrowserStateReducer { is SystemAction -> SystemReducer.reduce(state, action) is TabListAction -> TabListReducer.reduce(state, action) is TrackingProtectionAction -> TrackingProtectionStateReducer.reduce(state, action) is EngineAction -> EngineStateReducer.reduce(state, action) } } }