Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,16 @@ interface BrowserContract {

/**
* Create a tab that will be initialized with the [tabInitializer].
*
* @param tabType The type of tab being opened, which governs the browser behavior when it
* closes.
* @param foreground True if the tab will become the browser's focused tab, false if it will
* remain in the background.
*/
suspend fun createTab(
tabInitializer: TabInitializer,
tabType: TabModel.Type = TabModel.Type.NORMAL
tabType: TabModel.Type = TabModel.Type.NORMAL,
foreground: Boolean = true
): TabModel

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -668,7 +668,7 @@ class BrowserPresenter @Inject constructor(
shouldSelect: Boolean,
tabType: TabModel.Type = TabModel.Type.NORMAL
) {
val tab = model.createTab(tabInitializer, tabType = tabType)
val tab = model.createTab(tabInitializer, tabType = tabType, foreground = shouldSelect)
state.updateSelf { updateTabViewState() }
if (shouldSelect) {
selectTab(model.selectTab(tab.id))
Expand Down
133 changes: 88 additions & 45 deletions app/src/main/java/acr/browser/lightning/browser/tab/TabAdapter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import acr.browser.lightning.connectivity.ConnectivityProvider
import acr.browser.lightning.constant.DESKTOP_USER_AGENT
import acr.browser.lightning.download.PendingDownload
import acr.browser.lightning.ids.ViewIdGenerator
import acr.browser.lightning.pool.ObjectPool
import acr.browser.lightning.preview.PreviewModel
import acr.browser.lightning.ssl.SslCertificateInfo
import acr.browser.lightning.ssl.SslState
Expand Down Expand Up @@ -47,6 +48,8 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import kotlin.time.Duration.Companion.milliseconds

Expand All @@ -57,11 +60,13 @@ import kotlin.time.Duration.Companion.milliseconds
class TabAdapter @AssistedInject constructor(
@Assisted override val id: Int,
@Assisted private val tabInitializer: TabInitializer,
@Assisted private val webViewLazy: Lazy<WebView>,
@Assisted private val webViewPool: ObjectPool<WebView>,
@Assisted private val requestHeaders: Map<String, String>,
@Assisted private val tabWebViewClient: TabWebViewClient,
@Assisted override var tabType: TabModel.Type,
@Assisted private val tabCoroutineScope: TabCoroutineScope,
@Assisted private var priority: Priority,
private val tabPager: TabPager,
private val tabWebChromeClientFactory: TabWebChromeClient.Factory,
private val userAgentProvider: UserAgentProvider,
private val viewIdGenerator: ViewIdGenerator,
Expand All @@ -76,14 +81,20 @@ class TabAdapter @AssistedInject constructor(
fun create(
id: Int,
tabInitializer: TabInitializer,
webView: Lazy<WebView>,
webViewPool: ObjectPool<WebView>,
requestHeaders: Map<String, String>,
tabWebViewClient: TabWebViewClient,
tabType: TabModel.Type,
tabCoroutineScope: TabCoroutineScope,
priority: Priority,
): TabAdapter
}

enum class Priority {
LOW,
HIGH,
}

private var latentInitializer: FreezableInitializer? = tabInitializer as? FreezableInitializer

private var findInPageQuery: String? = null
Expand All @@ -94,48 +105,46 @@ class TabAdapter @AssistedInject constructor(

private val tabWebChromeClient by lazy { tabWebChromeClientFactory.create(tabCoroutineScope) }

private val webViewLazyWithInitialization: WebView by lazy {
webViewLazy.value.apply {
webViewClient = tabWebViewClient
webChromeClient = tabWebChromeClient
setDownloadListener { url, userAgent, contentDisposition, mimetype, contentLength ->
tabCoroutineScope.launch {
downloadsShareFlow.emit(
PendingDownload(
url = url,
userAgent = userAgent,
contentDisposition = contentDisposition,
mimeType = mimetype,
contentLength = contentLength
)
private fun WebView.setup() {
webViewClient = tabWebViewClient
webChromeClient = tabWebChromeClient
setDownloadListener { url, userAgent, contentDisposition, mimetype, contentLength ->
tabCoroutineScope.launch {
downloadsShareFlow.emit(
PendingDownload(
url = url,
userAgent = userAgent,
contentDisposition = contentDisposition,
mimeType = mimetype,
contentLength = contentLength
)
}
)
}
id = this@TabAdapter.id
}
id = this@TabAdapter.id

setCompositeOnFocusChangeListener("focus_change") { _, hasFocus ->
tabCoroutineScope.launch {
if (hasFocus) {
focusSharedFlow.emit(Unit)
}
setCompositeOnFocusChangeListener("focus_change") { _, hasFocus ->
tabCoroutineScope.launch {
if (hasFocus) {
focusSharedFlow.emit(Unit)
}
}
}

setCompositeTouchListener("toggle", createToolbarAwareTouchListener(context))
setCompositeTouchListener("toggle", createToolbarAwareTouchListener(context))

setCompositeTouchListener("focus") { _, event ->
if (event.action == MotionEvent.ACTION_DOWN) {
tabCoroutineScope.launch {
focusSharedFlow.emit(Unit)
}
setCompositeTouchListener("focus") { _, event ->
if (event.action == MotionEvent.ACTION_DOWN) {
tabCoroutineScope.launch {
focusSharedFlow.emit(Unit)
}
false
}
false
}

tabCoroutineScope.launch {
connectivityProvider.hasInternetAccess.collectLatest {
setNetworkAvailable(it)
}
tabCoroutineScope.launch {
connectivityProvider.hasInternetAccess.collectLatest {
setNetworkAvailable(it)
}
}
}
Expand All @@ -144,18 +153,39 @@ class TabAdapter @AssistedInject constructor(
previewModel.previewForId(id)
}

private suspend fun webView(): WebView = withContext(coroutineDispatchers.main) {
webViewLazyWithInitialization
}
private var _acquiredWebView: ObjectPool.AcquiredObject<WebView>? = null

private suspend fun webViewIfInitialized(): WebView? = withContext(coroutineDispatchers.main) {
if (webViewLazy.isInitialized()) {
webViewLazyWithInitialization
} else {
null
private val webViewMutex = Mutex()

private suspend fun webView(): WebView {
return withContext(coroutineDispatchers.main) {
webViewMutex.withLock {
_acquiredWebView?.actual ?: webViewPool.acquire(
highPriority = priority == Priority.HIGH
).also {
_acquiredWebView = it
it.actual.setup()
tabPager.addTab(id, it.actual)
tabCoroutineScope.launch {
it.awaitRelease {
val bundle = save()
freeze()
latentInitializer = FreezableInitializer(
bundle = bundle,
delegate = BundleInitializer(bundle),
initialTitle = title.orEmpty(),
id = id
)
_acquiredWebView = null
}
}
}.actual
}
}
}

private fun webViewIfInitialized(): WebView? = _acquiredWebView?.actual

private val titleStateFlow = MutableStateFlow(
latentInitializer?.initialTitle
)
Expand Down Expand Up @@ -385,14 +415,17 @@ class TabAdapter @AssistedInject constructor(
override fun showHideToolbar(): Flow<Boolean> = showHideFlow

override suspend fun foreground() {
priority = Priority.HIGH
webView().resumeTimers()
webView().settings.offscreenPreRaster = true
webView().onResume()
latentInitializer?.let(::loadFromInitializer)
latentInitializer = null
tabPager.selectTab(id)
}

override suspend fun background(backgroundAll: Boolean) {
priority = Priority.LOW
webViewIfInitialized()?.apply {
onPause()
settings.offscreenPreRaster = false
Expand All @@ -402,17 +435,27 @@ class TabAdapter @AssistedInject constructor(
}
}

override suspend fun destroy() {
viewIdGenerator.releaseViewId(id)
previewModel.prune()
override suspend fun freeze() {
webViewIfInitialized()?.apply {
stopLoading()
onPause()
clearHistory()
removeAllViews()
destroy()
}
if (priority == Priority.HIGH) {
tabPager.clearTab(id)
} else {
tabPager.removeTab(id)
}
}

override suspend fun destroy() {
viewIdGenerator.releaseViewId(id)
previewModel.prune()
freeze()
tabCoroutineScope.cancel()
_acquiredWebView?.let { webViewPool.release(it) }
}

override suspend fun restore(bundle: Bundle) {
Expand Down
15 changes: 11 additions & 4 deletions app/src/main/java/acr/browser/lightning/browser/tab/TabFactory.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import acr.browser.lightning.concurrency.CoroutineDispatchers
import acr.browser.lightning.concurrency.TabCoroutineScope
import acr.browser.lightning.di.FaviconCacheDir
import acr.browser.lightning.di.GeneratedHtmlDir
import acr.browser.lightning.pool.ObjectPool
import android.webkit.WebView
import androidx.webkit.WebViewAssetLoader.InternalStoragePathHandler
import kotlinx.coroutines.CoroutineScope
Expand All @@ -26,14 +27,15 @@ class TabFactory @Inject constructor(
) {

/**
* Constructs a tab from the [webView] with the provided [tabInitializer].
* Constructs a tab from the [webViewFactory] with the provided [tabInitializer].
*/
suspend fun constructTab(
id: Int,
tabInitializer: TabInitializer,
webView: Lazy<WebView>,
webViewPool: ObjectPool<WebView>,
tabType: TabModel.Type,
tabSettings: TabSettings,
foreground: Boolean,
): TabModel = withContext(coroutineDispatchers.main) {
val headers = webViewFactory.createRequestHeaders()
val tabCoroutineScope = TabCoroutineScope(
Expand All @@ -42,7 +44,7 @@ class TabFactory @Inject constructor(
tabAdapterFactory.create(
id = id,
tabInitializer = tabInitializer,
webView = webView,
webViewPool = webViewPool,
requestHeaders = headers,
tabWebViewClient = tabWebViewClientFactory.create(
headers = headers,
Expand All @@ -52,7 +54,12 @@ class TabFactory @Inject constructor(
tabSettings = tabSettings,
),
tabType = tabType,
tabCoroutineScope = tabCoroutineScope
tabCoroutineScope = tabCoroutineScope,
priority = if (foreground) {
TabAdapter.Priority.HIGH
} else {
TabAdapter.Priority.LOW
}
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,11 @@ interface TabModel {
*/
suspend fun background(backgroundAll: Boolean)

/**
* Release resources held by the current tab without destroying it.
*/
suspend fun freeze()

/**
* Teardown the current tab and release held resources.
*/
Expand Down
14 changes: 3 additions & 11 deletions app/src/main/java/acr/browser/lightning/browser/tab/TabPager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class TabPager @Inject constructor(
private val webViewLongPressHandler: WebViewLongPressHandler
) {

private val webViews: MutableMap<Int, Lazy<WebView>> = mutableMapOf()
private val webViews: MutableMap<Int, WebView> = mutableMapOf()

var longPressListener: ((id: Int, longPress: LongPress) -> Unit)? = null

Expand All @@ -29,7 +29,7 @@ class TabPager @Inject constructor(
*/
fun selectTab(id: Int) {
container.removeWebViews(excludeId = id)
val webView = webViews[id]!!.value
val webView = webViews[id]!!
if (webView.parent != container) {
container.addView(
webView,
Expand Down Expand Up @@ -58,18 +58,10 @@ class TabPager @Inject constructor(
container.removeWebViews()
}

/**
* Clear the container of the [WebView] currently shown and remove all webviews.
*/
fun clearAllTabs() {
webViews.clear()
container.removeWebViews()
}

/**
* Add a [WebView] to the list of views shown by this pager.
*/
fun addTab(id: Int, webView: Lazy<WebView>) {
fun addTab(id: Int, webView: WebView) {
webViews[id] = webView
}

Expand Down
Loading
Loading