Skip to content

Commit 1fc50a7

Browse files
Grisha Kruglovgrigoryk
Grisha Kruglov
authored andcommitted
Add an in-memory implementation of concept-storage
1 parent 28cbd95 commit 1fc50a7

File tree

10 files changed

+294
-2
lines changed

10 files changed

+294
-2
lines changed

automation/taskcluster/artifacts.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@
6767
public/build/browser.search.maven.zip:
6868
path: /build/android-components/components/browser/search/build/target.maven.zip
6969
name: browser-search
70+
public/build/browser.storage-memory.maven.zip:
71+
path: /build/android-components/components/browser/storage-memory/build/target.maven.zip
72+
name: browser-storage-memory
7073
public/build/browser.domains.maven.zip:
7174
path: /build/android-components/components/browser/domains/build/target.maven.zip
7275
name: browser-domains

components/browser/engine-system/src/test/java/mozilla/components/browser/engine/system/SystemEngineViewTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ class SystemEngineViewTest {
257257

258258
val engineView = SystemEngineView(RuntimeEnvironment.application)
259259
val historyDelegate = object : HistoryTrackingDelegate {
260-
override fun onVisited(uri: String, isReload: Boolean?, privateMode: Boolean) {
260+
override fun onVisited(uri: String, isReload: Boolean, privateMode: Boolean) {
261261
fail()
262262
}
263263

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# [Android Components](../../../README.md) > Browser > Memory Storage
2+
3+
An in-memory implementation of `concept-storage`. Data is not persisted to disk, and does not survive a restart. Not suitable for syncing.
4+
5+
### Setting up the dependency
6+
7+
Use Gradle to download the library from [maven.mozilla.org](https://maven.mozilla.org/) ([Setup repository](../../../README.md#maven-repository)):
8+
9+
```Groovy
10+
implementation "org.mozilla.components:browser-storage-memory:{latest-version}"
11+
```
12+
13+
## License
14+
15+
This Source Code Form is subject to the terms of the Mozilla Public
16+
License, v. 2.0. If a copy of the MPL was not distributed with this
17+
file, You can obtain one at http://mozilla.org/MPL/2.0/
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
apply plugin: 'com.android.library'
6+
apply plugin: 'kotlin-android'
7+
8+
android {
9+
compileSdkVersion Config.compileSdkVersion
10+
11+
defaultConfig {
12+
minSdkVersion Config.minSdkVersion
13+
targetSdkVersion Config.targetSdkVersion
14+
}
15+
16+
buildTypes {
17+
release {
18+
minifyEnabled false
19+
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
20+
}
21+
}
22+
}
23+
24+
dependencies {
25+
implementation project(':concept-storage')
26+
27+
implementation Deps.kotlin_stdlib
28+
implementation Deps.kotlin_coroutines
29+
30+
testImplementation Deps.testing_junit
31+
testImplementation Deps.testing_robolectric
32+
testImplementation Deps.testing_mockito
33+
34+
testImplementation project(':support-test')
35+
}
36+
37+
archivesBaseName = "storage-memory"
38+
39+
apply from: '../../../publish.gradle'
40+
ext.configurePublish(
41+
'org.mozilla.components',
42+
'storage-memory',
43+
'In-memory implementation of concept-storage.')
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Add project specific ProGuard rules here.
2+
# You can control the set of applied configuration files using the
3+
# proguardFiles setting in build.gradle.
4+
#
5+
# For more details, see
6+
# http://developer.android.com/guide/developing/tools/proguard.html
7+
8+
# If your project uses WebView with JS, uncomment the following
9+
# and specify the fully qualified class name to the JavaScript interface
10+
# class:
11+
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12+
# public *;
13+
#}
14+
15+
# Uncomment this to preserve the line number information for
16+
# debugging stack traces.
17+
#-keepattributes SourceFile,LineNumberTable
18+
19+
# If you keep the line number information, uncomment this to
20+
# hide the original source file name.
21+
#-renamesourcefileattribute SourceFile
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<!-- This Source Code Form is subject to the terms of the Mozilla Public
2+
- License, v. 2.0. If a copy of the MPL was not distributed with this
3+
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
4+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
5+
package="mozilla.components.browser.storage.memory" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
package mozilla.components.browser.storage.memory
6+
7+
import android.support.annotation.VisibleForTesting
8+
import mozilla.components.concept.storage.HistoryStorage
9+
import mozilla.components.concept.storage.PageObservation
10+
import mozilla.components.concept.storage.VisitType
11+
12+
data class Visit(val timestamp: Long, val type: VisitType)
13+
14+
/**
15+
* An in-memory implementation of [mozilla.components.concept.storage.HistoryStorage].
16+
*/
17+
class InMemoryHistoryStorage : HistoryStorage {
18+
@VisibleForTesting
19+
internal val pages: LinkedHashMap<String, MutableList<Visit>> = linkedMapOf()
20+
@VisibleForTesting
21+
internal val pageMeta: HashMap<String, PageObservation> = hashMapOf()
22+
23+
override fun recordVisit(uri: String, visitType: VisitType) {
24+
val now = System.currentTimeMillis()
25+
26+
synchronized(pages) {
27+
if (!pages.containsKey(uri)) {
28+
pages[uri] = mutableListOf(Visit(now, visitType))
29+
} else {
30+
pages[uri]!!.add(Visit(now, visitType))
31+
}
32+
}
33+
}
34+
35+
override fun recordObservation(uri: String, observation: PageObservation) {
36+
synchronized(pageMeta) {
37+
pageMeta[uri] = observation
38+
}
39+
}
40+
41+
override fun getVisited(uris: List<String>, callback: (List<Boolean>) -> Unit) {
42+
callback(synchronized(pages) {
43+
uris.map {
44+
if (pages[it] != null && pages[it]!!.size > 0) {
45+
return@map true
46+
}
47+
return@map false
48+
}
49+
})
50+
}
51+
52+
override fun getVisited(callback: (List<String>) -> Unit) {
53+
callback(synchronized(pages) {
54+
(pages.keys.toList())
55+
})
56+
}
57+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
package mozilla.components.browser.storage.memory
6+
7+
import mozilla.components.concept.storage.PageObservation
8+
import mozilla.components.concept.storage.VisitType
9+
import mozilla.components.support.test.mock
10+
import org.junit.Assert.assertEquals
11+
import org.junit.Test
12+
import org.mockito.Mockito.verify
13+
import org.mockito.Mockito.reset
14+
15+
class InMemoryHistoryStorageTest {
16+
@Test
17+
fun `store can be used to track visit information`() {
18+
val history = InMemoryHistoryStorage()
19+
20+
assertEquals(0, history.pages.size)
21+
22+
history.recordVisit("http://www.mozilla.org", VisitType.LINK)
23+
assertEquals(1, history.pages.size)
24+
assertEquals(1, history.pages["http://www.mozilla.org"]!!.size)
25+
assertEquals(VisitType.LINK, history.pages["http://www.mozilla.org"]!![0].type)
26+
27+
// Reloads are recorded.
28+
history.recordVisit("http://www.mozilla.org", VisitType.RELOAD)
29+
assertEquals(1, history.pages.size)
30+
assertEquals(2, history.pages["http://www.mozilla.org"]!!.size)
31+
assertEquals(VisitType.LINK, history.pages["http://www.mozilla.org"]!![0].type)
32+
assertEquals(VisitType.RELOAD, history.pages["http://www.mozilla.org"]!![1].type)
33+
34+
// Visits for multiple pages are tracked.
35+
history.recordVisit("http://www.firefox.com", VisitType.LINK)
36+
assertEquals(2, history.pages.size)
37+
assertEquals(2, history.pages["http://www.mozilla.org"]!!.size)
38+
assertEquals(VisitType.LINK, history.pages["http://www.mozilla.org"]!![0].type)
39+
assertEquals(VisitType.RELOAD, history.pages["http://www.mozilla.org"]!![1].type)
40+
assertEquals(1, history.pages["http://www.firefox.com"]!!.size)
41+
assertEquals(VisitType.LINK, history.pages["http://www.firefox.com"]!![0].type)
42+
}
43+
44+
@Test
45+
fun `store can be used to record and retrieve history via webview-style callbacks`() {
46+
val history = InMemoryHistoryStorage()
47+
48+
val callback = mock<(List<String>) -> Unit>()
49+
50+
// Empty.
51+
history.getVisited(callback)
52+
verify(callback).invoke(listOf())
53+
reset(callback)
54+
history.getVisited(callback)
55+
verify(callback).invoke(listOf())
56+
reset(callback)
57+
58+
// Regular visits are tracked.
59+
history.recordVisit("https://www.mozilla.org", VisitType.LINK)
60+
history.getVisited(callback)
61+
verify(callback).invoke(listOf("https://www.mozilla.org"))
62+
reset(callback)
63+
64+
// Multiple visits can be tracked, results ordered by "URL's first seen first".
65+
history.recordVisit("https://www.firefox.com", VisitType.LINK)
66+
history.getVisited(callback)
67+
verify(callback).invoke(listOf("https://www.mozilla.org", "https://www.firefox.com"))
68+
reset(callback)
69+
70+
// Visits marked as reloads can be tracked.
71+
history.recordVisit("https://www.firefox.com", VisitType.RELOAD)
72+
history.getVisited(callback)
73+
verify(callback).invoke(listOf("https://www.mozilla.org", "https://www.firefox.com"))
74+
reset(callback)
75+
76+
// Visited urls are certainly a set.
77+
history.recordVisit("https://www.firefox.com", VisitType.LINK)
78+
history.recordVisit("https://www.mozilla.org", VisitType.LINK)
79+
history.recordVisit("https://www.wikipedia.org", VisitType.LINK)
80+
history.getVisited(callback)
81+
verify(callback).invoke(listOf("https://www.mozilla.org", "https://www.firefox.com", "https://www.wikipedia.org"))
82+
}
83+
84+
@Test
85+
fun `store can be used to record and retrieve history via gecko-style callbacks`() {
86+
val history = InMemoryHistoryStorage()
87+
88+
// Empty.
89+
val callback = mock<(List<Boolean>) -> Unit>()
90+
91+
history.getVisited(listOf(), callback)
92+
verify(callback).invoke(listOf())
93+
reset(callback)
94+
95+
// Regular visits are tracked
96+
history.recordVisit("https://www.mozilla.org", VisitType.LINK)
97+
history.getVisited(listOf("https://www.mozilla.org"), callback)
98+
verify(callback).invoke(listOf(true))
99+
reset(callback)
100+
101+
// Duplicate requests are handled.
102+
history.getVisited(listOf("https://www.mozilla.org", "https://www.mozilla.org"), callback)
103+
verify(callback).invoke(listOf(true, true))
104+
reset(callback)
105+
106+
// Visit map is returned in correct order.
107+
history.getVisited(listOf("https://www.mozilla.org", "https://www.unknown.com"), callback)
108+
verify(callback).invoke(listOf(true, false))
109+
reset(callback)
110+
111+
history.getVisited(listOf("https://www.unknown.com", "https://www.mozilla.org"), callback)
112+
verify(callback).invoke(listOf(false, true))
113+
reset(callback)
114+
115+
// Multiple visits can be tracked. Reloads can be tracked.
116+
history.recordVisit("https://www.firefox.com", VisitType.LINK)
117+
history.recordVisit("https://www.mozilla.org", VisitType.RELOAD)
118+
history.recordVisit("https://www.wikipedia.org", VisitType.LINK)
119+
history.getVisited(listOf("https://www.firefox.com", "https://www.wikipedia.org", "https://www.unknown.com", "https://www.mozilla.org"), callback)
120+
verify(callback).invoke(listOf(true, true, false, true))
121+
}
122+
123+
@Test
124+
fun `store can be used to track page meta information - title changes`() {
125+
val history = InMemoryHistoryStorage()
126+
assertEquals(0, history.pageMeta.size)
127+
128+
// Title changes are recorded.
129+
history.recordObservation("https://www.wikipedia.org", PageObservation("Wikipedia"))
130+
assertEquals(1, history.pageMeta.size)
131+
assertEquals(PageObservation("Wikipedia"), history.pageMeta["https://www.wikipedia.org"])
132+
133+
history.recordObservation("https://www.wikipedia.org", PageObservation("Википедия"))
134+
assertEquals(1, history.pageMeta.size)
135+
assertEquals(PageObservation("Википедия"), history.pageMeta["https://www.wikipedia.org"])
136+
137+
// Titles for different pages are recorded.
138+
history.recordObservation("https://www.firefox.com", PageObservation("Firefox"))
139+
history.recordObservation("https://www.mozilla.org", PageObservation("Мозилла"))
140+
assertEquals(3, history.pageMeta.size)
141+
assertEquals(PageObservation("Википедия"), history.pageMeta["https://www.wikipedia.org"])
142+
assertEquals(PageObservation("Firefox"), history.pageMeta["https://www.firefox.com"])
143+
assertEquals(PageObservation("Мозилла"), history.pageMeta["https://www.mozilla.org"])
144+
}
145+
}

components/concept/engine/src/main/java/mozilla/components/concept/engine/history/HistoryTrackingDelegate.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ interface HistoryTrackingDelegate {
1515
/**
1616
* A URI visit happened that an engine considers worthy of being recorded in browser's history.
1717
*/
18-
fun onVisited(uri: String, isReload: Boolean? = false, privateMode: Boolean)
18+
fun onVisited(uri: String, isReload: Boolean = false, privateMode: Boolean)
1919

2020
/**
2121
* Title changed for a given URI.

settings.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ setupProject(':browser-errorpages', 'components/browser/errorpages', 'Responsive
5050
setupProject(':browser-menu', 'components/browser/menu', 'A customizable menu for browsers.')
5151
setupProject(':browser-search', 'components/browser/search', 'Search plugins and companion code to load, parse and use them.')
5252
setupProject(':browser-session', 'components/browser/session', 'An abstract layer hiding the actual browser engine implementation.')
53+
setupProject(':browser-storage-memory', 'components/browser/storage-memory', 'A memory-backed implementation of core data storage.')
5354
setupProject(':browser-tabstray', 'components/browser/tabstray', 'A tabs tray component for browsers.')
5455
setupProject(':browser-toolbar', 'components/browser/toolbar', 'A customizable toolbar for browsers.')
5556

0 commit comments

Comments
 (0)