switch from timestamps to event sequence for events

This commit is contained in:
Fabian Freund
2026-02-10 16:20:35 +01:00
parent e0cce93ffb
commit 592c6afdc5
30 changed files with 840 additions and 254 deletions
@@ -11,6 +11,7 @@ import android.app.Activity
import android.content.Context import android.content.Context
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.FrameLayout import android.widget.FrameLayout
import eu.weblibre.flutter_mozilla_components.ext.EventSequence
import eu.weblibre.flutter_mozilla_components.pigeons.GeckoStateEvents import eu.weblibre.flutter_mozilla_components.pigeons.GeckoStateEvents
import io.flutter.plugin.common.StandardMessageCodec import io.flutter.plugin.common.StandardMessageCodec
import io.flutter.plugin.platform.PlatformView import io.flutter.plugin.platform.PlatformView
@@ -58,7 +59,7 @@ private class NativeFragmentView(
super.onFlutterViewAttached(flutterView) super.onFlutterViewAttached(flutterView)
components.engineReportedInitialized = false components.engineReportedInitialized = false
flutterEvents.onViewReadyStateChange(System.currentTimeMillis(), true) { _ -> } flutterEvents.onViewReadyStateChange(EventSequence.next(), true) { _ -> }
} }
override fun getView(): View { override fun getView(): View {
@@ -1,5 +1,6 @@
package eu.weblibre.flutter_mozilla_components.api package eu.weblibre.flutter_mozilla_components.api
import eu.weblibre.flutter_mozilla_components.ext.EventSequence
import eu.weblibre.flutter_mozilla_components.feature.MLEngineFeature import eu.weblibre.flutter_mozilla_components.feature.MLEngineFeature
import eu.weblibre.flutter_mozilla_components.feature.ResultConsumer import eu.weblibre.flutter_mozilla_components.feature.ResultConsumer
import eu.weblibre.flutter_mozilla_components.pigeons.GeckoMlApi import eu.weblibre.flutter_mozilla_components.pigeons.GeckoMlApi
@@ -68,7 +69,7 @@ class GeckoMlApiImpl(
) )
runOnUiThread { runOnUiThread {
stateEvents.onMlProgress(System.currentTimeMillis(), mlProgress) { result -> stateEvents.onMlProgress(EventSequence.next(), mlProgress) { result ->
result.onFailure { error -> result.onFailure { error ->
android.util.Log.e("GeckoMlApi", "Failed to emit progress event: ${error.message}") android.util.Log.e("GeckoMlApi", "Failed to emit progress event: ${error.message}")
} }
@@ -7,6 +7,7 @@
package eu.weblibre.flutter_mozilla_components.api package eu.weblibre.flutter_mozilla_components.api
import eu.weblibre.flutter_mozilla_components.GlobalComponents import eu.weblibre.flutter_mozilla_components.GlobalComponents
import eu.weblibre.flutter_mozilla_components.ext.EventSequence
import eu.weblibre.flutter_mozilla_components.feature.PrefManagerFeature import eu.weblibre.flutter_mozilla_components.feature.PrefManagerFeature
import eu.weblibre.flutter_mozilla_components.feature.ResultConsumer import eu.weblibre.flutter_mozilla_components.feature.ResultConsumer
import eu.weblibre.flutter_mozilla_components.pigeons.GeckoPref import eu.weblibre.flutter_mozilla_components.pigeons.GeckoPref
@@ -201,7 +202,7 @@ class GeckoPrefApiImpl : GeckoPrefApi, BrowserPrefObserverDelegate {
override fun onPreferenceChange(observedPreference: BrowserPreference<*>) { override fun onPreferenceChange(observedPreference: BrowserPreference<*>) {
components.flutterEvents.onPreferenceChange( components.flutterEvents.onPreferenceChange(
System.currentTimeMillis(), GeckoPref( EventSequence.next(), GeckoPref(
name = observedPreference.pref, name = observedPreference.pref,
value = observedPreference.value, value = observedPreference.value,
defaultValue = observedPreference.defaultValue, defaultValue = observedPreference.defaultValue,
@@ -7,6 +7,7 @@
package eu.weblibre.flutter_mozilla_components.api package eu.weblibre.flutter_mozilla_components.api
import eu.weblibre.flutter_mozilla_components.GlobalComponents import eu.weblibre.flutter_mozilla_components.GlobalComponents
import eu.weblibre.flutter_mozilla_components.ext.EventSequence
import eu.weblibre.flutter_mozilla_components.api.GeckoDeleteBrowsingDataControllerImpl.Companion import eu.weblibre.flutter_mozilla_components.api.GeckoDeleteBrowsingDataControllerImpl.Companion
import eu.weblibre.flutter_mozilla_components.ext.toWebPBytes import eu.weblibre.flutter_mozilla_components.ext.toWebPBytes
import eu.weblibre.flutter_mozilla_components.pigeons.AutocompleteResult import eu.weblibre.flutter_mozilla_components.pigeons.AutocompleteResult
@@ -77,7 +78,7 @@ class GeckoSuggestionApiImpl(
runOnUiThread { runOnUiThread {
suggestionEvents.onSuggestionResult( suggestionEvents.onSuggestionResult(
System.currentTimeMillis(), EventSequence.next(),
provider, provider,
mappedResults mappedResults
) { } ) { }
@@ -17,6 +17,7 @@ import eu.weblibre.flutter_mozilla_components.pigeons.SourceValue
import eu.weblibre.flutter_mozilla_components.pigeons.WebExtensionActionType import eu.weblibre.flutter_mozilla_components.pigeons.WebExtensionActionType
import eu.weblibre.flutter_mozilla_components.pigeons.WebExtensionData import eu.weblibre.flutter_mozilla_components.pigeons.WebExtensionData
import eu.weblibre.flutter_mozilla_components.GlobalComponents import eu.weblibre.flutter_mozilla_components.GlobalComponents
import eu.weblibre.flutter_mozilla_components.ext.EventSequence
import eu.weblibre.flutter_mozilla_components.ext.toWebPBytes import eu.weblibre.flutter_mozilla_components.ext.toWebPBytes
import eu.weblibre.flutter_mozilla_components.pigeons.FindResultState import eu.weblibre.flutter_mozilla_components.pigeons.FindResultState
import eu.weblibre.flutter_mozilla_components.pigeons.GeckoStateEvents import eu.weblibre.flutter_mozilla_components.pigeons.GeckoStateEvents
@@ -135,7 +136,7 @@ class GeckoTabsApiImpl : GeckoTabsApi {
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
components.flutterEvents.onIconChange( components.flutterEvents.onIconChange(
System.currentTimeMillis(), EventSequence.next(),
tab.id, tab.id,
iconBytes iconBytes
) { } ) { }
@@ -159,7 +160,7 @@ class GeckoTabsApiImpl : GeckoTabsApi {
val bytes = it.toWebPBytes() val bytes = it.toWebPBytes()
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
components.flutterEvents.onThumbnailChange( components.flutterEvents.onThumbnailChange(
System.currentTimeMillis(), EventSequence.next(),
tab.id, tab.id,
bytes bytes
) { } ) { }
@@ -191,21 +192,21 @@ class GeckoTabsApiImpl : GeckoTabsApi {
if (onSelectedTabChange) { if (onSelectedTabChange) {
components.flutterEvents.onSelectedTabChange( components.flutterEvents.onSelectedTabChange(
System.currentTimeMillis(), EventSequence.next(),
selectedTab selectedTab
) { } ) { }
} }
if (onTabListChange) { if (onTabListChange) {
components.flutterEvents.onTabListChange( components.flutterEvents.onTabListChange(
System.currentTimeMillis(), EventSequence.next(),
tabs.map { it.id }) { } tabs.map { it.id }) { }
} }
tabs.forEach { tab -> tabs.forEach { tab ->
if (onTabContentStateChange) { if (onTabContentStateChange) {
components.flutterEvents.onTabContentStateChange( components.flutterEvents.onTabContentStateChange(
System.currentTimeMillis(), EventSequence.next(),
TabContentState( TabContentState(
id = tab.id, id = tab.id,
parentId = tab.parentId, parentId = tab.parentId,
@@ -226,7 +227,7 @@ class GeckoTabsApiImpl : GeckoTabsApi {
if (onSecurityInfoStateChange) { if (onSecurityInfoStateChange) {
components.flutterEvents.onSecurityInfoStateChange( components.flutterEvents.onSecurityInfoStateChange(
System.currentTimeMillis(), EventSequence.next(),
tab.id, tab.id,
SecurityInfoState( SecurityInfoState(
tab.content.securityInfo.isSecure, tab.content.securityInfo.isSecure,
@@ -238,7 +239,7 @@ class GeckoTabsApiImpl : GeckoTabsApi {
if (onReaderableStateChange) { if (onReaderableStateChange) {
components.flutterEvents.onReaderableStateChange( components.flutterEvents.onReaderableStateChange(
System.currentTimeMillis(), EventSequence.next(),
tab.id, tab.id,
ReaderableState( ReaderableState(
tab.readerState.readerable, tab.readerState.readerable,
@@ -249,7 +250,7 @@ class GeckoTabsApiImpl : GeckoTabsApi {
if (onHistoryStateChange) { if (onHistoryStateChange) {
components.flutterEvents.onHistoryStateChange( components.flutterEvents.onHistoryStateChange(
System.currentTimeMillis(), EventSequence.next(),
tab.id, tab.id,
HistoryState( HistoryState(
items = tab.content.history.items.map { item -> items = tab.content.history.items.map { item ->
@@ -264,7 +265,7 @@ class GeckoTabsApiImpl : GeckoTabsApi {
if (onFindResults) { if (onFindResults) {
components.flutterEvents.onFindResults( components.flutterEvents.onFindResults(
System.currentTimeMillis(), EventSequence.next(),
tab.id, tab.id,
tab.content.findResults.map { result -> tab.content.findResults.map { result ->
FindResultState( FindResultState(
@@ -321,7 +322,7 @@ class GeckoTabsApiImpl : GeckoTabsApi {
badgeBackgroundColor = browserAction.badgeBackgroundColor?.toLong(), badgeBackgroundColor = browserAction.badgeBackgroundColor?.toLong(),
) )
components.addonEvents.onUpsertWebExtensionAction( components.addonEvents.onUpsertWebExtensionAction(
System.currentTimeMillis(), EventSequence.next(),
extension.id, extension.id,
WebExtensionActionType.BROWSER, WebExtensionActionType.BROWSER,
data data
@@ -336,7 +337,7 @@ class GeckoTabsApiImpl : GeckoTabsApi {
val imageBytes = icon.toWebPBytes() val imageBytes = icon.toWebPBytes()
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
components.addonEvents.onUpdateWebExtensionIcon( components.addonEvents.onUpdateWebExtensionIcon(
System.currentTimeMillis(), EventSequence.next(),
extension.id, extension.id,
WebExtensionActionType.BROWSER, WebExtensionActionType.BROWSER,
imageBytes imageBytes
@@ -362,7 +363,7 @@ class GeckoTabsApiImpl : GeckoTabsApi {
badgeBackgroundColor = pageAction.badgeBackgroundColor?.toLong(), badgeBackgroundColor = pageAction.badgeBackgroundColor?.toLong(),
) )
components.addonEvents.onUpsertWebExtensionAction( components.addonEvents.onUpsertWebExtensionAction(
System.currentTimeMillis(), EventSequence.next(),
extension.id, extension.id,
WebExtensionActionType.PAGE, WebExtensionActionType.PAGE,
data data
@@ -377,7 +378,7 @@ class GeckoTabsApiImpl : GeckoTabsApi {
val imageBytes = icon.toWebPBytes() val imageBytes = icon.toWebPBytes()
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
components.addonEvents.onUpdateWebExtensionIcon( components.addonEvents.onUpdateWebExtensionIcon(
System.currentTimeMillis(), EventSequence.next(),
extension.id, extension.id,
WebExtensionActionType.PAGE, WebExtensionActionType.PAGE,
imageBytes imageBytes
@@ -6,7 +6,9 @@
package eu.weblibre.flutter_mozilla_components.components package eu.weblibre.flutter_mozilla_components.components
import eu.weblibre.flutter_mozilla_components.GlobalComponents
import eu.weblibre.flutter_mozilla_components.api.ReaderViewEventsImpl import eu.weblibre.flutter_mozilla_components.api.ReaderViewEventsImpl
import eu.weblibre.flutter_mozilla_components.ext.EventSequence
import eu.weblibre.flutter_mozilla_components.ext.toWebPBytes import eu.weblibre.flutter_mozilla_components.ext.toWebPBytes
import eu.weblibre.flutter_mozilla_components.pigeons.FindResultState import eu.weblibre.flutter_mozilla_components.pigeons.FindResultState
import eu.weblibre.flutter_mozilla_components.pigeons.GeckoAddonEvents import eu.weblibre.flutter_mozilla_components.pigeons.GeckoAddonEvents
@@ -44,7 +46,7 @@ class Events(
.debounce { 50 } .debounce { 50 }
.collect { tabId -> .collect { tabId ->
flutterEvents.onSelectedTabChange( flutterEvents.onSelectedTabChange(
System.currentTimeMillis(), EventSequence.next(),
tabId tabId
) { _ -> } ) { _ -> }
} }
@@ -70,7 +72,7 @@ class Events(
.collect { tab -> .collect { tab ->
val iconBytes = tab.content.icon?.toWebPBytes() val iconBytes = tab.content.icon?.toWebPBytes()
flutterEvents.onIconChange( flutterEvents.onIconChange(
System.currentTimeMillis(), EventSequence.next(),
tab.id, tab.id,
iconBytes iconBytes
) { _ -> } ) { _ -> }
@@ -85,7 +87,7 @@ class Events(
.debounce(15) .debounce(15)
.collect { tab -> .collect { tab ->
flutterEvents.onSecurityInfoStateChange( flutterEvents.onSecurityInfoStateChange(
System.currentTimeMillis(), EventSequence.next(),
tab.id, tab.id,
SecurityInfoState( SecurityInfoState(
tab.content.securityInfo.isSecure, tab.content.securityInfo.isSecure,
@@ -110,7 +112,7 @@ class Events(
.debounce(25) .debounce(25)
.collect { tab -> .collect { tab ->
flutterEvents.onReaderableStateChange( flutterEvents.onReaderableStateChange(
System.currentTimeMillis(), EventSequence.next(),
tab.id, tab.id,
ReaderableState( ReaderableState(
tab.readerState.readerable, tab.readerState.readerable,
@@ -135,7 +137,7 @@ class Events(
.debounce(15) .debounce(15)
.collect { tab -> .collect { tab ->
flutterEvents.onHistoryStateChange( flutterEvents.onHistoryStateChange(
System.currentTimeMillis(), EventSequence.next(),
tab.id, tab.id,
HistoryState( HistoryState(
items = tab.content.history.items.map { item -> items = tab.content.history.items.map { item ->
@@ -170,7 +172,7 @@ class Events(
.debounce(15) .debounce(15)
.collect { tab -> .collect { tab ->
flutterEvents.onTabContentStateChange( flutterEvents.onTabContentStateChange(
System.currentTimeMillis(), EventSequence.next(),
TabContentState( TabContentState(
id = tab.id, id = tab.id,
parentId = tab.parentId, parentId = tab.parentId,
@@ -0,0 +1,22 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package eu.weblibre.flutter_mozilla_components.ext
import java.util.concurrent.atomic.AtomicLong
/**
* Thread-safe monotonic sequence counter for event ordering.
*
* Replaces System.currentTimeMillis() for event timestamps sent to Flutter,
* guaranteeing strictly increasing values regardless of wall-clock changes
* and eliminating same-millisecond collisions.
*/
object EventSequence {
private val counter = AtomicLong(0)
fun next(): Long = counter.incrementAndGet()
}
@@ -7,6 +7,7 @@
package eu.weblibre.flutter_mozilla_components.feature package eu.weblibre.flutter_mozilla_components.feature
import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting
import eu.weblibre.flutter_mozilla_components.ext.EventSequence
import eu.weblibre.flutter_mozilla_components.pigeons.BrowserExtensionEvents import eu.weblibre.flutter_mozilla_components.pigeons.BrowserExtensionEvents
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
@@ -86,7 +87,7 @@ object BrowserExtensionFeature {
runOnUiThread { runOnUiThread {
extensionEvents.onFeedRequested( extensionEvents.onFeedRequested(
System.currentTimeMillis(), EventSequence.next(),
url url
) { _ -> } ) { _ -> }
} }
@@ -8,6 +8,7 @@ package eu.weblibre.flutter_mozilla_components.feature
import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting
import eu.weblibre.flutter_mozilla_components.GlobalComponents import eu.weblibre.flutter_mozilla_components.GlobalComponents
import eu.weblibre.flutter_mozilla_components.ext.EventSequence
import eu.weblibre.flutter_mozilla_components.pigeons.ContainerSiteAssignment import eu.weblibre.flutter_mozilla_components.pigeons.ContainerSiteAssignment
import eu.weblibre.flutter_mozilla_components.pigeons.GeckoStateEvents import eu.weblibre.flutter_mozilla_components.pigeons.GeckoStateEvents
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -115,7 +116,7 @@ object ContainerProxyFeature {
if (status == "success") { if (status == "success") {
runOnUiThread { runOnUiThread {
events.onContainerSiteAssignment( events.onContainerSiteAssignment(
System.currentTimeMillis(), EventSequence.next(),
ContainerSiteAssignment( ContainerSiteAssignment(
requestId = requestId, requestId = requestId,
tabId = components.core.store.state.selectedTabId, tabId = components.core.store.state.selectedTabId,
@@ -14,6 +14,7 @@ import androidx.core.graphics.Insets
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsAnimationCompat import androidx.core.view.WindowInsetsAnimationCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import eu.weblibre.flutter_mozilla_components.ext.EventSequence
import eu.weblibre.flutter_mozilla_components.pigeons.GeckoViewportEvents import eu.weblibre.flutter_mozilla_components.pigeons.GeckoViewportEvents
import mozilla.components.support.base.log.logger.Logger import mozilla.components.support.base.log.logger.Logger
import org.mozilla.gecko.util.ThreadUtils.runOnUiThread import org.mozilla.gecko.util.ThreadUtils.runOnUiThread
@@ -185,7 +186,7 @@ class KeyboardVisibilityFeature(
lastKeyboardHeight = heightPx lastKeyboardHeight = heightPx
lastKeyboardVisible = isVisible lastKeyboardVisible = isVisible
val timestamp = System.currentTimeMillis() val timestamp = EventSequence.next()
logger.debug("$TAG: Keyboard change - height=$heightPx, visible=$isVisible, animating=$isAnimating") logger.debug("$TAG: Keyboard change - height=$heightPx, visible=$isVisible, animating=$isAnimating")
@@ -6,6 +6,7 @@
package eu.weblibre.flutter_mozilla_components.feature package eu.weblibre.flutter_mozilla_components.feature
import eu.weblibre.flutter_mozilla_components.ext.EventSequence
import eu.weblibre.flutter_mozilla_components.pigeons.GeckoTabContentEvents import eu.weblibre.flutter_mozilla_components.pigeons.GeckoTabContentEvents
import eu.weblibre.flutter_mozilla_components.pigeons.TabContent import eu.weblibre.flutter_mozilla_components.pigeons.TabContent
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@@ -50,7 +51,7 @@ class ReadabilityExtractFeature(
override fun onPortMessage(message: Any, port: Port) { override fun onPortMessage(message: Any, port: Port) {
if (message is JSONObject) { if (message is JSONObject) {
tabContentEvents.onContentUpdate( tabContentEvents.onContentUpdate(
System.currentTimeMillis(), EventSequence.next(),
contentArg = TabContent( contentArg = TabContent(
tabId = tabId, tabId = tabId,
fullContentMarkdown = message.tryGetString("fullContentMarkdown"), fullContentMarkdown = message.tryGetString("fullContentMarkdown"),
@@ -7,6 +7,7 @@ package eu.weblibre.flutter_mozilla_components.feature
import android.os.Handler import android.os.Handler
import android.os.HandlerThread import android.os.HandlerThread
import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting
import eu.weblibre.flutter_mozilla_components.ext.EventSequence
import eu.weblibre.flutter_mozilla_components.ext.toWebPBytes import eu.weblibre.flutter_mozilla_components.ext.toWebPBytes
import eu.weblibre.flutter_mozilla_components.pigeons.GeckoAddonEvents import eu.weblibre.flutter_mozilla_components.pigeons.GeckoAddonEvents
import eu.weblibre.flutter_mozilla_components.pigeons.WebExtensionActionType import eu.weblibre.flutter_mozilla_components.pigeons.WebExtensionActionType
@@ -66,7 +67,7 @@ class WebExtensionToolbarFeature(
.filter { !store.state.extensions.containsKey(it) || store.state.extensions[it]?.enabled == false } .filter { !store.state.extensions.containsKey(it) || store.state.extensions[it]?.enabled == false }
.forEach { extensionId -> .forEach { extensionId ->
addonEvents.onRemoveWebExtensionAction( addonEvents.onRemoveWebExtensionAction(
timestampArg = System.currentTimeMillis(), timestampArg = EventSequence.next(),
extensionIdArg = extensionId, extensionIdArg = extensionId,
actionTypeArg = WebExtensionActionType.BROWSER, actionTypeArg = WebExtensionActionType.BROWSER,
) {} ) {}
@@ -77,7 +78,7 @@ class WebExtensionToolbarFeature(
.filter { !store.state.extensions.containsKey(it) || store.state.extensions[it]?.enabled == false } .filter { !store.state.extensions.containsKey(it) || store.state.extensions[it]?.enabled == false }
.forEach { extensionId -> .forEach { extensionId ->
addonEvents.onRemoveWebExtensionAction( addonEvents.onRemoveWebExtensionAction(
timestampArg = System.currentTimeMillis(), timestampArg = EventSequence.next(),
extensionIdArg = extensionId, extensionIdArg = extensionId,
actionTypeArg = WebExtensionActionType.PAGE, actionTypeArg = WebExtensionActionType.PAGE,
) {} ) {}
@@ -106,7 +107,7 @@ class WebExtensionToolbarFeature(
webExtensionBrowserActions.keys.toList().forEach { extensionId -> webExtensionBrowserActions.keys.toList().forEach { extensionId ->
if (extensionId !in enabledExtensionIds) { if (extensionId !in enabledExtensionIds) {
addonEvents.onRemoveWebExtensionAction( addonEvents.onRemoveWebExtensionAction(
timestampArg = System.currentTimeMillis(), timestampArg = EventSequence.next(),
extensionIdArg = extensionId, extensionIdArg = extensionId,
actionTypeArg = WebExtensionActionType.BROWSER, actionTypeArg = WebExtensionActionType.BROWSER,
) {} ) {}
@@ -117,7 +118,7 @@ class WebExtensionToolbarFeature(
webExtensionPageActions.keys.toList().forEach { extensionId -> webExtensionPageActions.keys.toList().forEach { extensionId ->
if (extensionId !in enabledExtensionIds) { if (extensionId !in enabledExtensionIds) {
addonEvents.onRemoveWebExtensionAction( addonEvents.onRemoveWebExtensionAction(
timestampArg = System.currentTimeMillis(), timestampArg = EventSequence.next(),
extensionIdArg = extensionId, extensionIdArg = extensionId,
actionTypeArg = WebExtensionActionType.PAGE, actionTypeArg = WebExtensionActionType.PAGE,
) {} ) {}
@@ -130,7 +131,7 @@ class WebExtensionToolbarFeature(
if (extensionNotAllowedInTab(extension, tab)) { if (extensionNotAllowedInTab(extension, tab)) {
webExtensionPageActions[extension.id]?.let { webExtensionPageActions[extension.id]?.let {
addonEvents.onRemoveWebExtensionAction( addonEvents.onRemoveWebExtensionAction(
timestampArg = System.currentTimeMillis(), timestampArg = EventSequence.next(),
extensionIdArg = extension.id, extensionIdArg = extension.id,
actionTypeArg = WebExtensionActionType.PAGE, actionTypeArg = WebExtensionActionType.PAGE,
) {} ) {}
@@ -138,7 +139,7 @@ class WebExtensionToolbarFeature(
} }
webExtensionBrowserActions[extension.id]?.let { webExtensionBrowserActions[extension.id]?.let {
addonEvents.onRemoveWebExtensionAction( addonEvents.onRemoveWebExtensionAction(
timestampArg = System.currentTimeMillis(), timestampArg = EventSequence.next(),
extensionIdArg = extension.id, extensionIdArg = extension.id,
actionTypeArg = WebExtensionActionType.BROWSER, actionTypeArg = WebExtensionActionType.BROWSER,
) {} ) {}
@@ -220,7 +221,7 @@ class WebExtensionToolbarFeature(
) )
addonEvents.onUpsertWebExtensionAction( addonEvents.onUpsertWebExtensionAction(
timestampArg = System.currentTimeMillis(), timestampArg = EventSequence.next(),
extensionIdArg = extension.id, extensionIdArg = extension.id,
actionTypeArg = if (isPageAction) WebExtensionActionType.PAGE else WebExtensionActionType.BROWSER, actionTypeArg = if (isPageAction) WebExtensionActionType.PAGE else WebExtensionActionType.BROWSER,
extensionDataArg = data extensionDataArg = data
@@ -235,7 +236,7 @@ class WebExtensionToolbarFeature(
val imageBytes = icon.toWebPBytes() val imageBytes = icon.toWebPBytes()
runOnUiThread { runOnUiThread {
addonEvents.onUpdateWebExtensionIcon( addonEvents.onUpdateWebExtensionIcon(
timestampArg = System.currentTimeMillis(), timestampArg = EventSequence.next(),
extensionIdArg = extensionId, extensionIdArg = extensionId,
actionTypeArg = if (isPageAction) WebExtensionActionType.PAGE else WebExtensionActionType.BROWSER, actionTypeArg = if (isPageAction) WebExtensionActionType.PAGE else WebExtensionActionType.BROWSER,
iconArg = imageBytes iconArg = imageBytes
@@ -10,6 +10,7 @@ import android.content.Context
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import eu.weblibre.flutter_mozilla_components.GlobalComponents import eu.weblibre.flutter_mozilla_components.GlobalComponents
import eu.weblibre.flutter_mozilla_components.ext.EventSequence
import eu.weblibre.flutter_mozilla_components.api.ReaderViewEventsImpl import eu.weblibre.flutter_mozilla_components.api.ReaderViewEventsImpl
import eu.weblibre.flutter_mozilla_components.api.ReaderViewControllerListener import eu.weblibre.flutter_mozilla_components.api.ReaderViewControllerListener
import eu.weblibre.flutter_mozilla_components.pigeons.ReaderViewController import eu.weblibre.flutter_mozilla_components.pigeons.ReaderViewController
@@ -36,12 +37,12 @@ class ReaderViewIntegration(
if (enabled) { if (enabled) {
feature.showReaderView() feature.showReaderView()
readerViewController.appearanceButtonVisibility(System.currentTimeMillis(),true) { _ -> }; readerViewController.appearanceButtonVisibility(EventSequence.next(),true) { _ -> };
} else { } else {
feature.hideReaderView() feature.hideReaderView()
feature.hideControls() feature.hideControls()
readerViewController.appearanceButtonVisibility(System.currentTimeMillis(),false) { _ -> }; readerViewController.appearanceButtonVisibility(EventSequence.next(),false) { _ -> };
} }
} }
@@ -6,6 +6,7 @@ package eu.weblibre.flutter_mozilla_components.middleware
import android.graphics.Bitmap import android.graphics.Bitmap
import android.util.Log import android.util.Log
import eu.weblibre.flutter_mozilla_components.GlobalComponents import eu.weblibre.flutter_mozilla_components.GlobalComponents
import eu.weblibre.flutter_mozilla_components.ext.EventSequence
import eu.weblibre.flutter_mozilla_components.ext.resize import eu.weblibre.flutter_mozilla_components.ext.resize
import eu.weblibre.flutter_mozilla_components.ext.toWebPBytes import eu.weblibre.flutter_mozilla_components.ext.toWebPBytes
import eu.weblibre.flutter_mozilla_components.pigeons.AudioHitResult import eu.weblibre.flutter_mozilla_components.pigeons.AudioHitResult
@@ -55,7 +56,7 @@ class FlutterEventMiddleware(private val flutterEvents: GeckoStateEvents) : Midd
val bytes = resized.toWebPBytes() val bytes = resized.toWebPBytes()
runOnUiThread { runOnUiThread {
flutterEvents.onThumbnailChange(System.currentTimeMillis(), action.sessionId, bytes) { _ -> } flutterEvents.onThumbnailChange(EventSequence.next(), action.sessionId, bytes) { _ -> }
} }
} }
//UpdateReaderConnectRequiredAction seems to be the only event that is called predictable //UpdateReaderConnectRequiredAction seems to be the only event that is called predictable
@@ -64,7 +65,7 @@ class FlutterEventMiddleware(private val flutterEvents: GeckoStateEvents) : Midd
if(!components.engineReportedInitialized) { if(!components.engineReportedInitialized) {
runOnUiThread { runOnUiThread {
flutterEvents.onEngineReadyStateChange( flutterEvents.onEngineReadyStateChange(
System.currentTimeMillis(), EventSequence.next(),
true true
) { _ -> } ) { _ -> }
} }
@@ -75,7 +76,7 @@ class FlutterEventMiddleware(private val flutterEvents: GeckoStateEvents) : Midd
is TabListAction.AddTabAction -> { is TabListAction.AddTabAction -> {
runOnUiThread { runOnUiThread {
flutterEvents.onTabAdded( flutterEvents.onTabAdded(
System.currentTimeMillis(), EventSequence.next(),
action.tab.id action.tab.id
) { _ -> } ) { _ -> }
} }
@@ -85,7 +86,7 @@ class FlutterEventMiddleware(private val flutterEvents: GeckoStateEvents) : Midd
runOnUiThread { runOnUiThread {
flutterEvents.onIconUpdate( flutterEvents.onIconUpdate(
System.currentTimeMillis(), EventSequence.next(),
action.pageUrl, action.pageUrl,
bytes bytes
) { _ -> } ) { _ -> }
@@ -94,7 +95,7 @@ class FlutterEventMiddleware(private val flutterEvents: GeckoStateEvents) : Midd
is ContentAction.UpdateHitResultAction -> { is ContentAction.UpdateHitResultAction -> {
runOnUiThread { runOnUiThread {
flutterEvents.onLongPress( flutterEvents.onLongPress(
System.currentTimeMillis(), EventSequence.next(),
action.sessionId, action.sessionId,
when(val result = action.hitResult) { when(val result = action.hitResult) {
is HitResult.AUDIO -> AudioHitResult(result.src, result.title) is HitResult.AUDIO -> AudioHitResult(result.src, result.title)
@@ -112,7 +113,7 @@ class FlutterEventMiddleware(private val flutterEvents: GeckoStateEvents) : Midd
is ContentAction.AddFindResultAction -> { is ContentAction.AddFindResultAction -> {
runOnUiThread { runOnUiThread {
flutterEvents.onFindResults( flutterEvents.onFindResults(
System.currentTimeMillis(), EventSequence.next(),
action.sessionId, action.sessionId,
listOf( listOf(
FindResultState( FindResultState(
@@ -127,7 +128,7 @@ class FlutterEventMiddleware(private val flutterEvents: GeckoStateEvents) : Midd
is ContentAction.ClearFindResultsAction -> { is ContentAction.ClearFindResultsAction -> {
runOnUiThread { runOnUiThread {
flutterEvents.onFindResults( flutterEvents.onFindResults(
System.currentTimeMillis(), EventSequence.next(),
action.sessionId, action.sessionId,
listOf() listOf()
) { _ -> } ) { _ -> }
@@ -136,7 +137,7 @@ class FlutterEventMiddleware(private val flutterEvents: GeckoStateEvents) : Midd
// is ReaderAction.UpdateReaderScrollYAction -> { // is ReaderAction.UpdateReaderScrollYAction -> {
// runOnUiThread { // runOnUiThread {
// flutterEvents.onScrollChange( // flutterEvents.onScrollChange(
// System.currentTimeMillis(), // EventSequence.next(),
// action.tabId, // action.tabId,
// action.scrollY.toLong() // action.scrollY.toLong()
// ) { _ -> } // ) { _ -> }
@@ -51,18 +51,18 @@ class GeckoAddonService extends GeckoAddonEvents {
@override @override
void onRemoveWebExtensionAction( void onRemoveWebExtensionAction(
int timestamp, int sequence,
String extensionId, String extensionId,
WebExtensionActionType actionType, WebExtensionActionType actionType,
) { ) {
switch (actionType) { switch (actionType) {
case WebExtensionActionType.browser: case WebExtensionActionType.browser:
_browserExtensionSubject.addWhenMoreRecent(timestamp, extensionId, ( _browserExtensionSubject.addWhenMoreRecent(sequence, extensionId, (
extensionId: extensionId, extensionId: extensionId,
data: null, data: null,
)); ));
case WebExtensionActionType.page: case WebExtensionActionType.page:
_pageExtensionSubject.addWhenMoreRecent(timestamp, extensionId, ( _pageExtensionSubject.addWhenMoreRecent(sequence, extensionId, (
extensionId: extensionId, extensionId: extensionId,
data: null, data: null,
)); ));
@@ -71,19 +71,19 @@ class GeckoAddonService extends GeckoAddonEvents {
@override @override
void onUpdateWebExtensionIcon( void onUpdateWebExtensionIcon(
int timestamp, int sequence,
String extensionId, String extensionId,
WebExtensionActionType actionType, WebExtensionActionType actionType,
Uint8List icon, Uint8List icon,
) { ) {
switch (actionType) { switch (actionType) {
case WebExtensionActionType.browser: case WebExtensionActionType.browser:
_browserIconSubject.addWhenMoreRecent(timestamp, extensionId, ( _browserIconSubject.addWhenMoreRecent(sequence, extensionId, (
extensionId: extensionId, extensionId: extensionId,
bytes: icon, bytes: icon,
)); ));
case WebExtensionActionType.page: case WebExtensionActionType.page:
_pageIconSubject.addWhenMoreRecent(timestamp, extensionId, ( _pageIconSubject.addWhenMoreRecent(sequence, extensionId, (
extensionId: extensionId, extensionId: extensionId,
bytes: icon, bytes: icon,
)); ));
@@ -92,19 +92,19 @@ class GeckoAddonService extends GeckoAddonEvents {
@override @override
void onUpsertWebExtensionAction( void onUpsertWebExtensionAction(
int timestamp, int sequence,
String extensionId, String extensionId,
WebExtensionActionType actionType, WebExtensionActionType actionType,
WebExtensionData extensionData, WebExtensionData extensionData,
) { ) {
switch (actionType) { switch (actionType) {
case WebExtensionActionType.browser: case WebExtensionActionType.browser:
_browserExtensionSubject.addWhenMoreRecent(timestamp, extensionId, ( _browserExtensionSubject.addWhenMoreRecent(sequence, extensionId, (
extensionId: extensionId, extensionId: extensionId,
data: extensionData, data: extensionData,
)); ));
case WebExtensionActionType.page: case WebExtensionActionType.page:
_pageExtensionSubject.addWhenMoreRecent(timestamp, extensionId, ( _pageExtensionSubject.addWhenMoreRecent(sequence, extensionId, (
extensionId: extensionId, extensionId: extensionId,
data: extensionData, data: extensionData,
)); ));
@@ -58,8 +58,8 @@ class GeckoBrowserExtensionService extends BrowserExtensionEvents {
} }
@override @override
void onFeedRequested(int timestamp, String url) { void onFeedRequested(int sequence, String url) {
_feedRequest.addWhenMoreRecent(timestamp, null, url); _feedRequest.addWhenMoreRecent(sequence, null, url);
} }
void dispose() { void dispose() {
@@ -20,6 +20,7 @@ typedef ThumbnailEvent = ({String tabId, Uint8List? bytes});
typedef FindResultsEvent = ({String tabId, List<FindResultState> results}); typedef FindResultsEvent = ({String tabId, List<FindResultState> results});
typedef LongPressEvent = ({String tabId, HitResult hitResult}); typedef LongPressEvent = ({String tabId, HitResult hitResult});
typedef ScrollEvent = ({String tabId, int scrollY}); typedef ScrollEvent = ({String tabId, int scrollY});
typedef ManifestUpdateEvent = ({String tabId, PwaManifest? manifest});
class GeckoEventService extends GeckoStateEvents { class GeckoEventService extends GeckoStateEvents {
// Stream controllers // Stream controllers
@@ -44,6 +45,7 @@ class GeckoEventService extends GeckoStateEvents {
final _tabAddedSubject = PublishSubject<String>(); final _tabAddedSubject = PublishSubject<String>();
final _mlProgressSubject = PublishSubject<MlProgressData>(); final _mlProgressSubject = PublishSubject<MlProgressData>();
final _manifestUpdateSubject = PublishSubject<ManifestUpdateEvent>();
// Event streams // Event streams
ValueStream<bool> get viewReadyStateEvents => _viewStateSubject.stream; ValueStream<bool> get viewReadyStateEvents => _viewStateSubject.stream;
@@ -68,52 +70,46 @@ class GeckoEventService extends GeckoStateEvents {
Stream<String> get tabAddedStream => _tabAddedSubject.stream; Stream<String> get tabAddedStream => _tabAddedSubject.stream;
Stream<MlProgressData> get mlProgressEvents => _mlProgressSubject.stream; Stream<MlProgressData> get mlProgressEvents => _mlProgressSubject.stream;
Stream<ManifestUpdateEvent> get manifestUpdateEvents =>
_manifestUpdateSubject.stream;
@override @override
void onViewReadyStateChange(int timestamp, bool state) { void onViewReadyStateChange(int sequence, bool state) {
_viewStateSubject.addWhenMoreRecent(timestamp, null, state); _viewStateSubject.addWhenMoreRecent(sequence, null, state);
} }
@override @override
void onEngineReadyStateChange(int timestamp, bool state) { void onEngineReadyStateChange(int sequence, bool state) {
_engineStateSubject.addWhenMoreRecent(timestamp, null, state); _engineStateSubject.addWhenMoreRecent(sequence, null, state);
} }
// Overridden methods // Overridden methods
@override @override
void onTabListChange(int timestamp, List<String?> tabIds) { void onTabListChange(int sequence, List<String?> tabIds) {
_tabListSubject.addWhenMoreRecent( _tabListSubject.addWhenMoreRecent(sequence, null, tabIds.nonNulls.toList());
timestamp,
null,
tabIds.nonNulls.toList(),
);
} }
@override @override
void onSelectedTabChange(int timestamp, String? id) { void onSelectedTabChange(int sequence, String? id) {
_selectedTabSubject.addWhenMoreRecent(timestamp, id, id); _selectedTabSubject.addWhenMoreRecent(sequence, id, id);
} }
@override @override
void onTabContentStateChange(int timestamp, TabContentState state) { void onTabContentStateChange(int sequence, TabContentState state) {
_tabContentSubject.addWhenMoreRecent(timestamp, state.id, state); _tabContentSubject.addWhenMoreRecent(sequence, state.id, state);
} }
@override @override
void onHistoryStateChange(int timestamp, String id, HistoryState state) { void onHistoryStateChange(int sequence, String id, HistoryState state) {
_historySubject.addWhenMoreRecent(timestamp, id, ( _historySubject.addWhenMoreRecent(sequence, id, (
tabId: id, tabId: id,
history: state, history: state,
)); ));
} }
@override @override
void onReaderableStateChange( void onReaderableStateChange(int sequence, String id, ReaderableState state) {
int timestamp, _readerableSubject.addWhenMoreRecent(sequence, id, (
String id,
ReaderableState state,
) {
_readerableSubject.addWhenMoreRecent(timestamp, id, (
tabId: id, tabId: id,
readerable: state, readerable: state,
)); ));
@@ -121,87 +117,91 @@ class GeckoEventService extends GeckoStateEvents {
@override @override
void onSecurityInfoStateChange( void onSecurityInfoStateChange(
int timestamp, int sequence,
String id, String id,
SecurityInfoState state, SecurityInfoState state,
) { ) {
_securityInfoSubject.addWhenMoreRecent(timestamp, id, ( _securityInfoSubject.addWhenMoreRecent(sequence, id, (
tabId: id, tabId: id,
securityInfo: state, securityInfo: state,
)); ));
} }
@override @override
void onIconChange(int timestamp, String id, Uint8List? bytes) { void onIconChange(int sequence, String id, Uint8List? bytes) {
_iconChangeSubject.addWhenMoreRecent(timestamp, id, ( _iconChangeSubject.addWhenMoreRecent(sequence, id, (
tabId: id, tabId: id,
bytes: bytes, bytes: bytes,
)); ));
} }
@override @override
void onIconUpdate(int timestamp, String url, Uint8List bytes) { void onIconUpdate(int sequence, String url, Uint8List bytes) {
_iconUpdateSubject.addWhenMoreRecent(timestamp, url, ( _iconUpdateSubject.addWhenMoreRecent(sequence, url, (
url: url, url: url,
bytes: bytes, bytes: bytes,
)); ));
} }
@override @override
void onThumbnailChange(int timestamp, String id, Uint8List? bytes) { void onThumbnailChange(int sequence, String id, Uint8List? bytes) {
_thumbnailSubject.addWhenMoreRecent(timestamp, id, ( _thumbnailSubject.addWhenMoreRecent(sequence, id, (
tabId: id, tabId: id,
bytes: bytes, bytes: bytes,
)); ));
} }
@override @override
void onFindResults(int timestamp, String id, List<FindResultState?> results) { void onFindResults(int sequence, String id, List<FindResultState?> results) {
_findResultsSubject.addWhenMoreRecent(timestamp, id, ( _findResultsSubject.addWhenMoreRecent(sequence, id, (
tabId: id, tabId: id,
results: results.nonNulls.toList(), results: results.nonNulls.toList(),
)); ));
} }
@override @override
void onLongPress(int timestamp, String id, HitResult hitResult) { void onLongPress(int sequence, String id, HitResult hitResult) {
_longPressSubject.addWhenMoreRecent(timestamp, id, ( _longPressSubject.addWhenMoreRecent(sequence, id, (
tabId: id, tabId: id,
hitResult: hitResult, hitResult: hitResult,
)); ));
} }
@override @override
void onTabAdded(int timestamp, String tabId) { void onTabAdded(int sequence, String tabId) {
_tabAddedSubject.addWhenMoreRecent(timestamp, null, tabId); _tabAddedSubject.addWhenMoreRecent(sequence, null, tabId);
} }
// @override // @override
// void onScrollChange(int timestamp, String tabId, int scrollY) { // void onScrollChange(int sequence, String tabId, int scrollY) {
// _scrollEventSubject.addWhenMoreRecent(timestamp, tabId, ( // _scrollEventSubject.addWhenMoreRecent(sequence, tabId, (
// tabId: tabId, // tabId: tabId,
// scrollY: scrollY, // scrollY: scrollY,
// )); // ));
// } // }
@override @override
void onPreferenceChange(int timestamp, GeckoPref value) { void onPreferenceChange(int sequence, GeckoPref value) {
_prefUpdateSubject.addWhenMoreRecent(timestamp, value.name, value); _prefUpdateSubject.addWhenMoreRecent(sequence, value.name, value);
} }
@override @override
void onContainerSiteAssignment( void onContainerSiteAssignment(
int timestamp, int sequence,
ContainerSiteAssignment details, ContainerSiteAssignment details,
) { ) {
_siteAssignementSubject.addWhenMoreRecent( _siteAssignementSubject.addWhenMoreRecent(
timestamp, sequence,
details.requestId, details.requestId,
details, details,
); );
} }
@override @override
void onMlProgress(int sequence, MlProgressData progress) {
_mlProgressSubject.addWhenMoreRecent(sequence, null, progress);
}
void onMlProgress(int timestamp, MlProgressData progress) { void onMlProgress(int timestamp, MlProgressData progress) {
_mlProgressSubject.addWhenMoreRecent(timestamp, null, progress); _mlProgressSubject.addWhenMoreRecent(timestamp, null, progress);
} }
@@ -236,5 +236,6 @@ class GeckoEventService extends GeckoStateEvents {
await _prefUpdateSubject.close(); await _prefUpdateSubject.close();
await _siteAssignementSubject.close(); await _siteAssignementSubject.close();
await _mlProgressSubject.close(); await _mlProgressSubject.close();
await _manifestUpdateSubject.close();
} }
} }
@@ -27,8 +27,8 @@ class GeckoReaderableService extends ReaderViewController {
} }
@override @override
void appearanceButtonVisibility(int timestamp, bool visible) { void appearanceButtonVisibility(int sequence, bool visible) {
_appearanceVisibility.addWhenMoreRecent(timestamp, null, visible); _appearanceVisibility.addWhenMoreRecent(sequence, null, visible);
} }
GeckoReaderableService.setUp({ GeckoReaderableService.setUp({
@@ -54,12 +54,12 @@ class GeckoSuggestionsService extends GeckoSuggestionEvents {
@override @override
void onSuggestionResult( void onSuggestionResult(
int timestamp, int sequence,
GeckoSuggestionType suggestionType, GeckoSuggestionType suggestionType,
List<GeckoSuggestion> suggestions, List<GeckoSuggestion> suggestions,
) { ) {
_suggestionsSubject.addWhenMoreRecent( _suggestionsSubject.addWhenMoreRecent(
timestamp, sequence,
suggestionType, suggestionType,
suggestions, suggestions,
); );
@@ -28,8 +28,8 @@ class GeckoTabContentService extends GeckoTabContentEvents {
} }
@override @override
void onContentUpdate(int timestamp, TabContent content) { void onContentUpdate(int sequence, TabContent content) {
_contentSubject.addWhenMoreRecent(timestamp, content.tabId, content); _contentSubject.addWhenMoreRecent(sequence, content.tabId, content);
} }
Future<void> dispose() async { Future<void> dispose() async {
@@ -101,12 +101,12 @@ class GeckoViewportService extends GeckoViewportEvents {
@override @override
void onKeyboardVisibilityChanged( void onKeyboardVisibilityChanged(
int timestamp, int sequence,
int heightPx, int heightPx,
bool isVisible, bool isVisible,
bool isAnimating, bool isAnimating,
) { ) {
_keyboardSubject.addWhenMoreRecent(timestamp, null, ( _keyboardSubject.addWhenMoreRecent(sequence, null, (
heightPx: heightPx, heightPx: heightPx,
isVisible: isVisible, isVisible: isVisible,
isAnimating: isAnimating, isAnimating: isAnimating,
@@ -9,17 +9,29 @@ import 'package:rxdart/rxdart.dart';
final _lastEventTimes = <Subject, Map<dynamic, int>>{}; final _lastEventTimes = <Subject, Map<dynamic, int>>{};
extension SubjectAddRecent<T> on Subject<T> { extension SubjectAddRecent<T> on Subject<T> {
void addWhenMoreRecent(int timestamp, dynamic identifier, T value) { /// Adds [value] to this Subject only if [sequence] is greater than
/// the last sequence number for this [identifier].
///
/// Uses atomic event sequence numbers (from native EventSequence) to ensure
/// events are processed in order and prevent out-of-order updates.
/// Sequence numbers are monotonically increasing integers (0, 1, 2, ...).
void addWhenMoreRecent(int sequence, dynamic identifier, T value) {
_lastEventTimes[this] ??= {}; _lastEventTimes[this] ??= {};
if ((_lastEventTimes[this]?[identifier] ?? 0) < timestamp) { if ((_lastEventTimes[this]?[identifier] ?? 0) < sequence) {
_lastEventTimes[this]![identifier] = timestamp; _lastEventTimes[this]![identifier] = sequence;
add(value); add(value);
} }
} }
/// Updates this BehaviorSubject only if [sequence] is greater than
/// the last sequence number for this [identifier].
///
/// Uses atomic event sequence numbers (from native EventSequence) to ensure
/// events are processed in order and prevent out-of-order updates.
/// Sequence numbers are monotonically increasing integers (0, 1, 2, ...).
void updateWhenMoreRecent( void updateWhenMoreRecent(
int timestamp, int sequence,
dynamic identifier, dynamic identifier,
T Function(T? currentValue) update, T Function(T? currentValue) update,
) { ) {
@@ -27,8 +39,8 @@ extension SubjectAddRecent<T> on Subject<T> {
_lastEventTimes[this] ??= {}; _lastEventTimes[this] ??= {};
if ((_lastEventTimes[this]?[identifier] ?? 0) < timestamp) { if ((_lastEventTimes[this]?[identifier] ?? 0) < sequence) {
_lastEventTimes[this]![identifier] = timestamp; _lastEventTimes[this]![identifier] = sequence;
add(update((this as BehaviorSubject<T>).valueOrNull)); add(update((this as BehaviorSubject<T>).valueOrNull));
} }
} }
File diff suppressed because it is too large Load Diff
@@ -1392,36 +1392,35 @@ class ContainerSiteAssignment {
@FlutterApi() @FlutterApi()
abstract class GeckoStateEvents { abstract class GeckoStateEvents {
void onViewReadyStateChange(int timestamp, bool state); void onViewReadyStateChange(int sequence, bool state);
void onEngineReadyStateChange(int timestamp, bool state); void onEngineReadyStateChange(int sequence, bool state);
void onIconUpdate(int timestamp, String url, Uint8List bytes); void onIconUpdate(int sequence, String url, Uint8List bytes);
void onTabAdded(int timestamp, String tabId); void onTabAdded(int sequence, String tabId);
void onTabListChange(int timestamp, List<String> tabIds); void onTabListChange(int sequence, List<String> tabIds);
void onSelectedTabChange(int timestamp, String? id); void onSelectedTabChange(int sequence, String? id);
void onTabContentStateChange(int timestamp, TabContentState state); void onTabContentStateChange(int sequence, TabContentState state);
void onHistoryStateChange(int timestamp, String id, HistoryState state); void onHistoryStateChange(int sequence, String id, HistoryState state);
void onReaderableStateChange(int timestamp, String id, ReaderableState state); void onReaderableStateChange(int sequence, String id, ReaderableState state);
void onSecurityInfoStateChange( void onSecurityInfoStateChange(
int timestamp, int sequence,
String id, String id,
SecurityInfoState state, SecurityInfoState state,
); );
void onIconChange(int timestamp, String id, Uint8List? bytes); void onIconChange(int sequence, String id, Uint8List? bytes);
void onThumbnailChange(int timestamp, String id, Uint8List? bytes); void onThumbnailChange(int sequence, String id, Uint8List? bytes);
void onFindResults(int timestamp, String id, List<FindResultState> results); void onFindResults(int sequence, String id, List<FindResultState> results);
void onLongPress(int timestamp, String id, HitResult hitResult); void onLongPress(int sequence, String id, HitResult hitResult);
// void onScrollChange(int timestamp, String tabId, int scrollY); // void onScrollChange(int sequence, String tabId, int scrollY);
void onPreferenceChange(int timestamp, GeckoPref value); void onPreferenceChange(int sequence, GeckoPref value);
void onContainerSiteAssignment( void onContainerSiteAssignment(int sequence, ContainerSiteAssignment details);
int timestamp,
ContainerSiteAssignment details, void onMlProgress(int sequence, MlProgressData progress);
);
void onMlProgress(int timestamp, MlProgressData progress); void onMlProgress(int timestamp, MlProgressData progress);
} }
@@ -1439,7 +1438,7 @@ abstract class ReaderViewEvents {
@FlutterApi() @FlutterApi()
abstract class ReaderViewController { abstract class ReaderViewController {
void appearanceButtonVisibility(int timestamp, bool visible); void appearanceButtonVisibility(int sequence, bool visible);
} }
@HostApi() @HostApi()
@@ -1465,20 +1464,20 @@ abstract class GeckoAddonsApi {
@FlutterApi() @FlutterApi()
abstract class GeckoAddonEvents { abstract class GeckoAddonEvents {
void onUpsertWebExtensionAction( void onUpsertWebExtensionAction(
int timestamp, int sequence,
String extensionId, String extensionId,
WebExtensionActionType actionType, WebExtensionActionType actionType,
WebExtensionData extensionData, WebExtensionData extensionData,
); );
void onRemoveWebExtensionAction( void onRemoveWebExtensionAction(
int timestamp, int sequence,
String extensionId, String extensionId,
WebExtensionActionType actionType, WebExtensionActionType actionType,
); );
void onUpdateWebExtensionIcon( void onUpdateWebExtensionIcon(
int timestamp, int sequence,
String extensionId, String extensionId,
WebExtensionActionType actionType, WebExtensionActionType actionType,
Uint8List icon, Uint8List icon,
@@ -1495,7 +1494,7 @@ abstract class GeckoSuggestionApi {
@FlutterApi() @FlutterApi()
abstract class GeckoSuggestionEvents { abstract class GeckoSuggestionEvents {
void onSuggestionResult( void onSuggestionResult(
int timestamp, int sequence,
GeckoSuggestionType suggestionType, GeckoSuggestionType suggestionType,
List<GeckoSuggestion> suggestions, List<GeckoSuggestion> suggestions,
); );
@@ -1503,7 +1502,7 @@ abstract class GeckoSuggestionEvents {
@FlutterApi() @FlutterApi()
abstract class GeckoTabContentEvents { abstract class GeckoTabContentEvents {
void onContentUpdate(int timestamp, TabContent content); void onContentUpdate(int sequence, TabContent content);
} }
@HostApi() @HostApi()
@@ -1579,7 +1578,7 @@ abstract class GeckoDownloadsApi {
@FlutterApi() @FlutterApi()
abstract class BrowserExtensionEvents { abstract class BrowserExtensionEvents {
void onFeedRequested(int timestamp, String url); void onFeedRequested(int sequence, String url);
} }
class GeckoHeader { class GeckoHeader {
@@ -1737,12 +1736,12 @@ abstract class GeckoViewportEvents {
/// This is detected natively using WindowInsets API and provides /// This is detected natively using WindowInsets API and provides
/// accurate keyboard height information. /// accurate keyboard height information.
/// ///
/// [timestamp] Event timestamp for ordering. /// [sequence] Event sequence number for ordering.
/// [heightPx] Keyboard height in pixels (0 when hidden). /// [heightPx] Keyboard height in pixels (0 when hidden).
/// [isVisible] Whether the keyboard is currently visible. /// [isVisible] Whether the keyboard is currently visible.
/// [isAnimating] Whether the keyboard is currently animating. /// [isAnimating] Whether the keyboard is currently animating.
void onKeyboardVisibilityChanged( void onKeyboardVisibilityChanged(
int timestamp, int sequence,
int heightPx, int heightPx,
bool isVisible, bool isVisible,
bool isAnimating, bool isAnimating,
@@ -22,11 +22,13 @@ package eu.weblibre.simple_intent_receiver
import io.flutter.plugin.common.BinaryMessenger import io.flutter.plugin.common.BinaryMessenger
import eu.weblibre.simple_intent_receiver.pigeons.IntentEvents import eu.weblibre.simple_intent_receiver.pigeons.IntentEvents
import eu.weblibre.simple_intent_receiver.pigeons.Intent as PigeonIntent import eu.weblibre.simple_intent_receiver.pigeons.Intent as PigeonIntent
import eu.weblibre.flutter_mozilla_components.ext.EventSequence
class IntentReceiver(messenger: BinaryMessenger) { class IntentReceiver(messenger: BinaryMessenger) {
private val intentEvents: IntentEvents = IntentEvents(messenger) private val intentEvents: IntentEvents = IntentEvents(messenger)
fun sendIntent(timestamp: Long, intent: PigeonIntent) { fun sendIntent(intent: PigeonIntent) {
intentEvents.onIntentReceived(timestamp, intent) { } intentEvents.onIntentReceived(EventSequence.next(), intent) { }
} }
} }
}
@@ -114,7 +114,7 @@ class SimpleIntentReceiverPlugin: FlutterPlugin, ActivityAware, PluginRegistry.N
} }
val pigeonIntent = convertToPigeonIntent(intent) val pigeonIntent = convertToPigeonIntent(intent)
intentReceiver?.sendIntent(System.currentTimeMillis(), pigeonIntent) intentReceiver?.sendIntent(pigeonIntent)
return true return true
} }
@@ -133,12 +133,12 @@ class IntentEvents(private val binaryMessenger: BinaryMessenger, private val mes
IntentPigeonCodec() IntentPigeonCodec()
} }
} }
fun onIntentReceived(timestampArg: Long, intentArg: Intent, callback: (Result<Unit>) -> Unit) fun onIntentReceived(sequenceArg: Long, intentArg: Intent, callback: (Result<Unit>) -> Unit)
{ {
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
val channelName = "dev.flutter.pigeon.simple_intent_receiver.IntentEvents.onIntentReceived$separatedMessageChannelSuffix" val channelName = "dev.flutter.pigeon.simple_intent_receiver.IntentEvents.onIntentReceived$separatedMessageChannelSuffix"
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec) val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
channel.send(listOf(timestampArg, intentArg)) { channel.send(listOf(sequenceArg, intentArg)) {
if (it is List<*>) { if (it is List<*>) {
if (it.size > 1) { if (it.size > 1) {
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
@@ -29,8 +29,8 @@ class IntentReceiver extends IntentEvents {
Stream<Intent> get events => _controller.stream; Stream<Intent> get events => _controller.stream;
@override @override
void onIntentReceived(int timestamp, Intent intent) { void onIntentReceived(int sequence, Intent intent) {
if (_lastAdded == null || timestamp > _lastAdded!) { if (_lastAdded == null || sequence > _lastAdded!) {
_controller.add(intent); _controller.add(intent);
} }
} }
@@ -128,7 +128,7 @@ class _PigeonCodec extends StandardMessageCodec {
abstract class IntentEvents { abstract class IntentEvents {
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec(); static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
void onIntentReceived(int timestamp, Intent intent); void onIntentReceived(int sequence, Intent intent);
static void setUp(IntentEvents? api, {BinaryMessenger? binaryMessenger, String messageChannelSuffix = '',}) { static void setUp(IntentEvents? api, {BinaryMessenger? binaryMessenger, String messageChannelSuffix = '',}) {
messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
@@ -143,14 +143,14 @@ abstract class IntentEvents {
assert(message != null, assert(message != null,
'Argument for dev.flutter.pigeon.simple_intent_receiver.IntentEvents.onIntentReceived was null.'); 'Argument for dev.flutter.pigeon.simple_intent_receiver.IntentEvents.onIntentReceived was null.');
final List<Object?> args = (message as List<Object?>?)!; final List<Object?> args = (message as List<Object?>?)!;
final int? arg_timestamp = (args[0] as int?); final int? arg_sequence = (args[0] as int?);
assert(arg_timestamp != null, assert(arg_sequence != null,
'Argument for dev.flutter.pigeon.simple_intent_receiver.IntentEvents.onIntentReceived was null, expected non-null int.'); 'Argument for dev.flutter.pigeon.simple_intent_receiver.IntentEvents.onIntentReceived was null, expected non-null int.');
final Intent? arg_intent = (args[1] as Intent?); final Intent? arg_intent = (args[1] as Intent?);
assert(arg_intent != null, assert(arg_intent != null,
'Argument for dev.flutter.pigeon.simple_intent_receiver.IntentEvents.onIntentReceived was null, expected non-null Intent.'); 'Argument for dev.flutter.pigeon.simple_intent_receiver.IntentEvents.onIntentReceived was null, expected non-null Intent.');
try { try {
api.onIntentReceived(arg_timestamp!, arg_intent!); api.onIntentReceived(arg_sequence!, arg_intent!);
return wrapResponse(empty: true); return wrapResponse(empty: true);
} on PlatformException catch (e) { } on PlatformException catch (e) {
return wrapResponse(error: e); return wrapResponse(error: e);
@@ -51,5 +51,5 @@ class Intent {
) )
@FlutterApi() @FlutterApi()
abstract class IntentEvents { abstract class IntentEvents {
void onIntentReceived(int timestamp, Intent intent); void onIntentReceived(int sequence, Intent intent);
} }