Commit 802c7588 authored by Christian Sadilek's avatar Christian Sadilek Committed by Sebastian Kaspari
Browse files

Closes #3533: browser-state: Dispatch actions from Session

parent b1765d28
......@@ -8,7 +8,15 @@ import android.graphics.Bitmap
import mozilla.components.browser.session.engine.EngineSessionHolder
import mozilla.components.browser.session.engine.request.LoadRequestMetadata
import mozilla.components.browser.session.engine.request.LoadRequestOption
import mozilla.components.browser.session.ext.syncDispatch
import mozilla.components.browser.session.ext.toSecurityInfoState
import mozilla.components.browser.session.tab.CustomTabConfig
import mozilla.components.browser.state.action.ContentAction.UpdateLoadingStateAction
import mozilla.components.browser.state.action.ContentAction.UpdateProgressAction
import mozilla.components.browser.state.action.ContentAction.UpdateSecurityInfo
import mozilla.components.browser.state.action.ContentAction.UpdateSearchTermsAction
import mozilla.components.browser.state.action.ContentAction.UpdateTitleAction
import mozilla.components.browser.state.action.ContentAction.UpdateUrlAction
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.concept.engine.HitResult
import mozilla.components.concept.engine.manifest.WebAppManifest
......@@ -163,59 +171,66 @@ class Session(
/**
* The currently loading or loaded URL.
*/
var url: String by Delegates.observable(initialUrl) {
_, old, new -> notifyObservers(old, new) { onUrlChanged(this@Session, new) }
var url: String by Delegates.observable(initialUrl) { _, old, new ->
if (notifyObservers(old, new) { onUrlChanged(this@Session, new) }) {
store?.syncDispatch(UpdateUrlAction(id, new))
}
}
/**
* The title of the currently displayed website changed.
*/
var title: String by Delegates.observable("") {
_, old, new -> notifyObservers(old, new) { onTitleChanged(this@Session, new) }
var title: String by Delegates.observable("") { _, old, new ->
if (notifyObservers(old, new) { onTitleChanged(this@Session, new) }) {
store?.syncDispatch(UpdateTitleAction(id, new))
}
}
/**
* The progress loading the current URL.
*/
var progress: Int by Delegates.observable(0) {
_, old, new -> notifyObservers(old, new) { onProgress(this@Session, new) }
var progress: Int by Delegates.observable(0) { _, old, new ->
if (notifyObservers(old, new) { onProgress(this@Session, new) }) {
store?.syncDispatch(UpdateProgressAction(id, new))
}
}
/**
* Loading state, true if this session's url is currently loading, otherwise false.
*/
var loading: Boolean by Delegates.observable(false) {
_, old, new -> notifyObservers(old, new) { onLoadingStateChanged(this@Session, new) }
var loading: Boolean by Delegates.observable(false) { _, old, new ->
if (notifyObservers(old, new) { onLoadingStateChanged(this@Session, new) }) {
store?.syncDispatch(UpdateLoadingStateAction(id, new))
}
}
/**
* Navigation state, true if there's an history item to go back to, otherwise false.
*/
var canGoBack: Boolean by Delegates.observable(false) {
_, old, new -> notifyObservers(old, new) { onNavigationStateChanged(this@Session, new, canGoForward) }
var canGoBack: Boolean by Delegates.observable(false) { _, old, new ->
notifyObservers(old, new) { onNavigationStateChanged(this@Session, new, canGoForward) }
}
/**
* Navigation state, true if there's an history item to go forward to, otherwise false.
*/
var canGoForward: Boolean by Delegates.observable(false) {
_, old, new -> notifyObservers(old, new) { onNavigationStateChanged(this@Session, canGoBack, new) }
var canGoForward: Boolean by Delegates.observable(false) { _, old, new ->
notifyObservers(old, new) { onNavigationStateChanged(this@Session, canGoBack, new) }
}
/**
* The currently / last used search terms (or an empty string).
*/
var searchTerms: String by Delegates.observable("") {
_, _, new -> notifyObservers {
onSearch(this@Session, new)
}
var searchTerms: String by Delegates.observable("") { _, _, new ->
notifyObservers { onSearch(this@Session, new) }
store?.syncDispatch(UpdateSearchTermsAction(id, new))
}
/**
* Set when a load request is received, indicating if the request came from web content, or via a redirect.
*/
var loadRequestMetadata: LoadRequestMetadata by Delegates.observable(LoadRequestMetadata.blank) {
_, _, new -> notifyObservers {
var loadRequestMetadata: LoadRequestMetadata by Delegates.observable(LoadRequestMetadata.blank) { _, _, new ->
notifyObservers {
onLoadRequest(
this@Session,
new.url,
......@@ -229,22 +244,23 @@ class Session(
* Security information indicating whether or not the current session is
* for a secure URL, as well as the host and SSL certificate authority, if applicable.
*/
var securityInfo: SecurityInfo by Delegates.observable(SecurityInfo()) {
_, old, new -> notifyObservers(old, new) { onSecurityChanged(this@Session, new) }
var securityInfo: SecurityInfo by Delegates.observable(SecurityInfo()) { _, old, new ->
notifyObservers(old, new) { onSecurityChanged(this@Session, new) }
store?.syncDispatch(UpdateSecurityInfo(id, new.toSecurityInfoState()))
}
/**
* Configuration data in case this session is used for a Custom Tab.
*/
var customTabConfig: CustomTabConfig? by Delegates.observable<CustomTabConfig?>(null) {
_, _, new -> notifyObservers { onCustomTabConfigChanged(this@Session, new) }
var customTabConfig: CustomTabConfig? by Delegates.observable<CustomTabConfig?>(null) { _, _, new ->
notifyObservers { onCustomTabConfigChanged(this@Session, new) }
}
/**
* The Web App Manifest for the currently visited page (or null).
*/
var webAppManifest: WebAppManifest? by Delegates.observable<WebAppManifest?>(null) {
_, _, new -> notifyObservers { onWebAppManifestChanged(this@Session, new) }
var webAppManifest: WebAppManifest? by Delegates.observable<WebAppManifest?>(null) { _, _, new ->
notifyObservers { onWebAppManifestChanged(this@Session, new) }
}
/**
......@@ -314,8 +330,8 @@ class Session(
/**
* The target of the latest thumbnail.
*/
var thumbnail: Bitmap? by Delegates.observable<Bitmap?>(null) {
_, _, new -> notifyObservers { onThumbnailChanged(this@Session, new) }
var thumbnail: Bitmap? by Delegates.observable<Bitmap?>(null) { _, _, new ->
notifyObservers { onThumbnailChanged(this@Session, new) }
}
/**
......@@ -366,16 +382,15 @@ class Session(
* [Consumable] State for a prompt request from web content.
*/
var promptRequest: Consumable<PromptRequest> by Delegates.vetoable(Consumable.empty()) {
_, _, request ->
val consumers = wrapConsumers<PromptRequest> { onPromptRequested(this@Session, it) }
!request.consumeBy(consumers)
_, _, request ->
val consumers = wrapConsumers<PromptRequest> { onPromptRequested(this@Session, it) }
!request.consumeBy(consumers)
}
/**
* [Consumable] request to open/create a window.
*/
var openWindowRequest: Consumable<WindowRequest> by Delegates.vetoable(Consumable.empty()) {
_, _, request ->
var openWindowRequest: Consumable<WindowRequest> by Delegates.vetoable(Consumable.empty()) { _, _, request ->
val consumers = wrapConsumers<WindowRequest> { onOpenWindowRequested(this@Session, it) }
!request.consumeBy(consumers)
}
......@@ -383,8 +398,7 @@ class Session(
/**
* [Consumable] request to close a window.
*/
var closeWindowRequest: Consumable<WindowRequest> by Delegates.vetoable(Consumable.empty()) {
_, _, request ->
var closeWindowRequest: Consumable<WindowRequest> by Delegates.vetoable(Consumable.empty()) { _, _, request ->
val consumers = wrapConsumers<WindowRequest> { onCloseWindowRequested(this@Session, it) }
!request.consumeBy(consumers)
}
......@@ -430,10 +444,18 @@ class Session(
/**
* Helper method to notify observers only if a value changed.
*
* @param old the previous value of a session property
* @param new the current (new) value of a session property
*
* @return true if the observers where notified (the values changed), otherwise false.
*/
private fun notifyObservers(old: Any?, new: Any?, block: Observer.() -> Unit) {
if (old != new) {
private fun notifyObservers(old: Any?, new: Any?, block: Observer.() -> Unit): Boolean {
return if (old != new) {
notifyObservers(block)
true
} else {
false
}
}
......
......@@ -36,6 +36,9 @@ private fun Session.toContentState(): ContentState {
)
}
private fun Session.SecurityInfo.toSecurityInfoState(): SecurityInfoState {
/**
* Creates a matching [SecurityInfoState] from a [Session.SecurityInfo]
*/
fun Session.SecurityInfo.toSecurityInfoState(): SecurityInfoState {
return SecurityInfoState(secure, host, issuer)
}
......@@ -12,7 +12,10 @@ import kotlinx.coroutines.runBlocking
import mozilla.components.browser.session.Session.Source
import mozilla.components.browser.session.engine.request.LoadRequestMetadata
import mozilla.components.browser.session.engine.request.LoadRequestOption
import mozilla.components.browser.session.ext.toSecurityInfoState
import mozilla.components.browser.session.tab.CustomTabConfig
import mozilla.components.browser.state.action.ContentAction
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.concept.engine.HitResult
import mozilla.components.concept.engine.manifest.Size
import mozilla.components.concept.engine.manifest.WebAppManifest
......@@ -91,6 +94,19 @@ class SessionTest {
verifyNoMoreInteractions(observer)
}
@Test
fun `action is dispatched when URL changes`() {
val store: BrowserStore = mock()
`when`(store.dispatch(any())).thenReturn(mock())
val session = Session("https://www.mozilla.org")
session.store = store
session.url = "http://www.firefox.com"
verify(store).dispatch(ContentAction.UpdateUrlAction(session.id, session.url))
verifyNoMoreInteractions(store)
}
@Test
fun `observer is notified when progress changes`() {
val observer = mock(Session.Observer::class.java)
......@@ -105,6 +121,19 @@ class SessionTest {
verifyNoMoreInteractions(observer)
}
@Test
fun `action is dispatched when progress changes`() {
val store: BrowserStore = mock()
`when`(store.dispatch(any())).thenReturn(mock())
val session = Session("https://www.mozilla.org")
session.store = store
session.progress = 75
verify(store).dispatch(ContentAction.UpdateProgressAction(session.id, session.progress))
verifyNoMoreInteractions(store)
}
@Test
fun `observer is notified when loading state changes`() {
val observer = mock(Session.Observer::class.java)
......@@ -119,6 +148,19 @@ class SessionTest {
verifyNoMoreInteractions(observer)
}
@Test
fun `action is dispatched when loading state changes`() {
val store: BrowserStore = mock()
`when`(store.dispatch(any())).thenReturn(mock())
val session = Session("https://www.mozilla.org")
session.store = store
session.loading = true
verify(store).dispatch(ContentAction.UpdateLoadingStateAction(session.id, session.loading))
verifyNoMoreInteractions(store)
}
@Test
fun `observer is notified when navigation state changes`() {
val observer = mock(Session.Observer::class.java)
......@@ -165,6 +207,19 @@ class SessionTest {
verifyNoMoreInteractions(observer)
}
@Test
fun `action is dispatched when search terms are set`() {
val store: BrowserStore = mock()
`when`(store.dispatch(any())).thenReturn(mock())
val session = Session("https://www.mozilla.org")
session.store = store
session.searchTerms = "mozilla android"
verify(store).dispatch(ContentAction.UpdateSearchTermsAction(session.id, session.searchTerms))
verifyNoMoreInteractions(store)
}
@Test
fun `observer is notified when load request is triggered`() {
val observer = mock(Session.Observer::class.java)
......@@ -206,6 +261,19 @@ class SessionTest {
assertEquals("issuer", info!!.issuer)
}
@Test
fun `action is dispatched when security info is set`() {
val store: BrowserStore = mock()
`when`(store.dispatch(any())).thenReturn(mock())
val session = Session("https://www.mozilla.org")
session.store = store
session.securityInfo = Session.SecurityInfo(true, "mozilla.org", "issuer")
verify(store).dispatch(ContentAction.UpdateSecurityInfo(session.id, session.securityInfo.toSecurityInfoState()))
verifyNoMoreInteractions(store)
}
@Test
fun `observer is notified when custom tab config is set`() {
var config: CustomTabConfig? = null
......@@ -469,6 +537,19 @@ class SessionTest {
assertEquals("Internet for people, not profit — Mozilla", session.title)
}
@Test
fun `action is dispatched when title changes`() {
val store: BrowserStore = mock()
`when`(store.dispatch(any())).thenReturn(mock())
val session = Session("https://www.mozilla.org")
session.store = store
session.title = "Internet for people, not profit — Mozilla"
verify(store).dispatch(ContentAction.UpdateTitleAction(session.id, session.title))
verifyNoMoreInteractions(store)
}
@Test
fun `website title is empty by default`() {
val session = Session("https://www.mozilla.org")
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment