Commit 313eaa01 authored by Christian Sadilek's avatar Christian Sadilek
Browse files

Closes #7741: Move Session.Source to browser state

parent 84d7a0f7
......@@ -31,6 +31,7 @@ import mozilla.components.browser.state.action.TabListAction.AddTabAction
import mozilla.components.browser.state.action.TrackingProtectionAction
import mozilla.components.browser.state.selector.findTabOrCustomTab
import mozilla.components.browser.state.state.CustomTabConfig
import mozilla.components.browser.state.state.SessionState
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.concept.engine.content.blocking.Tracker
import mozilla.components.concept.engine.manifest.WebAppManifest
......@@ -45,11 +46,11 @@ import kotlin.properties.Delegates
/**
* Value type that represents the state of a browser session. Changes can be observed.
*/
@Suppress("TooManyFunctions")
@Suppress("TooManyFunctions", "LongParameterList")
class Session(
initialUrl: String,
val private: Boolean = false,
val source: Source = Source.NONE,
val source: SessionState.Source = SessionState.Source.NONE,
val id: String = UUID.randomUUID().toString(),
val contextId: String? = null,
delegate: Observable<Observer> = ObserverRegistry()
......@@ -111,66 +112,6 @@ class Session(
*/
data class SecurityInfo(val secure: Boolean = false, val host: String = "", val issuer: String = "")
/**
* Represents the origin of a session to describe how and why it was created.
*/
enum class Source {
/**
* Created to handle an ACTION_SEND (share) intent
*/
ACTION_SEND,
/**
* Created to handle an ACTION_SEARCH and ACTION_WEB_SEARCH intent
*/
ACTION_SEARCH,
/**
* Created to handle an ACTION_VIEW intent
*/
ACTION_VIEW,
/**
* Created to handle a CustomTabs intent
*/
CUSTOM_TAB,
/**
* User interacted with the home screen
*/
HOME_SCREEN,
/**
* User interacted with a menu
*/
MENU,
/**
* User opened a new tab
*/
NEW_TAB,
/**
* Default value and for testing purposes
*/
NONE,
/**
* Default value and for testing purposes
*/
TEXT_SELECTION,
/**
* User entered a URL or search term
*/
USER_ENTERED,
/**
* This session was restored
*/
RESTORED
}
/**
* The currently loading or loaded URL.
*/
......
......@@ -20,7 +20,8 @@ fun Session.toTabSessionState(): TabSessionState {
toContentState(),
toTrackingProtectionState(),
parentId = parentId,
contextId = contextId
contextId = contextId,
source = source
)
}
......
......@@ -8,6 +8,7 @@ import androidx.annotation.VisibleForTesting
import mozilla.components.browser.session.Session
import mozilla.components.browser.session.SessionManager
import mozilla.components.browser.state.state.ReaderState
import mozilla.components.browser.state.state.SessionState
import mozilla.components.concept.engine.Engine
import mozilla.components.support.ktx.android.org.json.tryGetString
import org.json.JSONArray
......@@ -125,7 +126,7 @@ internal fun deserializeSession(
json.getString(Keys.SESSION_URL_KEY),
// Currently, snapshot cannot contain private sessions.
false,
Session.Source.RESTORED,
SessionState.Source.RESTORED,
if (restoreId) {
json.getString(Keys.SESSION_UUID_KEY)
} else {
......
......@@ -9,6 +9,7 @@ import mozilla.components.browser.state.action.ReaderAction
import mozilla.components.browser.state.selector.findTab
import mozilla.components.browser.state.selector.selectedTab
import mozilla.components.browser.state.state.ReaderState
import mozilla.components.browser.state.state.SessionState
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.concept.engine.Engine
import mozilla.components.concept.engine.EngineSession
......@@ -42,16 +43,25 @@ class SessionManagerMigrationTest {
sessionManager.add(Session("https://www.mozilla.org", private = true))
sessionManager.add(Session("https://www.firefox.com", contextId = "1"))
sessionManager.add(Session("https://getpocket.com", source = SessionState.Source.ACTION_VIEW))
assertEquals(2, sessionManager.sessions.size)
assertEquals(2, store.state.tabs.size)
assertEquals(3, sessionManager.sessions.size)
assertEquals(3, store.state.tabs.size)
assertEquals("https://www.mozilla.org", store.state.tabs[0].content.url)
assertEquals("https://www.firefox.com", store.state.tabs[1].content.url)
assertTrue(store.state.tabs[0].content.private)
assertFalse(store.state.tabs[1].content.private)
assertNull(store.state.tabs[0].contextId)
assertEquals(SessionState.Source.NONE, store.state.tabs[0].source)
assertEquals("https://www.firefox.com", store.state.tabs[1].content.url)
assertFalse(store.state.tabs[1].content.private)
assertEquals("1", store.state.tabs[1].contextId)
assertEquals(SessionState.Source.NONE, store.state.tabs[1].source)
assertEquals("https://getpocket.com", store.state.tabs[2].content.url)
assertFalse(store.state.tabs[2].content.private)
assertNull(store.state.tabs[2].contextId)
assertEquals(SessionState.Source.ACTION_VIEW, store.state.tabs[2].source)
}
@Test
......@@ -104,6 +114,7 @@ class SessionManagerMigrationTest {
val tab = store.state.customTabs[0]
assertEquals("https://www.mozilla.org", tab.content.url)
assertEquals(SessionState.Source.CUSTOM_TAB, tab.source)
}
@Test
......
......@@ -7,6 +7,7 @@ package mozilla.components.browser.session
import android.graphics.Bitmap
import mozilla.components.browser.state.selector.findTab
import mozilla.components.browser.state.state.CustomTabConfig
import mozilla.components.browser.state.state.SessionState
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.concept.engine.Engine
import mozilla.components.concept.engine.EngineSession
......@@ -678,7 +679,7 @@ class SessionManagerTest {
assertEquals(actualEngineSession, sessionManager.getOrCreateEngineSession(session))
assertEquals(actualEngineSession, session.engineSessionHolder.engineSession)
val privateSession = Session("https://www.mozilla.org", true, Session.Source.NONE)
val privateSession = Session("https://www.mozilla.org", true, SessionState.Source.NONE)
sessionManager.add(privateSession)
assertNull(store.state.findTab(privateSession.id)!!.engineState.engineSession)
assertEquals(privateEngineSession, sessionManager.getOrCreateEngineSession(privateSession))
......
......@@ -9,7 +9,7 @@ import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking
import mozilla.components.browser.session.Session.Source
import mozilla.components.browser.state.state.SessionState.Source
import mozilla.components.browser.session.engine.request.LaunchIntentMetadata
import mozilla.components.browser.session.engine.request.LoadRequestMetadata
import mozilla.components.browser.session.engine.request.LoadRequestOption
......
......@@ -7,6 +7,7 @@ package mozilla.components.browser.session.ext
import androidx.test.ext.junit.runners.AndroidJUnit4
import mozilla.components.browser.session.Session
import mozilla.components.browser.state.state.CustomTabConfig
import mozilla.components.browser.state.state.SessionState
import mozilla.components.support.test.mock
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
......@@ -26,6 +27,8 @@ class SessionExtensionsTest {
assertEquals(tabState.id, session.id)
assertEquals(tabState.content.url, session.url)
assertEquals(tabState.parentId, session.parentId)
assertEquals(tabState.source, session.source)
assertEquals(SessionState.Source.NONE, tabState.source)
assertNull(tabState.contextId)
}
......@@ -35,9 +38,22 @@ class SessionExtensionsTest {
val tabState = session.toTabSessionState()
assertEquals(tabState.id, session.id)
assertEquals(tabState.content.url, session.url)
assertEquals(tabState.source, session.source)
assertEquals(SessionState.Source.NONE, tabState.source)
assertEquals(tabState.contextId, session.contextId)
}
@Test
fun `toTabSessionState - Can convert tab session with source`() {
val session = Session("https://mozilla.org", source = SessionState.Source.ACTION_VIEW)
val tabState = session.toTabSessionState()
assertEquals(tabState.id, session.id)
assertEquals(tabState.content.url, session.url)
assertNull(tabState.contextId)
assertEquals(tabState.source, session.source)
assertEquals(SessionState.Source.ACTION_VIEW, tabState.source)
}
@Test
fun `toCustomTabSessionState - Can convert custom tab session`() {
val session = Session("https://mozilla.org")
......@@ -46,6 +62,8 @@ class SessionExtensionsTest {
val customTabState = session.toCustomTabSessionState()
assertEquals(customTabState.id, session.id)
assertEquals(customTabState.content.url, session.url)
assertEquals(SessionState.Source.CUSTOM_TAB, customTabState.source)
assertEquals(customTabState.contextId, session.contextId)
assertSame(customTabState.config, session.customTabConfig)
assertNull(customTabState.contextId)
}
......@@ -58,6 +76,8 @@ class SessionExtensionsTest {
val customTabState = session.toCustomTabSessionState()
assertEquals(customTabState.id, session.id)
assertEquals(customTabState.content.url, session.url)
assertEquals(SessionState.Source.CUSTOM_TAB, customTabState.source)
assertEquals(customTabState.contextId, session.contextId)
assertSame(customTabState.config, session.customTabConfig)
assertEquals(customTabState.contextId, session.contextId)
}
......
......@@ -11,7 +11,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import kotlinx.coroutines.Job
import kotlinx.coroutines.runBlocking
import mozilla.components.browser.session.Session
import mozilla.components.browser.session.Session.Source
import mozilla.components.browser.state.state.SessionState.Source
import mozilla.components.browser.session.SessionManager
import mozilla.components.concept.engine.Engine
import mozilla.components.concept.engine.EngineSession
......
......@@ -8,6 +8,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import mozilla.components.browser.session.Session
import mozilla.components.browser.session.SessionManager.Snapshot
import mozilla.components.browser.state.state.ReaderState
import mozilla.components.browser.state.state.SessionState
import mozilla.components.concept.engine.Engine
import mozilla.components.concept.engine.EngineSession
import mozilla.components.concept.engine.EngineSessionState
......@@ -30,7 +31,7 @@ class SnapshotSerializerTest {
fun `Serialize and deserialize session`() {
val originalSession = Session(
"https://www.mozilla.org",
source = Session.Source.ACTION_VIEW,
source = SessionState.Source.ACTION_VIEW,
id = "test-id",
contextId = "test-context-id"
).apply {
......@@ -41,7 +42,7 @@ class SnapshotSerializerTest {
val restoredSession = deserializeSession(json, restoreId = true, restoreParentId = false)
assertEquals("https://www.mozilla.org", restoredSession.url)
assertEquals(Session.Source.RESTORED, restoredSession.source)
assertEquals(SessionState.Source.RESTORED, restoredSession.source)
assertEquals("test-id", restoredSession.id)
assertEquals("test-context-id", restoredSession.contextId)
assertEquals("Hello World", restoredSession.title)
......@@ -61,7 +62,7 @@ class SnapshotSerializerTest {
val originalSession = Session(
"https://www.mozilla.org",
source = Session.Source.ACTION_VIEW,
source = SessionState.Source.ACTION_VIEW,
id = "test-id",
contextId = "test-context-id"
).apply {
......@@ -78,7 +79,7 @@ class SnapshotSerializerTest {
val restoredItem = serializer.itemFromJSON(engine, json)
assertEquals("https://www.mozilla.org", restoredItem.session.url)
assertEquals(Session.Source.RESTORED, restoredItem.session.source)
assertEquals(SessionState.Source.RESTORED, restoredItem.session.source)
assertEquals("test-id", restoredItem.session.id)
assertEquals("test-context-id", restoredItem.session.contextId)
assertEquals("Hello World", restoredItem.session.title)
......@@ -91,7 +92,7 @@ class SnapshotSerializerTest {
whenever(engine.createSessionState(any())).thenReturn(mock())
val originalSession = Session(
"https://www.mozilla.org",
source = Session.Source.ACTION_VIEW,
source = SessionState.Source.ACTION_VIEW,
id = "test-id",
contextId = "test-context-id"
).apply {
......@@ -108,7 +109,7 @@ class SnapshotSerializerTest {
var restoredItem = serializer.itemFromJSON(engine, json)
assertEquals("https://www.mozilla.org", restoredItem.session.url)
assertEquals(Session.Source.RESTORED, restoredItem.session.source)
assertEquals(SessionState.Source.RESTORED, restoredItem.session.source)
assertEquals("test-id", restoredItem.session.id)
assertEquals("test-context-id", restoredItem.session.contextId)
assertEquals("Hello World", restoredItem.session.title)
......@@ -149,7 +150,7 @@ class SnapshotSerializerTest {
)
assertEquals("https://www.mozilla.org", restoredSession.url)
assertEquals(Session.Source.RESTORED, restoredSession.source)
assertEquals(SessionState.Source.RESTORED, restoredSession.source)
assertEquals("test-id", restoredSession.id)
}
......@@ -171,7 +172,7 @@ class SnapshotSerializerTest {
fun `Deserialize session without restoring id`() {
val originalSession = Session(
"https://www.mozilla.org",
source = Session.Source.ACTION_VIEW,
source = SessionState.Source.ACTION_VIEW,
id = "test-id"
).apply {
title = "Hello World"
......@@ -181,7 +182,7 @@ class SnapshotSerializerTest {
val restoredSession = deserializeSession(json, restoreId = false, restoreParentId = false)
assertEquals("https://www.mozilla.org", restoredSession.url)
assertEquals(Session.Source.RESTORED, restoredSession.source)
assertEquals(SessionState.Source.RESTORED, restoredSession.source)
assertNotEquals("test-id", restoredSession.id)
assertTrue(restoredSession.id.isNotBlank())
assertEquals("Hello World", restoredSession.title)
......@@ -197,7 +198,7 @@ class SnapshotSerializerTest {
fun `Deserialize session without restoring parent id`() {
val originalSession = Session(
"https://www.mozilla.org",
source = Session.Source.ACTION_VIEW,
source = SessionState.Source.ACTION_VIEW,
id = "test-id"
).apply {
parentId = "test-parent-id"
......@@ -207,7 +208,7 @@ class SnapshotSerializerTest {
val json = serializeSession(originalSession)
val restoredSession = deserializeSession(json, restoreId = true, restoreParentId = true)
assertEquals("https://www.mozilla.org", restoredSession.url)
assertEquals(Session.Source.RESTORED, restoredSession.source)
assertEquals(SessionState.Source.RESTORED, restoredSession.source)
assertEquals("test-id", restoredSession.id)
assertEquals("test-parent-id", restoredSession.parentId)
assertTrue(restoredSession.id.isNotBlank())
......
......@@ -24,7 +24,8 @@ data class CustomTabSessionState(
val config: CustomTabConfig,
override val engineState: EngineState = EngineState(),
override val extensionState: Map<String, WebExtensionState> = emptyMap(),
override val contextId: String? = null
override val contextId: String? = null,
override val source: SessionState.Source = SessionState.Source.CUSTOM_TAB
) : SessionState {
override fun createCopy(
......
......@@ -16,6 +16,7 @@ package mozilla.components.browser.state.state
* @property contextId the session context ID of the session. The session context ID specifies the
* contextual identity to use for the session's cookie store.
* https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Work_with_contextual_identities
* @property source the [Source] of this session to describe how and why it was created.
*/
interface SessionState {
val id: String
......@@ -24,6 +25,7 @@ interface SessionState {
val engineState: EngineState
val extensionState: Map<String, WebExtensionState>
val contextId: String?
val source: Source
/**
* Copy the class and override some parameters.
......@@ -37,4 +39,64 @@ interface SessionState {
extensionState: Map<String, WebExtensionState> = this.extensionState,
contextId: String? = this.contextId
): SessionState
/**
* Represents the origin of a session to describe how and why it was created.
*/
enum class Source {
/**
* Created to handle an ACTION_SEND (share) intent
*/
ACTION_SEND,
/**
* Created to handle an ACTION_SEARCH and ACTION_WEB_SEARCH intent
*/
ACTION_SEARCH,
/**
* Created to handle an ACTION_VIEW intent
*/
ACTION_VIEW,
/**
* Created to handle a CustomTabs intent
*/
CUSTOM_TAB,
/**
* User interacted with the home screen
*/
HOME_SCREEN,
/**
* User interacted with a menu
*/
MENU,
/**
* User opened a new tab
*/
NEW_TAB,
/**
* Default value and for testing purposes
*/
NONE,
/**
* Default value and for testing purposes
*/
TEXT_SELECTION,
/**
* User entered a URL or search term
*/
USER_ENTERED,
/**
* This session was restored
*/
RESTORED
}
}
......@@ -31,7 +31,8 @@ data class TabSessionState(
override val extensionState: Map<String, WebExtensionState> = emptyMap(),
val readerState: ReaderState = ReaderState(),
override val contextId: String? = null,
val lastAccess: Long = 0L
val lastAccess: Long = 0L,
override val source: SessionState.Source = SessionState.Source.NONE
) : SessionState {
override fun createCopy(
......@@ -65,7 +66,8 @@ fun createTab(
title: String = "",
thumbnail: Bitmap? = null,
contextId: String? = null,
lastAccess: Long = 0L
lastAccess: Long = 0L,
source: SessionState.Source = SessionState.Source.NONE
): TabSessionState {
return TabSessionState(
id = id,
......@@ -79,6 +81,7 @@ fun createTab(
extensionState = extensions,
readerState = readerState,
contextId = contextId,
lastAccess = lastAccess
lastAccess = lastAccess,
source = source
)
}
......@@ -6,6 +6,7 @@ package mozilla.components.browser.state.action
import mozilla.components.browser.state.state.BrowserState
import mozilla.components.browser.state.state.CustomTabConfig
import mozilla.components.browser.state.state.SessionState
import mozilla.components.browser.state.state.createCustomTab
import mozilla.components.browser.state.state.createTab
import mozilla.components.browser.state.store.BrowserStore
......@@ -30,6 +31,7 @@ class CustomTabListActionTest {
assertEquals(0, store.state.tabs.size)
assertEquals(1, store.state.customTabs.size)
assertEquals(SessionState.Source.CUSTOM_TAB, store.state.customTabs[0].source)
assertEquals(customTab, store.state.customTabs[0])
assertSame(config, store.state.customTabs[0].config)
}
......
......@@ -7,6 +7,7 @@ package mozilla.components.browser.state.action
import mozilla.components.browser.state.selector.findTab
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.state.createCustomTab
import mozilla.components.browser.state.state.createTab
import mozilla.components.browser.state.store.BrowserStore
......@@ -93,6 +94,21 @@ class TabListActionTest {
assertEquals(tab2.id, store.state.tabs[3].parentId)
}
@Test
fun `AddTabAction - Specify source`() {
val store = BrowserStore()
val tab1 = createTab("https://www.mozilla.org")
val tab2 = createTab("https://www.firefox.com", source = SessionState.Source.MENU)
store.dispatch(TabListAction.AddTabAction(tab1)).joinBlocking()
store.dispatch(TabListAction.AddTabAction(tab2)).joinBlocking()
assertEquals(2, store.state.tabs.size)
assertEquals(SessionState.Source.NONE, store.state.tabs[0].source)
assertEquals(SessionState.Source.MENU, store.state.tabs[1].source)
}
@Test
fun `AddTabAction - Tabs with parent are added after (next to) parent`() {
val store = BrowserStore()
......
......@@ -11,6 +11,7 @@ import android.provider.Browser
import androidx.annotation.VisibleForTesting
import mozilla.components.browser.session.Session
import mozilla.components.browser.session.SessionManager
import mozilla.components.browser.state.state.SessionState
import mozilla.components.concept.engine.EngineSession
import mozilla.components.feature.intent.ext.putSessionId
import mozilla.components.feature.intent.processing.IntentProcessor
......@@ -57,7 +58,7 @@ class CustomTabIntentProcessor(
val url = safeIntent.dataString
return if (!url.isNullOrEmpty() && matches(intent)) {
val session = Session(url, private = isPrivate, source = Session.Source.CUSTOM_TAB)
val session = Session(url, private = isPrivate, source = SessionState.Source.CUSTOM_TAB)
session.customTabConfig = createCustomTabConfigFromIntent(intent, resources)
sessionManager.add(session)
......
......@@ -11,7 +11,7 @@ import androidx.browser.customtabs.CustomTabsIntent
import androidx.test.ext.junit.runners.AndroidJUnit4
import kotlinx.coroutines.ExperimentalCoroutinesApi
import mozilla.components.browser.session.Session
import mozilla.components.browser.session.Session.Source
import mozilla.components.browser.state.state.SessionState.Source
import mozilla.components.browser.session.SessionManager
import mozilla.components.concept.engine.Engine
import mozilla.components.concept.engine.EngineSession
......
......@@ -13,7 +13,7 @@ import android.content.Intent.ACTION_WEB_SEARCH
import android.content.Intent.EXTRA_TEXT
import android.nfc.NfcAdapter.ACTION_NDEF_DISCOVERED
import mozilla.components.browser.session.Session