diff --git a/app/lib/features/geckoview/domain/providers.dart b/app/lib/features/geckoview/domain/providers.dart index 9bfe5ba1..64026a76 100644 --- a/app/lib/features/geckoview/domain/providers.dart +++ b/app/lib/features/geckoview/domain/providers.dart @@ -205,3 +205,43 @@ class EngineReadyState extends _$EngineReadyState { return currentState; } } + +/// Stream of ML model progress events +@Riverpod(keepAlive: true) +Stream mlProgressEvents(Ref ref) { + final service = ref.watch(eventServiceProvider); + return service.mlProgressEvents; +} + +/// Tracks active ML model downloads +@Riverpod() +class MlDownloadState extends _$MlDownloadState { + @override + MlProgressData? build() { + ref.listen( + mlProgressEventsProvider, + (previous, next) { + next.whenData((progress) { + if (progress.type == MlProgressType.downloading) { + if (progress.status == MlProgressStatus.done) { + // Keep showing for 2 seconds after completion + Future.delayed(const Duration(seconds: 2), () { + if (state != null && state!.id == progress.id) { + state = null; + } + }); + } else { + state = progress; + } + } + }); + }, + ); + + return null; + } + + void clear() { + state = null; + } +} diff --git a/app/lib/features/geckoview/domain/providers.g.dart b/app/lib/features/geckoview/domain/providers.g.dart index feedd47a..c47aab1f 100644 --- a/app/lib/features/geckoview/domain/providers.g.dart +++ b/app/lib/features/geckoview/domain/providers.g.dart @@ -298,3 +298,106 @@ abstract class _$EngineReadyState extends $Notifier { element.handleValue(ref, created); } } + +/// Stream of ML model progress events + +@ProviderFor(mlProgressEvents) +const mlProgressEventsProvider = MlProgressEventsProvider._(); + +/// Stream of ML model progress events + +final class MlProgressEventsProvider + extends + $FunctionalProvider< + AsyncValue, + MlProgressData, + Stream + > + with $FutureModifier, $StreamProvider { + /// Stream of ML model progress events + const MlProgressEventsProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'mlProgressEventsProvider', + isAutoDispose: false, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$mlProgressEventsHash(); + + @$internal + @override + $StreamProviderElement $createElement( + $ProviderPointer pointer, + ) => $StreamProviderElement(pointer); + + @override + Stream create(Ref ref) { + return mlProgressEvents(ref); + } +} + +String _$mlProgressEventsHash() => r'41c1e6aece7f9ee2bebe5d189c6bec753c2fefc8'; + +/// Tracks active ML model downloads + +@ProviderFor(MlDownloadState) +const mlDownloadStateProvider = MlDownloadStateProvider._(); + +/// Tracks active ML model downloads +final class MlDownloadStateProvider + extends $NotifierProvider { + /// Tracks active ML model downloads + const MlDownloadStateProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'mlDownloadStateProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$mlDownloadStateHash(); + + @$internal + @override + MlDownloadState create() => MlDownloadState(); + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(MlProgressData? value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), + ); + } +} + +String _$mlDownloadStateHash() => r'10be3767d448ea8fca8bf35578c343f3b9ddbb4e'; + +/// Tracks active ML model downloads + +abstract class _$MlDownloadState extends $Notifier { + MlProgressData? build(); + @$mustCallSuper + @override + void runBuild() { + final created = build(); + final ref = this.ref as $Ref; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier, + MlProgressData?, + Object?, + Object? + >; + element.handleValue(ref, created); + } +} diff --git a/app/lib/features/geckoview/features/browser/presentation/controllers/tab_view_controllers.dart b/app/lib/features/geckoview/features/browser/presentation/controllers/tab_view_controllers.dart index de2b98f9..b36e7515 100644 --- a/app/lib/features/geckoview/features/browser/presentation/controllers/tab_view_controllers.dart +++ b/app/lib/features/geckoview/features/browser/presentation/controllers/tab_view_controllers.dart @@ -30,8 +30,12 @@ part 'tab_view_controllers.g.dart'; @Riverpod(keepAlive: true) class TabSuggestionsController extends _$TabSuggestionsController { - void toggle() { - state = !state; + void enable() { + state = true; + } + + void disable() { + state = false; } void hide() { diff --git a/app/lib/features/geckoview/features/browser/presentation/controllers/tab_view_controllers.g.dart b/app/lib/features/geckoview/features/browser/presentation/controllers/tab_view_controllers.g.dart index 273c0819..0224ad97 100644 --- a/app/lib/features/geckoview/features/browser/presentation/controllers/tab_view_controllers.g.dart +++ b/app/lib/features/geckoview/features/browser/presentation/controllers/tab_view_controllers.g.dart @@ -42,7 +42,7 @@ final class TabSuggestionsControllerProvider } String _$tabSuggestionsControllerHash() => - r'c60912e474177f2a0f15167743db439d157cd852'; + r'27a22674f9e4efb01e70120338202e95acef4791'; abstract class _$TabSuggestionsController extends $Notifier { bool build(); diff --git a/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_view_header.dart b/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_view_header.dart index 72d246d1..8c1ec8de 100644 --- a/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_view_header.dart +++ b/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_view_header.dart @@ -6,6 +6,7 @@ import 'package:flutter_material_design_icons/flutter_material_design_icons.dart import 'package:flutter_mozilla_components/flutter_mozilla_components.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:weblibre/core/routing/routes.dart'; +import 'package:weblibre/features/geckoview/domain/providers.dart'; import 'package:weblibre/features/geckoview/domain/repositories/tab.dart'; import 'package:weblibre/features/geckoview/features/browser/presentation/controllers/tab_view_controllers.dart'; import 'package:weblibre/features/geckoview/features/tabs/domain/providers/selected_container.dart'; @@ -142,20 +143,75 @@ class TabViewHeader extends HookConsumerWidget { final tabSuggestionsEnabled = ref.watch( tabSuggestionsControllerProvider, ); + final downloadProgress = ref.watch( + mlDownloadStateProvider, + ); - return IconButton.filledTonal( - icon: const Icon(MdiIcons.imageAutoAdjust), - isSelected: tabSuggestionsEnabled, - iconSize: 18, - padding: EdgeInsets.zero, - onPressed: () { - ref - .read( - tabSuggestionsControllerProvider - .notifier, + return Badge( + isLabelVisible: downloadProgress != null, + offset: const Offset(-2, 2), + label: downloadProgress != null + ? Text( + '${downloadProgress.progress.toInt()}%', + style: const TextStyle(fontSize: 10), ) - .toggle(); - }, + : null, + child: IconButton.filledTonal( + icon: const Icon(MdiIcons.imageAutoAdjust), + isSelected: tabSuggestionsEnabled, + iconSize: 18, + padding: EdgeInsets.zero, + onPressed: () async { + if (!tabSuggestionsEnabled) { + final result = await showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + icon: const Icon(MdiIcons.download), + title: const Text( + 'Enable AI Tab Suggestions', + ), + content: const Text( + 'Enabling this feature may require downloading AI models. ' + 'The download size and progress cannot be determined in advance.\n\n' + 'Do you want to continue?', + ), + actions: [ + TextButton( + onPressed: () { + Navigator.pop(context, false); + }, + child: const Text('Cancel'), + ), + TextButton( + onPressed: () { + Navigator.pop(context, true); + }, + child: const Text('Enable'), + ), + ], + ); + }, + ); + + if (result == true) { + ref + .read( + tabSuggestionsControllerProvider + .notifier, + ) + .enable(); + } + } else { + ref + .read( + tabSuggestionsControllerProvider + .notifier, + ) + .disable(); + } + }, + ), ); }, ), diff --git a/packages/flutter_mozilla_components/android/src/main/assets/extensions/ml_engine/api/ml.js b/packages/flutter_mozilla_components/android/src/main/assets/extensions/ml_engine/api/ml.js index 6836947b..da605a1a 100644 --- a/packages/flutter_mozilla_components/android/src/main/assets/extensions/ml_engine/api/ml.js +++ b/packages/flutter_mozilla_components/android/src/main/assets/extensions/ml_engine/api/ml.js @@ -92,11 +92,55 @@ function isEngineClosed(engine) { return !engine || engine?.engineStatus === "closed"; } +/** + * Create a progress callback that emits progress via the event + * @param {string} modelType The type of model being loaded + * @param {function} progressEmitter Function to emit progress events + * @return {function} Progress callback function + */ +function createProgressCallback(modelType, progressEmitter) { + return (progressData) => { + if (progressEmitter) { + progressEmitter.async({ + modelType: modelType, + progress: progressData.progress || 0, + type: progressData.type, + statusText: progressData.statusText, + totalLoaded: progressData.totalLoaded || 0, + currentLoaded: progressData.currentLoaded || 0, + total: progressData.total || 0, + units: progressData.units || "bytes", + ok: progressData.ok || false, + id: progressData.id, + }); + } + }; +} + this.ml = class extends ExtensionAPI { + constructor(extension) { + super(extension); + this.embeddingEngine = null; + this.topicEngine = null; + this.progressEmitter = null; + } + getAPI(context) { + const self = this; + return { experiments: { ml: { + onProgress: new ExtensionCommon.EventManager({ + context, + name: "ml.onProgress", + register: (fire) => { + self.progressEmitter = fire; + return () => { + self.progressEmitter = null; + }; + }, + }).api(), async generateEmbeddings(textToEmbedList) { const inputData = { inputArgs: textToEmbedList, @@ -106,8 +150,11 @@ this.ml = class extends ExtensionAPI { }, }; - if (isEngineClosed(this.embeddingEngine)) { - this.embeddingEngine = await createEngine(SMART_TAB_GROUPING_CONFIG.embedding); + if (isEngineClosed(self.embeddingEngine)) { + self.embeddingEngine = await createEngine( + SMART_TAB_GROUPING_CONFIG.embedding, + createProgressCallback("Embedding Model", self.progressEmitter) + ); } const request = { @@ -115,12 +162,12 @@ this.ml = class extends ExtensionAPI { options: inputData.runOptions, }; - const generated = await this.embeddingEngine.run(request); + const generated = await self.embeddingEngine.run(request); return JSON.stringify(generated); }, async predictTopic(keywords, documents) { - if (isEngineClosed(this.topicEngine)) { + if (isEngineClosed(self.topicEngine)) { const { featureId, engineId, @@ -143,7 +190,10 @@ this.ml = class extends ExtensionAPI { backend, }; - this.topicEngine = await createEngine(initData); + self.topicEngine = await createEngine( + initData, + createProgressCallback("Topic Generation Model", self.progressEmitter) + ); } const inputArgs = createModelInput( @@ -161,7 +211,7 @@ this.ml = class extends ExtensionAPI { options: requestInfo.runOptions, }; - const res = await this.topicEngine.run(request); + const res = await self.topicEngine.run(request); const generated = cutAtDuplicateWords((res[0]["generated_text"] || "").trim()); diff --git a/packages/flutter_mozilla_components/android/src/main/assets/extensions/ml_engine/background.js b/packages/flutter_mozilla_components/android/src/main/assets/extensions/ml_engine/background.js index d5cc040a..1fbd0788 100644 --- a/packages/flutter_mozilla_components/android/src/main/assets/extensions/ml_engine/background.js +++ b/packages/flutter_mozilla_components/android/src/main/assets/extensions/ml_engine/background.js @@ -23,6 +23,13 @@ function sendErrorForRequest(id) { } } +browser.experiments.ml.onProgress.addListener((progressData) => { + port.postMessage({ + type: "mlProgress", + progress: progressData + }); +}); + port.onMessage.addListener(async (message) => { let requestId = message["id"] switch (message["action"]) { diff --git a/packages/flutter_mozilla_components/android/src/main/assets/extensions/ml_engine/manifest.json b/packages/flutter_mozilla_components/android/src/main/assets/extensions/ml_engine/manifest.json index 2d2bec86..27748826 100644 --- a/packages/flutter_mozilla_components/android/src/main/assets/extensions/ml_engine/manifest.json +++ b/packages/flutter_mozilla_components/android/src/main/assets/extensions/ml_engine/manifest.json @@ -53,7 +53,6 @@ "nativeMessagingFromContent", "geckoViewAddons", "cookies", - "menus", "scripting", "storage", "" diff --git a/packages/flutter_mozilla_components/android/src/main/assets/extensions/ml_engine/schema.json b/packages/flutter_mozilla_components/android/src/main/assets/extensions/ml_engine/schema.json index 4466697b..0024a374 100644 --- a/packages/flutter_mozilla_components/android/src/main/assets/extensions/ml_engine/schema.json +++ b/packages/flutter_mozilla_components/android/src/main/assets/extensions/ml_engine/schema.json @@ -31,6 +31,20 @@ { "namespace": "experiments.ml", "description": "Machine Learning utilities", + "events": [ + { + "name": "onProgress", + "type": "function", + "description": "Fired when ML model loading or processing makes progress", + "parameters": [ + { + "name": "progressData", + "type": "object", + "description": "Progress information including modelType, progress percentage, type, and status" + } + ] + } + ], "functions": [ { "name": "generateEmbeddings", diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/api/GeckoBrowserApiImpl.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/api/GeckoBrowserApiImpl.kt index bf5f4141..44e0f803 100644 --- a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/api/GeckoBrowserApiImpl.kt +++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/api/GeckoBrowserApiImpl.kt @@ -238,7 +238,7 @@ class GeckoBrowserApiImpl : GeckoBrowserApi { GeckoTabsApi.setUp(_flutterPluginBinding.binaryMessenger, GeckoTabsApiImpl()) GeckoIconsApi.setUp(_flutterPluginBinding.binaryMessenger, GeckoIconsApiImpl()) GeckoCookieApi.setUp(_flutterPluginBinding.binaryMessenger, GeckoCookieApiImpl()) - GeckoMlApi.setUp(_flutterPluginBinding.binaryMessenger, GeckoMlApiImpl()) + GeckoMlApi.setUp(_flutterPluginBinding.binaryMessenger, GeckoMlApiImpl(_flutterPluginBinding.binaryMessenger, _flutterEvents)) GeckoPrefApi.setUp(_flutterPluginBinding.binaryMessenger, GeckoPrefApiImpl()) GeckoContainerProxyApi.setUp( _flutterPluginBinding.binaryMessenger, diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/api/GeckoMlApiImpl.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/api/GeckoMlApiImpl.kt index 722e9be8..b1fa23a0 100644 --- a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/api/GeckoMlApiImpl.kt +++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/api/GeckoMlApiImpl.kt @@ -3,10 +3,82 @@ package eu.weblibre.flutter_mozilla_components.api import eu.weblibre.flutter_mozilla_components.feature.MLEngineFeature import eu.weblibre.flutter_mozilla_components.feature.ResultConsumer import eu.weblibre.flutter_mozilla_components.pigeons.GeckoMlApi +import eu.weblibre.flutter_mozilla_components.pigeons.GeckoStateEvents +import eu.weblibre.flutter_mozilla_components.pigeons.MlProgressData +import eu.weblibre.flutter_mozilla_components.pigeons.MlProgressType +import eu.weblibre.flutter_mozilla_components.pigeons.MlProgressStatus +import io.flutter.plugin.common.BinaryMessenger import org.json.JSONArray import org.json.JSONObject +import org.mozilla.gecko.util.ThreadUtils.runOnUiThread + +class GeckoMlApiImpl( + private val binaryMessenger: BinaryMessenger, + private val stateEvents: GeckoStateEvents +) : GeckoMlApi { + + init { + // Register progress callback with ML engine + MLEngineFeature.progressCallback = { progressData -> + emitProgress(progressData) + } + } + + private fun emitProgress(progressData: JSONObject) { + try { + val modelType = progressData.optString("modelType", "Unknown") + val progress = progressData.optDouble("progress", 0.0) + val typeString = progressData.optString("type", "") + val statusString = progressData.optString("statusText", "") + val totalLoaded = progressData.optLong("totalLoaded", 0) + val currentLoaded = progressData.optLong("currentLoaded", 0) + val total = progressData.optLong("total", 0) + val units = progressData.optString("units", "bytes") + val ok = progressData.optBoolean("ok", false) + val id = if (progressData.has("id")) progressData.optString("id") else null + + // Map type string to enum + val type = when (typeString) { + "downloading" -> MlProgressType.DOWNLOADING + "loading_from_cache" -> MlProgressType.LOADING_FROM_CACHE + "running_inference" -> MlProgressType.RUNNING_INFERENCE + else -> MlProgressType.DOWNLOADING + } + + // Map status string to enum + val status = when (statusString.uppercase()) { + "INITIATE" -> MlProgressStatus.INITIATE + "SIZE_ESTIMATE" -> MlProgressStatus.SIZE_ESTIMATE + "IN_PROGRESS" -> MlProgressStatus.IN_PROGRESS + "DONE" -> MlProgressStatus.DONE + else -> MlProgressStatus.IN_PROGRESS + } + + val mlProgress = MlProgressData( + modelType = modelType, + progress = progress, + type = type, + status = status, + totalLoaded = totalLoaded, + currentLoaded = currentLoaded, + total = total, + units = units, + ok = ok, + id = id + ) + + runOnUiThread { + stateEvents.onMlProgress(System.currentTimeMillis(), mlProgress) { result -> + result.onFailure { error -> + android.util.Log.e("GeckoMlApi", "Failed to emit progress event: ${error.message}") + } + } + } + } catch (e: Exception) { + android.util.Log.e("GeckoMlApi", "Error parsing progress data", e) + } + } -class GeckoMlApiImpl : GeckoMlApi { private fun List?.toJson(): JSONArray { return JSONArray().apply { this@toJson?.forEach { put(it) } diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/feature/MLEngineFeature.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/feature/MLEngineFeature.kt index a6cdd588..c891b74a 100644 --- a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/feature/MLEngineFeature.kt +++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/feature/MLEngineFeature.kt @@ -31,6 +31,9 @@ object MLEngineFeature { private val requestHandlers = HashMap>() private val mutex = Mutex() + // Progress callback for ML operations + var progressCallback: ((JSONObject) -> Unit)? = null + @VisibleForTesting // This is an internal var to make it mutable for unit testing purposes only internal var extensionController = BuiltInWebExtensionController( @@ -63,9 +66,17 @@ object MLEngineFeature { override fun onPortMessage(message: Any, port: Port) { runBlocking { withContext(Dispatchers.Default) { - mutex.withLock { - val messageJSON = message as JSONObject; + val messageJSON = message as JSONObject; + // Check if this is a progress message + if (messageJSON.has("type") && messageJSON.getString("type") == "mlProgress") { + val progressData = messageJSON.getJSONObject("progress") + progressCallback?.invoke(progressData) + return@withContext + } + + // Handle regular request/response messages + mutex.withLock { val requestId = messageJSON.getInt("id") val status = messageJSON.getString("status") if (status == "success") { diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/pigeons/Gecko.g.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/pigeons/Gecko.g.kt index d59b081e..62093a3a 100644 --- a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/pigeons/Gecko.g.kt +++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/pigeons/Gecko.g.kt @@ -385,6 +385,33 @@ enum class LogLevel(val raw: Int) { } } +/** Type of ML model operation */ +enum class MlProgressType(val raw: Int) { + DOWNLOADING(0), + LOADING_FROM_CACHE(1), + RUNNING_INFERENCE(2); + + companion object { + fun ofRaw(raw: Int): MlProgressType? { + return values().firstOrNull { it.raw == raw } + } + } +} + +/** Status of the ML operation */ +enum class MlProgressStatus(val raw: Int) { + INITIATE(0), + SIZE_ESTIMATE(1), + IN_PROGRESS(2), + DONE(3); + + companion object { + fun ofRaw(raw: Int): MlProgressStatus? { + return values().firstOrNull { it.raw == raw } + } + } +} + enum class GeckoFetchMethod(val raw: Int) { GET(0), HEAD(1), @@ -2289,6 +2316,75 @@ data class GeckoPref ( override fun hashCode(): Int = toList().hashCode() } +/** + * Progress information for ML model operations + * + * Generated class from Pigeon that represents data sent in messages. + */ +data class MlProgressData ( + /** The type of ML model being loaded */ + val modelType: String, + /** Percentage of completion (0-100) */ + val progress: Double, + /** Type of operation (download, cache load, or inference) */ + val type: MlProgressType, + /** Current status of the operation */ + val status: MlProgressStatus, + /** Total bytes loaded so far */ + val totalLoaded: Long, + /** Bytes loaded in current update */ + val currentLoaded: Long, + /** Total size estimate */ + val total: Long, + /** Units of measurement (e.g., "bytes") */ + val units: String, + /** Whether the operation completed successfully */ + val ok: Boolean, + /** Unique identifier for this operation */ + val id: String? = null +) + { + companion object { + fun fromList(pigeonVar_list: List): MlProgressData { + val modelType = pigeonVar_list[0] as String + val progress = pigeonVar_list[1] as Double + val type = pigeonVar_list[2] as MlProgressType + val status = pigeonVar_list[3] as MlProgressStatus + val totalLoaded = pigeonVar_list[4] as Long + val currentLoaded = pigeonVar_list[5] as Long + val total = pigeonVar_list[6] as Long + val units = pigeonVar_list[7] as String + val ok = pigeonVar_list[8] as Boolean + val id = pigeonVar_list[9] as String? + return MlProgressData(modelType, progress, type, status, totalLoaded, currentLoaded, total, units, ok, id) + } + } + fun toList(): List { + return listOf( + modelType, + progress, + type, + status, + totalLoaded, + currentLoaded, + total, + units, + ok, + id, + ) + } + override fun equals(other: Any?): Boolean { + if (other !is MlProgressData) { + return false + } + if (this === other) { + return true + } + return GeckoPigeonUtils.deepEquals(toList(), other.toList()) } + + override fun hashCode(): Int = toList().hashCode() +} + /** Generated class from Pigeon that represents data sent in messages. */ data class ContainerSiteAssignment ( val requestId: String, @@ -2653,270 +2749,285 @@ private open class GeckoPigeonCodec : StandardMessageCodec() { } 148.toByte() -> { return (readValue(buffer) as Long?)?.let { - GeckoFetchMethod.ofRaw(it.toInt()) + MlProgressType.ofRaw(it.toInt()) } } 149.toByte() -> { return (readValue(buffer) as Long?)?.let { - GeckoFetchRedircet.ofRaw(it.toInt()) + MlProgressStatus.ofRaw(it.toInt()) } } 150.toByte() -> { return (readValue(buffer) as Long?)?.let { - GeckoFetchCookiePolicy.ofRaw(it.toInt()) + GeckoFetchMethod.ofRaw(it.toInt()) } } 151.toByte() -> { return (readValue(buffer) as Long?)?.let { - BookmarkNodeType.ofRaw(it.toInt()) + GeckoFetchRedircet.ofRaw(it.toInt()) } } 152.toByte() -> { - return (readValue(buffer) as? List)?.let { - TranslationOptions.fromList(it) + return (readValue(buffer) as Long?)?.let { + GeckoFetchCookiePolicy.ofRaw(it.toInt()) } } 153.toByte() -> { - return (readValue(buffer) as? List)?.let { - ReaderState.fromList(it) + return (readValue(buffer) as Long?)?.let { + BookmarkNodeType.ofRaw(it.toInt()) } } 154.toByte() -> { return (readValue(buffer) as? List)?.let { - LastMediaAccessState.fromList(it) + TranslationOptions.fromList(it) } } 155.toByte() -> { return (readValue(buffer) as? List)?.let { - HistoryMetadataKey.fromList(it) + ReaderState.fromList(it) } } 156.toByte() -> { return (readValue(buffer) as? List)?.let { - PackageCategoryValue.fromList(it) + LastMediaAccessState.fromList(it) } } 157.toByte() -> { return (readValue(buffer) as? List)?.let { - ExternalPackage.fromList(it) + HistoryMetadataKey.fromList(it) } } 158.toByte() -> { return (readValue(buffer) as? List)?.let { - LoadUrlFlagsValue.fromList(it) + PackageCategoryValue.fromList(it) } } 159.toByte() -> { return (readValue(buffer) as? List)?.let { - SourceValue.fromList(it) + ExternalPackage.fromList(it) } } 160.toByte() -> { return (readValue(buffer) as? List)?.let { - TabState.fromList(it) + LoadUrlFlagsValue.fromList(it) } } 161.toByte() -> { return (readValue(buffer) as? List)?.let { - RecoverableTab.fromList(it) + SourceValue.fromList(it) } } 162.toByte() -> { return (readValue(buffer) as? List)?.let { - RecoverableBrowserState.fromList(it) + TabState.fromList(it) } } 163.toByte() -> { return (readValue(buffer) as? List)?.let { - IconRequest.fromList(it) + RecoverableTab.fromList(it) } } 164.toByte() -> { return (readValue(buffer) as? List)?.let { - ResourceSize.fromList(it) + RecoverableBrowserState.fromList(it) } } 165.toByte() -> { return (readValue(buffer) as? List)?.let { - Resource.fromList(it) + IconRequest.fromList(it) } } 166.toByte() -> { return (readValue(buffer) as? List)?.let { - IconResult.fromList(it) + ResourceSize.fromList(it) } } 167.toByte() -> { return (readValue(buffer) as? List)?.let { - CookiePartitionKey.fromList(it) + Resource.fromList(it) } } 168.toByte() -> { return (readValue(buffer) as? List)?.let { - Cookie.fromList(it) + IconResult.fromList(it) } } 169.toByte() -> { return (readValue(buffer) as? List)?.let { - VisitInfo.fromList(it) + CookiePartitionKey.fromList(it) } } 170.toByte() -> { return (readValue(buffer) as? List)?.let { - HistoryItem.fromList(it) + Cookie.fromList(it) } } 171.toByte() -> { return (readValue(buffer) as? List)?.let { - HistoryState.fromList(it) + VisitInfo.fromList(it) } } 172.toByte() -> { return (readValue(buffer) as? List)?.let { - ReaderableState.fromList(it) + HistoryItem.fromList(it) } } 173.toByte() -> { return (readValue(buffer) as? List)?.let { - SecurityInfoState.fromList(it) + HistoryState.fromList(it) } } 174.toByte() -> { return (readValue(buffer) as? List)?.let { - TabContentState.fromList(it) + ReaderableState.fromList(it) } } 175.toByte() -> { return (readValue(buffer) as? List)?.let { - FindResultState.fromList(it) + SecurityInfoState.fromList(it) } } 176.toByte() -> { return (readValue(buffer) as? List)?.let { - CustomSelectionAction.fromList(it) + TabContentState.fromList(it) } } 177.toByte() -> { return (readValue(buffer) as? List)?.let { - WebExtensionData.fromList(it) + FindResultState.fromList(it) } } 178.toByte() -> { return (readValue(buffer) as? List)?.let { - GeckoSuggestion.fromList(it) + CustomSelectionAction.fromList(it) } } 179.toByte() -> { return (readValue(buffer) as? List)?.let { - TabContent.fromList(it) + WebExtensionData.fromList(it) } } 180.toByte() -> { return (readValue(buffer) as? List)?.let { - ContentBlocking.fromList(it) + GeckoSuggestion.fromList(it) } } 181.toByte() -> { return (readValue(buffer) as? List)?.let { - DohSettings.fromList(it) + TabContent.fromList(it) } } 182.toByte() -> { return (readValue(buffer) as? List)?.let { - GeckoEngineSettings.fromList(it) + ContentBlocking.fromList(it) } } 183.toByte() -> { return (readValue(buffer) as? List)?.let { - AutocompleteResult.fromList(it) + DohSettings.fromList(it) } } 184.toByte() -> { return (readValue(buffer) as? List)?.let { - UnknownHitResult.fromList(it) + GeckoEngineSettings.fromList(it) } } 185.toByte() -> { return (readValue(buffer) as? List)?.let { - ImageHitResult.fromList(it) + AutocompleteResult.fromList(it) } } 186.toByte() -> { return (readValue(buffer) as? List)?.let { - VideoHitResult.fromList(it) + UnknownHitResult.fromList(it) } } 187.toByte() -> { return (readValue(buffer) as? List)?.let { - AudioHitResult.fromList(it) + ImageHitResult.fromList(it) } } 188.toByte() -> { return (readValue(buffer) as? List)?.let { - ImageSrcHitResult.fromList(it) + VideoHitResult.fromList(it) } } 189.toByte() -> { return (readValue(buffer) as? List)?.let { - PhoneHitResult.fromList(it) + AudioHitResult.fromList(it) } } 190.toByte() -> { return (readValue(buffer) as? List)?.let { - EmailHitResult.fromList(it) + ImageSrcHitResult.fromList(it) } } 191.toByte() -> { return (readValue(buffer) as? List)?.let { - GeoHitResult.fromList(it) + PhoneHitResult.fromList(it) } } 192.toByte() -> { return (readValue(buffer) as? List)?.let { - DownloadState.fromList(it) + EmailHitResult.fromList(it) } } 193.toByte() -> { return (readValue(buffer) as? List)?.let { - ShareInternetResourceState.fromList(it) + GeoHitResult.fromList(it) } } 194.toByte() -> { return (readValue(buffer) as? List)?.let { - AddonCollection.fromList(it) + DownloadState.fromList(it) } } 195.toByte() -> { return (readValue(buffer) as? List)?.let { - GeckoPref.fromList(it) + ShareInternetResourceState.fromList(it) } } 196.toByte() -> { return (readValue(buffer) as? List)?.let { - ContainerSiteAssignment.fromList(it) + AddonCollection.fromList(it) } } 197.toByte() -> { return (readValue(buffer) as? List)?.let { - GeckoHeader.fromList(it) + GeckoPref.fromList(it) } } 198.toByte() -> { return (readValue(buffer) as? List)?.let { - GeckoFetchRequest.fromList(it) + MlProgressData.fromList(it) } } 199.toByte() -> { return (readValue(buffer) as? List)?.let { - GeckoFetchResponse.fromList(it) + ContainerSiteAssignment.fromList(it) } } 200.toByte() -> { return (readValue(buffer) as? List)?.let { - BookmarkNode.fromList(it) + GeckoHeader.fromList(it) } } 201.toByte() -> { + return (readValue(buffer) as? List)?.let { + GeckoFetchRequest.fromList(it) + } + } + 202.toByte() -> { + return (readValue(buffer) as? List)?.let { + GeckoFetchResponse.fromList(it) + } + } + 203.toByte() -> { + return (readValue(buffer) as? List)?.let { + BookmarkNode.fromList(it) + } + } + 204.toByte() -> { return (readValue(buffer) as? List)?.let { BookmarkInfo.fromList(it) } @@ -3002,222 +3113,234 @@ private open class GeckoPigeonCodec : StandardMessageCodec() { stream.write(147) writeValue(stream, value.raw.toLong()) } - is GeckoFetchMethod -> { + is MlProgressType -> { stream.write(148) writeValue(stream, value.raw.toLong()) } - is GeckoFetchRedircet -> { + is MlProgressStatus -> { stream.write(149) writeValue(stream, value.raw.toLong()) } - is GeckoFetchCookiePolicy -> { + is GeckoFetchMethod -> { stream.write(150) writeValue(stream, value.raw.toLong()) } - is BookmarkNodeType -> { + is GeckoFetchRedircet -> { stream.write(151) writeValue(stream, value.raw.toLong()) } - is TranslationOptions -> { + is GeckoFetchCookiePolicy -> { stream.write(152) - writeValue(stream, value.toList()) + writeValue(stream, value.raw.toLong()) } - is ReaderState -> { + is BookmarkNodeType -> { stream.write(153) - writeValue(stream, value.toList()) + writeValue(stream, value.raw.toLong()) } - is LastMediaAccessState -> { + is TranslationOptions -> { stream.write(154) writeValue(stream, value.toList()) } - is HistoryMetadataKey -> { + is ReaderState -> { stream.write(155) writeValue(stream, value.toList()) } - is PackageCategoryValue -> { + is LastMediaAccessState -> { stream.write(156) writeValue(stream, value.toList()) } - is ExternalPackage -> { + is HistoryMetadataKey -> { stream.write(157) writeValue(stream, value.toList()) } - is LoadUrlFlagsValue -> { + is PackageCategoryValue -> { stream.write(158) writeValue(stream, value.toList()) } - is SourceValue -> { + is ExternalPackage -> { stream.write(159) writeValue(stream, value.toList()) } - is TabState -> { + is LoadUrlFlagsValue -> { stream.write(160) writeValue(stream, value.toList()) } - is RecoverableTab -> { + is SourceValue -> { stream.write(161) writeValue(stream, value.toList()) } - is RecoverableBrowserState -> { + is TabState -> { stream.write(162) writeValue(stream, value.toList()) } - is IconRequest -> { + is RecoverableTab -> { stream.write(163) writeValue(stream, value.toList()) } - is ResourceSize -> { + is RecoverableBrowserState -> { stream.write(164) writeValue(stream, value.toList()) } - is Resource -> { + is IconRequest -> { stream.write(165) writeValue(stream, value.toList()) } - is IconResult -> { + is ResourceSize -> { stream.write(166) writeValue(stream, value.toList()) } - is CookiePartitionKey -> { + is Resource -> { stream.write(167) writeValue(stream, value.toList()) } - is Cookie -> { + is IconResult -> { stream.write(168) writeValue(stream, value.toList()) } - is VisitInfo -> { + is CookiePartitionKey -> { stream.write(169) writeValue(stream, value.toList()) } - is HistoryItem -> { + is Cookie -> { stream.write(170) writeValue(stream, value.toList()) } - is HistoryState -> { + is VisitInfo -> { stream.write(171) writeValue(stream, value.toList()) } - is ReaderableState -> { + is HistoryItem -> { stream.write(172) writeValue(stream, value.toList()) } - is SecurityInfoState -> { + is HistoryState -> { stream.write(173) writeValue(stream, value.toList()) } - is TabContentState -> { + is ReaderableState -> { stream.write(174) writeValue(stream, value.toList()) } - is FindResultState -> { + is SecurityInfoState -> { stream.write(175) writeValue(stream, value.toList()) } - is CustomSelectionAction -> { + is TabContentState -> { stream.write(176) writeValue(stream, value.toList()) } - is WebExtensionData -> { + is FindResultState -> { stream.write(177) writeValue(stream, value.toList()) } - is GeckoSuggestion -> { + is CustomSelectionAction -> { stream.write(178) writeValue(stream, value.toList()) } - is TabContent -> { + is WebExtensionData -> { stream.write(179) writeValue(stream, value.toList()) } - is ContentBlocking -> { + is GeckoSuggestion -> { stream.write(180) writeValue(stream, value.toList()) } - is DohSettings -> { + is TabContent -> { stream.write(181) writeValue(stream, value.toList()) } - is GeckoEngineSettings -> { + is ContentBlocking -> { stream.write(182) writeValue(stream, value.toList()) } - is AutocompleteResult -> { + is DohSettings -> { stream.write(183) writeValue(stream, value.toList()) } - is UnknownHitResult -> { + is GeckoEngineSettings -> { stream.write(184) writeValue(stream, value.toList()) } - is ImageHitResult -> { + is AutocompleteResult -> { stream.write(185) writeValue(stream, value.toList()) } - is VideoHitResult -> { + is UnknownHitResult -> { stream.write(186) writeValue(stream, value.toList()) } - is AudioHitResult -> { + is ImageHitResult -> { stream.write(187) writeValue(stream, value.toList()) } - is ImageSrcHitResult -> { + is VideoHitResult -> { stream.write(188) writeValue(stream, value.toList()) } - is PhoneHitResult -> { + is AudioHitResult -> { stream.write(189) writeValue(stream, value.toList()) } - is EmailHitResult -> { + is ImageSrcHitResult -> { stream.write(190) writeValue(stream, value.toList()) } - is GeoHitResult -> { + is PhoneHitResult -> { stream.write(191) writeValue(stream, value.toList()) } - is DownloadState -> { + is EmailHitResult -> { stream.write(192) writeValue(stream, value.toList()) } - is ShareInternetResourceState -> { + is GeoHitResult -> { stream.write(193) writeValue(stream, value.toList()) } - is AddonCollection -> { + is DownloadState -> { stream.write(194) writeValue(stream, value.toList()) } - is GeckoPref -> { + is ShareInternetResourceState -> { stream.write(195) writeValue(stream, value.toList()) } - is ContainerSiteAssignment -> { + is AddonCollection -> { stream.write(196) writeValue(stream, value.toList()) } - is GeckoHeader -> { + is GeckoPref -> { stream.write(197) writeValue(stream, value.toList()) } - is GeckoFetchRequest -> { + is MlProgressData -> { stream.write(198) writeValue(stream, value.toList()) } - is GeckoFetchResponse -> { + is ContainerSiteAssignment -> { stream.write(199) writeValue(stream, value.toList()) } - is BookmarkNode -> { + is GeckoHeader -> { stream.write(200) writeValue(stream, value.toList()) } - is BookmarkInfo -> { + is GeckoFetchRequest -> { stream.write(201) writeValue(stream, value.toList()) } + is GeckoFetchResponse -> { + stream.write(202) + writeValue(stream, value.toList()) + } + is BookmarkNode -> { + stream.write(203) + writeValue(stream, value.toList()) + } + is BookmarkInfo -> { + stream.write(204) + writeValue(stream, value.toList()) + } else -> super.writeValue(stream, value) } } @@ -4945,6 +5068,23 @@ class GeckoStateEvents(private val binaryMessenger: BinaryMessenger, private val } } } + fun onMlProgress(timestampArg: Long, progressArg: MlProgressData, callback: (Result) -> Unit) +{ + val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + val channelName = "dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onMlProgress$separatedMessageChannelSuffix" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(timestampArg, progressArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(GeckoPigeonUtils.createConnectionError(channelName))) + } + } + } } /** Generated class from Pigeon that represents Flutter messages that can be called from Kotlin. */ class GeckoLogging(private val binaryMessenger: BinaryMessenger, private val messageChannelSuffix: String = "") { diff --git a/packages/flutter_mozilla_components/lib/flutter_mozilla_components.dart b/packages/flutter_mozilla_components/lib/flutter_mozilla_components.dart index a75207b1..504db898 100644 --- a/packages/flutter_mozilla_components/lib/flutter_mozilla_components.dart +++ b/packages/flutter_mozilla_components/lib/flutter_mozilla_components.dart @@ -60,6 +60,9 @@ export 'src/pigeons/gecko.g.dart' ImageHitResult, ImageSrcHitResult, LogLevel, + MlProgressData, + MlProgressStatus, + MlProgressType, PhoneHitResult, QueryParameterStripping, Resource, diff --git a/packages/flutter_mozilla_components/lib/src/domain/services/gecko_event.dart b/packages/flutter_mozilla_components/lib/src/domain/services/gecko_event.dart index 46fa9e83..1919ed25 100644 --- a/packages/flutter_mozilla_components/lib/src/domain/services/gecko_event.dart +++ b/packages/flutter_mozilla_components/lib/src/domain/services/gecko_event.dart @@ -43,6 +43,7 @@ class GeckoEventService extends GeckoStateEvents { final _siteAssignementSubject = PublishSubject(); final _tabAddedSubject = PublishSubject(); + final _mlProgressSubject = PublishSubject(); // Event streams ValueStream get viewReadyStateEvents => _viewStateSubject.stream; @@ -66,6 +67,7 @@ class GeckoEventService extends GeckoStateEvents { _siteAssignementSubject.stream; Stream get tabAddedStream => _tabAddedSubject.stream; + Stream get mlProgressEvents => _mlProgressSubject.stream; @override void onViewReadyStateChange(int timestamp, bool state) { @@ -199,6 +201,11 @@ class GeckoEventService extends GeckoStateEvents { ); } + @override + void onMlProgress(int timestamp, MlProgressData progress) { + _mlProgressSubject.addWhenMoreRecent(timestamp, null, progress); + } + GeckoEventService.setUp({ BinaryMessenger? binaryMessenger, String messageChannelSuffix = '', @@ -228,5 +235,6 @@ class GeckoEventService extends GeckoStateEvents { await _tabAddedSubject.close(); await _prefUpdateSubject.close(); await _siteAssignementSubject.close(); + await _mlProgressSubject.close(); } } diff --git a/packages/flutter_mozilla_components/lib/src/pigeons/gecko.g.dart b/packages/flutter_mozilla_components/lib/src/pigeons/gecko.g.dart index 37db2c89..dd271d3b 100644 --- a/packages/flutter_mozilla_components/lib/src/pigeons/gecko.g.dart +++ b/packages/flutter_mozilla_components/lib/src/pigeons/gecko.g.dart @@ -217,6 +217,21 @@ enum LogLevel { error, } +/// Type of ML model operation +enum MlProgressType { + downloading, + loadingFromCache, + runningInference, +} + +/// Status of the ML operation +enum MlProgressStatus { + initiate, + sizeEstimate, + inProgress, + done, +} + enum GeckoFetchMethod { get, head, @@ -2881,6 +2896,103 @@ class GeckoPref { ; } +/// Progress information for ML model operations +class MlProgressData { + MlProgressData({ + required this.modelType, + required this.progress, + required this.type, + required this.status, + required this.totalLoaded, + required this.currentLoaded, + required this.total, + required this.units, + required this.ok, + this.id, + }); + + /// The type of ML model being loaded + String modelType; + + /// Percentage of completion (0-100) + double progress; + + /// Type of operation (download, cache load, or inference) + MlProgressType type; + + /// Current status of the operation + MlProgressStatus status; + + /// Total bytes loaded so far + int totalLoaded; + + /// Bytes loaded in current update + int currentLoaded; + + /// Total size estimate + int total; + + /// Units of measurement (e.g., "bytes") + String units; + + /// Whether the operation completed successfully + bool ok; + + /// Unique identifier for this operation + String? id; + + List _toList() { + return [ + modelType, + progress, + type, + status, + totalLoaded, + currentLoaded, + total, + units, + ok, + id, + ]; + } + + Object encode() { + return _toList(); } + + static MlProgressData decode(Object result) { + result as List; + return MlProgressData( + modelType: result[0]! as String, + progress: result[1]! as double, + type: result[2]! as MlProgressType, + status: result[3]! as MlProgressStatus, + totalLoaded: result[4]! as int, + currentLoaded: result[5]! as int, + total: result[6]! as int, + units: result[7]! as String, + ok: result[8]! as bool, + id: result[9] as String?, + ); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + bool operator ==(Object other) { + if (other is! MlProgressData || other.runtimeType != runtimeType) { + return false; + } + if (identical(this, other)) { + return true; + } + return _deepEquals(encode(), other.encode()); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + int get hashCode => Object.hashAll(_toList()) +; +} + class ContainerSiteAssignment { ContainerSiteAssignment({ required this.requestId, @@ -3348,168 +3460,177 @@ class _PigeonCodec extends StandardMessageCodec { } else if (value is LogLevel) { buffer.putUint8(147); writeValue(buffer, value.index); - } else if (value is GeckoFetchMethod) { + } else if (value is MlProgressType) { buffer.putUint8(148); writeValue(buffer, value.index); - } else if (value is GeckoFetchRedircet) { + } else if (value is MlProgressStatus) { buffer.putUint8(149); writeValue(buffer, value.index); - } else if (value is GeckoFetchCookiePolicy) { + } else if (value is GeckoFetchMethod) { buffer.putUint8(150); writeValue(buffer, value.index); - } else if (value is BookmarkNodeType) { + } else if (value is GeckoFetchRedircet) { buffer.putUint8(151); writeValue(buffer, value.index); - } else if (value is TranslationOptions) { + } else if (value is GeckoFetchCookiePolicy) { buffer.putUint8(152); - writeValue(buffer, value.encode()); - } else if (value is ReaderState) { + writeValue(buffer, value.index); + } else if (value is BookmarkNodeType) { buffer.putUint8(153); - writeValue(buffer, value.encode()); - } else if (value is LastMediaAccessState) { + writeValue(buffer, value.index); + } else if (value is TranslationOptions) { buffer.putUint8(154); writeValue(buffer, value.encode()); - } else if (value is HistoryMetadataKey) { + } else if (value is ReaderState) { buffer.putUint8(155); writeValue(buffer, value.encode()); - } else if (value is PackageCategoryValue) { + } else if (value is LastMediaAccessState) { buffer.putUint8(156); writeValue(buffer, value.encode()); - } else if (value is ExternalPackage) { + } else if (value is HistoryMetadataKey) { buffer.putUint8(157); writeValue(buffer, value.encode()); - } else if (value is LoadUrlFlagsValue) { + } else if (value is PackageCategoryValue) { buffer.putUint8(158); writeValue(buffer, value.encode()); - } else if (value is SourceValue) { + } else if (value is ExternalPackage) { buffer.putUint8(159); writeValue(buffer, value.encode()); - } else if (value is TabState) { + } else if (value is LoadUrlFlagsValue) { buffer.putUint8(160); writeValue(buffer, value.encode()); - } else if (value is RecoverableTab) { + } else if (value is SourceValue) { buffer.putUint8(161); writeValue(buffer, value.encode()); - } else if (value is RecoverableBrowserState) { + } else if (value is TabState) { buffer.putUint8(162); writeValue(buffer, value.encode()); - } else if (value is IconRequest) { + } else if (value is RecoverableTab) { buffer.putUint8(163); writeValue(buffer, value.encode()); - } else if (value is ResourceSize) { + } else if (value is RecoverableBrowserState) { buffer.putUint8(164); writeValue(buffer, value.encode()); - } else if (value is Resource) { + } else if (value is IconRequest) { buffer.putUint8(165); writeValue(buffer, value.encode()); - } else if (value is IconResult) { + } else if (value is ResourceSize) { buffer.putUint8(166); writeValue(buffer, value.encode()); - } else if (value is CookiePartitionKey) { + } else if (value is Resource) { buffer.putUint8(167); writeValue(buffer, value.encode()); - } else if (value is Cookie) { + } else if (value is IconResult) { buffer.putUint8(168); writeValue(buffer, value.encode()); - } else if (value is VisitInfo) { + } else if (value is CookiePartitionKey) { buffer.putUint8(169); writeValue(buffer, value.encode()); - } else if (value is HistoryItem) { + } else if (value is Cookie) { buffer.putUint8(170); writeValue(buffer, value.encode()); - } else if (value is HistoryState) { + } else if (value is VisitInfo) { buffer.putUint8(171); writeValue(buffer, value.encode()); - } else if (value is ReaderableState) { + } else if (value is HistoryItem) { buffer.putUint8(172); writeValue(buffer, value.encode()); - } else if (value is SecurityInfoState) { + } else if (value is HistoryState) { buffer.putUint8(173); writeValue(buffer, value.encode()); - } else if (value is TabContentState) { + } else if (value is ReaderableState) { buffer.putUint8(174); writeValue(buffer, value.encode()); - } else if (value is FindResultState) { + } else if (value is SecurityInfoState) { buffer.putUint8(175); writeValue(buffer, value.encode()); - } else if (value is CustomSelectionAction) { + } else if (value is TabContentState) { buffer.putUint8(176); writeValue(buffer, value.encode()); - } else if (value is WebExtensionData) { + } else if (value is FindResultState) { buffer.putUint8(177); writeValue(buffer, value.encode()); - } else if (value is GeckoSuggestion) { + } else if (value is CustomSelectionAction) { buffer.putUint8(178); writeValue(buffer, value.encode()); - } else if (value is TabContent) { + } else if (value is WebExtensionData) { buffer.putUint8(179); writeValue(buffer, value.encode()); - } else if (value is ContentBlocking) { + } else if (value is GeckoSuggestion) { buffer.putUint8(180); writeValue(buffer, value.encode()); - } else if (value is DohSettings) { + } else if (value is TabContent) { buffer.putUint8(181); writeValue(buffer, value.encode()); - } else if (value is GeckoEngineSettings) { + } else if (value is ContentBlocking) { buffer.putUint8(182); writeValue(buffer, value.encode()); - } else if (value is AutocompleteResult) { + } else if (value is DohSettings) { buffer.putUint8(183); writeValue(buffer, value.encode()); - } else if (value is UnknownHitResult) { + } else if (value is GeckoEngineSettings) { buffer.putUint8(184); writeValue(buffer, value.encode()); - } else if (value is ImageHitResult) { + } else if (value is AutocompleteResult) { buffer.putUint8(185); writeValue(buffer, value.encode()); - } else if (value is VideoHitResult) { + } else if (value is UnknownHitResult) { buffer.putUint8(186); writeValue(buffer, value.encode()); - } else if (value is AudioHitResult) { + } else if (value is ImageHitResult) { buffer.putUint8(187); writeValue(buffer, value.encode()); - } else if (value is ImageSrcHitResult) { + } else if (value is VideoHitResult) { buffer.putUint8(188); writeValue(buffer, value.encode()); - } else if (value is PhoneHitResult) { + } else if (value is AudioHitResult) { buffer.putUint8(189); writeValue(buffer, value.encode()); - } else if (value is EmailHitResult) { + } else if (value is ImageSrcHitResult) { buffer.putUint8(190); writeValue(buffer, value.encode()); - } else if (value is GeoHitResult) { + } else if (value is PhoneHitResult) { buffer.putUint8(191); writeValue(buffer, value.encode()); - } else if (value is DownloadState) { + } else if (value is EmailHitResult) { buffer.putUint8(192); writeValue(buffer, value.encode()); - } else if (value is ShareInternetResourceState) { + } else if (value is GeoHitResult) { buffer.putUint8(193); writeValue(buffer, value.encode()); - } else if (value is AddonCollection) { + } else if (value is DownloadState) { buffer.putUint8(194); writeValue(buffer, value.encode()); - } else if (value is GeckoPref) { + } else if (value is ShareInternetResourceState) { buffer.putUint8(195); writeValue(buffer, value.encode()); - } else if (value is ContainerSiteAssignment) { + } else if (value is AddonCollection) { buffer.putUint8(196); writeValue(buffer, value.encode()); - } else if (value is GeckoHeader) { + } else if (value is GeckoPref) { buffer.putUint8(197); writeValue(buffer, value.encode()); - } else if (value is GeckoFetchRequest) { + } else if (value is MlProgressData) { buffer.putUint8(198); writeValue(buffer, value.encode()); - } else if (value is GeckoFetchResponse) { + } else if (value is ContainerSiteAssignment) { buffer.putUint8(199); writeValue(buffer, value.encode()); - } else if (value is BookmarkNode) { + } else if (value is GeckoHeader) { buffer.putUint8(200); writeValue(buffer, value.encode()); - } else if (value is BookmarkInfo) { + } else if (value is GeckoFetchRequest) { buffer.putUint8(201); writeValue(buffer, value.encode()); + } else if (value is GeckoFetchResponse) { + buffer.putUint8(202); + writeValue(buffer, value.encode()); + } else if (value is BookmarkNode) { + buffer.putUint8(203); + writeValue(buffer, value.encode()); + } else if (value is BookmarkInfo) { + buffer.putUint8(204); + writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } @@ -3577,115 +3698,123 @@ class _PigeonCodec extends StandardMessageCodec { return value == null ? null : LogLevel.values[value]; case 148: final value = readValue(buffer) as int?; - return value == null ? null : GeckoFetchMethod.values[value]; + return value == null ? null : MlProgressType.values[value]; case 149: final value = readValue(buffer) as int?; - return value == null ? null : GeckoFetchRedircet.values[value]; + return value == null ? null : MlProgressStatus.values[value]; case 150: final value = readValue(buffer) as int?; - return value == null ? null : GeckoFetchCookiePolicy.values[value]; + return value == null ? null : GeckoFetchMethod.values[value]; case 151: final value = readValue(buffer) as int?; - return value == null ? null : BookmarkNodeType.values[value]; + return value == null ? null : GeckoFetchRedircet.values[value]; case 152: - return TranslationOptions.decode(readValue(buffer)!); + final value = readValue(buffer) as int?; + return value == null ? null : GeckoFetchCookiePolicy.values[value]; case 153: - return ReaderState.decode(readValue(buffer)!); + final value = readValue(buffer) as int?; + return value == null ? null : BookmarkNodeType.values[value]; case 154: - return LastMediaAccessState.decode(readValue(buffer)!); + return TranslationOptions.decode(readValue(buffer)!); case 155: - return HistoryMetadataKey.decode(readValue(buffer)!); + return ReaderState.decode(readValue(buffer)!); case 156: - return PackageCategoryValue.decode(readValue(buffer)!); + return LastMediaAccessState.decode(readValue(buffer)!); case 157: - return ExternalPackage.decode(readValue(buffer)!); + return HistoryMetadataKey.decode(readValue(buffer)!); case 158: - return LoadUrlFlagsValue.decode(readValue(buffer)!); + return PackageCategoryValue.decode(readValue(buffer)!); case 159: - return SourceValue.decode(readValue(buffer)!); + return ExternalPackage.decode(readValue(buffer)!); case 160: - return TabState.decode(readValue(buffer)!); + return LoadUrlFlagsValue.decode(readValue(buffer)!); case 161: - return RecoverableTab.decode(readValue(buffer)!); + return SourceValue.decode(readValue(buffer)!); case 162: - return RecoverableBrowserState.decode(readValue(buffer)!); + return TabState.decode(readValue(buffer)!); case 163: - return IconRequest.decode(readValue(buffer)!); + return RecoverableTab.decode(readValue(buffer)!); case 164: - return ResourceSize.decode(readValue(buffer)!); + return RecoverableBrowserState.decode(readValue(buffer)!); case 165: - return Resource.decode(readValue(buffer)!); + return IconRequest.decode(readValue(buffer)!); case 166: - return IconResult.decode(readValue(buffer)!); + return ResourceSize.decode(readValue(buffer)!); case 167: - return CookiePartitionKey.decode(readValue(buffer)!); + return Resource.decode(readValue(buffer)!); case 168: - return Cookie.decode(readValue(buffer)!); + return IconResult.decode(readValue(buffer)!); case 169: - return VisitInfo.decode(readValue(buffer)!); + return CookiePartitionKey.decode(readValue(buffer)!); case 170: - return HistoryItem.decode(readValue(buffer)!); + return Cookie.decode(readValue(buffer)!); case 171: - return HistoryState.decode(readValue(buffer)!); + return VisitInfo.decode(readValue(buffer)!); case 172: - return ReaderableState.decode(readValue(buffer)!); + return HistoryItem.decode(readValue(buffer)!); case 173: - return SecurityInfoState.decode(readValue(buffer)!); + return HistoryState.decode(readValue(buffer)!); case 174: - return TabContentState.decode(readValue(buffer)!); + return ReaderableState.decode(readValue(buffer)!); case 175: - return FindResultState.decode(readValue(buffer)!); + return SecurityInfoState.decode(readValue(buffer)!); case 176: - return CustomSelectionAction.decode(readValue(buffer)!); + return TabContentState.decode(readValue(buffer)!); case 177: - return WebExtensionData.decode(readValue(buffer)!); + return FindResultState.decode(readValue(buffer)!); case 178: - return GeckoSuggestion.decode(readValue(buffer)!); + return CustomSelectionAction.decode(readValue(buffer)!); case 179: - return TabContent.decode(readValue(buffer)!); + return WebExtensionData.decode(readValue(buffer)!); case 180: - return ContentBlocking.decode(readValue(buffer)!); + return GeckoSuggestion.decode(readValue(buffer)!); case 181: - return DohSettings.decode(readValue(buffer)!); + return TabContent.decode(readValue(buffer)!); case 182: - return GeckoEngineSettings.decode(readValue(buffer)!); + return ContentBlocking.decode(readValue(buffer)!); case 183: - return AutocompleteResult.decode(readValue(buffer)!); + return DohSettings.decode(readValue(buffer)!); case 184: - return UnknownHitResult.decode(readValue(buffer)!); + return GeckoEngineSettings.decode(readValue(buffer)!); case 185: - return ImageHitResult.decode(readValue(buffer)!); + return AutocompleteResult.decode(readValue(buffer)!); case 186: - return VideoHitResult.decode(readValue(buffer)!); + return UnknownHitResult.decode(readValue(buffer)!); case 187: - return AudioHitResult.decode(readValue(buffer)!); + return ImageHitResult.decode(readValue(buffer)!); case 188: - return ImageSrcHitResult.decode(readValue(buffer)!); + return VideoHitResult.decode(readValue(buffer)!); case 189: - return PhoneHitResult.decode(readValue(buffer)!); + return AudioHitResult.decode(readValue(buffer)!); case 190: - return EmailHitResult.decode(readValue(buffer)!); + return ImageSrcHitResult.decode(readValue(buffer)!); case 191: - return GeoHitResult.decode(readValue(buffer)!); + return PhoneHitResult.decode(readValue(buffer)!); case 192: - return DownloadState.decode(readValue(buffer)!); + return EmailHitResult.decode(readValue(buffer)!); case 193: - return ShareInternetResourceState.decode(readValue(buffer)!); + return GeoHitResult.decode(readValue(buffer)!); case 194: - return AddonCollection.decode(readValue(buffer)!); + return DownloadState.decode(readValue(buffer)!); case 195: - return GeckoPref.decode(readValue(buffer)!); + return ShareInternetResourceState.decode(readValue(buffer)!); case 196: - return ContainerSiteAssignment.decode(readValue(buffer)!); + return AddonCollection.decode(readValue(buffer)!); case 197: - return GeckoHeader.decode(readValue(buffer)!); + return GeckoPref.decode(readValue(buffer)!); case 198: - return GeckoFetchRequest.decode(readValue(buffer)!); + return MlProgressData.decode(readValue(buffer)!); case 199: - return GeckoFetchResponse.decode(readValue(buffer)!); + return ContainerSiteAssignment.decode(readValue(buffer)!); case 200: - return BookmarkNode.decode(readValue(buffer)!); + return GeckoHeader.decode(readValue(buffer)!); case 201: + return GeckoFetchRequest.decode(readValue(buffer)!); + case 202: + return GeckoFetchResponse.decode(readValue(buffer)!); + case 203: + return BookmarkNode.decode(readValue(buffer)!); + case 204: return BookmarkInfo.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); @@ -5356,6 +5485,8 @@ abstract class GeckoStateEvents { void onContainerSiteAssignment(int timestamp, ContainerSiteAssignment details); + void onMlProgress(int timestamp, MlProgressData progress); + static void setUp(GeckoStateEvents? api, {BinaryMessenger? binaryMessenger, String messageChannelSuffix = '',}) { messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; { @@ -5824,6 +5955,34 @@ abstract class GeckoStateEvents { }); } } + { + final pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onMlProgress$messageChannelSuffix', pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (api == null) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onMlProgress was null.'); + final List args = (message as List?)!; + final int? arg_timestamp = (args[0] as int?); + assert(arg_timestamp != null, + 'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onMlProgress was null, expected non-null int.'); + final MlProgressData? arg_progress = (args[1] as MlProgressData?); + assert(arg_progress != null, + 'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onMlProgress was null, expected non-null MlProgressData.'); + try { + api.onMlProgress(arg_timestamp!, arg_progress!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } } } diff --git a/packages/flutter_mozilla_components/pigeons/gecko.dart b/packages/flutter_mozilla_components/pigeons/gecko.dart index 863bc47b..388420ca 100644 --- a/packages/flutter_mozilla_components/pigeons/gecko.dart +++ b/packages/flutter_mozilla_components/pigeons/gecko.dart @@ -1113,6 +1113,67 @@ abstract class GeckoPrefApi { void unregisterPrefForObservation(String name); } +/// Type of ML model operation +enum MlProgressType { + downloading, + loadingFromCache, + runningInference, +} + +/// Status of the ML operation +enum MlProgressStatus { + initiate, + sizeEstimate, + inProgress, + done, +} + +/// Progress information for ML model operations +class MlProgressData { + /// The type of ML model being loaded + final String modelType; + + /// Percentage of completion (0-100) + final double progress; + + /// Type of operation (download, cache load, or inference) + final MlProgressType type; + + /// Current status of the operation + final MlProgressStatus status; + + /// Total bytes loaded so far + final int totalLoaded; + + /// Bytes loaded in current update + final int currentLoaded; + + /// Total size estimate + final int total; + + /// Units of measurement (e.g., "bytes") + final String units; + + /// Whether the operation completed successfully + final bool ok; + + /// Unique identifier for this operation + final String? id; + + const MlProgressData({ + required this.modelType, + required this.progress, + required this.type, + required this.status, + required this.totalLoaded, + required this.currentLoaded, + required this.total, + required this.units, + required this.ok, + this.id, + }); +} + @HostApi() abstract class GeckoMlApi { @async @@ -1233,6 +1294,8 @@ abstract class GeckoStateEvents { int timestamp, ContainerSiteAssignment details, ); + + void onMlProgress(int timestamp, MlProgressData progress); } @FlutterApi()